Variables, Directives & Fragments
|
This section documents the current GraphQL specification (October 2021), plus the working draft for
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production, as GraphQL tooling iterates quickly. |
This page covers the pieces of a GraphQL document that go beyond a flat selection set: variables that parameterize an operation, the built-in directives that conditionally include or skip fields, fragments that reuse and specialize selection sets, and the incremental-delivery directives that let a server send a response in more than one part. It assumes the query/mutation/field vocabulary from Queries and Fields.
Query variables
A hard-coded literal in a query document means the client has to rebuild the query string for every different argument value. Variables avoid that: an operation declares typed variables in parentheses after its name, and the client supplies their values separately from the query text.
query GetHuman($id: ID!, $withFriends: Boolean = false) {
human(id: $id) {
name
friends @include(if: $withFriends) {
name
}
}
}
The accompanying variables are sent as a JSON object alongside the query, not interpolated into it:
{
"id": "1000",
"withFriends": true
}
Each variable has a type — ID!, Boolean, a custom input type, and so on — and an optional default value
introduced with =, used whenever the client omits that variable entirely. A ! suffix marks the variable (and
therefore the argument it feeds) as non-nullable: the request is rejected during validation if a non-nullable
variable without a default is left out. Variables may only be referenced inside the operation that declares
them, and their declared type must be compatible with the type of every argument position where they are used.
See graphql.org’s guide to queries for the full variable and default-value
rules.
Executable directives: @include and @skip
A directive is an @name annotation attached to a field, fragment spread, or inline fragment that changes how
the executor treats it. GraphQL’s two built-in executable directives — so called because they act on a
client’s query document rather than on a schema definition — both take a single if: Boolean! argument:
query GetHuman($id: ID!, $showFriends: Boolean!, $hideAppearsIn: Boolean!) {
human(id: $id) {
name
friends @include(if: $showFriends) {
name
}
appearsIn @skip(if: $hideAppearsIn)
}
}
@include(if: $var) keeps the annotated field or fragment in the selection only when $var is true;
@skip(if: $var) removes it only when $var is true. Both are evaluated per request from the supplied
variable values, so the same query document can shape its own response without the client building several
variants of the string. If a field carries both @skip(if: true) and @include(if: true), @skip wins and the
field is omitted — see graphql.org’s directives section for the precise
interaction.
These two directives are part of the finalized spec and only affect how a document already sent to the server
is executed. They are unrelated to directives declared in a schema (custom directives such as @deprecated
or @auth that a schema author defines and applies to type-system locations); that side of directives — declaring one, choosing its locations, and using it to drive schema behavior — is covered on
Directives.
Named fragments
A fragment is a reusable, named selection set defined against a specific type. It lets several operations (or several places in the same operation) share a common set of fields instead of repeating them:
fragment HumanFields on Human {
id
name
homePlanet
friends {
name
}
}
query GetHuman($id: ID!) {
human(id: $id) {
...HumanFields
appearsIn
}
}
query CompareHumans($id1: ID!, $id2: ID!) {
first: human(id: $id1) {
...HumanFields
}
second: human(id: $id2) {
...HumanFields
}
}
fragment HumanFields on Human \{ … } declares the fragment’s name and its type condition — the type its
fields are selected against, Human here. …HumanFields is the fragment spread: wherever it appears, the
executor inlines the fragment’s selection set as if it had been written out directly. A fragment can spread
other fragments, and a single document can define several fragments that different operations mix and match.
Fragments are how most non-trivial GraphQL clients keep large selection sets maintainable; see
graphql.org’s guide to queries for the fragment section, including fragment
variables and reuse across a whole application.
Inline fragments and type conditions
An inline fragment is a fragment without a name, written directly inside a selection set. It exists mainly to select fields conditionally on the concrete type of a value when the field’s declared type is an interface or a union — something a flat selection set cannot express, because not every concrete type behind an interface or union implements the same fields:
query SearchResults($text: String!) {
search(text: $text) {
__typename
... on Human {
name
homePlanet
}
... on Droid {
name
primaryFunction
}
... on Starship {
name
length
}
}
}
… on Human \{ … } is the inline fragment’s type condition: its fields are only selected — and only
returned — when the runtime object at that position is actually a Human. Because a union type has no fields
of its own, every selection under a union field must live inside inline (or named) fragments with type
conditions; an interface field can mix directly-selected common fields with type-conditioned fragments for the
fields specific to each implementing type. This is the query-side counterpart to defining interfaces and unions
in the schema, covered in depth on
Interfaces, Unions & Inputs. An inline fragment can also
appear without a type condition, purely to attach a directive (… @include(if: $cond) \{ … }) to a group of
fields as a unit.
The __typename meta-field
typename is a meta-field implicitly available on every object, interface, and union type; it resolves to
the name of the concrete object type at that position in the response, as a String, without needing to be
declared in the schema. It is what lets a client tell, at runtime, which branch of an interface or union
selection actually matched — the SearchResults example above requests it for exactly that reason, and a
typed client (Apollo Client, Relay, urql) uses it to pick the right generated type for each item in the result.
Because it costs nothing to resolve and is invaluable for caching and debugging, it is common practice to
request typename on any selection that touches an interface or union. See
graphql.org’s guide to queries ("Meta fields") for the other built-in
meta-fields.
Incremental delivery: @defer and @stream
@defer and @stream are directives that let a server split a single response into several parts, sending the
fields that are cheap or already available immediately and following up with the rest as it becomes ready,
instead of holding the whole response until every field resolves. @defer, applied to a fragment spread or
inline fragment, marks a selection whose result can arrive later:
query GetHuman($id: ID!) {
human(id: $id) {
id
name
... @defer(label: "friendsDefer") {
friends {
name
}
}
}
}
@stream, applied to a list field, lets the server deliver the list’s items incrementally instead of waiting
for the whole list to resolve, optionally skipping an initial batch with initialCount:
query GetHuman($id: ID!) {
human(id: $id) {
id
name
friends @stream(initialCount: 2, label: "friendsStream") {
name
}
}
}
Conceptually, a @defer/@stream response arrives as an initial payload followed by one or more incremental
payloads, each identified by its label and a path into the response shape it patches, with a hasNext flag
telling the client whether more payloads are coming:
{
"data": { "human": { "id": "1000", "name": "Luke Skywalker" } },
"hasNext": true
}
{
"incremental": [
{
"label": "friendsDefer",
"path": ["human"],
"data": { "friends": [ { "name": "Han Solo" }, { "name": "Leia Organa" } ] }
}
],
"hasNext": false
}
@defer and @stream are not part of the finalized October 2021 GraphQL specification. They are defined by
the GraphQL working draft spec, are already widely implemented by mainstream
servers and clients (Apollo Server/Client, Relay, and others), but remain subject to change until finalized.
The exact multipart/streaming wire format sketched above — how payloads are framed and transported over HTTP — is itself part of that incremental-delivery specification work, not the query-language grammar; check a given
server’s and client’s own documentation for the transport it actually implements before relying on the response
shape above verbatim.