Infrahub uses cursor-based pagination with limit/offset controls and configurable ordering for all list queries.
Limit and Offset
Control the number of results and starting position:
query {
BuiltinTag(
limit: 20
offset: 0
) {
count
edges {
node {
id
name {
value
}
}
}
}
}
Maximum number of results to return per page
Number of results to skip from the beginning
Response Structure
Paginated queries return:
Total number of items matching the query (across all pages)
Array of edges containing the paginated results
permissions
PaginatedObjectPermission
Permission metadata for the current user
Calculate Total Pages
query FirstPage {
BuiltinTag(limit: 20, offset: 0) {
count # Total items: e.g., 157
edges {
node {
id
}
}
}
}
With count: 157 and limit: 20:
- Total pages:
ceil(157 / 20) = 8
- Page 1: offset 0
- Page 2: offset 20
- Page 3: offset 40
- …
- Page 8: offset 140
Next Page Pattern
query SecondPage {
BuiltinTag(
limit: 20
offset: 20 # Skip first 20 results
) {
count
edges {
node {
id
}
}
}
}
Implementation Example
interface PaginationState {
currentPage: number;
pageSize: number;
}
function getOffset(state: PaginationState): number {
return (state.currentPage - 1) * state.pageSize;
}
function getTotalPages(totalCount: number, pageSize: number): number {
return Math.ceil(totalCount / pageSize);
}
// Usage
const state = { currentPage: 3, pageSize: 20 };
const offset = getOffset(state); // 40
const { data } = await client.query({
query: GET_TAGS,
variables: {
limit: state.pageSize,
offset: offset,
},
});
const totalPages = getTotalPages(data.BuiltinTag.count, state.pageSize);
Ordering Results
Control the ordering of results using the order parameter:
query {
CoreAccount(
order: {
node_metadata: {
created_at: ASC
}
}
limit: 20
) {
edges {
node {
name {
value
}
}
}
}
}
Disable ordering (use natural database order)
order.node_metadata
InfrahubNodeMetadataOrder
Order by node metadata fields
Order Direction
Two ordering directions are available:
Ascending order (A-Z, 0-9, oldest-newest)
Descending order (Z-A, 9-0, newest-oldest)
Order by creation or update timestamps:
query RecentlyUpdated {
BuiltinTag(
order: {
node_metadata: {
updated_at: DESC
}
}
limit: 10
) {
edges {
node {
id
name {
value
}
}
}
}
}
Available metadata order fields:
created_at - Creation timestamp
updated_at - Last update timestamp
Disable Ordering
For performance, disable ordering when order doesn’t matter:
query UnorderedResults {
BuiltinTag(
order: { disable: true }
limit: 100
) {
edges {
node {
id
}
}
}
}
Relationships can also be paginated:
query {
CoreAccountGroup {
edges {
node {
name {
value
}
members(
limit: 10
offset: 0
order: {
node_metadata: {
created_at: ASC
}
}
) {
count
edges {
node {
id
}
}
}
}
}
}
}
Paginate through hierarchical relationships:
query {
BuiltinIPPrefix(
is_top_level__value: true
limit: 10
) {
edges {
node {
prefix {
value
}
children(
limit: 20
offset: 0
) {
count
edges {
node {
prefix {
value
}
}
}
}
}
}
}
}
Choose Appropriate Page Sizes
# Small page size for UI lists
query UIList {
BuiltinTag(limit: 25) {
count
edges {
node {
id
}
}
}
}
# Larger page size for batch processing
query BatchProcess {
BuiltinTag(limit: 100) {
count
edges {
node {
id
}
}
}
}
Limit Nested Results
Prevent over-fetching by limiting nested relationships:
query {
CoreAccountGroup {
edges {
node {
# Limit to first 5 members
members(limit: 5) {
count # Still shows total count
edges {
node {
id
}
}
}
}
}
}
}
const PAGE_SIZE = 20;
let offset = 0;
let hasMore = true;
while (hasMore) {
const { data } = await client.query({
query: GET_TAGS,
variables: { limit: PAGE_SIZE, offset },
});
const results = data.BuiltinTag.edges;
processResults(results);
offset += PAGE_SIZE;
hasMore = offset < data.BuiltinTag.count;
}
Page Navigation
interface PaginationControls {
currentPage: number;
totalPages: number;
pageSize: number;
totalCount: number;
}
function buildPaginationControls(
totalCount: number,
currentPage: number,
pageSize: number
): PaginationControls {
return {
currentPage,
totalPages: Math.ceil(totalCount / pageSize),
pageSize,
totalCount,
};
}
function goToPage(page: number, pageSize: number) {
return client.query({
query: GET_TAGS,
variables: {
limit: pageSize,
offset: (page - 1) * pageSize,
},
});
}
Load More Pattern
const [items, setItems] = useState([]);
const [offset, setOffset] = useState(0);
const PAGE_SIZE = 20;
const loadMore = async () => {
const { data } = await client.query({
query: GET_TAGS,
variables: {
limit: PAGE_SIZE,
offset,
},
});
setItems([...items, ...data.BuiltinTag.edges]);
setOffset(offset + PAGE_SIZE);
};
query PaginatedAccounts($limit: Int!, $offset: Int!) {
CoreAccount(
limit: $limit
offset: $offset
order: {
node_metadata: {
created_at: DESC
}
}
) {
count
edges {
node {
id
name {
value
}
account_type {
value
}
member_of_groups(limit: 5) {
count
edges {
node {
... on CoreAccountGroup {
name {
value
}
}
}
}
}
}
}
permissions {
create
update
delete
}
}
}
Variables:
{
"limit": 25,
"offset": 0
}
Response:
{
"data": {
"CoreAccount": {
"count": 157,
"edges": [
{
"node": {
"id": "abc-123",
"name": { "value": "admin" },
"account_type": { "value": "User" },
"member_of_groups": {
"count": 12,
"edges": [
{
"node": {
"name": { "value": "Administrators" }
}
}
]
}
}
}
],
"permissions": {
"create": true,
"update": true,
"delete": false
}
}
}
}
Best Practices
- Always use limit: Prevent accidentally fetching thousands of results
- Show total count: Display “Showing 1-20 of 157” for better UX
- Order consistently: Use explicit ordering for predictable pagination
- Cache results: Use Apollo Client or similar to cache paginated data
- Handle empty pages: Check if
edges.length === 0
- Validate page numbers: Ensure page numbers are within valid range
See Also