Hooks API
Hooks give solution developers a way to add custom logic to the framework of lists, fields and operations Keystone provides.
This document describes:
- How and where to configure hooks of different types
- The specific arguments and usage information of different hook sets
For a more general overview of the concepts, patterns and function of the Keystone hook system, see the Hook Guide.
Hook Types
Hooks can be categories into three types depending on where in the list schema they're attached:
The hook sets that span these types have very similar signatures. Any differences are called out in the documentation below.
List Hooks
List hooks can be defined by the system developer by specifying the hooks attribute of a list configuration when calling createList().
Hooks for the create, update and delete operations are available.
Usage
keystone.createList('User', {
  fields: {
    name: { type: Text },
    ...
  },
  hooks: {
    // Hooks for create and update operations
    resolveInput: async (...) => { ... },
    validateInput: async (...) => { ... },
    beforeChange: async (...) => { ... },
    afterChange: async (...) => { ... },
    // Hooks for delete operations
    validateDelete: async (...) => { ... },
    beforeDelete: async (...) => { ... },
    afterDelete: async (...) => { ... },
  },
  ...
});
Field Hooks
Field hooks can be defined by the system developer by specifying the hooks attribute of a field configuration when calling createList().
Hooks for the create, update and delete operations are available.
Usage
keystone.createList('User', {
  fields: {
    name: {
      type: Text,
      hooks: {
        // Hooks for create and update operations
        resolveInput: async (...) => { ... },
        validateInput: async (...) => { ... },
        beforeChange: async (...) => { ... },
        afterChange: async (...) => { ... },
        // Hooks for delete operations
        validateDelete: async (...) => { ... },
        beforeDelete: async (...) => { ... },
        afterDelete: async (...) => { ... },
      },
      ...
    },
    ...
  },
  ...
});
Field Type Hooks
Field Type hooks are associated with a particular field type and are applied to all fields of that type.
Custom field types can implement hooks by implementing the following hook methods on the Field base class.
See the Custom Field Types guide for more info.
Hooks for the create, update and delete operations are available.
Usage
class CustomFieldType extends Field {
  // Hooks for create and update operations
  async resolveInput(...) { ... }
  async validateInput(...) { ... }
  async beforeChange(...) { ... }
  async afterChange(...) { ... }
  // Hooks for delete operations
  async beforeDelete(...) { ... }
  async validateDelete(...) { ... }
  async afterDelete(...) { ... }
}
Hook Sets
resolveInput
Used to modify the resolvedData.
- Invoked after access control and field defaults are applied
- Available for createandupdateoperations
The return of resolveInput can be a Promise or an Object.
It should resolve to the same structure as the resolvedData.
The result is passed to the next function in the execution order.
Arguments
| Argument | Type | Description | 
|---|---|---|
| operation | String | The operation being performed (ie. createorupdate) | 
| existingItem | Objectorundefined | The current stored item (or undefinedforcreateoperations) | 
| originalInput | Object | The data received by the GraphQL mutation | 
| resolvedData | Object | The data received by the GraphQL mutation plus defaults values | 
| context | Apollo Context | The Apollo contextobject for this request | 
| actions | Object | Contains a queryfunctions that queries the list within the current operations context, see Query Helper | 
Usage
const resolveInput = ({
  operation,
  existingItem,
  originalInput,
  resolvedData,
  context,
  actions,
}) => {
  // Input resolution logic
  // Object returned is used in place of resolvedData
  return resolvedData;
};
validateInput
Used to verify the resolvedData is valid.
- Invoked after all resolveInputhooks have resolved
- Available for createandupdateoperations
If errors are found in resolvedData the function should either throw or call the supplied addFieldValidationError argument.
Return values are ignored.
Arguments
| Argument | Type | Description | 
|---|---|---|
| operation | String | The operation being performed (ie. createorupdate) | 
| existingItem | Objectorundefined | The current stored item (or undefinedforcreateoperations) | 
| originalInput | Object | The data received by the GraphQL mutation | 
| resolvedData | Object | The data received by the GraphQL mutation plus defaults values | 
| context | Apollo Context | The Apollo contextobject for this request | 
| actions | Object | Contains a queryfunctions that queries the list within the current operations context, see Query Helper | 
| addFieldValidationError | Function | Used to set a field validation error; accepts a String | 
Usage
const validateInput = ({
  operation,
  existingItem,
  originalInput,
  resolvedData,
  context,
  actions,
  addFieldValidationError,
}) => {
  // Throw error objects or register validation errors with addFieldValidationError(<String>)
  // Return values ignored
};
beforeChange
Used to cause side effects before the primary operation is executed.
- Invoked after all validateInputhooks have resolved
- Available for createandupdateoperations
beforeChange hooks can't manipulate the data passed to the primary operation but perform operations before data is saved.
Return values are ignored.
Arguments
| Argument | Type | Description | 
|---|---|---|
| operation | String | The operation being performed (ie. createorupdate) | 
| existingItem | Objectorundefined | The current stored item (or undefinedforcreateoperations) | 
| originalInput | Object | The data received by the GraphQL mutation | 
| resolvedData | Object | The data received by the GraphQL mutation plus defaults values | 
| context | Apollo Context | The Apollo contextobject for this request | 
| actions | Object | Contains a queryfunctions that queries the list within the current operations context, see Query Helper | 
Usage
const beforeChange = ({
  operation,
  existingItem,
  originalInput,
  resolvedData,
  context,
  actions,
}) => {
  // Perform side effects
  // Return values ignored
};
afterChange
Used to cause side effects after the primary operation is executed.
- Invoked after the primary operation has completed
- Available for createandupdateoperations
afterChange hooks perform actions after data is saved.
It receives both the "pre-update" item that was stored (existingItem) and the resultant, "post-update" item data (updatedItem).
This includes any DB-level defaults.
Notably, for create operations, this includes the item's id.
Return values are ignored.
Arguments
| Argument | Type | Description | 
|---|---|---|
| operation | String | The operation being performed (ie. createorupdate) | 
| existingItem | Objectorundefined | The previously stored item (or undefinedforcreateoperations) | 
| originalInput | Object | The data received by the GraphQL mutation | 
| updatedItem | Object | The new/currently stored item | 
| context | Apollo Context | The Apollo contextobject for this request | 
| actions | Object | Contains a queryfunctions that queries the list within the current operations context, see Query Helper | 
Usage
const afterChange = ({
  operation,
  existingItem,
  originalInput,
  updatedItem,
  context,
  actions,
}) => {
  // Perform side effects
  // Return values ignored
};
validateDelete
Used to verify a delete operation is valid, ie. will maintain data consitency.
- Invoked after access control has been tested
- Available for deleteoperations
Should throw or register errors with addFieldValidationError(<String>) if the delete operation is invalid.
Arguments
| Argument | Type | Description | 
|---|---|---|
| operation | String | The operation being performed ( deletein this case) | 
| existingItem | Object | The current stored item | 
| context | Apollo Context | The Apollo contextobject for this request | 
| actions | Object | Contains a queryfunctions that queries the list within the current operations context, see Query Helper | 
| addFieldValidationError | Function | Used to set a field validation error; accepts a String | 
Usage
const validateDelete = ({
  operation,
  existingItem,
  context,
  actions,
  addFieldValidationError,
}) => {
  // Throw error objects or register validation errors with addFieldValidationError(<String>)
  // Return values ignored
};
beforeDelete
Used to cause side effects before the delete operation is executed.
- Invoked after all validateDeletehooks have resolved
- Available for deleteoperations
Perform actions before the delete operation is executed. Return values are ignored.
Arguments
| Argument | Type | Description | 
|---|---|---|
| operation | String | The operation being performed ( deletein this case) | 
| existingItem | Object | The current stored item | 
| context | Apollo Context | The Apollo contextobject for this request | 
| actions | Object | Contains a queryfunctions that queries the list within the current operations context, see Query Helper | 
Usage
const beforeDelete = ({
  operation,
  existingItem,
  context,
  actions,
}) => {
  // Perform side effects
  // Return values ignored
};
afterDelete
Used to cause side effects before the delete operation is executed.
- Invoked after the delete operation has been executed
- Available for deleteoperations
Perform actions after the delete operation has been executed.
This is the last chance to operate on the previously stored item, supplied as existingItem.
Return values are ignored.
Arguments
| Argument | Type | Description | 
|---|---|---|
| operation | String | The operation being performed ( deletein this case) | 
| existingItem | Object | The previously stored item, now deleted | 
| context | Apollo Context | The Apollo contextobject for this request | 
| actions | Object | Contains a queryfunctions that queries the list within the current operations context, see Query Helper | 
Usage
const afterDelete = ({
  operation,
  existingItem,
  context,
  actions,
}) => {
  // Perform side effects
  // Return values ignored
};
actions Argument
All hooks receive an actions object as an argument.
It contains helper functionality for accessing the GraphQL schema, optionally within the context of the current request.
When used, this context reuse causes access control to be applied as though the caller themselves initiated an operation.
It can therefore be useful for performing additional operations on behalf of the caller.
The actions object currently contains a single function: query.
Query Helper
Perform a GraphQL query, optionally within the context of the current request.
It returns a Promise<Object> containing the standard GraphQL errors and data properties.
Argument
| Argument | Type | Description | 
|---|---|---|
| queryString | String | A graphQL query string | 
| options | Object | Config for the query | 
| options.skipAccessControl | Boolean | By default access control of the user making the initial request is still tested. Pass as trueto disable access control checks. | 
| options.variables | Object | The variables passed to the graphql query for the given queryString | 
Usage
const myHook = ({
  // ...
  actions: { query },
}) => {
  const queryString = `
    query {
      # GraphQL query here...
    }
  `;
  const options = {
    skipAccessControl: false,
    variables: { /* GraphQL variables here.. */ },
  };
  const { errors, data } = await query(queryString, options);
  // Check for errors
  // Do something with data
};