Skip to main content
This page shows the common @defer patterns and the response parts that each one produces. The responses come from the router’s test suite, so they show exactly what the router sends. Defer describes the multipart framing. This page shows only the JSON payload of each part.

Schema used on this page

The examples run against the Cosmo demo, a federated graph with an Employee entity spread over several subgraphs. Employee.derivedMood declares @requires(fields: "currentMood"), so it depends on the mood subgraph.

Defer a fragment from another subgraph

This is the most common pattern. The router returns the fields from a fast subgraph first. The fields from a slow subgraph follow.
Initial response:
Incremental response:
The pending entry tells the client that the path of fragment 1 is ["employee"]. The incremental entry delivers hobbies for that path. The position of the @defer fragment inside the selection set does not matter. A fragment before details produces the same two parts.

Defer the whole query

A @defer fragment at the root selection set defers every field. The initial response is then empty. This is rarely useful on its own. The router allows it, because if variables can turn individual fragments on and off. Different variable values then produce different response shapes.
Initial response:
Incremental response:
The initial response arrives as soon as the router validates and plans the operation. data is an empty object, and path is empty, because the deferred fragment is on the root.

Nested @defer

A client can nest a fragment inside another deferred fragment. The router delivers the inner fragment after the outer one.
Initial response:
First incremental response:
Second incremental response:
The initial response announces only fragment 1. The router announces fragment 2 in the first incremental response, together with the delivery of its parent. A client therefore updates its pending map from every part, not only from the first.

Sibling @defer fragments

Two fragments at the same level are independent. The router delivers each one in its own part as soon as it is ready.
The initial response announces both fragments:
Two more parts follow, one per fragment. The part that completes the last outstanding fragment carries hasNext: false. The fragments run concurrently. The arrival order of the two parts depends on which subgraph answers first. A client matches a part to a fragment by id, never by arrival order.

@defer inside a list

The router announces a fragment inside a list field once, and delivers it once for all list items. Each item gets its own incremental entry, with its own subPath.
Initial response:
Incremental response:
products has three items, and one of them is a Consultancy. The pending entry points at the list, ["products"]. The incremental entry adds [0] as subPath, so the client merges it into products[0]. The list items appear as {} in the initial response, because the fragment covers all of their selected fields. A list with several matching items produces several incremental entries in the same part. Each entry carries its own index.

Fields selected both inside and outside @defer

The router delivers a field selected outside a deferred fragment in the initial response. This also applies when the same field is inside the fragment.
Initial response:
Incremental response:
details.forename is in the initial response. The router defers details.location. It arrives with subPath: ["details"], because it belongs to an object below the path of the fragment. One fragment can therefore produce several incremental entries in one part.

Named fragments

@defer works on fragment spreads and inside fragment bodies.
This is equivalent to an inline fragment with the same selections. The initial response carries the list with id only. It announces one fragment for the whole list:
The incremental response contains one incremental entry per employee. Each entry carries its list index as subPath. A @defer inside the fragment body, for example around hobbies, has the same effect as an inline fragment at that position in every spread.

Labels

A label names a fragment in the response.
Initial response:
Incremental response:
The router returns the label on the pending entry only. incremental and completed entries reference the fragment by id. A label is a static string. A label must be unique across all @defer and @stream directives in the document.

Conditional @defer

The if argument turns deferral on and off per request.
With {"deferHobbies": true}, the response is a multipart response with two parts. With {"deferHobbies": false}, the router returns one JSON response with Content-Type: application/json. data then contains hobbies:
The variable value belongs to the cache key of the query plan. The router plans each variant once and caches them separately. The cache warmer accepts operations with @defer.

Header propagation

Header forwarding rules apply to every subgraph request of an operation. They also apply to the requests for deferred fragments. A header from the client request reaches the deferred subgraphs and the primary subgraphs in the same way.

Clients

curl

-N disables output buffering, so curl prints each part when it arrives. The output shows the raw multipart stream:
Add -i to print the response headers as well, including Content-Type: multipart/mixed; boundary="graphql"; incrementalSpec=v0.2.

fetch

The browser fetch API exposes the response as a stream. The following example reads the stream and splits it at the part boundary. It prints the JSON payload of each part when the part arrives.
The console shows the two payloads from the curl example above. The stream ends when the reader reports done. To build the complete result, merge each incremental entry at pending[id].path, followed by subPath. Payload fields describes this merge.

Apollo Client

Apollo Client 4.1 and newer supports the response format the router sends. Configure the GraphQL17Alpha9Handler as the incremental handler:
The handler sends Accept: multipart/mixed;incrementalSpec=v0.2 for operations that contain @defer. The router accepts that header. A query with @defer then delivers partial data to useQuery and watchQuery as parts arrive. dataState moves from streaming to complete. Apollo Client rewrites the operation before it sends the operation. It adds a generated label such as ac_0 to every @defer fragment. The router returns those labels on the corresponding pending entries. The Defer20220824Handler implements the older format of Apollo Router. It sends Accept: multipart/mixed;deferSpec=20220824. That handler merges the deferred data at the wrong place. The router therefore rejects such a request with the error code DEFER_UNSUPPORTED_SPEC. See Send a request for the error body. Use GraphQL17Alpha9Handler.