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
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.
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 codeServerParseError
- Occurs when the server response cannot be parsed as valid JSONLocalStateError
- Represents errors in local state configuration or executionUnconventionalError
- Wraps non-standard errors (e.g., thrown symbols or objects) to ensure consistent error handlingAny 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).
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:
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:
Policy | Description |
---|---|
none | If 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. |
ignore | Errors 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. |
all | Both 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:
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 theerror
field.Observable-based APIs (e.g.
client.watchQuery
) - Errors are emitted through the observernext
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.
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.
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.
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.
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.
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.
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.
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}
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.Advanced error handling with Apollo Link
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
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:
The
ErrorLink
for GraphQL errorsThe
RetryLink
for network errors
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:
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});
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.