docs/source/data/errors.mdx
import TopLevelAwait from "../shared/top-level-await.mdx"
Use the <code>executionOptions</code> option to configure how many variable coercion errors are allowed before execution terminates.
<!-- cSpell:ignore typenam -->Whenever Apollo Server encounters errors while processing a GraphQL operation, its response to the client includes an errors array containing each error that occurred. Each error in the array has an extensions field that provides additional useful information, including an error code and (while in development mode) a stacktrace.
Here's an example error response caused by misspelling the __typename field in a query:
{
"errors": [
{
"message": "Cannot query field \"__typenam\" on type \"Query\".",
"locations": [
{
"line": 1,
"column": 2
}
],
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED",
"stacktrace": [
"GraphQLError: Cannot query field \"__typenam\" on type \"Query\".",
" at Object.Field (/my_project/node_modules/graphql/validation/rules/FieldsOnCorrectTypeRule.js:48:31)",
" ...additional lines..."
]
}
}
]
}
To help with debugging, Apollo Server provides an ApolloServerErrorCode enum, which you can use to check if your error is one of the different types produced by Apollo Server.
You can check an error's code to determine why an error occurred and also add logic to respond to different types of errors, like so:
import { ApolloServerErrorCode } from '@apollo/server/errors';
if (error.extensions?.code === ApolloServerErrorCode.GRAPHQL_PARSE_FAILED) {
// respond to the syntax error
} else if (error.extensions?.code === "MY_CUSTOM_CODE") {
// do something else
}
Apollo Server's variety of error codes enables requesting clients to respond differently to different error types. You can also create your own custom errors and codes.
GRAPHQL_PARSE_FAILEDThe GraphQL operation string contains a syntax error.
</td> </tr> <tr> <td>GRAPHQL_VALIDATION_FAILEDThe GraphQL operation is not valid against the server's schema.
</td> </tr> <tr> <td>BAD_USER_INPUTThe GraphQL operation includes an invalid value for a field argument.
</td> </tr> <tr> <td>PERSISTED_QUERY_NOT_FOUNDA client sent the hash of a query string to execute via automatic persisted queries, but the query was not in the APQ cache.
</td> </tr> <tr> <td>PERSISTED_QUERY_NOT_SUPPORTEDA client sent the hash of a query string to execute via automatic persisted queries, but the server has disabled APQ.
</td> </tr> <tr> <td>OPERATION_RESOLUTION_FAILUREThe request was parsed successfully and is valid against the server's schema, but the server couldn't resolve which operation to run.
This occurs when a request containing multiple named operations doesn't specify which operation to run (i.e.,operationName), or if the named operation isn't included in the request.
BAD_REQUESTAn error occurred before your server could attempt to parse the given GraphQL operation.
</td> </tr> <tr> <td>INTERNAL_SERVER_ERRORAn unspecified error occurred.
When Apollo Server formats an error in a response, it sets the code extension to this value if no other code is set.
</td> </tr> </tbody> </table>You can create a custom errors and codes using the graphql package's GraphQLError class, like so:
import { GraphQLError } from 'graphql';
throw new GraphQLError(message, {
extensions: { code: 'YOUR_ERROR_CODE', myCustomExtensions },
});
Custom errors can provide additional context, enabling your clients to understand why an error is happening. We recommend making clear errors for common cases, for example, when a user isn't logged in (UNAUTHENTICATED), or someone is forbidden from performing an action:
import { GraphQLError } from 'graphql';
throw new GraphQLError('You are not authorized to perform this action.', {
extensions: {
code: 'FORBIDDEN',
},
});
Apollo Server throws errors automatically when applicable. For example, it throws a GRAPHQL_VALIDATION_FAILED error whenever an incoming operation isn't valid against the server's schema.
Your resolvers can also throw errors in situations where Apollo Server doesn't do so automatically.
For example, this resolver throws a custom error if the integer value provided for a user's ID is less than 1:
import { GraphQLError } from 'graphql';
const typeDefs = `#graphql
type Query {
userWithID(id: ID!): User
}
type User {
id: ID!
name: String!
}
`;
const resolvers = {
Query: {
userWithID: (_, args) => {
// highlight-start
if (args.id < 1) {
throw new GraphQLError('Invalid argument value', {
extensions: {
code: 'BAD_USER_INPUT',
},
});
}
// highlight-end
// ...fetch correct user...
},
},
};
If a resolver throws a generic error that is not a GraphQLError instance, that error is still thrown with an extensions field that includes a stacktrace and code (specifically INTERNAL_SERVER_ERROR), along with any other relevant error details.
Whenever you throw a GraphQLError, you can add arbitrary fields to the error's extensions object to provide additional context to the client. You specify these fields in an object you provide to the error's constructor.
This example builds on the one above by adding the name of the GraphQL argument that was invalid:
<ExpansionPanel title="Click to expand">import { GraphQLError } from 'graphql';
const typeDefs = `#graphql
type Query {
userWithID(id: ID!): User
}
type User {
id: ID!
name: String!
}
`;
const resolvers = {
Query: {
userWithID: (_, args) => {
if (args.id < 1) {
throw new GraphQLError('Invalid argument value', {
// highlight-start
extensions: {
code: 'BAD_USER_INPUT',
argumentName: 'id',
},
// highlight-end
});
}
// ...fetch correct user...
},
},
};
This results in a response like the following:
<ExpansionPanel title="Click to expand">{
"errors": [
{
"message": "Invalid argument value",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": ["userWithID"],
"extensions": {
"code": "BAD_USER_INPUT",
// highlight-start
"argumentName": "id",
// highlight-end
"stacktrace": [
"GraphQLError: Invalid argument value",
" at userWithID (/my-project/index.js:25:13)",
" ...more lines..."
]
}
}
]
}
stacktraceThe stacktrace error field is useful while developing and debugging your server, but you probably don't want to expose it to clients in production.
By default, Apollo Server omits the stacktrace field if the NODE_ENV environment variable is set to either production or test.
You can override this default behavior by passing the includeStacktraceInErrorResponses option to the constructor of ApolloServer. If includeStacktraceInErrorResponses is true, stacktrace is always included. If it's false, stacktrace is always omitted.
Note that when stacktrace is omitted, it's also unavailable to your application. To log error stacktraces without including them in responses to clients, see Masking and logging errors.
You can edit Apollo Server error details before they're passed to a client or reported to Apollo Studio. This enables you to omit sensitive or irrelevant data.
The ApolloServer constructor accepts a formatError hook that is run on each error before it's passed back to the client. You can use this function to log or mask particular errors.
The formatError hook receives two arguments: the first is the error formatted as a JSON object (to be sent with the response), and the second is the original error (wrapped in GraphQLError if thrown by a resolver).
The
formatErrorfunction does not modify errors that are sent to Apollo Studio as part of usage reporting. See For Apollo Studio reporting.
The below example returns a user-friendly message whenever Apollo Server throws a GRAPHQL_VALIDATION_FAILED error:
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
import { ApolloServerErrorCode } from '@apollo/server/errors';
const server = new ApolloServer({
typeDefs,
resolvers,
// highlight-start
formatError: (formattedError, error) => {
// Return a different error message
if (
formattedError.extensions.code ===
ApolloServerErrorCode.GRAPHQL_VALIDATION_FAILED
) {
return {
...formattedError,
message: "Your query doesn't match the schema. Try double-checking it!",
};
}
// Otherwise return the formatted error. This error can also
// be manipulated in other ways, as long as it's returned.
return formattedError;
},
// highlight-end
});
const { url } = await startStandaloneServer(server);
console.log(`🚀 Server listening at: ${url}`);
As another example, here we return a more generic error whenever the original error's message begins with Database Error: :
formatError: (formattedError, error) => {
// highlight-start
if (formattedError.message.startsWith('Database Error: ')) {
return { message: 'Internal server error' };
}
// highlight-end
// Otherwise return the formatted error.
return formattedError;
},
If you want to access the originally thrown error (without the JSON formatting), you can use formatError's second argument.
For example, if you are using a database package in your app and you'd like to do something when your server throws a specific type of database error:
formatError: (formattedError, error) => {
if (error instanceof CustomDBError) {
// do something specific
}
},
Note, if a resolver throws the error, a GraphQLError is wrapped around the initially thrown error. This GraphQLError neatly formats the error and contains useful fields, such as the path where the error occurred.
If you want to remove the outer GraphQLError to access the originally thrown error you can use unwrapResolverError from @apollo/server/errors. The unwrapResolverError function can remove the GraphQLError wrapping from a resolver error or return the error unaltered if it isn't from a resolver.
So, we can rewrite the above code snippet to work for errors thrown in and outside of resolvers, like so:
import { unwrapResolverError } from '@apollo/server/errors';
new ApolloServer({
formatError: (formattedError, error) => {
// unwrapResolverError removes the outer GraphQLError wrapping from
// errors thrown in resolvers, enabling us to check the instance of
// the original error
if (unwrapResolverError(error) instanceof CustomDBError) {
return { message: 'Internal server error' };
}
},
});
To make context-specific adjustments to the error received by
formatError(such as localization or personalization), consider creating a plugin that uses thedidEncounterErrorslifecycle event to attach additional properties to the error. These properties can be accessed fromformatError.
📣 New in Apollo Server 4+: error details are not included in traces by default. Instead,
<masked>replaces each error's message, and themaskedByerror extension replaces all other extensions. ThemaskedByextension includes the name of the plugin that performed the masking (ApolloServerPluginUsageReportingorApolloServerPluginInlineTrace).
You can use Apollo Studio to analyze your server's error rates. By default, the operations sent to Studio as detailed traces don't contain error details.
If you do want error information sent to Studio, you can send every error, or you can modify or redact specific errors before they're transmitted.
To send all errors to Studio you can pass { unmodified: true } to sendErrors, like so:
new ApolloServer({
// etc.
plugins: [
ApolloServerPluginUsageReporting({
// If you pass unmodified: true to the usage reporting
// plugin, Apollo Studio receives ALL error details
sendErrors: { unmodified: true },
}),
],
});
If you want to report specific errors or modify an error before reporting it, you can pass a function to the sendErrors.transform option, like so:
new ApolloServer({
// etc.
plugins: [
ApolloServerPluginUsageReporting({
sendErrors: {
transform: (err) => {
if (err.extensions.code === 'MY_CUSTOM_CODE') {
// returning null will skip reporting this error
return null;
}
// All other errors are reported.
return err;
},
},
}),
],
});
The usage reporting plugin is installed automatically with its default configuration if you provide an Apollo API key to Apollo Server. To customize the usage reporting plugin's behavior, you need to install it explicitly with a custom configuration, as shown in the examples below.
The function you pass to transform is called for each error (GraphQLError) to be reported to Studio. The error is provided as the function's first argument. The function can either:
err.message to remove potentially sensitive information)null to prevent the error from being reported entirelyNote that returning null also affects Studio's aggregated statistics about how many operations contain errors and at what paths those errors appear.
As mentioned above, you can use the unwrapResolverError (from @apollo/server/errors) to remove the GraphQLError wrapping an original error.
For federated graphs, define your
transformfunction in each subgraph's inline trace plugin to rewrite field errors. If you want to transform your gateway's parsing or validation errors, you can define yourtransformfunction in your gateway.
Let's say our server is throwing an UNAUTHENTICATED error whenever a user enters an incorrect password. We can avoid reporting these errors to Apollo Studio by defining a transform function, like so:
import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting';
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
// highlight-start
ApolloServerPluginUsageReporting({
sendErrors: {
transform: (err) => {
// Return `null` to avoid reporting `UNAUTHENTICATED` errors
if (err.extensions.code === 'UNAUTHENTICATED') {
return null;
}
// All other errors will be reported.
return err;
},
},
}),
// highlight-end
],
});
This example configuration ensures that any UNAUTHENTICATED error that's thrown within a resolver is only reported to the client, and never sent to Apollo Studio. All other errors are transmitted to Studio normally.
When generating an error (e.g., new GraphQLError("Failure!")), the error's message is the most common extension (in this case it's Failure!). However, any number of extensions can be attached to the error (such as a code extension).
We can check these extensions when determining whether an error should be reported to Apollo Studio using the transform function as follows:
import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting';
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
ApolloServerPluginUsageReporting({
sendErrors: {
transform: (err) => {
// Using a more stable, known error extension (e.g. `err.code`) would be
// more defensive, however checking the `message` might serve most needs!
// highlight-start
if (err.message && err.message.startsWith('Known error message')) {
return null;
}
// highlight-end
// All other errors should still be reported!
return err;
},
},
}),
],
});
This example configuration ensures that any error that starts with Known error message is not transmitted to Apollo Studio, but all other errors are sent as normal.
As mentioned above, by default, the operations sent to Studio as detailed traces don't contain error details.
If you do want to send an error's details to Apollo Studio, but need to redact some information first, the transform function can help.
For example, if there is personally identifiable information in the error message, like an API key:
import { GraphQLError } from 'graphql';
throw new GraphQLError(
"The x-api-key:12345 doesn't have sufficient privileges.",
);
The transform function can ensure that such information is not sent to Apollo Studio and potentially revealed outside its intended scope:
import { ApolloServer } from '@apollo/server';
import { ApolloServerPluginUsageReporting } from '@apollo/server/plugin/usageReporting';
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [
ApolloServerPluginUsageReporting({
sendErrors: {
transform: (err) => {
// Make sure that a specific pattern is removed from all error messages.
err.message = err.message.replace(/x-api-key:[A-Z0-9-]+/, 'REDACTED');
return err;
},
},
}),
],
});
In this case, the error above is reported to Apollo Studio as:
The REDACTED doesn't have sufficient privileges.
GraphQL, by design, does not use the same conventions from REST to communicate via HTTP verbs and status codes. Client information should be contained in the schema or as part of the standard response errors field. We recommend using the included Error Codes or Custom Errors for error consistency rather than directly modifying the HTTP response.
Apollo Server uses different HTTP status codes in various situations:
allowBatchedHttpRequests isn't enabled, if CSRF prevention blocks a request, or if the client provided invalid variables (eg, missing a required variable, or providing a variable of the wrong type).
status400ForVariableCoercionErrors: true option to your ApolloServer constructor. In Apollo Server v5, this option defaults to true and is deprecated; it will be removed in a future major version.GET with a mutation, or any HTTP method other than GET or POST), then Apollo Server responds with a 405 status code.context function throws, Apollo Server responds with a 500 status code.There are three ways to change an HTTP status code or set custom response headers, you can: throw an error in a resolver, throw an error in your context function, or write a plugin.
While Apollo Server does enable you to set HTTP status codes based on errors thrown by resolvers, best practices for GraphQL over HTTP encourage sending 200 whenever an operation executes. So, we don't recommend using this mechanism in resolvers, just in the context function or in a plugin hooking into an early stage of the request pipeline.
Be aware that GraphQL client libraries might not treat all response status codes the same, so it will be up to your team to decide which patterns to use.
To change the HTTP status code and response headers based on an error thrown in either a resolver or context function, throw a GraphQLError with an http extension, like so:
import { GraphQLError } from 'graphql';
const resolvers = {
Query: {
someField() {
throw new GraphQLError('the error message', {
extensions: {
code: 'SOMETHING_BAD_HAPPENED',
http: {
status: 404,
headers: new Map([
['some-header', 'it was bad'],
['another-header', 'seriously'],
]),
},
},
});
}
}
}
You don't need to include status unless you want to override the default status code (200 for a resolver or 500 for a context function). The optional headers field should provide a Map with lowercase header names.
If your setup includes multiple resolvers which throw errors that set status codes or set the same header, Apollo Server might resolve this conflict in an arbitrary way (which could change in future versions). Instead, we recommend writing a plugin (as shown below).
You can also set the HTTP status code and headers from a plugin. As an example, here is how you could set a custom response header and status code based on a GraphQL error:
const setHttpPlugin = {
async requestDidStart() {
return {
async willSendResponse({ response }) {
response.http.headers.set('custom-header', 'hello');
if (response.body.kind === 'single' &&
response.body.singleResult.errors?.[0]?.extensions?.code === 'TEAPOT') {
response.http.status = 418;
}
},
};
},
};
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [setHttpPlugin],
});