CONCEPT Cited by 2 sources
GraphQL schema directive¶
Definition¶
A GraphQL schema directive is a directive whose declaration permits it on one or more TypeSystem locations — the parts of the schema that describe types rather than executable queries. Schema directives declare server-side behaviour, validation, or resolution logic attached to a schema element; clients never write schema directives in their queries.
The GraphQL spec enumerates 11 TypeSystem locations a directive may target:
| Location | Example |
|---|---|
SCHEMA |
schema @foo { query: Query } |
SCALAR |
scalar x @foo |
OBJECT |
type Product @foo { } |
FIELD_DEFINITION |
type X { field: String @foo } |
ARGUMENT_DEFINITION |
type X { field(arg: Int @foo): String } |
INTERFACE |
interface X @foo {} |
UNION |
union X @foo = A \| B |
ENUM |
enum X @foo { A B } |
ENUM_VALUE |
enum X { A @foo B } |
INPUT_OBJECT |
input X @foo { } |
INPUT_FIELD_DEFINITION |
input X { field: String @foo } |
The spec's built-in schema directives are @deprecated and
@specifiedBy; everything else is custom per-organisation.
Contrast with query directives¶
The partner primitive
query directive
targets ExecutableDefinition locations — the parts of
queries, not schemas (QUERY, MUTATION, FIELD,
FRAGMENT_DEFINITION, etc.). The two directive classes
carry different purposes:
| Aspect | Schema directive | Query directive |
|---|---|---|
| Written by | Schema author (server team) | Client author |
| Carries | Server-side behaviour / validation / resolver logic | Client-side metadata |
| Lifecycle | Tied to the deployed schema | Embedded in the operation |
| Example | @isAuthenticated (authorize) |
@tracingTag (tag the span) |
| Composable with resolvers | Yes (graphql-tools wraps) | No (extract from AST instead) |
Zalando's 2023 survey names the distinction directly: "the query directives are generally useful for clients to express certain types of metadata for the query. The schema directives are generally useful for declaratively specifying common server-side behaviors, for example, authorization requirements, marking sensitive data, etc." (Source: sources/2023-10-18-zalando-understanding-graphql-directives-practical-use-cases-at-zalando).
Implementation idiom¶
Schema directives are typically implemented by wrapping
the resolver: the-guild's graphql-tools library exposes
a mapSchema transform that visits each annotated field and
composes the directive's logic before, after, or around the
existing resolver:
// Illustration of schema directives execution in
// the query execution pipeline
const resolvers = {
Query: {
async product(_, { id }) {
// schema directives (pre)
schemaDirectivesExecutions();
// resolver logic
const product = await getProduct(id);
// schema directives (post)
schemaDirectivesExecutions();
return product;
},
},
};
The directive function takes the original resolver and returns a new resolver, letting the schema express authorization, logging, caching, or validation declaratively without polluting each resolver body with the same wrapper.
Why schema directives win at scale¶
- Declarative. The schema itself documents which fields require auth, which arguments are PII, which enums are stable. Readers see the policy without reading resolver code.
- Server-controlled. The schema author can change the directive's semantics without client coordination — clients never see or write them.
- Breaking-change-friendly. Renaming a directive or changing its arguments does not break any persisted query (unlike changing a field name or argument type), because clients do not reference schema directives by name.
- Composable with other server-side tooling. Schema linters can inspect directive presence and fail builds; build-time codegen can emit boilerplate based on directive annotations (patterns/directive-driven-entity-codegen).
Seen in¶
- Zalando UBFF — the canonical production instance on
the wiki. Schema directive use-cases explicitly documented:
@isAuthenticated(authorization with step-up auth),@sensitive(PII redaction — concepts/sensitive-field-logging-redaction),@requireExplicitEndpoint(per-path GraphQL sub-surfaces — patterns/directive-based-http-endpoint-partitioning),@draft/@allowedFor/@component(field lifecycle — patterns/directive-based-field-lifecycle),@final/@extensibleEnum(enum governance — concepts/final-enum, concepts/extensible-enum),@resolveEntityId(ID- wrapping + codegen — patterns/directive-driven-entity-codegen) (Sources: sources/2023-10-18-zalando-understanding-graphql-directives-practical-use-cases-at-zalando, sources/2022-02-16-zalando-graphql-persisted-queries-and-schema-stability). - graphql-tools (the-guild) — canonical NodeJS implementation substrate for schema directives; Zalando's posts cite it as their recommended library.
Related¶
- concepts/graphql-query-directive — the partner primitive on the ExecutableDefinition side.
- concepts/graphql-persisted-queries — gate-mode
persisted queries interact with schema directives (e.g.
@draftblocks persist-registration). - concepts/draft-schema-field · concepts/component-scoped-field-access · concepts/sensitive-field-logging-redaction — specific schema-directive instances.
- patterns/directive-based-field-authorization · patterns/directive-based-pii-redaction · patterns/directive-based-http-endpoint-partitioning · patterns/directive-based-field-lifecycle · patterns/directive-driven-entity-codegen — pattern canonicalisations.
- systems/graphql
- systems/zalando-graphql-ubff