Directives
|
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. |
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: |
Executable (operation) directives |
Locations inside a client’s query document: |
\@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 |
|---|---|---|
|
Executable |
Removes the annotated field or fragment from the selection when |
|
Executable |
Keeps the annotated field or fragment in the selection only when |
|
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. |
|
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
-
Variables, Directives & Fragments for the executable
@include/@skip/@defer/@streamdirectives applied from the client side. -
Interfaces, Unions & Inputs for
@specifiedByand custom scalar serialization. -
Schema Design for deprecation as part of a longer-lived schema-evolution strategy.
-
Introspection for the
Schema/Directivetypes this page’s final example queries.