Variables, Directives & Fragments

This section documents the current GraphQL specification (October 2021), plus the working draft for @defer/@stream/@oneOf, and the GraphQL-over-HTTP specification, as published at graphql.org and spec.graphql.org — which are the references these pages are written and verified against — and, for the integration pages, against Spring for GraphQL (2.0.x), Strawberry, Ariadne, and the client docs for Apollo Client / urql / Relay. No specific patch version is pinned. Some surfaces (Apollo Router/managed federation, graphql-ws internals, the Relay compiler internals, GraalVM native) are linked rather than documented in depth.

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.