Filtering & sorting

The GraphQL API page has a compact operator summary. This is the full reference: every operator by field type, combinators, sorting, system columns, and inherited fields.

The Where filter

A structured filter passed in the where argument:

query {
  blogPosts(where: { status: { eq: "published" } }) {
    id title
  }
}

Each field in where is an object with an operator. The operator type depends on the field type.

Operators by field type

Field type Operators
String, Text, Select eq, neq, in, notIn, contains, startsWith, endsWith
Number eq, neq, gt, gte, lt, lte, in
Boolean eq
Date, DateTime eq, neq, gt, gte, lt, lte
Content type (reference) eq, in
Array (values) contains, eq, in, notIn

Examples:

# String: substring (case-insensitive)
where: { title: { contains: "vue" } }

# Number: greater than or equal
where: { view_count: { gte: 100 } }

# Boolean: equals
where: { featured: { eq: true } }

# Date: after (ISO 8601)
where: { published_at: { gt: "2026-01-01T00:00:00Z" } }

# Reference: equals (by ID)
where: { author: { eq: "550e8400-e29b-41d4-a716-446655440000" } }

# Array: contains (membership check)
where: { tags: { contains: "vue" } }

# Multiple values
where: { status: { in: ["draft", "review"] } }

Case sensitivity

contains, startsWith, endsWith are case-insensitive (ILIKE). eq and neq on strings are case-sensitive. { title: { eq: "Vue" } } won’t match "vue", but { title: { contains: "vue" } } will.

Empty lists

{ slug: { in: [] } } returns 0 records. { slug: { notIn: [] } } returns all records.

AND / OR / NOT combinators

Fields at the same level are implicitly AND-joined:

where: {
  status: { eq: "published" }     # AND
  view_count: { gte: 100 }        # AND
}

OR — a list of filters (any must match):

where: {
  title: { contains: "Vue" }
  OR: [
    { status: { eq: "draft" } }
    { view_count: { gte: 75 } }
  ]
}
# means: title contains "Vue" AND (status = draft OR view_count >= 75)

AND — a list (explicit AND within a group):

where: {
  AND: [
    { status: { eq: "published" } }
    { author: { eq: "abc-123" } }
  ]
}

NOT — a single object (not a list):

where: {
  NOT: { status: { eq: "draft" } }
}
# means: status ≠ draft

Combinators can be nested without limits.

Sorting (orderBy)

A list of field + direction pairs:

query {
  blogPosts(orderBy: [{ field: PUBLISHED_AT, direction: DESC }]) {
    title published_at
  }
}

Multi-key sort — primary first, secondary second:

orderBy: [{ field: STATUS, direction: DESC }, { field: CREATED_AT, direction: ASC }]

Rules:

  • Field names are uppercase (PUBLISHED_AT, TITLE, CREATED_AT).
  • Direction: ASC (default) or DESC.
  • Arrays cannot be sorted.

System columns

These columns exist on every type, regardless of schema:

Column Filter Sortable
id eq, in yes
created_at date filter yes
updated_at date filter yes
where: { created_at: { gt: "2026-01-01T00:00:00Z" } }
orderBy: [{ field: CREATED_AT, direction: DESC }]

Inherited fields

If a type inherits fields via Extends, those fields are filterable and sortable too. For example, blog-post inherits status from page — the filter where: { status: { eq: "published" } } works.

What CANNOT be filtered

  • JSON — arbitrary JSON cannot be compared.
  • Polymorphic blocks (blocks on Page — Content type + array with no target type) — cannot be filtered or sorted.
  • An unknown field in where triggers an error.

Search vs Where

The search argument is a substring scan across all field values and the record’s ID (ILIKE on JSONB data and the id column). It combines with where via AND:

blogPosts(search: "vue", where: { status: { eq: "published" } })

search scans everywhere; where filters by specific fields.

Count with a filter

blogPostsCount(where: { status: { eq: "published" } })

Returns the number of records matching the filter — for pagination. Ignores limit and offset.

What’s next