website/pages/docs/using-directives.mdx
Directives let you customize query execution at a fine-grained level. They act like annotations in a GraphQL document, giving the server instructions about whether to include a field, how to format a response, or how to apply custom behavior.
GraphQL.js supports built-in directives like @include, @skip, and @deprecated out
of the box. If you want to create your own directives and apply custom behavior, you'll
need to implement the logic yourself.
This guide covers how GraphQL.js handles built-in directives, how to define and apply custom directives, and how to implement directive behavior during execution.
GraphQL defines several built-in directives, each serving a specific purpose during execution or in the schema. These include:
@include and @skip: Used in the execution language to conditionally include or skip
fields and fragments at runtime.@deprecated: Used in Schema Definition Language (SDL) to mark fields or enum values as
deprecated, with an optional reason. It appears in introspection but doesn't affect query execution.For example, the @include directive conditionally includes a field based on a Boolean variable:
query ($shouldInclude: Boolean!) {
greeting @include(if: $shouldInclude)
}
At runtime, GraphQL.js evaluates the if argument. If shouldInclude is false, the
greeting field in this example is skipped entirely and your resolver won't run.
import { graphql, buildSchema } from 'graphql';
const schema = buildSchema(`
type Query {
greeting: String
}
`);
const rootValue = {
greeting: () => 'Hello!',
};
const query = `
query($shouldInclude: Boolean!) {
greeting @include(if: $shouldInclude)
}
`;
const variables = { shouldInclude: true };
const result = await graphql({
schema,
source: query,
rootValue,
variableValues: variables,
});
console.log(result);
// → { data: { greeting: 'Hello!' } }
If shouldInclude is false, the result would be { data: {} }.
The @deprecated directive is used in the schema to indicate that a field or enum
value should no longer be used. It doesn't affect execution, but is included
in introspection output:
{
__type(name: "MyType") {
fields {
name
isDeprecated
deprecationReason
}
}
}
GraphQL.js automatically includes deprecation metadata in introspection. Tools like GraphiQL use this to show warnings, but GraphQL.js itself doesn't block or modify behavior. You can still query deprecated fields unless you add validation rules yourself.
To use a custom directive, you first define it in your schema using the
GraphQLDirective class. This defines the directive's name, where it can
be applied, and any arguments it accepts.
A directive in GraphQL.js is just metadata. It doesn't perform any behavior on its own.
Here's a basic example that declares an @uppercase directive that can be applied to fields:
import {
GraphQLDirective,
DirectiveLocation,
GraphQLNonNull,
GraphQLBoolean,
} from 'graphql';
const UppercaseDirective = new GraphQLDirective({
name: 'uppercase',
description: 'Converts the result of a field to uppercase.',
locations: [DirectiveLocation.FIELD],
args: {
enabled: {
type: GraphQLNonNull(GraphQLBoolean),
defaultValue: true,
description: 'Whether to apply the transformation.',
},
},
});
To make the directive available to your schema, you must explicitly include it:
import { GraphQLSchema } from 'graphql';
const schema = new GraphQLSchema({
query: QueryType,
directives: [UppercaseDirective],
});
Once added, tools like validation and introspection will recognize it.
After defining and adding your directive to the schema, clients can apply it in queries using
the @directiveName syntax. Arguments are passed in parentheses, similar to field arguments.
You can apply directives to:
The following examples show how to apply directives:
# Applied to a field
{
greeting @uppercase
}
# Applied to a fragment spread
{
...userFields @include(if: true)
}
# Applied to an inline fragment
{
... on User @skip(if: false) {
email
}
}
When a query is parsed, GraphQL.js includes directive nodes in the field's
Abstract Syntax Tree (AST). You can access these via info.fieldNodes inside
a resolver.
GraphQL.js doesn't execute custom directive logic for you. You must handle it during execution. There are two common approaches:
Inside a resolver, use the info object to access AST nodes and inspect directives.
You can check whether a directive is present and change behavior accordingly.
import { graphql, buildSchema, getDirectiveValues } from 'graphql';
const schema = buildSchema(`
directive @uppercase(enabled: Boolean = true) on FIELD
type Query {
greeting: String
}
`);
const rootValue = {
greeting: (source, args, context, info) => {
const directive = getDirectiveValues(
schema.getDirective('uppercase'),
info.fieldNodes[0],
info.variableValues,
);
const result = 'Hello, world';
if (directive?.enabled) {
return result.toUpperCase();
}
return result;
},
};
const query = `
query {
greeting @uppercase
}
`;
const result = await graphql({ schema, source: query, rootValue });
console.log(result);
// → { data: { greeting: 'HELLO, WORLD' } }
For more complex logic, you can preprocess the schema or query using AST visitors or wrap field resolvers. This lets you inject directive logic globally across multiple types or fields.
This approach is useful for:
Some common use cases for custom directives include:
@uppercase, @date(format: "YYYY-MM-DD"), @currency@auth(role: "admin") to protect fields@feature(name: "newHeader") to expose experimental features@log, @tag(name: "important"), or @metrics(id: "xyz")Custom directives should not be used where an argument could achieve the same
goal. Custom directives in GraphQL requests complicate caching (particularly
normalized caches) and are less well understood by tooling such as GraphQL; by
using arguments instead we maximize compatibility and consistency. For example,
our @uppercase directive would fit naturally as a field argument:
import { graphql, buildSchema } from 'graphql';
const schema = buildSchema(`
enum Format {
VERBATIM
UPPERCASE
}
type Query {
greeting(format: Format! = VERBATIM): String
}
`);
const rootValue = {
greeting: (source, args) => {
const result = 'Hello, world';
if (args.format === 'UPPERCASE') {
return result.toUpperCase();
}
return result;
},
};
const query = `
query {
greeting(format: UPPERCASE)
}
`;
const result = await graphql({
schema,
source: query,
rootValue,
});
console.log(result);
When working with custom directives in GraphQL.js, keep the following best practices in mind: