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.graphql schema file provided by the TypoGraph extension.

1. Include the shared PageInfo type 

The Pagination.graphql file ships with TypoGraph and defines:

PageInfo schema
type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}
Copied!

Add it as the first entry in schemaFiles in the site configuration:

config/sites/<site-identifier>/config.yaml
typograph:
  schemaFiles:
    - 'EXT:typograph/Resources/Private/Schemas/Pagination.graphql'
    - 'EXT:sitepackage/Resources/Private/Schemas/Query.graphql'
    # ...
Copied!

2. Define Connection and Edge types for your entity 

Example pagination schema for the Expert type
type Expert {
  familyName: String
  givenName: String
}

type ExpertConnection {
  edges: [ExpertEdge!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type ExpertEdge {
  cursor: String!
  node: Expert!
}
Copied!

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.

Query schema for plain and paginated Expert type
type Query {
  experts(familyName: String): [Expert]
  expertsConnection(familyName: String, first: Int, after: String): ExpertConnection
}
Copied!

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 familyName) continue to work as filters on both fields.

Both field names must be present in tableMapping.

Pagination Configuration 

Configure default and maximum page sizes in the site configuration:

config/sites/<site-identifier>/config.yaml
typograph:
  pagination:
    defaultLimit: 20
    maxLimit: 100
Copied!
Setting Default Description
defaultLimit 20 Page size when first is not provided
maxLimit 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.

First page
{
  expertsConnection(first: 10) {
    edges {
      cursor
      node {
        familyName
        givenName
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
    totalCount
  }
}
Copied!
Next page
{
  expertsConnection(first: 10, after: "Y3Vyc29yOjQy") {
    edges {
      cursor
      node {
        familyName
        givenName
      }
    }
    pageInfo {
      hasNextPage
      hasPreviousPage
      endCursor
    }
  }
}
Copied!
Combining filters with pagination
{
  expertsConnection(familyName: "Smith", first: 5) {
    edges {
      node {
        familyName
        givenName
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
    totalCount
  }
}
Copied!

Response Structure 

A paginated response has this shape
{
  "data": {
    "expertsConnection": {
      "edges": [
        {
          "cursor": "Y3Vyc29yOjE=",
          "node": {
            "familyName": "Smith",
            "givenName": "Jane"
          }
        }
      ],
      "pageInfo": {
        "hasNextPage": true,
        "hasPreviousPage": false,
        "startCursor": "Y3Vyc29yOjE=",
        "endCursor": "Y3Vyc29yOjE="
      },
      "totalCount": 42
    }
  }
}
Copied!
Field Description
edges Array of edge objects, each containing a cursor and a node (the actual entity)
pageInfo.hasNextPage true if more records exist after this page
pageInfo.hasPreviousPage true if an after cursor was provided (i.e. this is not the first page)
pageInfo.startCursor Cursor of the first edge in this page (null if empty)
pageInfo.endCursor Cursor of the last edge in this page (null if empty). Pass this as the after argument to fetch the next page.
totalCount Total number of matching records across all pages. Only queried from the database when this field is actually requested in the GraphQL selection.