Pagination
TypoGraph supports cursor-based pagination following the GraphQL Cursor Connections Specification.
How It Works
When a root query field returns a Connection type (a type whose name ends
with Connection), TypoGraph automatically applies cursor-based pagination. If
the return type is a plain list (e.g. [Discipline]), the resolver behaves as
before and no pagination is applied.
Cursors are opaque, base64-encoded strings. Clients should treat them as opaque tokens and never parse or construct them manually.
Schema Setup
To enable pagination for a type, you need to define three types in your GraphQL
schema and include the shared Pagination. schema file provided by the
TypoGraph extension.
2. Define Connection and Edge types for your entity
type Expert {
familyName: String
givenName: String
}
type ExpertConnection {
edges: [ExpertEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type ExpertEdge {
cursor: String!
node: Expert!
}
3. Define both a plain-list field and a Connection field in the Query type
The recommended convention is to expose two root fields per entity: a plain-list
field for unpaginated access and a *Connection field for paginated access.
This preserves backwards compatibility for clients that do not need pagination.
type Query {
experts(familyName: String): [Expert]
expertsConnection(familyName: String, first: Int, after: String): ExpertConnection
}
The first and after arguments on the Connection field are recognised as
pagination arguments and are not turned into WHERE conditions. All other
arguments (like family) continue to work as filters on both fields.
Both field names must be present in table.
Pagination Configuration
Configure default and maximum page sizes in the site configuration:
typograph:
pagination:
defaultLimit: 20
maxLimit: 100
| Setting | Default | Description |
|---|---|---|
default | 20 | Page size when first is not provided |
max | 100 | Upper bound for first; requests above this are clamped |
Querying with Pagination
Using the pagination arguments first and after, or last and before
allows for fine-grained pagination that can be combined the the usual filter
arguments.
{
expertsConnection(first: 10) {
edges {
cursor
node {
familyName
givenName
}
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
{
expertsConnection(first: 10, after: "Y3Vyc29yOjQy") {
edges {
cursor
node {
familyName
givenName
}
}
pageInfo {
hasNextPage
hasPreviousPage
endCursor
}
}
}
{
expertsConnection(familyName: "Smith", first: 5) {
edges {
node {
familyName
givenName
}
}
pageInfo {
hasNextPage
endCursor
}
totalCount
}
}
Response Structure
{
"data": {
"expertsConnection": {
"edges": [
{
"cursor": "Y3Vyc29yOjE=",
"node": {
"familyName": "Smith",
"givenName": "Jane"
}
}
],
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "Y3Vyc29yOjE=",
"endCursor": "Y3Vyc29yOjE="
},
"totalCount": 42
}
}
}
| Field | Description |
|---|---|
edges | Array of edge objects, each containing a cursor and a node (the actual entity) |
page | true if more records exist after this page |
page | true if an after cursor was provided (i.e. this is not the first page) |
page | Cursor of the first edge in this page (null if empty) |
page | Cursor of the last edge in this page (null if empty). Pass this as the after argument to fetch the next page. |
total | Total number of matching records across all pages. Only queried from the database when this field is actually requested in the GraphQL selection. |