django-graphex
GraphQL for Django, powered by graphql-core
and Pydantic. Define your GraphQL API straight from
your Django models — no DRF, no graphene, no django-filter.
- Model-first types & mutations —
DjangoModelType/DjangoModelMutationgive you query, list and create/update/delete from a singleMeta.model, validated and persisted with Pydantic v2 + the Django ORM (FK existence, uniqueness,unique_together, partial updates,choices→ Enum). - Logical filtering — one nested
filter:argument withand/or/not, per-field lookups, relation descent and plain-pk/UUID filtering (nodjango-filter). - Pagination — limit/offset, page and keyset cursor paginators with a
uniform
results/totalCountshape (and an automatic N+1 query optimizer). - Custom validation — DRF-style inline
validate_<field>()/validate()or aMeta.pydantic_model. - Permissions, security & directives — permission classes, depth & cost limits,
introspection control, and string/number/date/list directives.
Meta.only_fields/Meta.exclude_fieldsare a security boundary: a column a type hides is unreadable, unorderable and unfilterable through it. - Subscriptions — real-time GraphQL over Django Channels 4 (optional extra).
Coming from
graphene-djangoorgraphene-django-extras? See the Migration Guide for a step-by-step upgrade with before/after examples.Upgrading from
django-graphex1.x? 2.0 removed the graphene backend entirely — see the Upgrade Guide and thescripts/migrate_2_0.pycodemod.
Requirements
- Python: 3.12+ (3.13, 3.14 supported)
- Django: 5.2+ (5.2 LTS, 6.0 supported) — each Django version tested on the Python versions it officially supports
- graphql-core: >=3.2.11,<3.3
- pydantic: >=2,<3
Installation
# uv (recommended)
uv add django-graphex
# real-time subscriptions (adds Django Channels 4):
uv add "django-graphex[subscriptions]"
# pip
pip install django-graphex
pip install "django-graphex[subscriptions]"
The base install never imports channels; only the subscriptions extra does.
Quick start
from django.contrib.auth import get_user_model
from django.urls import path
from django_graphex.core import BooleanField, CharField, Field, Mutation, ObjectType
from django_graphex.fields import DjangoListObjectField, DjangoObjectField
from django_graphex.paginations import LimitOffsetGraphqlPagination
from django_graphex.schema import DjangoGraphQLSchema
from django_graphex.types import DjangoListObjectType, DjangoObjectType
from django_graphex.views import AuthenticatedGraphQLView
User = get_user_model()
class UserType(DjangoObjectType):
class Meta:
model = User
only_fields = ("id", "username", "first_name", "last_name")
filter_fields = {"username": ("icontains", "exact")}
class UserListType(DjangoListObjectType):
class Meta:
model = User
pagination = LimitOffsetGraphqlPagination()
class RegisterUser(Mutation):
ok = BooleanField()
user = Field(UserType)
class Arguments:
username = CharField(required=True)
password = CharField(required=True)
@classmethod
def mutate(cls, root, info, username, password):
user = User.objects.create_user(username=username, password=password)
return cls(ok=True, user=user)
class Query(ObjectType):
user = DjangoObjectField(UserType)
users = DjangoListObjectField(UserListType)
class Mutation(ObjectType):
register_user = RegisterUser.Field()
schema = DjangoGraphQLSchema(query=Query, mutation=Mutation)
urlpatterns = [
path("graphql/", AuthenticatedGraphQLView.as_view(schema=schema, graphiql=True)),
]
User is deliberately not mounted through a generic model mutation:
account creation must call create_user() so the password is hashed, and the
client must never choose staff/superuser flags. Use generated mutations for
ordinary application models; see the mutation guide.
Query it with the nested filter: argument (and / or / not):
{
users(filter: { isActive: { exact: true }, username: { icontains: "jo" } }) {
results(limit: 10, ordering: "-date_joined") { id username }
totalCount
}
}
Configuration
All settings live under a single DJANGO_GRAPHEX dict (every key is optional):
# settings.py
DJANGO_GRAPHEX = {
"DEFAULT_PAGINATION_CLASS": "django_graphex.paginations.LimitOffsetGraphqlPagination",
"DEFAULT_PAGE_SIZE": 20,
"MAX_PAGE_SIZE": 50,
# Response caching. Default is False (disabled).
# WARNING: cache keys are identity-salted per user (v1.2.1+), but shared
# caches can still leak data if misconfigured. Review the caching guide
# before enabling in production: docs/usage/caching.md
"CACHE_ACTIVE": False,
}
Two keys are not in that dict on purpose — both ship enabled, and pinning a key to its own default only hides that you depend on it:
REQUIRE_CSRF_HEADER(True) demands anX-Requested-Withheader on form-encoded andmultipart/form-dataPOSTs, which a browser can send cross-site with no CORS preflight.application/jsonclients are unaffected; form-encoded and multipart clients get HTTP 403 without it.MAX_SUBSCRIPTIONS_PER_CONNECTION(50) caps the concurrent operations onegraphql-transport-wssocket may hold.
Both are documented in Settings, and both are breaking for clients written against 2.2.0.
To use directives, add the middleware and pass all_directives to the schema:
DJANGO_GRAPHEX = {"MIDDLEWARE": ["django_graphex.middleware.GraphQLDirectiveMiddleware"]}
from django_graphex.directives import all_directives
from django_graphex.schema import DjangoGraphQLSchema
schema = DjangoGraphQLSchema(query=Query, mutation=Mutation, directives=all_directives)
Playground
A fully wired example project lives in examples/playground/. It exercises every major feature end-to-end — types, paginators, filtering, mutations, permissions, subscriptions, file uploads on both paths, the projection boundary on all three axes, and the query optimizer — and installs the library from this repo checkout (editable, no PyPI release needed). make test runs the end-to-end suite; several tests assert the verbatim answer strings its README quotes.
Documentation
📚 Full documentation — including the Quick Start, Model backend, Filtering, Pagination, Subscriptions, Settings and the Migration Guide.
License
MIT License — see the LICENSE file.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file django_graphex-3.1.0.tar.gz.
File metadata
- Download URL: django_graphex-3.1.0.tar.gz
- Upload date:
- Size: 2.1 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
16b420bfaf72351a49d86087d7b843e90fd19de1566348729f9b8dbe49083ff1
|
|
| MD5 |
d2e8bdce45f4a5acf557538e1bb1df2c
|
|
| BLAKE2b-256 |
07332f7467c93658c2e47062d08e3d198d48e60dfb01676a8c1bfbf3f1500a69
|
File details
Details for the file django_graphex-3.1.0-py3-none-any.whl.
File metadata
- Download URL: django_graphex-3.1.0-py3-none-any.whl
- Upload date:
- Size: 554.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.12.9 {"installer":{"name":"uv","version":"0.12.9","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
849565cdc7499c00e0c8959715af1428f12807d348b15119ab9e2cdc5e881688
|
|
| MD5 |
461fb03ddbab9e5a8095979b561d0822
|
|
| BLAKE2b-256 |
d6397963c1e9c9870206b770f2b26a577c329dfed5053b5c6a5434bc08efa02c
|