Skip to main content

Run a Django backend (Django ORM, Django Admin) alongside a Reflex app.

Project description

reflex-django

Table of contents

  1. About reflex-django
  2. Architecture
  3. How to set up
  4. Commands
  5. States, context, and bridges
  6. Declarative model CRUD (mixins)

About reflex-django

reflex-django is a Reflex plugin that runs a Django ASGI application and your Reflex app in one process under a single dev command (reflex run). HTTP requests whose paths match configured prefixes (for example Django Admin, optional API routes, and path-based static URLs) are forwarded to Django. Everything else—including the compiled Reflex frontend and Reflex’s Socket.IO event channel under /_event/…—is handled by Reflex.

Why it exists. Reflex gives you a Python-first reactive UI. Django gives you the ORM, migrations, the admin, sessions, authentication, internationalization, and the ecosystem of HTTP views and middleware you already rely on. reflex-django lets you keep that Django surface area without standing up a separate HTTP server for local development or simple deployments, while still using Reflex for the interactive UI.

Why Django developers need it. Reflex user actions arrive over WebSocket events, not through Django’s normal request/response cycle, so Django’s HTTP middleware (sessions, auth, locale) does not run for those events by default. reflex-django adds an explicit event bridge that rebuilds a synthetic HttpRequest from the browser data Reflex provides, attaches the session and user, and exposes that request through small APIs your event handlers can call. A separate HTTP bridge routes ordinary browser HTTP traffic on selected path prefixes to Django’s ASGI app. Together, these bridges make Django session auth and related patterns usable from Reflex without pretending the two stacks share one router.

Author. This package is written by Mohannad Irshedat.

Topic Versions
Django 6.0.x
Python 3.12+

Architecture

At a high level, reflex-django does three coordinated things:

  1. Plugin bootstrap — When rxconfig.py is loaded, ReflexDjangoPlugin sets DJANGO_SETTINGS_MODULE (if you pass settings_module), exports path prefix environment variables used by Django settings, and calls configure_django() so Django is initialized before your Reflex app module imports models or translation helpers.

  2. HTTP ASGI composition — After Reflex compiles your app, the plugin appends an api_transformer that wraps Reflex’s inner ASGI app. Incoming HTTP (and WebSocket upgrade traffic where applicable) is dispatched by URL path prefix: matching prefixes go to Django’s ASGI application (optionally wrapped for static files); non-matching traffic stays on Reflex. ASGI lifespan events are owned by Reflex.

  3. Per-event Django context — When install_event_bridge is true (the default), Reflex registers DjangoEventBridge middleware. On each Reflex event, the bridge builds a synthetic django.http.HttpRequest from event.router_data (cookies, headers, client IP, path), loads the Django session, optionally applies the same locale negotiation as LocaleMiddleware, resolves request.user via Django’s async aget_user, and binds that request on a context variable for the duration of the event. Your handlers call current_user(), current_request(), and related helpers, which read that binding.

PyPI and other indexes do not resolve relative image paths in the project description. Use an absolute URL (below) and keep img.png on your GitHub default branch, or change the URL to match your fork and branch name.

reflex-django architecture

ASCII overview (same idea everywhere, including plain-text viewers):

Browser
  │ HTTP (paths under admin / api / static …)
  ├──────────────────────────────► Django ASGI
  │
  │ HTTP + Socket.IO (Reflex UI, /_event/ …)
  └──────────────────────────────► Reflex ASGI (prefix dispatcher)
                                           │
                                           ▼
                               Reflex event → DjangoEventBridge
                                           → contextvars (current_request, …)
                                           → your @rx.event handlers

How to set up

Use uv to create a project, add dependencies, scaffold the Reflex frontend, create a Django project, then wire the plugin in rxconfig.py.

  1. Initialize a Python project:

    uv init
    
  2. Add Reflex and reflex-django:

    uv add reflex reflex-django
    
  3. Initialize the Reflex frontend (app name and layout follow Reflex’s CLI):

    uv run reflex init frontend
    
  4. Create a Django project package named backend (adjust the name if you prefer):

    uv run django-admin startproject backend .
    

    This typically produces backend/settings.py, backend/urls.py, and manage.py in the current directory.

  5. Configure rxconfig.py so Reflex loads Django with your settings module. Import the plugin and pass settings_module as the dotted path to your Django settings (for example backend.settings):

    import reflex as rx
    from reflex_django import ReflexDjangoPlugin
    
    config = rx.Config(
        app_name="myapp",
        plugins=[
            ReflexDjangoPlugin(settings_module="backend.settings"),
        ],
    )
    

Minimal Django expectations. Your INSTALLED_APPS should include the Django contrib apps you need (for example django.contrib.auth, django.contrib.sessions, and often django.contrib.admin) and reflex_django if you use bundled helpers. Set ROOT_URLCONF and mount admin (and any HTTP routes under an optional backend_prefix) so path-based routing matches what you configure on ReflexDjangoPlugin. Run the app with:

uv run reflex run

Commands

reflex-django registers a django command group on the reflex CLI (via a small import hook installed with the package) and also ships a standalone console script reflex-django.

Running Django management commands

  • Through Reflex:

    uv run reflex django migrate
    uv run reflex django makemigrations
    uv run reflex django createsuperuser
    uv run reflex django shell
    uv run reflex django collectstatic
    uv run reflex django help
    
  • Through the standalone entry point (equivalent forwarding for normal manage.py subcommands):

    uv run reflex-django migrate
    uv run reflex-django help
    

Any subcommand name other than the special cases below is forwarded to Django’s execute_from_command_line. The wrapper first loads your rxconfig (so ReflexDjangoPlugin can set DJANGO_SETTINGS_MODULE and path prefixes) and then calls configure_django(), so the same settings module Reflex uses at runtime is used for migrations and other management commands.

Init scaffolding (omitted here). reflex django init and reflex-django init exist to scaffold a starter tree but are considered beta; this README does not document them. Prefer the manual flow in How to set up above.


States, context, and bridges

This section walks through Reflex state, Django’s per-event request context (context variables), the two bridges, and the helper states that mirror Django into Reflex UI state—each with a small example. For declarative Django model CRUD as a generated rx.State subclass, see Declarative model CRUD (mixins) below.

Reflex rx.State (baseline)

In Reflex, a State class subclasses rx.State. Fields you declare are synchronized with the client where appropriate; values must be JSON-serializable when they cross the wire. You define event handlers with @rx.event (often async def when you call async Django APIs).

import reflex as rx


class CounterState(rx.State):
    count: int = 0

    @rx.event
    def increment(self):
        self.count += 1

Per-event Django context (reflex_django.context)

Reflex events do not carry a Django HttpRequest object. reflex-django stores the synthetic request built for the current event on a context variable. Public read helpers:

Function Role
current_request() Bound HttpRequest or None outside an event without the bridge
current_user() Django user or AnonymousUser
current_session() Session backend instance or None
current_language() Active language code after locale activation

Lower-level begin_event_request(request) and end_event_request() are used by the bridge and for tests or advanced scenarios; application code normally relies on the helpers above inside @rx.event handlers.

import reflex as rx
from reflex_django import current_user


class WhoamiState(rx.State):
    label: str = ""

    @rx.event
    async def refresh(self):
        user = current_user()
        self.label = user.get_username() if user.is_authenticated else "anonymous"

Bridge 1 — HTTP ASGI path dispatcher

ReflexDjangoPlugin installs an api_transformer that wraps Reflex’s ASGI app. Requests whose path starts with any configured prefix are sent to Django’s ASGI app; other paths stay on Reflex. Relevant plugin arguments:

Argument Meaning
backend_prefix Optional prefix for your own Django HTTP routes (for example "/api")
admin_prefix Prefix for Django Admin (default "/admin")
extra_prefixes Additional path prefixes routed to Django
install_event_bridge When True, registers DjangoEventBridge (default)
ReflexDjangoPlugin(
    settings_module="backend.settings",
    backend_prefix="/api",
    admin_prefix="/admin",
    extra_prefixes=("/billing",),
)

Your ROOT_URLCONF must define routes under those prefixes so Django can serve them.

Bridge 2 — DjangoEventBridge (Reflex middleware)

DjangoEventBridge runs at the start of each Reflex event. It constructs a synthetic HttpRequest, attaches the session, optionally runs locale negotiation when USE_I18N and REFLEX_DJANGO_I18N_EVENT_BRIDGE allow it, and sets request.user using aget_user. It then calls begin_event_request(request) so current_user() and friends work in your handlers.

To disable this behavior (for example if you do not use Django session auth from Reflex):

ReflexDjangoPlugin(settings_module="backend.settings", install_event_bridge=False)

DjangoUserState

DjangoUserState is a rx.State subclass that mirrors a JSON-safe snapshot of the current user (user_id, username, email, names, is_authenticated, staff/superuser flags, optional group_names). Use it for navbar and conditional UI.

  • Call sync_from_django from a page’s on_load (it is an @rx.event).
  • After login or logout, call await self.refresh_django_user_fields() from an async handler so the snapshot updates without a full navigation when appropriate.

Server-side authorization must still use current_user() (or stricter checks); client-visible state is for display only.

import reflex as rx
from reflex_django import DjangoUserState


class AuthState(DjangoUserState):
    pass


app = rx.App()

# app.add_page(index, on_load=AuthState.sync_from_django)

DjangoI18nState

DjangoI18nState exposes django_language_code and django_language_bidi, aligned with Django’s active language after the event bridge. Use sync_from_django on on_load, or await self.refresh_django_i18n_fields() after the user changes language via a Django-served view.

import reflex as rx
from reflex_django import DjangoI18nState


app = rx.App()

# app.add_page(home, on_load=DjangoI18nState.sync_from_django)

Reflex-facing Django context (processors and DjangoContextState)

Reflex serializes state to the browser, so any dict you merge into Reflex state must be JSON-serializable.

Two configuration modes (see reflex_django.reflex_context):

  1. REFLEX_DJANGO_CONTEXT_PROCESSORS — A non-empty tuple of dotted paths to callables (request) -> dict (or async). When this list is set, it is used exclusively. You are responsible for JSON-safe values.

  2. Template context processors — If REFLEX_DJANGO_CONTEXT_PROCESSORS is empty and REFLEX_DJANGO_USE_TEMPLATE_CONTEXT_PROCESSORS is True, the same dotted paths as in TEMPLATES[*].OPTIONS["context_processors"] are run, with built-in sanitization (for example user becomes a snapshot; request, perms, messages are omitted; other values must pass plain json.dumps).

Built-in processor callables you can list explicitly:

  • reflex_django.reflex_context.builtin_user_context — adds a template-shaped user key as a snapshot dict.
  • reflex_django.reflex_context.builtin_i18n_context — adds LANGUAGE_CODE, LANGUAGE_BIDI, and LANGUAGES.

Example settings snippet:

REFLEX_DJANGO_CONTEXT_PROCESSORS = (
    "reflex_django.reflex_context.builtin_user_context",
    "reflex_django.reflex_context.builtin_i18n_context",
)

collect_reflex_context(request) runs the configured processor list and returns one merged dict. You can await it inside a handler when current_request() is bound:

from reflex_django import current_request
from reflex_django.reflex_context import collect_reflex_context


@rx.event
async def debug_context(self):
    merged = await collect_reflex_context(current_request())
    # use merged (e.g. assign JSON-safe keys to state)

DjangoContextState holds django_context and a stringified django_context_json. Call load_django_context from on_load to populate them from processors using the bound request:

from reflex_django import DjangoContextState


# app.add_page(about, on_load=DjangoContextState.load_django_context)

Helper functions template_context_processor_paths() and reflex_context_processor_paths() introspect settings for tooling or documentation.

user_snapshot vs current_user

user_snapshot(user) returns the same flat dict shape used by DjangoUserState, given a user instance. It is useful in custom context processors, tests, or logging—without touching Reflex state.

current_user() returns the live Django user (or anonymous) for the current Reflex event after the bridge runs. Use it for permissions, ownership checks, and mutations on the server.

DjangoUserState fields are a UI snapshot; always re-check authorization with current_user() (or equivalent) inside event handlers that change data.

from reflex_django import current_user
from reflex_django.auth_state import user_snapshot


@rx.event
async def audit_banner(self):
    self.snapshot_json = user_snapshot(current_user())  # dict for display
    assert current_user().is_authenticated  # real check for protected actions

Declarative model CRUD (mixins)

reflex_django.mixins.crud (also re-exported from reflex_django.mixins) builds a Reflex rx.State subclass from a small declarative config so you can list, create, edit, and delete rows of a Django model without hand-writing the same event wiring each time.

Requirements. Django must be configured (plugin + INSTALLED_APPS including your app and auth/session as usual). The event bridge must be enabled so django_login_required() and require_login_user() see the session user. CRUD handlers use the default django_login_required() wrapper (anonymous users are redirected; login URL from REFLEX_DJANGO_LOGIN_URL unless you customize handlers yourself—see reflex_django.authz).

How it works

  1. You define a frozen ModelCRUDConfig pointing at your models.Model, the state attribute names you want on the client (list_var, error_var), which model fields appear in create/edit forms (form_fields), and optional owner_field (for example "user") so queries and writes are scoped to require_login_user().

  2. You call crud_mixin(cfg, base=…). It returns a new class named {Model.__name__}CRUDState with:

    • A list of row dicts under list_var (default serializer uses model_to_dict plus id, JSON-friendly datetimes).
    • String fields form_<name> and edit_<name> for each entry in form_fields, plus editing_id (-1 when not editing).
    • refresh_method: async reload from the ORM (respects owner_field, ordering).
    • on_load_event: async on_load target to call your refresh (login required).
    • add_event / delete_event: create and delete (login required).
    • start_edit, save_edit, cancel_edit: edit lifecycle (cancel_edit clears local edit state only and is not login-gated).
    • set_form_<field> / set_edit_<field>: @rx.event setters for inputs.
  3. You subclass that generated class when you need a stable app-specific name or extra state:

    class NotesState(crud_mixin(_NOTE_CRUD_CONFIG, base=AppState)):
        """Auth and shared session live on ``AppState``."""
    

base= should be your app’s shared rx.State subclass (for example one that holds login UI or DjangoUserState mixins) so the CRUD state participates in the same inheritance tree as the rest of your app.

state_module= defaults to the calling module’s __name__ so the dynamic class is registered on sys.modules for Reflex pickling. Pass it explicitly if you build state from a helper function in another module.

Example

from django.conf import settings
from django.db import models

import reflex as rx
from reflex_django.mixins.crud import ModelCRUDConfig, crud_mixin


class Note(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    title = models.CharField(max_length=200)
    content = models.TextField(blank=True)
    created_at = models.DateTimeField(auto_now_add=True)


_NOTE_CRUD_CONFIG = ModelCRUDConfig(
    model=Note,
    list_var="notes",
    form_fields=("title", "content"),
    error_var="notes_error",
    owner_field="user",
    ordering=("-created_at",),
    required_for_create=("title",),
    refresh_method="_refresh_note_rows",
    on_load_event="on_load_notes",
    add_event="add_note",
    delete_event="delete_note",
)


class AppState(rx.State):
    """Shared app base (login fields, etc.)."""
    pass


class NotesState(crud_mixin(_NOTE_CRUD_CONFIG, base=AppState)):
    """Notes CRUD; list lives in ``notes``, errors in ``notes_error``."""

# Typical page wiring (names match config):
# app.add_page(notes_page, route="/notes", on_load=NotesState.on_load_notes)

In your page component, bind inputs to NotesState.form_title, NotesState.set_form_title, and so on; call NotesState.add_note, NotesState.start_edit, NotesState.save_edit, NotesState.cancel_edit, NotesState.delete_note as on_click / table actions; render NotesState.notes (list of dicts, each with id) with rx.foreach.

Configurable ModelCRUDConfig fields include row_serializer, exclude_from_row, owner_field=None (no user scoping), and the default event names refresh_method, on_load_event, add_event, delete_event when you do not want the stock on_load_items / add_item names.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

reflex_django-0.1.2.tar.gz (58.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

reflex_django-0.1.2-py3-none-any.whl (51.9 kB view details)

Uploaded Python 3

File details

Details for the file reflex_django-0.1.2.tar.gz.

File metadata

  • Download URL: reflex_django-0.1.2.tar.gz
  • Upload date:
  • Size: 58.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.0 {"installer":{"name":"uv","version":"0.11.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for reflex_django-0.1.2.tar.gz
Algorithm Hash digest
SHA256 1ab4659d49eaf78a02c8e7ac9eab0913d50680036876a8827a6d0d715fc98bfa
MD5 eff8c600ef0b081e2f2863f531421f83
BLAKE2b-256 cf2f46af3d1d92a8a9e18bdec6d45330ca238c470425f927acea55b2bfef42da

See more details on using hashes here.

File details

Details for the file reflex_django-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: reflex_django-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 51.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.0 {"installer":{"name":"uv","version":"0.11.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for reflex_django-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1a078ce7e7779ac9c78a7d57a3a5a3a6734b5cdad3863fe0c1ebdf5c5210f8cf
MD5 5220d70fc0939acd7d8cccf4fd3d36d2
BLAKE2b-256 0ed62aa107e3e3e4e32dedab80c25cbcb8b44545551e1d97947d0088e13a72a2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page