Back to Blog
Lesson 49 of the GraphQL: Your First GraphQL Schema & Server course
API ArchitectureSeptember 5, 20264 min read

Documenting with Schema Directives: Metadata and Control in SDL

Learn how to use GraphQL directives to add metadata and custom logic to your schema, improving both your API's documentation and its runtime behavior.

GraphQLSDLDirectivesMetadataDocumentationSchema
Close-up of the word 'metadata' spelled out with wooden Scrabble tiles on a table.

Previously in this course, we explored how to manage API evolution using the @deprecated directive in GraphQL API Versioning. While that built-in directive is essential for signaling changes, directives offer far more power than just deprecation. They allow us to attach custom metadata to our Schema Definition Language (SDL) and even intercept the execution of our resolvers.

Understanding Directives from First Principles

In GraphQL, a directive is a way to "annotate" parts of your schema or your query with additional instructions. Syntactically, they start with the @ symbol. Think of them as decorators that tell the GraphQL engine: "Treat this field differently."

There are two primary categories of directives:

  1. Built-in Directives: These are part of the GraphQL specification. You don't need to write any extra code to make them work; they are handled natively by your server engine.
  2. Custom Directives: These allow you to define your own logic. You can use them to enforce business rules (like @auth), transform data (like @uppercase), or add metadata for external tools to consume.

Using Built-in Directives

As we saw when defining our typeDefs, the schema acts as a contract. Built-in directives help refine that contract.

The most common built-in directives are @deprecated and the conditional directives used in queries: @include(if: Boolean) and @skip(if: Boolean).

  • @deprecated: Used in your schema to signal that a field should no longer be used.
  • @include(if: $condition): Only returns the field if the argument is true.
  • @skip(if: $condition): Skips the field if the argument is true.

These directives are powerful because they allow the client to shape the response dynamically without requiring the server to create multiple different endpoints.

The Role of Custom Directives

While built-in directives are helpful, custom directives are where you truly gain control over your schema's metadata. By defining a custom directive, you can attach specific behaviors to types or fields that the GraphQL execution engine will process.

Common use cases for custom directives include:

  • Authorization: Adding @auth(role: ADMIN) to ensure only authorized users access a field.
  • Formatting: Using @formatDate(format: "YYYY-MM-DD") to transform output.
  • Caching: Using @cacheControl(maxAge: 60) to define how long a specific field can be cached.

Worked Example: Adding Metadata with Directives

Let's assume we want to add a restricted flag to our schema fields to help our front-end developers identify sensitive data during development.

First, we define the directive in our SDL:

GraphQL
directive @restricted on FIELD_DEFINITION

type User {
  id: ID!
  username: String!
  email: String! @restricted
}

In this example, the @restricted directive acts as metadata. While the GraphQL engine might not "do" anything with it by default, you can use introspection—which we covered in Understanding Introspection—to scan your schema, find all fields marked as @restricted, and programmatically hide them in your UI or generate compliance reports.

Hands-on Exercise

  1. Open your project's typeDefs.
  2. Add a standard @deprecated directive to one of your older fields that you might want to phase out.
  3. Define a new directive named @upper in your typeDefs (e.g., directive @upper on FIELD_DEFINITION).
  4. Apply this @upper directive to a String field, like username.
  5. Note: To make the @upper directive actually transform the data, you would need to implement a schema transformer in your server setup (which we will touch upon in later, more advanced modules). For now, observe how simply adding the directive changes the schema structure discovered by the documentation tab in Apollo Sandbox.

Common Pitfalls

  • Overusing Directives: It's tempting to put all your business logic into directives. However, logic inside a directive can be harder to unit test than logic inside a standard resolver. Reserve directives for cross-cutting concerns like auth or logging.
  • Schema Bloat: Don't use directives for data that should be in the schema itself. If you need to describe a field, a comment in the SDL (e.g., """ This is a description """) is better for documentation than a custom directive.
  • Ignoring Specification: Remember that custom directives require implementation logic. If you define one in your SDL but don't provide the execution logic in your server code, the directive will be ignored by the runtime.

FAQ

Can I use directives on Input types? Yes, but you must define the directive to support INPUT_FIELD_DEFINITION in your schema definition.

Do directives replace resolvers? No. Directives wrap or modify the behavior around resolvers; they do not replace the data-fetching logic itself.

How do I see custom directives in my documentation? Most GraphQL introspection tools, including the Apollo Sandbox documentation tab, automatically pick up and display directives defined in your schema.

Recap

Directives allow us to attach metadata and custom behavior to our schema. We’ve moved beyond simple data modeling into controlling the execution of our API. By leveraging built-in directives like @deprecated and planning for custom ones, you ensure your schema remains a clean, self-documenting contract that is easy for clients to consume.

Up next: We will shift focus to the production side of your API by learning about Monitoring GraphQL Performance and identifying bottlenecks.

Similar Posts