Pagination & Filtering
The Worksome GraphQL API returns collections as paginated lists. Instead of fetching every record at once, you request a page of results and navigate forward or backward through the full set. This keeps responses fast and predictable regardless of how much data exists.
Worksome uses Lighthouse’s offset-based pagination model. Paginated queries return a data array of results alongside a paginatorInfo object that tells you where you are in the result set.
Basic Pagination
Every paginated query accepts a first argument that controls the page size. The response includes a data array with the requested items and a paginatorInfo object with metadata about the result set.
query { hires(first: 10) { data { id worker { name } company { name } activeStatus } paginatorInfo { currentPage lastPage total hasMorePages } } }
The response looks like this:
{ "data": { "hires": { "data": [ { "id": "SGlyZTox", "worker": { "name": "Jane Doe" }, "company": { "name": "Acme Corp" }, "activeStatus": "ACTIVE" } ], "paginatorInfo": { "currentPage": 1, "lastPage": 5, "total": 48, "hasMorePages": true } } } }
Understanding paginatorInfo
| Field | Type | Description |
|---|---|---|
currentPage |
Int! |
The current page number (1-based). |
lastPage |
Int! |
The last available page number. |
total |
Int! |
Total number of records matching the query. |
count |
Int! |
Number of records returned on this page. |
hasMorePages |
Boolean! |
Whether there are more pages after the current one. |
perPage |
Int! |
The number of items requested per page. |
firstItem |
Int |
The index of the first item on this page. |
lastItem |
Int |
The index of the last item on this page. |
Navigating Pages
Use the page argument to jump to a specific page in the result set. Combined with first, this gives you offset-based pagination.
query { hires(first: 10, page: 2) { data { id activeStatus } paginatorInfo { currentPage lastPage total hasMorePages } } }
A typical pagination flow:
- Fetch the first page with
first: 10(page defaults to1). - Check
paginatorInfo.hasMorePagesto see if more results exist. - Request the next page by incrementing
page. - Repeat until
hasMorePagesisfalse.
Tip
Use paginatorInfo.lastPage to show the user how many pages are available, and total to display the overall count of matching records.
Filtering
Most paginated queries accept filter arguments that narrow down the results before pagination is applied. Filters vary by resource but follow a consistent pattern.
Filtering Hires
The hires query supports several filters:
query { hires( first: 10 activeStatus: [ACTIVE] search: "Jane" startDateRange: { from: "2025-01-01", to: "2025-12-31" } ) { data { id worker { name } activeStatus startDate } paginatorInfo { total hasMorePages } } }
Available filter arguments for hires:
| Argument | Type | Description |
|---|---|---|
activeStatus |
[HireActiveStatus!] |
Filter by one or more hire statuses (e.g., [ACTIVE], [OFFERED, ACTIVE]). |
accounts |
[ID!] |
Filter by one or more company IDs (the company side of the hire). |
search |
String |
Free-text search across hire fields. |
startDateRange |
DateRangeInput |
Filter by start date range (from/to). |
endDateRange |
DateRangeInput |
Filter by end date range (from/to). |
orderBy |
[HiresOrderByClauseInput!] |
Sort results. Each clause is { field: HireOrderByColumn!, order: SortOrder! } (see Sorting). |
Filtering Contracts
query { contracts( first: 20 statuses: [ACTIVE, DRAFT] currencies: [USD, EUR] ) { data { id status currency } paginatorInfo { total hasMorePages } } }
Available filter arguments for contracts:
| Argument | Type | Description |
|---|---|---|
accounts |
[ID!] |
Filter by account IDs. |
currencies |
[Currency!] |
Filter by currency enum values (e.g., [USD, EUR]). |
statuses |
[ContractStatus!] |
Filter by contract statuses. Valid values: DRAFT, ACTIVE, ARCHIVED. |
locationPreferences |
[LocationPreference!] |
Filter by location preferences. |
Filtering Invoices
query { invoices( first: 15 status: [UNPAID] search: "INV-2025" currency: [USD] ) { data { id number currency grossAmount date } paginatorInfo { total hasMorePages } } }
Available filter arguments for invoices:
| Argument | Type | Description |
|---|---|---|
accounts |
[ID!] |
Filter by account IDs. |
status |
[InvoiceStatus!] |
Filter by one or more invoice statuses. Valid values: PAID, UNPAID, OVERDUE, CREDITED. |
search |
String |
Free-text search across invoice fields. |
currency |
[Currency!] |
Filter by one or more currency enum values. |
orderBy |
[QueryInvoicesOrderByOrderByClause!] |
Sort results. Each clause is { column: InvoicesOrderByColumn!, order: SortOrder! } (see Sorting). |
Sorting
Queries that support an orderBy argument let you control the sort order of results. The orderBy argument takes an array of sort clauses, each specifying a column and an order direction. The exact key for the column varies between input types — most use field, but a few (e.g. QueryInvoicesOrderByOrderByClause) use column — so check the input type for the query you are calling.
query { hires(first: 10, orderBy: [{ field: CREATED_AT, order: DESC }]) { data { id activeStatus createdAt } paginatorInfo { total } } }
You can sort by multiple clauses. The first entry is the primary sort, the second is the tiebreaker, and so on:
query { invoices( first: 20 orderBy: [ { column: DATE, order: DESC } { column: NUMBER, order: ASC } ] ) { data { id number date } paginatorInfo { total } } }
Tip
Check the schema reference for each query to see which columns are available for sorting. The sortable columns are defined as enums (e.g., HireOrderByColumn, InvoicesOrderByColumn). Valid InvoicesOrderByColumn values are DATE, DUE_DATE, NUMBER, and TOTAL_AMOUNT.
Combining Pagination with Filters
Filters and pagination work together naturally. Filters narrow the result set first, and then pagination splits the filtered results into pages.
Here is a practical example that fetches the second page of active hires for a specific company, sorted by start date:
query { hires( first: 10 page: 2 activeStatus: [ACTIVE] accounts: ["Q29tcGFueTox"] orderBy: [{ field: START_DATE, order: DESC }] ) { data { id worker { name } company { name } activeStatus startDate } paginatorInfo { currentPage lastPage total hasMorePages } } }
Warning
When building paginated UIs, always apply your filters and sort order consistently across page requests. Changing filters between pages will produce unexpected results since the underlying result set changes.
Best Practices
Choose an appropriate page size
- Start with a page size of 10-25 items for most use cases.
- The maximum allowed page size is typically 100. Requesting more may result in an error.
- Smaller pages mean faster responses and lower memory usage on both client and server.
Avoid deep pages
- Requesting high page numbers (e.g.,
page: 500) can be slow because the server still has to count through all preceding records. - If you need to process a large dataset, iterate sequentially from page 1 rather than jumping to arbitrary pages.
- For bulk data exports, consider using smaller page sizes and processing pages sequentially.
Use filters to reduce result sets
- The more specific your filters, the smaller the result set and the faster the query.
- Apply filters server-side rather than fetching all records and filtering on the client.
Request only the fields you need
- GraphQL lets you request exactly the fields you need. Avoid selecting large nested objects when you only need an ID or name.
- Requesting fewer fields reduces response size and server processing time.
Cache total counts carefully
- The
totalfield inpaginatorInforequires counting all matching records, which can be expensive for large datasets. - If you only need to know whether more results exist, use
hasMorePagesinstead. - Cache the total count on the client side and avoid re-fetching it on every page request when the underlying data has not changed.
Warning
For large datasets, prefer sequential page-by-page iteration over jumping to arbitrary page numbers. This produces more predictable performance and avoids timeouts.