What Directives Do
A directive is a @name(args) annotation attached to a field, fragment, or schema definition that changes how the GraphQL execution engine or schema-building tooling treats that location, without requiring a new keyword in the query language itself. Directives come in two flavors: executable directives, which appear in a query/mutation/subscription document and affect execution — like the built-in @include and @skip — and type-system directives, which appear in the schema definition itself and affect validation, code generation, or resolver behavior, such as @deprecated or custom directives like @auth.
Cricket analogy: A directive is like a fielding instruction shouted mid-over — 'skip the review' or 'include the replay' — that changes how a specific ball is handled without rewriting the entire match rulebook.
Built-in Executable Directives
The GraphQL specification defines three built-in directives that every spec-compliant server supports out of the box: @include(if: Boolean) includes a field only when the condition is true, @skip(if: Boolean) omits a field when the condition is true, and @deprecated(reason: String) marks a schema field or enum value as deprecated with an explanation surfaced in introspection and tooling like GraphiQL. @include and @skip are evaluated purely on the client's declared variables at query time — they let one query document conditionally shape its own response instead of requiring the client to maintain multiple near-duplicate query strings.
Cricket analogy: @include(if: $showReplay) is like a broadcast director's toggle to cut to the DRS review footage only when the umpire actually calls for it, avoiding a separate broadcast feed for every possible scenario.
query PlayerProfile($id: ID!, $showStats: Boolean!, $hideBio: Boolean!) {
player(id: $id) {
name
bio @skip(if: $hideBio)
careerStats @include(if: $showStats) {
matches
average
}
legacyRanking @deprecated(reason: "Use eloRating instead")
}
}@include and @skip can both be applied to the same field simultaneously; if @skip(if: true) is present, it takes precedence and the field is omitted regardless of the @include condition, per the GraphQL specification's execution rules.
Custom Directives for Cross-Cutting Concerns
Beyond the built-ins, GraphQL lets a schema author define custom directives — commonly type-system directives applied in the SDL, such as @auth(requires: Role), @rateLimit(max: Int, window: String), or @cost(complexity: Int) — that a server implementation reads and enforces through directive resolvers, schema transformers, or middleware wired up in tools like graphql-tools's mapSchema or Apollo Server's schema directives API. This turns cross-cutting concerns like authorization, rate limiting, and field-level cost estimation into declarative annotations sitting right next to the field they govern, instead of scattering imperative checks throughout resolver code.
Cricket analogy: A custom @auth(requires: SELECTOR) directive is like stamping 'selectors only' on a confidential team-selection document, so the access rule lives right on the document itself instead of a separate rulebook someone has to remember to check.
directive @auth(requires: Role = USER) on FIELD_DEFINITION
enum Role {
USER
ADMIN
}
type Mutation {
deleteUser(id: ID!): Boolean! @auth(requires: ADMIN)
updateProfile(input: ProfileInput!): User! @auth(requires: USER)
}Custom directives declared in SDL do nothing on their own — the directive definition only documents where it's legal to use; the actual enforcement logic must be wired up separately via a schema transformer or directive resolver. Forgetting to implement the enforcement side is a common source of security bugs where a directive like @auth looks like it's protecting a field but silently does nothing at runtime.
Directive Locations and Validation
Every directive definition declares the exact locations it's legal to appear in — FIELD, FIELD_DEFINITION, FRAGMENT_SPREAD, OBJECT, ARGUMENT_DEFINITION, and many more — using the on keyword in its SDL declaration, and the GraphQL validator rejects a query or schema at parse/build time if a directive is used somewhere it isn't declared for. This location system is what keeps directives predictable: @include/@skip are only valid on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT (executable locations), while something like @deprecated is valid on FIELD_DEFINITION | ENUM_VALUE (type-system locations), so a schema author can't accidentally misuse one in the wrong context.
Cricket analogy: Directive locations are like ICC playing-condition clauses that only apply to specific match formats — a Powerplay rule valid only in T20 and ODI locations is rejected outright if someone tries to invoke it during a Test match.
- Directives are
@name(args)annotations that modify execution or schema behavior without new query-language syntax. - Executable directives (
@include,@skip) appear in operations and conditionally shape the response at query time. - Type-system directives (
@deprecated, custom ones) appear in the schema and affect validation, tooling, or resolver behavior. - Custom directives like
@author@rateLimitdeclaratively express cross-cutting concerns next to the fields they govern. - Declaring a custom directive in SDL only documents its legal locations — enforcement logic must be implemented separately.
- Every directive declares valid locations via
on, and the validator rejects misuse outside those locations. @skip(if: true)takes precedence over@includewhen both are applied to the same field.
Practice what you learned
1. Which three directives are built into the GraphQL specification?
2. What happens if both @include(if: true) and @skip(if: true) are applied to the same field?
3. What must happen for a custom directive like @auth to actually enforce anything at runtime?
4. What does the `on` keyword specify in a directive definition?
5. Where would @deprecated typically be a valid directive location?
Was this page helpful?
You May Also Like
Pagination Patterns: Cursor vs Offset
Compare offset-based and cursor-based pagination in GraphQL, and understand why the Relay connection spec is the industry-standard approach.
Authentication and Authorization in GraphQL
Learn how to authenticate requests and enforce field-level authorization in a GraphQL API, and avoid common security pitfalls.
Schema Stitching and Federation
Learn the two main approaches to composing multiple GraphQL services into a single unified graph, and when to choose each.
Related Reading
Related Study Notes in Web Development
Browse all study notesWebSockets Study Notes
Web Development · 30 topics
Web DevelopmentWebAssembly Study Notes
WebAssembly · 30 topics
Web DevelopmentgRPC Study Notes
Protocol Buffers · 30 topics
Web DevelopmentSpring Boot Study Notes
Java · 30 topics
Web DevelopmentFlask Study Notes
Python · 30 topics
Web DevelopmentDjango Study Notes
Python · 30 topics