Docs
Launch GraphOS Studio

Creating Apollo Server plugins

Extend Apollo Server with custom functionality


All plugin lifecycle methods are async, except for willResolveField and schemaDidLoadOrUpdate.

You can create your own Apollo Server plugins to perform custom operations in response to certain events. For example, a basic logging plugin might log the GraphQL query string associated with each request that's sent to Apollo Server.

The anatomy of a plugin

If you're using TypeScript to create a plugin, your plugins should implement the ApolloServerPlugin interface.

Plugins are JavaScript objects that implement one or more functions that respond to events. Here's a basic plugin that responds to the serverWillStart event:

index.ts
const myPlugin = {
async serverWillStart() {
console.log('Server starting up!');
},
};
index.js
const myPlugin = {
async serverWillStart() {
console.log('Server starting up!');
},
};

You can define a plugin in the same file where you initialize Apollo Server, or you can export it as a separate module:

myPlugin.ts
export default {
async serverWillStart() {
console.log('Server starting up!');
},
};
myPlugin.js
export default {
async serverWillStart() {
console.log('Server starting up!');
},
};

To create a plugin that accepts options, create a function that accepts an options object and returns a properly structured plugin object, like so:

myPlugin.ts
export default (options: { logMessage: string }) => {
return {
async serverWillStart() {
console.log(options.logMessage);
},
};
};
myPlugin.js
export default (options) => {
return {
async serverWillStart() {
console.log(options.logMessage);
},
};
};

If your plugin needs to read the contextValue of a request (i.e., requestContext.contextValue), you must declare your plugin as an ApolloServerPlugin<YourContextType>, like so:

myPlugin.ts
interface MyContext {
token: string;
}
export default function (): ApolloServerPlugin<MyContext> {
return {
async requestDidStart({ contextValue }) {
// token is properly inferred as a string
console.log(contextValue.token);
},
};
}
myPlugin.js
export default function () {
return {
async requestDidStart({ contextValue }) {
// token is properly inferred as a string
console.log(contextValue.token);
},
};
}

Responding to events

A plugin specifies exactly which events it responds to by implementing functions that correspond to those events. The plugin in the examples above responds to the serverWillStart event, which fires when Apollo Server is preparing to start up. Almost all plugin events are async functions (i.e., functions that return Promises). The only exceptions are willResolveField and schemaDidLoadOrUpdate.

A plugin can respond to any combination of supported events.

Responding to request lifecycle events

Plugins can respond to the following events associated with the GraphQL request lifecycle:

However, the way you define these functions is slightly different from the serverWillStart example above. First, your plugin must define the requestDidStart function:

const myPlugin = {
async requestDidStart() {
console.log('Request started!');
},
};
const myPlugin = {
async requestDidStart() {
console.log('Request started!');
},
};

The requestDidStart event fires whenever Apollo Server receives a GraphQL request, before any of the lifecycle events listed above. You can respond to this event just like you respond to serverWillStart, but you also use this function to define responses for a request's lifecycle events, like so:

const myPlugin = {
async requestDidStart(requestContext) {
console.log('Request started!');
return {
async parsingDidStart(requestContext) {
console.log('Parsing started!');
},
async validationDidStart(requestContext) {
console.log('Validation started!');
},
};
},
};
const myPlugin = {
async requestDidStart(requestContext) {
console.log('Request started!');
return {
async parsingDidStart(requestContext) {
console.log('Parsing started!');
},
async validationDidStart(requestContext) {
console.log('Validation started!');
},
};
},
};

As shown, the requestDidStart function can optionally return an object that defines functions that respond to request lifecycle events. This structure organizes and encapsulates all of your plugin's request lifecycle logic, making it easier to reason about.

Request lifecycle event flow

The following diagram illustrates the sequence of events that fire for each request. Each of these events is documented in Apollo Server plugin events.

Important: Any event below that can result in "Success" can also result in an error. Whenever an error occurs, the didEncounterErrors event fires and the remainder of the "Success" path does not.

Success
Success
Success
Success
Response provided
No response provided
Success
(Fires once per resolver)
requestDidStart
didResolveSource
parsingDidStart*
validationDidStart*
didResolveOperation
responseForOperation
executionDidStart*
willResolveField*
didEncounterErrors
willSendResponse
*The indicated events also support end hooks that are called when their associated step completes.

End hooks

Event handlers for the following events can optionally return a function that is invoked after the corresponding lifecycle phase ends:

The executionDidStart hook returns an object containing an executionDidEnd function instead of just a function as an end hook. This is because the returned object can also contain willResolveField.

Just like the event handlers themselves, these end hooks are async functions (except for the end hook for willResolveField).

End hooks are passed any errors that occurred during the execution of that lifecycle phase. For example, the following plugin logs any errors that occur during any of the above lifecycle events:

const myPlugin = {
async requestDidStart() {
return {
async parsingDidStart() {
return async (err) => {
if (err) {
console.error(err);
}
};
},
async validationDidStart() {
// This end hook is unique in that it can receive an array of errors,
// which will contain every validation error that occurred.
return async (errs) => {
if (errs) {
errs.forEach((err) => console.error(err));
}
};
},
async executionDidStart() {
return {
async executionDidEnd(err) {
if (err) {
console.error(err);
}
},
};
},
};
},
};
const myPlugin = {
async requestDidStart() {
return {
async parsingDidStart() {
return async (err) => {
if (err) {
console.error(err);
}
};
},
async validationDidStart() {
// This end hook is unique in that it can receive an array of errors,
// which will contain every validation error that occurred.
return async (errs) => {
if (errs) {
errs.forEach((err) => console.error(err));
}
};
},
async executionDidStart() {
return {
async executionDidEnd(err) {
if (err) {
console.error(err);
}
},
};
},
};
},
};

Note that the validationDidStart end hook receives an array of errors that contains every validation error that occurred (if any). The willResolveField end hook receives the error thrown by the resolver as the first argument and the result of the resolver as the second argument. The arguments to each end hook are documented in the type definitions in Request lifecycle events.

Inspecting request and response details

As the example above shows, requestDidStart and request lifecycle functions accept a requestContext parameter. This parameter is of type GraphQLRequestContext, which includes a request (of type GraphQLRequest), along with a response field (of type GraphQLResponse) if it's available.

These types and their related subtypes are all defined in @apollo/server.

Installing custom plugins

Add your plugins to Apollo Server by providing a plugins configuration option to the ApolloServer constructor, like so:

import { ApolloServer } from '@apollo/server';
import ApolloServerOperationRegistry from '@apollo/server-plugin-operation-registry';
/* This example doesn't provide `typeDefs` or `resolvers`,
both of which are required to start the server. */
import { typeDefs, resolvers } from './separatelyDefined';
const server = new ApolloServer({
typeDefs,
resolvers,
// You can import plugins or define them in-line, as shown:
plugins: [
/* This plugin is from a package that's imported above. */
ApolloServerOperationRegistry({
/* options */
}),
/* This plugin is imported in-place. */
require('./localPluginModule'),
/* This plugin is defined in-line. */
{
async serverWillStart() {
console.log('Server starting up!');
},
},
],
});
import { ApolloServer } from '@apollo/server';
import ApolloServerOperationRegistry from '@apollo/server-plugin-operation-registry';
/* This example doesn't provide `typeDefs` or `resolvers`,
both of which are required to start the server. */
import { typeDefs, resolvers } from './separatelyDefined';
const server = new ApolloServer({
typeDefs,
resolvers,
// You can import plugins or define them in-line, as shown:
plugins: [
/* This plugin is from a package that's imported above. */
ApolloServerOperationRegistry({
/* options */
}),
/* This plugin is imported in-place. */
require('./localPluginModule'),
/* This plugin is defined in-line. */
{
async serverWillStart() {
console.log('Server starting up!');
},
},
],
});
Previous
Landing pages
Next
Event reference
Edit on GitHubEditForumsDiscord