Skip to main content

django-graphex

Codecov PyPI - Python Version Django Versions PyPI PyPI - License Downloads Ruff

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 / DjangoModelMutation give you query, list and create/update/delete from a single Meta.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 with and / or / not, per-field lookups, relation descent and plain-pk/UUID filtering (no django-filter).
  • Pagination — limit/offset, page and keyset cursor paginators with a uniform results / totalCount shape (and an automatic N+1 query optimizer).
  • Custom validation — DRF-style inline validate_<field>() / validate() or a Meta.pydantic_model.
  • Permissions, security & directives — permission classes, depth & cost limits, introspection control, and string/number/date/list directives. Meta.only_fields / Meta.exclude_fields are 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-django or graphene-django-extras? See the Migration Guide for a step-by-step upgrade with before/after examples.

Upgrading from django-graphex 1.x? 2.0 removed the graphene backend entirely — see the Upgrade Guide and the scripts/migrate_2_0.py codemod.

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.3.0,<3.4
  • pydantic: >=2,<3

django-graphex 4.1.0 requires graphql-core>=3.3.0,<3.4. The historical 3.1.1 release requires graphql-core>=3.2.13,<3.3. Follow the 4.0 upgrade guide when migrating custom executors, AST builders or subscription transports, and the 4.1 upgrade guide when moving from 4.0.

Installation

# uv (recommended)
uv add "django-graphex==4.1.0"
# real-time subscriptions (adds Django Channels 4):
uv add "django-graphex[subscriptions]==4.1.0"
# pip
pip install "django-graphex==4.1.0"
pip install "django-graphex[subscriptions]==4.1.0"

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,
}

When response caching is enabled, only HTTP 200 GraphQL data responses without errors are stored or reused; a failed request is executed again rather than replaying an old cached error. See the caching guide.

The built-in HTTP views are synchronous. Async resolvers or middleware are not executed there: an awaitable execution returns HTTP 500 with a controlled GraphQL JSON error asking for synchronous resolvers and middleware. See the view contract.

The library default is DEFAULT_PAGE_SIZE: 100 and MAX_PAGE_SIZE: None. Omit DEFAULT_PAGE_SIZE to inherit 100 for LimitOffset, Page, and Cursor paginators; the explicit 20 above is an application override. A default is not a maximum: configure MAX_PAGE_SIZE to cap client-requested sizes. Clients that previously relied on an unbounded omitted-size list should paginate past the first 100 rows or explicitly opt out; None has paginator-specific legacy behavior and does not mean “all rows” for Page pagination.

With MAX_QUERY_COST configured, GraphQLView checks the selected operation against GraphQL-coerced request variables before running resolvers. A variable limit above the budget now receives HTTP 400 / QUERY_TOO_COMPLEX, even if its static fallback estimate passed; MAX_PAGE_SIZE remains the recommended hard ceiling for lists with omitted sizes.

CLEAN_RESPONSE is off by default. When enabled for GraphQLView, it removes nulls, empty strings, and empty containers recursively, but retains numeric zero and false. Selected introspection operations bypass cleaning. See response cleaning.

For bounded model-backed limit/offset and page-number results, ties in the effective ordering use the primary key ascending unless it is already an ordering term. This preserves an explicit descending key, but does not prevent offset shifts when rows change between requests. See pagination ordering.

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 an X-Requested-With header on form-encoded and multipart/form-data POSTs, which a browser can send cross-site with no CORS preflight. application/json clients are unaffected; form-encoded and multipart clients get HTTP 403 without it.
  • MAX_SUBSCRIPTIONS_PER_CONNECTION (50) caps the concurrent operations one graphql-transport-ws socket 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.

The current core33 comparison uses eight committed portable results in core33-4.1.0-f9f4eae81954959ab71e4a9037d5ba06486d6a8e, measured from 4.1.0 source f9f4eae81954959ab71e4a9037d5ba06486d6a8e, not a published wheel. In the local SQLite create-comment operation, GraphEx/Ariadne means were 0.5159 vs 0.9268 ms at 50,000 comments (44.34% less mean time for GraphEx) and 0.5549 vs 0.9382 ms at 100,000 (40.85% less mean time). The separate p50 ratios were 0.54× (0.4758/0.8754 ms) and 0.51× (0.4592/0.8978 ms), respectively. These are median-of-three-run statistics, not a paired historical comparison: the four whole stacks differ in graphql-core version and the host load changed. The result is not a PostgreSQL or universal performance claim. The preceding core33-4.1.0-460efb9b7c29b097f2939c05e79514f2fda3242c and older series remain historical.

License

MIT License — see the LICENSE file.

Metadata

Release files for django-graphex 4.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-graphex 4.1.0
File Size Uploaded Provenance Actions
django_graphex-4.1.0.tar.gz 3.2 MB Without provenance Details for django_graphex-4.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-graphex 4.1.0
File Interpreter ABI Platform Actions
django_graphex-4.1.0-py3-none-any.whl Python 3 none any Details for django_graphex-4.1.0-py3-none-any.whl

Total release size: 3.8 MB

Release files / django_graphex-4.1.0.tar.gz

Download URL django_graphex-4.1.0.tar.gz
Size 3.2 MB
Tags Source
SHA-256 checksum
How to use checksums
fcd104038024fd197e3b6af73eae767a92f050899b3db361a30877138a943efa
BLAKE2b-256 checksum
How to use checksums
8cedc22e645b86bec0ccac311532847748a77caee1ce03d356b003c01ae4ac2c
Upload date
Uploaded using Trusted Publishing?
What is 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":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / django_graphex-4.1.0-py3-none-any.whl

Download URL django_graphex-4.1.0-py3-none-any.whl
Size 643.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f0485628b9ed4944ec4203c311ac52bc60603395a60015e1a36b5b54240cd740
BLAKE2b-256 checksum
How to use checksums
30b90f43b7487f26ff9a9691828687d3bbf042cba3fcd5b11dd64d4d750120e2
Upload date
Uploaded using Trusted Publishing?
What is 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":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for django-graphex 4.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page