---
description: Documentation on GraphQL directives in Undine.
---

# Directives

In this section, we'll cover how you can use GraphQL directives in Undine.
Directives are a way to add metadata to a GraphQL document or schema,
which can be accessed during document execution or by clients consuming your schema.

## Directive

In Undine, a GraphQL directive is implemented by subclassing the `Directive` class.

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION]): ...
```

Note that a `Directive` by itself doesn't do anything. It is only used as a way to define
additional metadata, which the GraphQL server or client can use at runtime.
If the directive implies some behavior, you'll need to add it, e.g., using a [`ValidationRule`](validation-rules.md).

Note that declared `Directives` are automatically added to the schema, even if they are not used.

A `Directive` always requires the _**locations**_ it can be used in to be set using the `locations` argument.
The locations can be divided into two categories: [_executable locations_](#executable-locations)
and [_type system locations_](#type-system-locations).

### Executable locations

Executable locations identify places in a GraphQL _document_ (i.e. "request") where a directive can be used.
See the example below on what these locations are.

#### `QUERY`

The `QUERY` location corresponds to _query_ operation.

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.QUERY], schema_name="new"): ...
```

In schema definition:

```graphql
query ($pk: Int!) @new {
  task(pk: $pk) {
    pk
    name
    done
  }
}
```

#### `MUTATION`

The `MUTATION` location corresponds to _mutation_ operation.

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.MUTATION], schema_name="new"): ...
```

In schema definition:

```graphql
mutation ($input: CreateTaskMutation!) @new {
  createTask(input: $input) {
    pk
  }
}
```

#### `SUBSCRIPTION`

The `SUBSCRIPTION` location corresponds to _subscription_ operation.

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.SUBSCRIPTION], schema_name="new"): ...
```

In schema definition:

```graphql
subscription @new {
  comments {
    username
    message
  }
}
```

#### `FIELD`

The `FIELD` location corresponds to a field selection on an operation.

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.FIELD], schema_name="new"): ...
```

In schema definition:

```graphql
query {
  task(pk: 1) {
    pk @new
    name
    done
  }
}
```

#### `FRAGMENT_DEFINITION`

The `FRAGMENT_DEFINITION` location corresponds to a fragment definition.

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.FRAGMENT_DEFINITION], schema_name="new"): ...
```

In schema definition:

```graphql
query {
  task(pk: 1) {
    ...taskFragment
  }
}

fragment taskFragment on TaskType @new {
  pk
  name
  done
}
```

#### `FRAGMENT_SPREAD`

The `FRAGMENT_SPREAD` location corresponds to a fragment spread.

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.FRAGMENT_SPREAD], schema_name="new"): ...
```

In schema definition:

```graphql
query {
  task(pk: 1) {
    ...taskFragment @new
  }
}

fragment taskFragment on TaskType {
  pk
  name
  done
}
```

#### `INLINE_FRAGMENT`

The `INLINE_FRAGMENT` location corresponds to an inline fragment.

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.INLINE_FRAGMENT], schema_name="new"): ...
```

In schema definition:

```graphql
query {
  node(id: "U3Vyc29yOnVzZXJuYW1lOjE=") {
    id
    ... on TaskType @new {
      name
    }
  }
}
```

#### `VARIABLE_DEFINITION`

The `VARIABLE_DEFINITION` location corresponds to a variable definition.

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.VARIABLE_DEFINITION], schema_name="new"): ...
```

In schema definition:

```graphql
query ($pk: Int! @new) {
  task(pk: $pk) {
    pk
    name
    done
  }
}
```

### Type system locations

Type system locations identify places in a GraphQL _schema_ (i.e. "API") where a directive can be used.
Since Undine is used to define the schema, each type system location corresponds to an Undine
object that accepts that "type" of directive.

#### `SCHEMA`

The `SCHEMA` location corresponds to the schema definition itself.
Directives can be added here by using the `schema_definition_directives` argument
in the `create_schema` function.

```python hl_lines="16"
from graphql import DirectiveLocation

from undine import Directive, Entrypoint, RootType, create_schema


class NewDirective(Directive, locations=[DirectiveLocation.SCHEMA], schema_name="new"): ...


class Query(RootType):
    @Entrypoint
    def example(self, value: str) -> str:
        return value


schema = create_schema(query=Query, schema_definition_directives=[NewDirective()])
```

In schema definition:

```graphql
directive @new on SCHEMA

schema @new {
  query: Query
}
```

#### `SCALAR`

The `SCALAR` location corresponds to the scalars defined in the schema.
In Undine, `ScalarType` accepts `Directives` declared for this location.

```python
from graphql import DirectiveLocation

from undine import Directive
from undine.scalars import ScalarType


class NewDirective(Directive, locations=[DirectiveLocation.SCALAR], schema_name="new"): ...


vector3_scalar = ScalarType(name="Vector3", directives=[NewDirective()])

# Alternatively...

vector3_scalar_alt = ScalarType(name="Vector3") @ NewDirective()
```

In schema definition:

```graphql
directive @new on SCALAR

scalar Vector3 @new
```


#### `OBJECT`

The `OBJECT` location corresponds to the ObjectTypes defined in the schema.
In Undine, `QueryTypes` and `RootTypes` accepts `Directives` declared for this location.

```python
from graphql import DirectiveLocation

from undine import Directive, Entrypoint, QueryType, RootType

from .models import Task


class NewDirective(Directive, locations=[DirectiveLocation.OBJECT], schema_name="new"): ...


class TaskType(QueryType[Task], directives=[NewDirective()]): ...


class Query(RootType, directives=[NewDirective()]):
    tasks = Entrypoint(TaskType, many=True)


# Alternatively...


@NewDirective()
class TaskTypeAlt(QueryType[Task]): ...


@NewDirective()
class QueryAlt(RootType):
    tasks = Entrypoint(TaskType, many=True)
```

In schema definition:

```graphql
directive @new on OBJECT

type TaskType @new {
  name: String!
  createdAt: DateTime!
}

type Query @new {
  tasks: [TaskType!]!
}
```

#### `FIELD_DEFINITION`

The `FIELD_DEFINITION` location corresponds to the fields defined in the schema.
In Undine, `Fields`, `InterfaceFields` and `Entrypoints` accepts `Directives` declared for this location.

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, Entrypoint, Field, InterfaceField, InterfaceType, QueryType, RootType

from .models import Task


class NewDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="new"): ...


class Named(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString), directives=[NewDirective()])

    # Alternatively...
    name_alt = InterfaceField(GraphQLNonNull(GraphQLString)) @ NewDirective()


@Named
class TaskType(QueryType[Task]):
    created_at = Field(directives=[NewDirective()])

    # Alternatively...
    created_at_alt = Field() @ NewDirective()


class Query(RootType):
    tasks = Entrypoint(TaskType, many=True, directives=[NewDirective()])

    # Alternatively...
    tasks_alt = Entrypoint(TaskType, many=True) @ NewDirective()
```

In schema definition:

```graphql
directive @new on FIELD_DEFINITION

interface Named {
  name: String! @new
}

type TaskType implements Named {
  name: String!
  createdAt: DateTime! @new
}

type Query {
  tasks: [TaskType!]! @new
}
```

#### `ARGUMENT_DEFINITION`

The `ARGUMENT_DEFINITION` location corresponds to the field arguments defined in the schema.
In Undine, `CalculationArguments` and `DirectiveArguments` accepts `Directives` declared for this location.

```python
from django.db.models import Value
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Calculation, CalculationArgument, Directive, DirectiveArgument, GQLInfo
from undine.typing import DjangoExpression


class NewDirective(Directive, locations=[DirectiveLocation.ARGUMENT_DEFINITION], schema_name="new"): ...


class VersionDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="version"):
    value = DirectiveArgument(GraphQLNonNull(GraphQLString), directives=[NewDirective()])

    # Alternatively...
    value_alt = DirectiveArgument(GraphQLNonNull(GraphQLString)) @ NewDirective()


class Calc(Calculation[int]):
    value = CalculationArgument(int, directives=[NewDirective()])

    # Alternatively...
    value_alt = CalculationArgument(int) @ NewDirective()

    def __call__(self, info: GQLInfo) -> DjangoExpression:
        return Value(self.value)
```

In schema definition:

```graphql
directive @new on ARGUMENT_DEFINITION

directive @version (
  value: String! @new
) on FIELD_DEFINITION

type TaskType {
  calc(
    value: Int! @new
  ): Int!
}
```

#### `INTERFACE`

The `INTERFACE` location corresponds to the interfaces defined in the schema.
In Undine, `InterfaceType` accepts `Directives` declared for this location.

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, InterfaceField, InterfaceType


class NewDirective(Directive, locations=[DirectiveLocation.INTERFACE], schema_name="new"): ...


class Named(InterfaceType, directives=[NewDirective()]):
    name = InterfaceField(GraphQLNonNull(GraphQLString))


# Alternatively...


@NewDirective()
class NamedAlt(InterfaceType):
    name = InterfaceField(GraphQLNonNull(GraphQLString))
```

In schema definition:

```graphql
directive @new on INTERFACE

interface Named @new {
  name: String!
}
```

#### `UNION`

The `UNION` location corresponds to the unions defined in the schema.
In Undine, `UnionType` accepts `Directives` declared for this location.

```python
from graphql import DirectiveLocation

from undine import Directive, QueryType, UnionType

from .models import Project, Task


class NewDirective(Directive, locations=[DirectiveLocation.UNION], schema_name="new"): ...


class TaskType(QueryType[Task]): ...


class ProjectType(QueryType[Project]): ...


class SearchObject(UnionType[TaskType, ProjectType], directives=[NewDirective()]): ...


# Alternatively...


@NewDirective()
class SearchObjectAlt(UnionType[TaskType, ProjectType]): ...
```

In schema definition:

```graphql
directive @new on UNION

union SearchObject @new = TaskType | ProjectType
```

#### `ENUM`

The `ENUM` location corresponds to the enums defined in the schema.
In Undine, `OrderSet` accepts `Directives` declared for this location.

```python
from graphql import DirectiveLocation

from undine import Directive, OrderSet

from .models import Task


class NewDirective(Directive, locations=[DirectiveLocation.ENUM], schema_name="new"): ...


class TaskOrderSet(OrderSet[Task], directives=[NewDirective()]): ...


# Alternatively...


@NewDirective()
class TaskOrderSetAlt(OrderSet[Task]): ...
```

In schema definition:

```graphql
directive @new on ENUM

enum TaskOrderSet @new {
  nameAsc
  nameDesc
}
```

#### `ENUM_VALUE`

The `ENUM_VALUE` location corresponds to the enum values defined in the schema.
In Undine, `Order` accepts `Directives` declared for this location.

```python
from graphql import DirectiveLocation

from undine import Directive, Order, OrderSet

from .models import Task


class NewDirective(Directive, locations=[DirectiveLocation.ENUM_VALUE], schema_name="new"): ...


class TaskOrderSet(OrderSet[Task]):
    name = Order("name", directives=[NewDirective()])

    # Alternatively...
    name_alt = Order("name") @ NewDirective()
```

In schema definition:

```graphql
directive @new on ENUM_VALUE

enum TaskOrderSet {
  nameAsc @new
  nameDesc @new
}  
```

#### `INPUT_OBJECT`

The `INPUT_OBJECT` location corresponds to the input objects defined in the schema.
In Undine, `MutationType` and `FilterSet` accept `Directives` declared for this location.

```python
from graphql import DirectiveLocation

from undine import Directive, FilterSet, MutationType

from .models import Task


class NewDirective(Directive, locations=[DirectiveLocation.INPUT_OBJECT], schema_name="new"): ...


class TaskFilterSet(FilterSet[Task], directives=[NewDirective()]): ...


class CreateTaskMutation(MutationType[Task], directives=[NewDirective()]): ...


# Alternatively...


@NewDirective()
class TaskFilterSetAlt(FilterSet[Task]): ...


@NewDirective()
class CreateTaskMutationAlt(MutationType[Task]): ...
```

In schema definition:

```graphql
directive @new on INPUT_OBJECT

input TaskFilterSet @new {
  name: String
}

input TaskCreateMutation @new {
  name: String
}
```

#### `INPUT_FIELD_DEFINITION`

The `INPUT_FIELD_DEFINITION` location corresponds to the input field definitions defined in the schema.
In Undine, `Input` and `Filter` accept `Directives` declared for this location.

```python
from graphql import DirectiveLocation

from undine import Directive, Filter, FilterSet, Input, MutationType

from .models import Task


class NewDirective(Directive, locations=[DirectiveLocation.INPUT_FIELD_DEFINITION], schema_name="new"): ...


class TaskFilterSet(FilterSet[Task]):
    name = Filter(directives=[NewDirective()])

    # Alternatively...
    name_alt = Filter() @ NewDirective()


class CreateTaskMutation(MutationType[Task]):
    name = Input(directives=[NewDirective()])

    # Alternatively...
    name_alt = Input() @ NewDirective()
```

In schema definition:

```graphql
directive @new on INPUT_FIELD_DEFINITION

input TaskFilterSet {
  name: String @new
}

input TaskCreateMutation {
  name: String @new
}
```

### Is repeatable

A directive can be declared as repeatable using the `is_repeatable` argument.
This means that the directive can be used multiple times in the same location.

```python
from graphql import DirectiveLocation

from undine import Directive, Entrypoint, RootType


class NewDirective(
    Directive,
    locations=[DirectiveLocation.FIELD_DEFINITION],
    schema_name="new",
    is_repeatable=True,
): ...


class Query(RootType):
    @Entrypoint(directives=[NewDirective(), NewDirective()])
    def example(self) -> str:
        return "Example"

    # Alternatively...
    @NewDirective()
    @NewDirective()
    @Entrypoint()
    def example_alt(self) -> str:
        return "Example"
```

In schema definition:

```graphql
directive @new repeatable on FIELD_DEFINITION

type Query {
  example: String! @new @new
}
```

### Schema name

By default, the name of the generated GraphQL directive for a `Directive` class
is the name of the `Directive` class. If you want to change the name separately,
you can do so by setting the `schema_name` argument:

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="new"): ...
```

### Extensions

You can provide custom extensions for the `Directive` by providing a
`extensions` argument with a dictionary containing them. These can then be used
however you wish to extend the functionality of the `Directive`.

```python
from graphql import DirectiveLocation

from undine import Directive


class NewDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], extensions={"foo": "bar"}): ...
```

`Directive` extensions are made available in the GraphQL directive extensions
after the schema is created. The `Directive` itself is found in the GraphQL directive extensions
under a key defined by the [`DIRECTIVE_EXTENSIONS_KEY`](settings.md#directive_extensions_key)
setting.

## DirectiveArgument

A `Directive` can optionally have a number of [`DirectiveArguments`](#directiveargument)
defined in the class body. These define the arguments that can or must be used with the directive.
A `DirectiveArgument` always requires _input type_ of the argument, which needs to be a GraphQL input type.

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, DirectiveArgument


class VersionDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="version"):
    value = DirectiveArgument(GraphQLNonNull(GraphQLString))
```

### Schema name

By default, the name of the GraphQL directive argument generated from a `DirectiveArgument` is the same
as the name of the `DirectiveArgument` on the `Directive` class (converted to _camelCase_ if
[`CAMEL_CASE_SCHEMA_FIELDS`](settings.md#camel_case_schema_fields) is enabled).
If you want to change the name of the GraphQL directive argument separately,
you can do so by setting the `schema_name` argument:

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, DirectiveArgument


class VersionDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="version"):
    value = DirectiveArgument(GraphQLNonNull(GraphQLString), schema_name="value")
```

This can be useful when the desired name of the GraphQL directive argument is a Python keyword
and cannot be used as the `DirectiveArgument` attribute name.

### Description

A description for a `DirectiveArgument` can be provided in on of two ways:

1) By setting the `description` argument.

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, DirectiveArgument


class VersionDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="version"):
    value = DirectiveArgument(GraphQLNonNull(GraphQLString), description="Version value.")
```

2) As class attribute docstring, if [`ENABLE_CLASS_ATTRIBUTE_DOCSTRINGS`](settings.md#enable_class_attribute_docstrings) is enabled.

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, DirectiveArgument


class VersionDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="version"):
    value = DirectiveArgument(GraphQLNonNull(GraphQLString))
    """Version value."""
```

### Default value

A `default_value` can be provided to set the default value for the `DirectiveArgument`.

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, DirectiveArgument


class VersionDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="version"):
    value = DirectiveArgument(GraphQLNonNull(GraphQLString), default_value="1.0.0")
```

### Deprecation reason

A `deprecation_reason` can be provided to mark the `DirectiveArgument` as deprecated.

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, DirectiveArgument


class VersionDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="version"):
    value = DirectiveArgument(GraphQLNonNull(GraphQLString), deprecation_reason="Use something else.")
```

### Directives

You can add directives to the `DirectiveArgument` by providing them using the `directives` argument.
The directive must be usable in the `ARGUMENT_DEFINITION` location.

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, DirectiveArgument


class NewDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION]): ...


class VersionDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="version"):
    value = DirectiveArgument(GraphQLNonNull(GraphQLString), directives=[NewDirective()])
```

You can also add them using the `@` operator (which kind of looks like GraphQL syntax):

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, DirectiveArgument


class NewDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION]): ...


class VersionDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="version"):
    value = DirectiveArgument(GraphQLNonNull(GraphQLString)) @ NewDirective()
```

### Extensions

You can provide custom extensions for the `DirectiveArgument` by providing a
`extensions` argument with a dictionary containing them. These can then be used
however you wish to extend the functionality of the `DirectiveArgument`.

```python
from graphql import DirectiveLocation, GraphQLNonNull, GraphQLString

from undine import Directive, DirectiveArgument


class VersionDirective(Directive, locations=[DirectiveLocation.FIELD_DEFINITION], schema_name="version"):
    value = DirectiveArgument(GraphQLNonNull(GraphQLString), extensions={"foo": "bar"})
```

`DirectiveArgument` extensions are made available in the GraphQL argument extensions
after the schema is created. The `DirectiveArgument` itself is found in the GraphQL argument extensions
under a key defined by the [`DIRECTIVE_ARGUMENT_EXTENSIONS_KEY`](settings.md#directive_argument_extensions_key)
setting.
