Skip to Page NavigationSkip to Page NavigationSkip to Content
Keystone 6 is now in General Availability!

GraphQL Queries - Filters

We recently improved our GraphQL API so it’s easier to program and reason about. If you were using it prior to August 17th 2021, read this guide for info on how to upgrade.

Keystone provides a powerful GraphQL API for querying the data in your system. At the core of this API are query filters. This guide will show you how to use filters to get data you need.

The filter references in this guide are based on the schema defined in the Task Manager example project.

Scalar Filters

If we want to find all the Tasks in our system, we can use the query tasks().

{
tasks {
id
label
}
}

In general we don't want to grab all the tasks at once, but we want to find a particular set of tasks which match a certain condition. In Keystone we can do this by passing a where argument to the tasks() query.

If we want to find all the tasks with a label equal to "Hello" we can write:

{
tasks(where: { label: { equals: "Hello" } }) {
id
label
}
}

Keystone provides a wide range of different filters. If we want to find all those tasks which don't have a label of "Hello" we can write:

{
tasks(where: { label: { not: { equals: "Hello" } } }) {
id
label
}
}

The text() field type also supports searching for sub-strings within a field:

{
tasks(where: { label: { contains: "He" } }) {
id
label
}
}

Different field types support different filters. The field finishBy: timestamp(), for example, lets you filter by tasks with a due date after a particular point in time:

{
tasks(where: { finishBy: { gt: "2022-01-01T00:00:00.000Z" } }) {
id
label
}
}

For a full list of the different filters provided by each field type, please check the Query Filter API.

For more complex queries, you can combine multiple filters, and only those items which match all conditions will be returned.

{
tasks(where: {
label: { contains: "He" },
finishBy: { gt: "2022-01-01T00:00:00.000Z" }
}) {
id
label
}
}

Combined Filters

For more advanced queries, you might need to explicitly combine different filters.

AND

The AND operater accepts a list of sub-filters, and will only return those items which match all of these conditions.

{
tasks(where: { AND: [
{ label: { contains: "H" } },
{ label: { contains: "ll" } }
] }) {
id
label
}
}

OR

The OR operater accepts a list of sub-filters, and will only return those items which match at least one of these conditions.

{
tasks(where: { OR: [
{ label: { contains: "H" } },
{ label: { contains: "ll" } }
] }) {
id
label
}
}

NOT

The NOT operater accepts a list of sub-filters, and will only return those items which don't match the conditions. You'll generally only pass a single filter to NOT rather than a list but if you do a pass a list, they're ANDed together.

{
tasks(where: { NOT: {
label: { contains: "H" }
} }) {
id
label
}
}

Relationship Filters

As well as filtering by scalar fields, you can also filter against relationship fields. The Relationship filters available depend on the number of item that can exist on the far side of the relationship, that is, whether you have many: true or many: false configured on the field.

To-One

If you have many: false configured on the relationship field, then you can find items based on a where filter using the fields from the related list.

For example, to find all the tasks where the task is assigned to a user named "Alice", we can run the following query.

{
tasks(where: { assignedTo: { name: { equals: "Alice" } } }) {
id
label
}
}

To find any tasks that have no assigned user, we can run the following query.

{
tasks(where: { assignedTo: null }) {
id
label
}
}

Conversely, if we wanted tasks where any user was assigned, we can invert the above using the NOT operator:

{
tasks(where: { NOT: { assignedTo: null } }) {
id
label
}
}

To-Many

If you have many: true configured on the relationship field, then you can find items based on whether some, none, or every of the related items match a condition. The condition itself can be a where filter using the fields from the related list or an empty object ({}) to indicate all related items should be matched.

Some

The some filter returns true when at least one of the related items match the conditions supplied.

For example, to find people that have one or more tasks where the task label contains "shopping", we can run the following query:

{
people(where: { tasks: { some: { label: { contains: "shopping" } } } }) {
id
name
}
}

To find people with any tasks at all, we could can pass an empty object. This indicate that no conditions should be applied to tasks so all tasks are matched:

{
people(where: { tasks: { some: {} } }) {
id
name
}
}

None

The none filter returns true when there are zero related items that match the conditions supplied.

For example, if we had an urgent task we were looking to assign, we could query for people who didn't have any task that were both incomplete and high-priority:

{
people(where: { tasks: { none: { priority: { equals: high }, isComplete: { equals: false } } } }) {
id
name
}
}

Or, if we wanted people with no assigned tasks at all (not even completed tasks), we could pass none as an empty object:

{
people(where: { tasks: { none: {} } }) {
id
name
}
}

Every

The every filter returns true when all related items match the conditions supplied.

For example, if we wanted to find people who have completed their assigned tasks, we could write:

{
people(where: { tasks: { every: { isComplete: { equals: true } } } }) {
id
name
}
}

Note the results will also include people with no tasks assigned at all. If you only wanted people who have both completed their assigned tasks and actually have at least one task assigned, you could add a some filter:

{
people(where: { tasks: { every: { isComplete: { equals: true } }, some: {} } }) {
id
name
}
}

Like the other "to-many" relationship filters, every does also accept an empty object, indicating no filters should be applied to the related items matched. However, doing so has no effect – it always evaluates to true, regardless of the properties of related items or whether they exist at all. So a query like this:

{
people(where: { tasks: { every: {} } }) {
id
name
}
}

Could be re-written as simply:

{
people {
id
name
}
}

In the relationship examples above we have been filtering items based on their related items, but we haven't been loading any information about the related items themselves. GraphQL gives us the ability to write queries that retrieve data across a graph of interconnected items - in Keystone, relationships form the links between these items. Queries that traverse these links apply filters both the top-level "list" fields (ie. tasks and people in these examples) and when selecting data about related items themselves.

This is easier to explain with examples so let's expand on one from earlier. Lets say we want to find people who have no high-priority, incomplete tasks but we want to see all the incomplete tasks they still have on their list. We could write something like this:

{
# Only return people who have no high-priority, incomplete tasks
people(where: {
tasks: {
none: {
priority: { equals: high },
isComplete: { equals: false }
}
}
}) {
id
name
# For each person, get tasks that are not complete
tasks(where: {
isComplete: { equals: false }
}) {
id
label
}
}
}

A good way to explore what's possible within your Keystone GraphQL API is using the GraphQL Playground that should be available on your GraphQL endpoint in dev (ie. at http://localhost:3000/api/graphql).