In GraphQL, what does applying a custom directive to a schema field actually do at execution time?
answer
- Who defines what it means?
- Validation is not behaviour
- Undefined fails loudly, unread fails silently
- The server has to go looking
- Only the built-ins have specified semantics
basics
~10 sNothing by itself. GraphQL defines no execution semantics for custom directives, so an application is validated metadata the server must be programmed to find and act on. A directive nothing reads is documentation.
solid answer
~50 sThe GraphQL specification defines behaviour only for its own built-in directives — `@skip` and `@include` in documents, `@deprecated` and `@specifiedBy` in the schema. For any directive you declare yourself, the specification says what a valid application looks like and nothing about what it does. Validation checks three things: the directive is defined, the location is one its definition allows, and the argument values typecheck. A schema that passes those checks executes exactly as if the annotation were deleted, unless server code looks for it. That gives an important asymmetry: applying an **undefined** directive fails the schema build loudly, while applying a **defined but unread** one is completely silent. So the meaning lives in your code — a build-time pass that rewrites the schema, a hook that reads the annotation while a field resolves, or a build tool such as a linter or client generator that consumes it before runtime.
code
graphql · 9 linesdirective @unit(symbol: String!) on FIELD_DEFINITION
type Animal {
id: ID!
earTag: String!
birthWeight: Float! @unit(symbol: "kg")
sire: Animal
dam: Animal
}go deeper
Recall the one-line rule: applying a custom directive changes nothing until server code reads it. Be able to name the built-ins the specification does define, and to say that a valid application is only validated metadata.
Explain what validation actually checks — defined, allowed location, well-typed arguments — and contrast the loud failure of an undefined directive with the silent nothing of an unread one. Name who in a stack does the reading.
Be ready to say why this matters operationally: annotation and behaviour are coupled only by a string, nothing warns you when they drift, and clients cannot observe applications, so a directive is never a client contract.
Own the policy question: when a schema should express something as a directive at all versus in the types, and how a team keeps track of which consumer reads each directive as the schema and the tooling around it grow.
### Two families of directive, and the very short list with defined meaning GraphQL has two kinds of directive. *Executable* directives appear inside an operation document, attached to a selection, a fragment or a variable definition. *Type-system* directives appear in the SDL, attached to a schema element — a field definition, an argument definition, an object type, an enum value. A **custom** directive is simply one your own schema declares, as opposed to the handful the specification itself declares. The specification assigns behaviour to its own built-ins only. `@skip` and `@include` decide whether a selection is included in the result. `@deprecated` marks a field, argument or enum value as deprecated and is surfaced through introspection. `@specifiedBy` points a custom scalar at a human-readable specification URL. That list is the entire set of directives whose meaning GraphQL defines. For every directive you invent, the specification is deliberately silent: it defines the syntax for declaring one and for applying one, and stops there. ### What validation actually guarantees When you apply a custom directive, three things are checked: the directive must be defined in the schema, the location must be one its definition allows, and the supplied arguments must be valid for their declared types. Passing those checks means the schema is *valid*. It does not mean anything will happen. Take a livestock pedigree graph whose `Animal.birthWeight` field carries `@unit(symbol: "kg")`. That schema executes byte-for-byte identically to the same schema with the annotation deleted, unless somebody wrote server code that looks for it. The number that comes back is whatever the resolver returned; the directive did not convert it, validate it, or label it. This creates an asymmetry that catches candidates out. Applying an *undefined* directive is a hard failure — the schema does not build. Applying a *defined but unread* directive is completely silent. So the characteristic failure of a custom directive is never a loud error. It is the annotation quietly being decoration, in a schema that reviews well and passes every check. ### Where the meaning actually lives The meaning lives in code you write, and it is attached in one of three places: * **In the server, at schema-build time.** The builder walks the schema, finds the applied directive at each location, and rewrites what it found — most commonly by replacing the field's resolver with a wrapper that adds the behaviour. * **In the server, at execution time.** A resolver, or a generic hook running around every field, reads the applied directive from the field's schema definition while the field is being resolved and branches on it. * **Outside the server entirely.** Plenty of useful directives never reach runtime: a schema linter enforces a convention, a typed client generator emits different code for an annotated field, a build step strips the directive after consuming it. Nothing is wrong with a directive that is purely a build-time or documentation marker — as long as everyone knows that is what it is. Apollo Federation is the instructive counter-example. Its directives do have defined meanings — but they are defined by *that* composition specification, and honoured because composition tools implement it. The meaning still does not come from GraphQL; it comes from a second specification layered on top. ### The consequences to be able to state out loud 1. **Not portable.** SDL carrying custom directives can move between servers; the behaviour cannot, because the behaviour was never in the SDL. 2. **Not a client contract.** Aside from the two built-ins that introspection exposes, a client cannot see that a directive was applied, so it cannot depend on it. Anything a client must react to belongs in the types, not in an annotation. 3. **Partial application is normal and invisible.** If the handler covers field definitions but the directive is also applied to an argument definition, half the applications simply do nothing — and no tool tells you. 4. **A directive is a name-based coupling.** SDL text and server code agree by string. Rename one side and the schema still validates while the behaviour disappears. ### The interview shape The question is usually asked because a candidate has used a schema where `@auth`-style or `@constraint`-style directives appeared to enforce themselves. The strong answer separates the two layers immediately: the specification gives you a *validated, structured place to hang metadata on schema elements*, and your server supplies every bit of behaviour. Say that plainly, then name who in your stack does the reading, and the question is answered.
- If a custom directive does nothing on its own, why put one in a schema at all?Because it is a validated, structured place to attach metadata to a schema element, and something in your pipeline can read it: a server pass that wraps resolvers, a schema linter enforcing a convention, a typed client generator, or a composition step. A directive that is purely documentation is also legitimate — the mistake is not knowing which consumer, if any, reads a given one.
- Can a client see that a custom directive was applied to a field it queries?No. Aside from the built-ins the specification exposes, applications of custom directives are not surfaced to clients, so a client cannot detect or depend on one. Anything a client must react to belongs in the types themselves — a field, an argument, a nullability change — not in an annotation the client cannot observe.
- What happens if you apply a directive that is not declared in the schema?The schema is invalid and the build fails. That is the asymmetry worth stating: an undefined directive is a hard error, while a perfectly defined directive that no code ever reads produces no error at all. The silent case is the one that reaches production.
An applied custom directive is a sticky note on a blueprint: perfectly legible and perfectly valid, but the building only changes if someone reads it and picks up a tool.
saying these in an interview costs you the question
- Claims the directive enforces itself once applied
- Thinks GraphQL defines semantics for any declared directive
- Says a client can read applied custom directives
- Expects an error when nothing consumes the directive
- Believes SDL carries the behaviour to another server
- Confuses @skip and @include with custom schema directives