Directives API Reference¶
This section provides detailed API documentation for GraphQL directives in django-graphex.
BaseExtraGraphQLDirective¶
Abstract base class for all custom GraphQL directives.
Methods¶
get_args() (staticmethod)¶
Define the arguments that the directive accepts.
Returns: dict of GraphQL arguments
Example:
@staticmethod
def get_args():
return {
"format": GraphQLArgument(
GraphQLString,
description="Format string"
)
}
resolve(value, args, directive, root, info, **kwargs) (staticmethod)¶
Process the field value with the directive.
Parameters:
- value (Any): The field value to transform
- args (dict): The directive's coerced arguments. The middleware builds
this with graphql.execution.values.get_directive_values, so variables are
already resolved, defaults applied and values type-coerced. Read arguments
with args.get("name") — never parse the AST by hand.
- directive (DirectiveNode): GraphQL directive AST node (kept for
introspection / backwards compatibility)
- root (Any): Root object
- info (ResolveInfo): GraphQL resolve info
- **kwargs: Additional keyword arguments
Returns: Transformed value
Arguments can be variables
Because args is built from get_directive_values, any directive argument
may be supplied as a GraphQL variable, e.g.
query($n: Int!) { title @truncate(length: $n) }.
Standard GraphQL Directives (spec built-ins)¶
all_directives holds 30 directive instances: the library's own 25 plus the
GraphQL specification's 5 — @skip, @include, @deprecated,
@specifiedBy and @oneOf (graphql-core's specified_directives bundle).
The last five are not django-graphex directives: @skip / @include are
evaluated by the graphql-core executor (no GraphQLDirectiveMiddleware
required), while @deprecated, @specifiedBy and @oneOf are type-system
(SDL) directives that annotate the schema — they never appear in queries.
Only the three documented below are ones you interact with; @specifiedBy
(custom-scalar specification URLs) and @oneOf (exactly-one-key input objects)
ride along with graphql-core's bundle and are never emitted by this library's
own compiler.
@skip¶
Excludes a field, fragment spread, or inline fragment when if is true.
Use a variable for the condition:
query Posts($skipAuthor: Boolean!) {
posts {
results {
title
author @skip(if: $skipAuthor) { name }
}
}
}
| Argument | Type | Required | Description |
|---|---|---|---|
if |
Boolean! |
Yes | Skip the selection when true |
@include¶
The mirror image of @skip: includes the selection only when if is true:
query Posts($withComments: Boolean!) {
posts {
results {
title
comments @include(if: $withComments) {
results { body }
}
}
}
}
| Argument | Type | Required | Description |
|---|---|---|---|
if |
Boolean! |
Yes | Include the selection when true |
@deprecated¶
Marks a field, argument, input field, or enum value as deprecated in the
schema (SDL). Clients discover it through introspection. You don't
hand-write this directive: pass deprecation_reason= in Python and the
compiler emits it for you — on descriptor fields (Field / the typed
shortcuts), on Django-mounted fields (DjangoObjectField,
DjangoListObjectField, DjangoFilterListField,
DjangoFilterPaginateListField, RetrieveField, QueryFields), and on
mutation builders (CreateField, UpdateField, DeleteField,
MutationFields). See Fields and
Mutations for the full kwarg reference.
from django_graphex.core import ObjectType, CharField
class UserType(ObjectType):
username = CharField()
email = CharField(deprecation_reason="Use contactEmail instead.")
| Argument | Type | Required | Description |
|---|---|---|---|
reason |
String |
No | Why it is deprecated (default: "No longer supported") |
Introspection hides deprecated fields by default
__type { fields { name } } omits a deprecated field unless the client
requests fields(includeDeprecated: true).
String Directives¶
DefaultGraphQLDirective¶
Provides default values for null or empty strings.
Only null and "" are substituted
Other falsy values are legitimate data and pass through untouched: 0,
0.0, false, an empty list and an empty object all keep their value.
Substituting them would replace a real value with the to string and
then fail serialization (Int cannot represent non-integer value,
Expected Iterable, but did not find one).
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
to |
String! |
Yes | Value to use as default |
Example¶
String directives pass null through
Every string directive (@uppercase, @lowercase, @capitalize,
@camel_case, @snake_case, @kebab_case, @swap_case, @strip,
@title_case, @center, @replace, @truncate, @slugify) returns
null for a null field value instead of rendering the literal text
"None". This matches @floor, @ceil, @round and @abs.
Base64GraphQLDirective¶
Encode or decode strings using base64. Input is encoded as UTF-8, so non-ASCII characters (accented letters, emoji, etc.) are handled correctly without raising errors.
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
op |
String |
No | Operation: "encode" or "decode" |
Example¶
Case Conversion Directives¶
LowercaseGraphQLDirective¶
Convert string to lowercase.
UppercaseGraphQLDirective¶
Convert string to uppercase.
CapitalizeGraphQLDirective¶
Capitalize first character, lowercase the rest.
TitleCaseGraphQLDirective¶
Convert to title case (first letter of each word capitalized).
SwapCaseGraphQLDirective¶
Swap case of all characters.
Example¶
Code Style Directives¶
CamelCaseGraphQLDirective¶
Convert string to camelCase.
SnakeCaseGraphQLDirective¶
Convert string to snake_case.
KebabCaseGraphQLDirective¶
Convert string to kebab-case.
Example¶
Each directive expects a specific input shape: @camel_case converts a
snake_case string, while @snake_case and @kebab_case convert a
space-separated Title Case string.
query {
field {
displayName @camel_case # "user_profile" → "userProfile"
apiName @snake_case # "Api Name" → "api_name"
urlSlug @kebab_case # "Blog Post Title" → "blog-post-title"
}
}
String Manipulation Directives¶
StripGraphQLDirective¶
Remove leading and trailing characters.
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
chars |
String |
No | Characters to strip (default: whitespace) |
CenterGraphQLDirective¶
Center string within a specified width.
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
width |
Int! |
Yes | Total width of result string |
fillchar |
String |
No | Character to use for padding (default: space) |
ReplaceGraphQLDirective¶
Replace occurrences of a substring.
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
old |
String! |
Yes | Substring to replace |
new |
String! |
Yes | Replacement string |
count |
Int |
No | Max number of replacements (-1 for all) |
TruncateGraphQLDirective¶
Shorten a string to length characters, appending end (default "…").
Unless killwords is true, the string is cut on a word boundary.
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
length |
Int! |
Yes | Maximum number of characters to keep |
end |
String |
No | Suffix appended when truncated (default: "…") |
killwords |
Boolean |
No | Cut in the middle of words instead of on a boundary (default: false) |
SlugifyGraphQLDirective¶
Convert a string into a URL-safe slug (Django slugify).
Example¶
query {
post {
content @strip
title @center(width: 50, fillchar: "=")
text @replace(old: "API", new: "Application Programming Interface")
summary @truncate(length: 80) # "A long summary…"
heading @truncate(length: 10, killwords: true, end: "...")
slug @slugify # "My Post!" → "my-post"
}
}
Number Directives¶
NumberGraphQLDirective¶
Format numbers using Python format strings. Format specs whose width or precision exceeds 100 are rejected with a field error (prevents memory exhaustion from client-supplied oversized specs).
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
as |
String! |
Yes | Python format string (width and precision capped at 100) |
Example¶
query {
product {
price @number(as: ".2f") # "123.45"
weight @number(as: ".3f") # "12.500"
rating @number(as: ".1%") # "45.2%"
}
}
CurrencyGraphQLDirective¶
Format numbers as currency.
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
symbol |
String |
No | Currency symbol (default: "$") |
Example¶
query {
product {
priceUSD @currency # "$123.45"
priceEUR @currency(symbol: "€") # "€123.45"
priceGBP @currency(symbol: "£") # "£123.45"
}
}
@number and @currency only format String fields
Both directives produce a formatted string. A field typed Int or
Float cannot serialize one, so on those fields the raw value is returned
unchanged and the formatting is skipped. Expose the value as a String
field (or a resolver returning a string) when you want the formatting to
apply. The format-spec width/precision cap still applies on every field
type.
Mathematical Directives¶
FloorGraphQLDirective¶
Return the floor of a number.
CeilGraphQLDirective¶
Return the ceiling of a number.
RoundGraphQLDirective¶
Round a number to precision decimal places (default 0). With a precision of
0 or less, an integer is returned.
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
precision |
Int |
No | Number of decimal places (default: 0) |
AbsGraphQLDirective¶
Return the absolute value of a number.
String / Float fields
@floor, @ceil, @round and @abs work on both Float and String
fields: when the field's GraphQL type is String, the result is returned as
a string. A null value passes through unchanged (returns null).
Example¶
query {
stats {
averageRating @floor # 4.7 → 4
priceEstimate @ceil # 99.1 → 100
score @round(precision: 1) # 4.27 → 4.3
delta @abs # -3.5 → 3.5
}
}
Date Directives¶
DateGraphQLDirective¶
Format dates and times using various formats.
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
format |
String |
No | Date format string (default: "default") |
Format Options¶
Predefined Formats¶
| Format | Description | Example |
|---|---|---|
"default" |
Standard format | "01 Dec 2023 14:30:00" |
"iso" |
ISO 8601 format | "2023-12-01T14:30:00" |
"javascript" |
JS Date format | "Fri Dec 01 2023 14:30:00" |
"time ago" |
Relative time | "2 hours ago" |
"time ago 2d" |
Relative with fallback | "Yesterday" or "Dec 01, 2023" |
Custom Format Tokens¶
| Token | Description | Example |
|---|---|---|
YYYY |
4-digit year | 2023 |
YY |
2-digit year | 23 |
MMMM |
Full month name | December |
MMM |
Short month name | Dec |
MM |
Month number (padded) | 12 |
DD |
Day of month (padded) | 01 |
dddd |
Full day name | Friday |
ddd |
Short day name | Fri |
HH |
Hour (24h, padded) | 14 |
hh |
Hour (12h, padded) | 02 |
mm |
Minutes (padded) | 30 |
ss |
Seconds (padded) | 45 |
A |
AM/PM | PM |
Example¶
query {
post {
createdAt @date(format: "YYYY-MM-DD") # "2023-12-01"
updatedAt @date(format: "MMMM DD, YYYY") # "December 01, 2023"
publishedAt @date(format: "time ago") # "2 hours ago"
timestamp @date(format: "DD/MM/YY HH:mm A") # "01/12/23 02:30 PM"
}
}
List Directives¶
ShuffleGraphQLDirective¶
Randomly reorder list elements.
Example¶
SampleGraphQLDirective¶
Get a random sample from a list. k is clamped to [0, len(list)], so a value
larger than the list simply returns all elements.
Arguments¶
| Argument | Type | Required | Description |
|---|---|---|---|
k |
Int! |
Yes | Number of items to sample |
Example¶
UniqueGraphQLDirective¶
De-duplicate a list, preserving the original order. Handles unhashable items (e.g. dicts) by falling back to equality comparison.
Example¶
Lists stay lists
List directives (@shuffle, @sample, @unique) return a list, so they can
only be applied to list fields. They never mutate the underlying queryset /
result cache — the source list is always copied first.
Middleware Integration¶
GraphQLDirectiveMiddleware¶
Middleware class that processes directives during query execution.
class GraphQLDirectiveMiddleware:
def resolve(self, next, root, info, **args):
# Process directives on field resolution
pass
Django Settings¶
DJANGO_GRAPHEX = {
'SCHEMA': 'myapp.schema.schema',
'MIDDLEWARE': [
'django_graphex.middleware.GraphQLDirectiveMiddleware',
],
}
Custom Directives¶
Creating Custom Directives¶
Define get_args() to declare the arguments and resolve() to transform the
value. The middleware passes the coerced arguments as the args dict, so you
just read args.get("name") — no AST parsing, and variables work for free.
from django_graphex.directives.base import BaseExtraGraphQLDirective
from graphql import GraphQLArgument, GraphQLNonNull, GraphQLString, GraphQLInt
class MaskGraphQLDirective(BaseExtraGraphQLDirective):
"""Keep the last `visible` characters, masking the rest with `char`."""
@staticmethod
def get_args():
return {
"visible": GraphQLArgument(
GraphQLNonNull(GraphQLInt),
description="Number of trailing characters to leave visible",
),
"char": GraphQLArgument(
GraphQLString,
description="Masking character (default: '*')",
),
}
@staticmethod
def resolve(value, args, directive, root, info, **kwargs):
if not value:
return value
text = str(value)
visible = args.get("visible") or 0
char = args.get("char") or "*"
if visible >= len(text):
return text
return char * (len(text) - visible) + text[len(text) - visible:]
Registering Custom Directives¶
from django_graphex.directives import all_directives
from django_graphex.schema import DjangoGraphQLSchema
# Add custom directive to the list (all_directives already bundles the five
# spec directives along with this library's own).
custom_directives = [
*all_directives,
MaskGraphQLDirective()
]
schema = DjangoGraphQLSchema(
query=Query,
directives=custom_directives
)
Using Custom Directives¶
query {
user {
creditCard @mask(visible: 4) # "************3456"
phone @mask(visible: 2, char: "x") # "xxxxxxx89"
}
}
Directive Utilities¶
Reading Arguments¶
You do not need to extract arguments from the AST. The middleware already
coerces them (resolving variables and applying defaults) and passes them to
resolve as the args dict:
@staticmethod
def resolve(value, args, directive, root, info, **kwargs):
precision = args.get("precision") or 0 # default + variable aware
...
Type Checking Helper¶
def ensure_string(value):
"""Ensure value is a string."""
if isinstance(value, str):
return value
return str(value) if value is not None else ""
Error Handling¶
Graceful Degradation¶
class SafeDirective(BaseExtraGraphQLDirective):
@staticmethod
def resolve(value, args, directive, root, info, **kwargs):
try:
# Directive processing logic
return process_value(value)
except Exception as e:
# Log error and return original value
logger.warning(f"Directive processing failed: {e}")
return value
Validation¶
class ValidatedDirective(BaseExtraGraphQLDirective):
@staticmethod
def resolve(value, args, directive, root, info, **kwargs):
if value is None:
return None
if not isinstance(value, (str, int, float)):
raise ValueError("Directive only accepts string/number values")
return process_value(value)
Performance Considerations¶
Caching Directive Results¶
from django.core.cache import cache
class CachedDirective(BaseExtraGraphQLDirective):
@staticmethod
def resolve(value, args, directive, root, info, **kwargs):
cache_key = f"directive_{hash(str(value))}"
cached_result = cache.get(cache_key)
if cached_result is not None:
return cached_result
result = expensive_processing(value)
cache.set(cache_key, result, 300) # 5 minutes
return result
Directive Ordering¶
# Directives are processed in the order they appear
query {
field @strip @uppercase @truncate(length: 50)
# 1. Strip whitespace
# 2. Convert to uppercase
# 3. Truncate to 50 characters
}
Best Practices¶
Directive Best Practices
- Handle Null Values: Always check for null/undefined values
- Provide Defaults: Use sensible defaults for optional arguments
- Validate Input: Validate argument types and values
- Error Gracefully: Return original value on processing errors
- Document Arguments: Provide clear descriptions for all arguments
- Consider Performance: Cache expensive operations when possible
- Test Thoroughly: Test with various input types and edge cases
Security Considerations¶
class SecureDirective(BaseExtraGraphQLDirective):
@staticmethod
def resolve(value, args, directive, root, info, **kwargs):
# Validate permissions
user = info.context.user
if not user.is_authenticated:
return value # Don't process for unauthenticated users
# Sanitize input
safe_value = escape_html(str(value))
return process_value(safe_value)
Testing Directives¶
import pytest
from graphql import graphql_sync
from django_graphex.directives import all_directives
from django_graphex.schema import DjangoGraphQLSchema
def test_uppercase_directive():
schema = DjangoGraphQLSchema(
query=Query,
directives=all_directives
)
query = '''
query {
user {
name @uppercase
}
}
'''
result = graphql_sync(schema.graphql_schema, query)
assert result.data['user']['name'] == 'JOHN DOE'
This comprehensive API reference covers all directive classes and utilities in django-graphex, providing developers with the knowledge needed to use and create custom GraphQL directives for their applications.