Skip to main content
Infrahub uses cursor-based pagination with limit/offset controls and configurable ordering for all list queries.

Basic Pagination

Limit and Offset

Control the number of results and starting position:
query {
  BuiltinTag(
    limit: 20
    offset: 0
  ) {
    count
    edges {
      node {
        id
        name {
          value
        }
      }
    }
  }
}
limit
Int
Maximum number of results to return per page
offset
Int
Number of results to skip from the beginning

Response Structure

Paginated queries return:
count
Int!
Total number of items matching the query (across all pages)
edges
[Edge!]!
Array of edges containing the paginated results
permissions
PaginatedObjectPermission
Permission metadata for the current user

Pagination Strategy

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

Order Input

Control the ordering of results using the order parameter:
query {
  CoreAccount(
    order: {
      node_metadata: {
        created_at: ASC
      }
    }
    limit: 20
  ) {
    edges {
      node {
        name {
          value
        }
      }
    }
  }
}
order
OrderInput
Ordering configuration
order.disable
Boolean
Disable ordering (use natural database order)
order.node_metadata
InfrahubNodeMetadataOrder
Order by node metadata fields

Order Direction

Two ordering directions are available:
ASC
Ascending order (A-Z, 0-9, oldest-newest)
DESC
Descending order (Z-A, 9-0, newest-oldest)

Metadata Ordering

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
      }
    }
  }
}

Nested Pagination

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
            }
          }
        }
      }
    }
  }
}

Hierarchical Pagination

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
              }
            }
          }
        }
      }
    }
  }
}

Performance Optimization

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
            }
          }
        }
      }
    }
  }
}

Pagination Patterns

Infinite Scroll

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;
}
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);
};

Example: Complete Pagination

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

  1. Always use limit: Prevent accidentally fetching thousands of results
  2. Show total count: Display “Showing 1-20 of 157” for better UX
  3. Order consistently: Use explicit ordering for predictable pagination
  4. Cache results: Use Apollo Client or similar to cache paginated data
  5. Handle empty pages: Check if edges.length === 0
  6. Validate page numbers: Ensure page numbers are within valid range

See Also

Build docs developers (and LLMs) love