Skip to Page NavigationSkip to Page NavigationSkip to Content
Keystone 6 is in Community Preview! What does that mean? see our Roadmap. For Keystone 5 docs, visit v5.keystonejs.com

Hooks API

Hooks allow you to execute code at different stages of the mutation lifecycle when performing create, update, and delete operations. Lists and fields both support the same set of hook functions, with some slight differences in the arguments they accept. The differences will be explicitly called out below.

For each hook, the fields hooks are applied to all fields first in parallel, followed by the list hooks.

All hook functions are async and, with the exception of resolveInput, do not return a value.

For examples of how to use hooks in your system please see the hooks guide.

import { config, createSchema, list } from '@keystone-next/keystone/schema';
import { text } from '@keystone-next/fields';
export default config({
lists: createSchema({
ListName: list({
hooks: {
// Hooks for create and update operations
resolveInput: async args => { /* ... */ },
validateInput: async args => { /* ... */ },
beforeChange: async args => { /* ... */ },
afterChange: async args => { /* ... */ },
// Hooks for delete operations
validateDelete: async args => { /* ... */ },
beforeDelete: async args => { /* ... */ },
afterDelete: async args => { /* ... */ },
},
fields: {
fieldName: text({
hooks: {
// Hooks for create and update operations
resolveInput: async args => { /* ... */ },
validateInput: async args => { /* ... */ },
beforeChange: async args => { /* ... */ },
afterChange: async args => { /* ... */ },
// Hooks for delete operations
validateDelete: async args => { /* ... */ },
beforeDelete: async args => { /* ... */ },
afterDelete: async args => { /* ... */ },
},
}),
},
}),
}),
});

Update and create hooks

Update and create hooks are called for all update and create operations. When updating or creating multiple values the hooks are called individually for each item being updated or created.

resolveInput

The resolveInput function is used to modify or augment the data values passed in to a create or update operation.

This hook is the final stage in the data resolving process, and is invoked after access control and field defaults are applied.

For field hooks, the return value should be an updated value for that specific field. For list hooks, the return value should be a resolved data object. The result of resolveInput will be passed as resolvedData into the next stages of the operation.

ArgumentDescription
listKeyThe key of the list being operated on.
fieldPathThe path of the field being operated on (field hooks only).
operationThe operation being performed ('create' or 'update').
originalInputThe value of data passed into the mutation.
existingItemThe currently stored item (undefined for create operations). This object is an unresolved list item. list item API for more details on unresolved list items.
resolvedDataA resolved data object. The resolved data value after default values, relationship resolvers, and field resolvers have been applied.
contextThe KeystoneContext object of the originating GraphQL operation.
import { config, createSchema, list } from '@keystone-next/keystone/schema';
import { text } from '@keystone-next/fields';
export default config({
lists: createSchema({
ListName: list({
hooks: {
resolveInput: async ({
listKey,
operation,
originalInput,
existingItem,
resolvedData,
context,
}) => {
/* ... */
return resolvedData;
},
},
fields: {
fieldName: text({
hooks: {
resolveInput: async ({
listKey,
fieldPath,
operation,
originalInput,
existingItem,
resolvedData,
context,
}) => {
/* ... */
return resolvedData[fieldName];
},
},
}),
},
}),
}),
});

validateInput

The validateInput function is used to validate the resolvedData that will be saved during a create or update operation.

It is invoked after the resolveInput hooks have been run.

If the resolvedData is invalid then the function should report validation errors with addValidationError(msg). These error messages will be returned as a ValidationFailureError from the GraphQL API, and the operation will not be completed.

ArgumentDescription
listKeyThe key of the list being operated on.
fieldPathThe path of the field being operated on (field hooks only).
operationThe operation being performed ('create' or 'update').
originalInputThe value of data passed into the mutation.
existingItemThe current value of the item being updated (undefined for create operations). This object is an unresolved list item. See the list item API for more details on unresolved list items.
resolvedDataA resolved data object. The resolved data value after all data resolver stages have been completed.
contextThe KeystoneContext object of the originating GraphQL operation.
addValidationError(msg)Used to set a validation error.
import { config, createSchema, list } from '@keystone-next/keystone/schema';
import { text } from '@keystone-next/fields';
export default config({
lists: createSchema({
ListName: list({
hooks: {
validateInput: async ({
listKey,
operation,
originalInput,
existingItem,
resolvedData,
context,
addValidationError,
}) => { /* ... */ },
},
fields: {
fieldName: text({
hooks: {
validateInput: async ({
listKey,
fieldPath,
operation,
originalInput,
existingItem,
resolvedData,
context,
addValidationError,
}) => { /* ... */ },
},
}),
},
}),
}),
});

beforeChange

The beforeChange function is used to perform side effects just before the data for a create or update operation is saved to the database.

It is invoked after all validateInput hooks have been run, but before the data is saved to the database.

ArgumentDescription
listKeyThe key of the list being operated on.
fieldPathThe path of the field being operated on (field hooks only).
operationThe operation being performed ('create' or 'update').
originalInputThe value of data passed into the mutation.
existingItemThe current value of the item being updated (undefined for create operations). This object is an unresolved list item. See the list item API for more details on unresolved list items.
resolvedDataA resolved data object. The resolved data value after all data resolver stages have been completed.
contextThe KeystoneContext object of the originating GraphQL operation.
import { config, createSchema, list } from '@keystone-next/keystone/schema';
import { text } from '@keystone-next/fields';
export default config({
lists: createSchema({
ListName: list({
hooks: {
beforeChange: async ({
listKey,
operation,
originalInput,
existingItem,
resolvedData,
context,
}) => { /* ... */ },
},
fields: {
fieldName: text({
hooks: {
beforeChange: async ({
listKey,
fieldPath,
operation,
originalInput,
existingItem,
resolvedData,
context,
}) => { /* ... */ },
},
}),
},
}),
}),
});

afterChange

The afterChange function is used to perform side effects after the data for a create or update operation has been saved to the database.

ArgumentDescription
listKeyThe key of the list being operated on.
fieldPathThe path of the field being operated on (field hooks only).
operationThe operation being performed ('create' or 'update').
originalInputThe value of data passed into the mutation.
existingItemThe previous value of the item being updated (undefined for create operations). This object is an unresolved list item. See the list item API for more details on unresolved list items.
updatedItemThe new value of the item being updated or created. This object is an unresolved list item. See the list item API for more details on unresolved list items.
contextThe KeystoneContext object of the originating GraphQL operation.
import { config, createSchema, list } from '@keystone-next/keystone/schema';
import { text } from '@keystone-next/fields';
export default config({
lists: createSchema({
ListName: list({
hooks: {
afterChange: async ({
listKey,
operation,
originalInput,
existingItem,
updatedItem,
context,
}) => { /* ... */ },
},
fields: {
fieldName: text({
hooks: {
afterChange: async ({
listKey,
fieldPath,
operation,
originalInput,
existingItem,
updatedItem,
context,
}) => { /* ... */ },
},
}),
},
}),
}),
});

Delete hooks

Delete hooks are called for all delete operations. When deleting multiple values the hooks are called individually for each item being deleted.

validateDelete

The validateDelete function is used to validate that deleting the selected item will not cause an issue in your system.

It is invoked after access control has been applied.

If the delete operation is invalid then the function should report validation errors with addValidationError(msg). These error messages will be returned as a ValidationFailureError from the GraphQL API.

ArgumentDescription
listKeyThe key of the list being operated on.
fieldPathThe path of the field being operated on (field hooks only).
operationThe operation being performed ('delete').
existingItemThe value of the item to be deleted. This object is an unresolved list item. See the list item API for more details on unresolved list items.
contextThe KeystoneContext object of the originating GraphQL operation.
addValidationError(msg)Used to set a validation error.
import { config, createSchema, list } from '@keystone-next/keystone/schema';
import { text } from '@keystone-next/fields';
export default config({
lists: createSchema({
ListName: list({
hooks: {
validateDelete: async ({
listKey,
operation,
existingItem,
context,
addValidationError,
}) => { /* ... */ },
},
fields: {
fieldName: text({
hooks: {
validateDelete: async ({
listKey,
fieldPath,
operation,
existingItem,
context,
addValidationError,
}) => { /* ... */ },
},
}),
},
}),
}),
});

beforeDelete

The beforeDelete function is used to perform side effects just before the data for a delete operation is removed from the database.

It is invoked after all validateDelete hooks have been run.

ArgumentDescription
listKeyThe key of the list being operated on.
fieldPathThe path of the field being operated on (field hooks only).
operationThe operation being performed ('delete').
existingItemThe value of the item to be deleted. This object is an unresolved list item. See the list item API for more details on unresolved list items.
contextThe KeystoneContext object of the originating GraphQL operation.
import { config, createSchema, list } from '@keystone-next/keystone/schema';
import { text } from '@keystone-next/fields';
export default config({
lists: createSchema({
ListName: list({
hooks: {
beforeDelete: async ({ listKey, operation, existingItem, context }) => {
/* ... */
},
},
fields: {
fieldName: text({
hooks: {
beforeDelete: async ({ listKey, fieldPath, operation, existingItem, context }) => {
/* ... */
},
},
}),
},
}),
}),
});

afterDelete

The afterDelete function is used to perform side effects after the data for a delete operation has been removed from the database.

ArgumentDescription
listKeyThe key of the list being operated on.
fieldPathThe path of the field being operated on (field hooks only).
operationThe operation being performed ('delete').
existingItemThe value of the item, now deleted. This object is an unresolved list item. See the list item API for more details on unresolved list items.
contextThe KeystoneContext object of the originating GraphQL operation.
import { config, createSchema, list } from '@keystone-next/keystone/schema';
import { text } from '@keystone-next/fields';
export default config({
lists: createSchema({
ListName: list({
hooks: {
afterDelete: async ({ listKey, operation, existingItem, context }) => {
/* ... */
},
},
fields: {
fieldName: text({
hooks: {
afterDelete: async ({ listKey, fieldPath, operation, existingItem, context }) => {
/* ... */
},
},
}),
},
}),
}),
});

Resolved data stages

Create and update operations take a data value for a single item from the GraphQL input and then perform a number of data resolving steps before writing the final value to the database.

At each stage of the data resolving process, the value of resolvedData can be modified or augmented. The final value of resolvedData is the value that will be validated and saved to the database.

The data resolving steps are applied in the following order:

  1. Initialisation: Set the value of resolvedData to the data input value from the GraphQL mutation.
  2. Defaults (built in, create only): Any fields which have a default value and are undefined in resolvedData will be set to their default value.
  3. Relationships (built in): The values for relationship fields on resolvedData are either an ID string (for one relationships), or an array of ID strings (for many relationships). This is the format expected when saving relationship fields to the database. The value is computed by combining the provided connect, create, disconnect, and disconnectAll values with the existing data in the database. Any nested create operations are performed as part of this process.
  4. Field values (built in): Some fields types take the value given in the GraphQL operation and convert it into a different type or format to be saved to the database.
  5. Field hooks (user defined): A resolveInput field hook can return a new value for its field, which will the current field value on resolvedData.
  6. List hooks (user defined): A resolveInput list hook can return a new value for the entire resolvedData object.