Skip to Page NavigationSkip to Page NavigationSkip to Content

GraphQL Schema Extension

Keystone automatically generates a GraphQL schema based on your Keystone config. This schema contains all GraphQL types, queries and mutations based on your lists and ends up as the generated schema.graphql file found in the root of your Keystone project. Generally changing the behavior of Keystone can be performed through Hooks, however, there are times when you need an extra GraphQL type or want a custom mutation or query, for these instances, Keystone has the extendGraphqlSchema option.

The extendGraphqlSchema option expects a function that takes the GraphQL Schema generated by Keystone and returns a valid GraphQL schema. You can then use this function to add or replace resolvers and types.

Using Keystone's graphql.extend

Keystone exports graphql from @keystone/core, this uses @graphql-ts/schema which can be used in combination with Context from .keystone/types to extend your GraphQL schema in a type-safe way.

You can then import this into your Keystone configuration file

import { graphql } from '@keystone/core';

Then you can use graphql.extend to add custom resolvers to your GraphQL Schema.

The following example adds a custom mutation called publishPost to the base Keystone Schema. It accepts one argument (args) of id which cannot be null and has the type of ID (graphql.nonNull(graphql.ID)). It updates the Post with the corresponding id and sets its status to 'published' and publishDate to the current time. It then returns the Post that is updated which has a GraphQL type of Post that is passed in from the base schema (base.object('Post')).

import { graphql, config } from '@keystone/core';
import { Context } from '.keystone/types';
export default config({
{/* ... */},
extendGraphqlSchema: graphql.extend(base => {
return {
mutation: {
publishPost: graphql.field({
type: base.object('Post'),
args: { id: graphql.arg({ type: graphql.nonNull(graphql.ID) }) },
resolve(source, { id }, context:Context) {
return context.db.Post.updateOne({
where: { id },
data: { status: 'published', publishDate: new Date().toISOString() },
});
},
}),
},
};
}),
});

Note context.db is used in the resolver, this ensures the correct Internal object with the correct type is returned to GraphQL.

A full example project using graphql-ts can be found in examples/extend-graphql-schema-graphql-ts on the Keystone GitHub repo.

Using Third-Party Tools

As extendGraphqlSchema expects a function that returns a valid GraphQL schema you can also use third-party GraphQL schema tools to help generate or merge schemas.

GraphQL-Tools Merge Schemas

GraphQL Tools mergeSchemas is a third-party package that can help with schema merging and adding custom resolvers and types, and then return an updated GraphQL schema to Keystone.

Start by installing @graphql-tools/schema

npm install @graphql-tools/schema

Then import into your Keystone configuration

import { mergeSchemas } from '@graphql-tools/schema';

You can then write custom resolvers and typeDefs to merge with your Keystone schema. For example, to add the same custom mutation as above (publishPost), you would add the typeDefs for publishPost and then the resolvers.Mutation.publishPost which performs the same function as above.

export default config({
{/* ... */},
extendGraphqlSchema: schema =>
mergeSchemas({
schemas: [schema],
typeDefs: `
type Mutation {
publishPost(id: ID!): Post
`,
resolvers: {
Mutation: {
publishPost: (root, { id }, context) => {
return context.db.Post.updateOne({
where: { id },
data: { status: 'published', publishDate: new Date().toUTCString() },
});
},
},
},
}),
});

Note - Before version 3.0.0 of @keystone-6/core, @graphql-tools/schema was exported from @keystone/core as graphQLSchemaExtension this was removed in favor of using the tool directly if required

A full example project using @graphql-tools/schema can be found in examples/extend-graphql-schema-graphql-tools on the Keystone GitHub repo.