Handling operation errors

Learn how to manage errors in your application


Apollo Client can encounter a variety of errors when executing operations on your GraphQL server. Apollo Client helps you handle these errors according to their type, enabling you to show appropriate information to the user when an error occurs.

Understanding errors

Errors in Apollo Client fall into two main categories: GraphQL errors and network errors. Each category has specific error classes that provide detailed information about what went wrong. Apollo Client adds any encountered errors to the error field returned by hooks like useQuery, wrapped in the appropriate error instance.

GraphQL errors

These are errors related to the server-side execution of a GraphQL operation. They include:

  • Syntax errors (e.g., a query was malformed)

  • Validation errors (e.g., a query included a schema field that doesn't exist)

  • Resolver errors (e.g., an error occurred while attempting to populate a query field)

If a syntax error or validation error occurs, your server doesn't execute the operation at all because it's invalid. If resolver errors occur, your server can still return partial data.

When a GraphQL error occurs, your server includes it in the errors array of its response:

Example error response
JSON
1{
2  "errors": [
3    {
4      "message": "Cannot query field \"nonexistentField\" on type \"Query\".",
5      "locations": [
6        {
7          "line": 2,
8          "column": 3
9        }
10      ],
11      "extensions": {
12        "code": "GRAPHQL_VALIDATION_FAILED",
13        "exception": {
14          "stacktrace": [
15            "GraphQLError: Cannot query field \"nonexistentField\" on type \"Query\".",
16            "...additional lines..."
17          ]
18        }
19      }
20    }
21  ],
22  "data": null
23}

In Apollo Client, GraphQL errors are represented by the CombinedGraphQLErrors error type. This is the most common error type you'll encounter in your application.

note
If a GraphQL error prevents your server from executing your operation at all, your server may respond with a non-2xx status code. Apollo Client handles this according to the GraphQL Over HTTP specification.

Partial data with resolver errors

An operation that produces resolver errors might also return partial data. This means that some (but not all) of the data your operation requested is included in your server's response. Apollo Client ignores partial data by default to avoid caching null values due to errors, but you can override this behavior by setting a GraphQL error policy.

Network errors

These are errors encountered while attempting to communicate with your GraphQL server. The error may be a result of a 4xx or 5xx response status code, a failure to communicate with the server (such as when the network is unavailable), a failure to parse the response as valid JSON, or a custom error thrown in an Apollo Link request handler.

In Apollo Client, network errors are represented by these error types:

  • CombinedProtocolErrors - Represents fatal transport-level errors that occur during multipart HTTP subscription execution.

  • ServerError - Occurs when the server responds with a non-200 HTTP status code

  • ServerParseError - Occurs when the server response cannot be parsed as valid JSON

  • LocalStateError - Represents errors in local state configuration or execution

  • UnconventionalError - Wraps non-standard errors (e.g., thrown symbols or objects) to ensure consistent error handling

  • Any other errors thrown inside the link chain (e.g. by browser APIs or other libraries) will be passed through as they are, as long as they are an object with at least a message property.

When a network error occurs, Apollo Client adds it to the error field returned by the useQuery hook (or whichever operation hook you used) as the error that was returned by the link chain (typically an Error instance).

You can add retry logic and other advanced network error handling to your application with Apollo Link.

Identifying error types

Every Apollo Client error class provides a static is method that reliably determines whether an error is of that specific type. This method provides a more robust alternative to instanceof because it avoids false positives and negatives, such as errors constructed in another realm (see Error.isError for more information).

TypeScript
1import {
2  CombinedGraphQLErrors,
3  CombinedProtocolErrors,
4  LocalStateError,
5  ServerError,
6  ServerParseError,
7  UnconventionalError,
8} from "@apollo/client/errors";
9
10// Comprehensive error handling example.
11function handleError(error: unknown) {
12  if (CombinedGraphQLErrors.is(error)) {
13    // Handle GraphQL errors
14  } else if (CombinedProtocolErrors.is(error)) {
15    // Handle multipart subscription protocol errors
16  } else if (LocalStateError.is(error)) {
17    // Handle errors thrown by the `LocalState` class
18  } else if (ServerError.is(error)) {
19    // Handle server HTTP errors
20  } else if (ServerParseError.is(error)) {
21    // Handle JSON parse errors
22  } else if (UnconventionalError.is(error)) {
23    // Handle errors thrown by irregular types
24  } else {
25    // Handle other errors
26  }
27}

All error classes extend the standard JavaScript Error class and provide additional properties specific to their error type. See the individual error class references for detailed information about each error class.

GraphQL error policies

If a GraphQL operation produces one or more GraphQL errors, your server's response might still include partial data in the data field:

JSON
1{
2  "data": {
3    "getInt": 12,
4    "getString": null
5  },
6  "errors": [
7    {
8      "message": "Failed to get string!"
9      // ...additional fields...
10    }
11  ]
12}

By default, Apollo Client throws away partial data and populates the error field of the useQuery hook (or whichever hook you're using). You can instead use these partial results by defining an error policy for your operation.

Apollo Client supports the following error policies:

PolicyDescription
noneIf the response includes errors, they are returned in the error field and the response data is set to undefined even if the server returns data in its response. This means network errors and GraphQL errors result in a similar response shape. This is the default error policy.
ignoreErrors are ignored (error is not populated), and any returned data is cached and rendered as if no errors occurred. Note: data may also be undefined in the event a network error occurs.
allBoth data and error are populated and any returned data is cached, enabling you to render both partial results and error information.

Setting an error policy

Specify an error policy in the options object you provide your operation hook (such as useQuery), like so:

JavaScript
1const MY_QUERY = gql`
2  query WillFail {
3    badField # This field's resolver produces an error
4    goodField # This field is populated successfully
5  }
6`;
7
8function ShowingSomeErrors() {
9  const { loading, error, data } = useQuery(MY_QUERY, { errorPolicy: "all" });
10
11  if (loading) return <span>loading...</span>;
12
13  return (
14    <div>
15      <h2>Good: {data?.goodField}</h2>
16      <pre>Bad: {error && <span>{error.message}</span>}</pre>
17    </div>
18  );
19}

This example uses the all error policy to render both partial data and error information whenever applicable.

Error handling across API types

The way Apollo Client exposes errors and how you handle them depends on both the API type you're using and the configured error policy.

Apollo Client provides three types of APIs, each with different error handling characteristics:

  • Promise-based APIs (e.g. client.query, client.mutate) - Errors either reject the promise or are returned in the result as the error field.

  • Observable-based APIs (e.g. client.watchQuery) - Errors are emitted through the observer next callback.

  • React hooks (e.g. useQuery, useMutation) - Errors are provided as part of the hook's return value.

Promise-based APIs

Promise-based APIs, like client.query and client.mutate, return promises that resolve or reject based on your error policy:

errorPolicy: "none"

With the default none error policy, an error causes the promise to reject. Use try-catch or the .catch method to prevent unhandled promise rejections.

TypeScript
1import { CombinedGraphQLErrors } from "@apollo/client/errors";
2
3try {
4  const result = await client.query({
5    query: MY_QUERY,
6    errorPolicy: "none", // default
7  });
8  console.log(result.data);
9} catch (error) {
10  if (CombinedGraphQLErrors.is(error)) {
11    console.log("GraphQL errors:", error.errors);
12  }
13  // Network errors and other errors also reject the promise
14}
errorPolicy: "all"

The all error policy allows promises to resolve successfully even when errors occur. Access the error in the error field on the returned result.

TypeScript
1import { CombinedGraphQLErrors } from "@apollo/client/errors";
2
3// With errorPolicy: "all" - returns both data and errors
4const result = await client.query({
5  query: MY_QUERY,
6  errorPolicy: "all",
7});
8
9if (result.error && CombinedGraphQLErrors.is(result.error)) {
10  console.log("GraphQL errors:", result.error.errors);
11  console.log("Partial data:", result.data); // May contain partial data
12}
errorPolicy: "ignore"

The ignore error policy causes Apollo Client to discard errors entirely, treating operations as if they succeeded. The promise resolves with available data and no error information.

TypeScript
1import { CombinedGraphQLErrors } from "@apollo/client/errors";
2
3// With errorPolicy: "ignore" - returns data but ignores errors
4const result = await client.query({
5  query: MY_QUERY,
6  errorPolicy: "ignore",
7});
8
9// result.error will be undefined even if an error occurs
10console.log("Data (errors ignored):", result.data);

Observable-based APIs

Observable-based APIs like client.watchQuery emit values and errors through the observer next callback. The error policy determines how the data and error properties are set:

errorPolicy: "none"

Errors are included as part of the result object emitted to the next callback through the error field, allowing the observable to continue receiving updates. data is always undefined when error is present.

TypeScript
1import { CombinedGraphQLErrors } from "@apollo/client/errors";
2
3const observable = client.watchQuery({
4  query: MY_QUERY,
5  errorPolicy: "none", // default
6});
7
8observable.subscribe({
9  next(result) {
10    if (result.error) {
11      console.log("Got error:", result.error.message);
12    } else {
13      console.log("Result:", result.data);
14    }
15  },
16});
errorPolicy: "all"

Errors are included as part of the result object emitted to the next callback through the error field, allowing the observable to continue running while providing access to both partial data and error information.

TypeScript
1const observable = client.watchQuery({
2  query: MY_QUERY,
3  errorPolicy: "all",
4});
5
6observable.subscribe({
7  next(result) {
8    if (result.error) {
9      console.log("Got error:", result.error.message);
10      console.log("Partial data", result.data);
11    } else {
12      console.log("Result:", result.data);
13    }
14  },
15});
errorPolicy: "ignore"

Results are emitted to the next callback with GraphQL errors completely discarded. The observable continues running as if no errors occurred, which is useful for non-critical data that can be safely omitted when errors happen.

TypeScript
1const observable = client.watchQuery({
2  query: MY_QUERY,
3  errorPolicy: "ignore",
4});
5
6observable.subscribe({
7  next(result) {
8    console.log("Result:", result.data);
9  },
10});

React hooks

React hooks like useQuery and useMutation provide errors through the error property in their return value, regardless of error policy. The error policy affects whether data is populated alongside the error.

TypeScript
1import { useQuery, useMutation } from "@apollo/client";
2import { CombinedGraphQLErrors } from "@apollo/client/errors";
3
4function MyComponent() {
5  const { data, loading, error } = useQuery(MY_QUERY);
6
7  if (error) {
8    if (CombinedGraphQLErrors.is(error)) {
9      return <div>Query failed: {error.errors[0].message}</div>;
10    }
11
12    return <div>Network error: {error.message}</div>;
13  }
14
15  return <div>{/* render data */}</div>;
16}
note
Suspense hooks, such as useSuspenseQuery, have different error handling behavior - they might throw errors to React error boundaries based on the configured errorPolicy, rather than returning them in the hook result. Learn more about error handling with Suspense hooks in the Suspense documentation.

The Apollo Link class enables you to configure advanced handling of errors that occur while executing GraphQL operations.

As a recommended first step, you can add an ErrorLink to your link chain that receives error details and acts on them.

The following example provides a link chain to the ApolloClient constructor with two links:

  • An ErrorLink that receives error details through the error handler callback. It logs the details of errors it finds.

  • An HttpLink that sends each GraphQL operation to your server (this is the chain's terminating link.)

Click to expand example
JavaScript
1import {
2  ApolloClient,
3  HttpLink,
4  InMemoryCache,
5  CombinedGraphQLErrors,
6  from,
7} from "@apollo/client";
8import { ErrorLink } from "@apollo/client/link/error";
9
10const errorLink = new ErrorLink(({ error }) => {
11  if (CombinedGraphQLErrors.is(error)) {
12    error.errors.forEach(({ message, locations, path }) =>
13      console.log(
14        `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
15      )
16    );
17  } else {
18    console.error("[Network error]:", error);
19  }
20});
21
22const httpLink = new HttpLink({ uri: "http://localhost:4000/graphql" });
23
24const client = new ApolloClient({
25  cache: new InMemoryCache(),
26  link: from([errorLink, httpLink]),
27});

Retrying operations

Apollo Link helps you retry failed operations that might be resolved by a followup attempt. We recommend different links depending on the type of error that occurred:

On GraphQL errors

The ErrorLink can retry a failed operation based on the type of GraphQL error that's returned. For example, when using token-based authentication, you might want to automatically handle re-authentication when the token expires.

To retry an operation, you return forward(operation) in your error handler function. Here's an example:

JavaScript
1new ErrorLink(({ error, operation, forward }) => {
2  if (CombinedGraphQLErrors.is(error)) {
3    for (let err of error.errors) {
4      switch (err.extensions.code) {
5        // Apollo Server, for example, sets `code` to `"UNAUTHENTICATED"`
6        // when an AuthenticationError is thrown in a resolver
7        case "UNAUTHENTICATED": {
8          // Modify the operation context with a new token
9          const oldHeaders = operation.getContext().headers;
10          operation.setContext({
11            headers: {
12              ...oldHeaders,
13              authorization: getNewToken(),
14            },
15          });
16          // Retry the request, returning the new observable
17          return forward(operation);
18        }
19      }
20    }
21  }
22
23  // Log the error for any unhandled GraphQL errors or network errors.
24  console.log(`[Error]: ${error.message}`);
25
26  // If nothing is returned from the error handler callback, the error will be
27  // emitted from the link chain as normal.
28});
note
If your retried operation also results in errors, those errors are not passed to ErrorLink to prevent an infinite loop of operations. This means that an ErrorLink can retry a particular operation only once.

If you don't want to retry an operation, your ErrorLink's error handler function should return nothing.

See the ErrorLink API reference for more details.

On network errors

To retry operations that encounter a network error, we recommend adding a RetryLink to your link chain. This link enables you to configure retry logic like exponential backoff and total number of attempts.

See the RetryLink API reference for more details.

Additional resources

Feedback

Edit on GitHub

Ask Community