Directives

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.

A directive is an @name annotation that attaches to a specific place in either a schema definition or a query document and changes how the server treats whatever it’s attached to. This page covers the directive syntax itself, the two kinds of directive location, the four built-in directives, and how a schema author defines and applies a custom one.

The @ directive syntax

Every directive is declared once, with a directive definition in the schema, and then applied by writing @name (with optional parenthesized arguments) immediately after whatever it modifies — a field, a type definition, a fragment spread, and so on:

directive @deprecated(
  reason: String = "No longer supported"
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION

The on clause lists every DirectiveLocation where the directive is legal to apply — the SDL location names above (FIELD_DEFINITION, ARGUMENT_DEFINITION, …​) come directly from the spec’s Type System > Directives section, which enumerates the complete set of type-system and executable locations. A directive’s arguments work exactly like a field’s arguments: named, typed, and optionally defaulted.

Type-system directives vs. executable directives

The on clause’s location list splits into two disjoint families, and a directive can only ever belong to one of them:

Kind Where it applies

Type-system (schema) directives

Locations inside a schema definition: SCHEMA, SCALAR, OBJECT, FIELD_DEFINITION, ARGUMENT_DEFINITION, INTERFACE, UNION, ENUM, ENUM_VALUE, INPUT_OBJECT, INPUT_FIELD_DEFINITION. Applied once, in the SDL, when the schema is built — they describe the schema itself, not any one request.

Executable (operation) directives

Locations inside a client’s query document: QUERY, MUTATION, SUBSCRIPTION, FIELD, FRAGMENT_DEFINITION, FRAGMENT_SPREAD, INLINE_FRAGMENT, VARIABLE_DEFINITION. Applied per request, in the query text the client sends — they change how that one document is executed.

\@deprecated above is a type-system directive: it marks a field or enum value as deprecated once, in the schema, for every client. @include/@skip, covered next, are executable directives: a client applies them per query to conditionally shape that query’s own selection set — Variables, Directives & Fragments covers @include and @skip (and the working-draft @defer/@stream incremental-delivery directives, also executable) in depth, including how the executor evaluates their if argument against supplied variables; this page does not repeat that material and instead focuses on the type-system side. See graphql.org/learn/schema (Directives section) for the introductory framing of both families, and the spec section linked above for the authoritative location list.

The built-in directives

The specification defines four directives out of the box — one executable pair, one executable-and-schema hybrid, and one purely schema-facing:

Directive Kind Purpose

@skip(if: Boolean!)

Executable

Removes the annotated field or fragment from the selection when if is true.

@include(if: Boolean!)

Executable

Keeps the annotated field or fragment in the selection only when if is true.

@deprecated(reason: String)

Type-system

Marks a field or enum value as deprecated, with an optional human-readable reason; introspection surfaces both the flag and the reason to tooling and clients.

@specifiedBy(url: String!)

Type-system

Attaches a specification URL to a custom scalar, telling clients and tooling exactly what serialization format it follows.

@skip and @include are detailed on Variables, Directives & Fragments; @specifiedBy is detailed, with a worked custom-scalar example, on Interfaces, Unions & Inputs. @deprecated is the one this page covers in full, since it’s the type-system directive most schemas apply directly and repeatedly:

type Product {
  id: ID!
  name: String!
  price: Float!
  legacySku: String @deprecated(reason: "Use `sku` instead; removed in a future release.")
  sku: String!
}

enum ShippingMethod {
  STANDARD
  EXPRESS
  OVERNIGHT
  FREIGHT @deprecated(reason: "Freight shipping was discontinued; use EXPRESS for bulk orders.")
}

A deprecated field or enum value keeps working for existing clients — @deprecated is a signal, not a removal — but introspection-aware tooling (GraphiQL, Apollo Sandbox, IDE plugins) greys it out in autocomplete and surfaces the reason text, which is why supplying a concrete, actionable reason matters more than the bare annotation. Schema Design covers @deprecated as part of a broader schema-evolution strategy — when to deprecate versus rename, and how long a deprecated field typically stays before removal.

Defining and applying a custom schema directive

A schema author can declare directives of their own wherever the built-ins don’t cover a cross-cutting concern the schema wants to express declaratively — authorization, rate limiting, formatting hints, and cache control are common examples. Declaring one follows the same directive …​ on <locations> form shown above:

directive @auth(role: String!) on FIELD_DEFINITION | OBJECT

directive @uppercase on FIELD_DEFINITION

type Query {
  publicPosts: [Post!]!
  adminReport: Report! @auth(role: "ADMIN")
}

type User {
  id: ID!
  email: String! @auth(role: "SELF_OR_ADMIN")
  displayName: String! @uppercase
}

Declaring @auth and @uppercase only adds two entries to the schema’s type-system directive list and marks where each may be applied — it does not, by itself, make the server do anything. A schema directive like these needs a corresponding piece of server code, usually called a directive resolver or SDL visitor, that walks the built schema, finds every location where the directive is applied, and wraps or rewrites the underlying resolver there — for example, wrapping adminReport’s resolver in a check that rejects the request unless the caller’s role matches `@auth’s `role argument, or wrapping displayName’s resolver so its returned string is upper-cased before being sent. The mechanism for writing that wrapper is necessarily framework-specific (a schema-directive visitor in `graphql-tools, a directive-aware plugin in Apollo Server, a SchemaDirectiveWiring in graphql-java, and so on) rather than part of the language-agnostic specification itself, so consult the server framework’s own documentation for its exact hook; the declaration and application syntax shown above, by contrast, is portable across every conforming GraphQL server. Spring Boot: Getting Started and the Spring Boot transports page cover the equivalent, framework-native way to attach cross-cutting behavior (@GraphQlExceptionHandler, interceptors) on the JVM, which is often a more idiomatic fit there than porting a custom directive resolver.

This page is generated with AI assistance. Verify directive syntax and behavior against the specification and graphql.org/learn/schema before relying on it.

Introspecting directives

Because directive declarations are themselves part of the schema, introspection exposes them like any other schema element: the Schema type’s directives: [Directive!]! field lists every directive known to the schema — built-in and custom alike — with each __Directive’s own `name, description, locations, and args. A client or tool can therefore discover @auth and @uppercase from the example above the same way it discovers @deprecated, without any out-of-band documentation:

query DirectiveIntrospection {
  __schema {
    directives {
      name
      description
      locations
      args {
        name
        type {
          name
        }
      }
    }
  }
}

Introspection covers the full Schema/Type introspection system this query draws on, including how tooling such as GraphiQL uses it to build autocomplete and documentation panels.

Next steps