Run a Django backend (Django ORM, Django Admin) alongside a Reflex app.
Project description
reflex-django
Table of contents
- About reflex-django
- Architecture
- How to set up
- Commands
- States, context, and bridges
- Declarative session login (mixins)
- 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:
-
Plugin bootstrap — When
rxconfig.pyis loaded,ReflexDjangoPluginsetsDJANGO_SETTINGS_MODULE(if you passsettings_module), exports path prefix environment variables used by Django settings, and callsconfigure_django()so Django is initialized before your Reflex app module imports models or translation helpers. -
HTTP ASGI composition — After Reflex compiles your app, the plugin appends an
api_transformerthat 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. -
Per-event Django context — When
install_event_bridgeis true (the default), Reflex registersDjangoEventBridgemiddleware. On each Reflex event, the bridge builds a syntheticdjango.http.HttpRequestfromevent.router_data(cookies, headers, client IP, path), loads the Django session, optionally applies the same locale negotiation asLocaleMiddleware, resolvesrequest.uservia Django’s asyncaget_user, and binds that request on a context variable for the duration of the event. Your handlers callcurrent_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.
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.
-
Initialize a Python project:
uv init -
Add Reflex and reflex-django:
uv add reflex reflex-django
-
Initialize the Reflex frontend (app name and layout follow Reflex’s CLI):
uv run reflex init frontend
-
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, andmanage.pyin the current directory. -
Configure
rxconfig.pyso Reflex loads Django with your settings module. Import the plugin and passsettings_moduleas the dotted path to your Django settings (for examplebackend.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 session login/logout as a generated rx.State subclass, see Declarative session login (mixins). For declarative Django model CRUD, 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.
Development vs production routing
- Development (
reflex run): Reflex runs Vite onfrontend_portand the ASGI backend onbackend_port. The plugin injects matchingserver.proxyrules into.web/vite.config.jsso Django prefixes (/admin,/api,extra_prefixes, etc.) work from the frontend URL (for examplehttp://localhost:3000/admin). You can also use the backend URL directly (http://localhost:8000/admin). - Production (
reflex run --env prod): A single server serves the compiled frontend and the backend. The same path-prefix dispatcher applies. The plugin also patches Reflex's catch-allStaticFilesmount so WebSocket connections (for example Socket.IO at/_event) are not routed intoStaticFiles, which only accepts HTTP.
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_djangofrom a page’son_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):
-
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. -
Template context processors — If
REFLEX_DJANGO_CONTEXT_PROCESSORSis empty andREFLEX_DJANGO_USE_TEMPLATE_CONTEXT_PROCESSORSisTrue, the same dotted paths as inTEMPLATES[*].OPTIONS["context_processors"]are run, with built-in sanitization (for exampleuserbecomes a snapshot;request,perms,messagesare omitted; other values must pass plainjson.dumps).
Built-in processor callables you can list explicitly:
reflex_django.reflex_context.builtin_user_context— adds a template-shapeduserkey as a snapshot dict.reflex_django.reflex_context.builtin_i18n_context— addsLANGUAGE_CODE,LANGUAGE_BIDI, andLANGUAGES.
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
Canned authentication pages
Inspired by reflex-local-auth, reflex-django ships ready-made login, registration, and password-reset pages backed by Django sessions and the stock User model.
Quick start
In django_settings.py (or your settings module):
REFLEX_DJANGO_AUTH = {
"SIGNUP_ENABLED": True,
"PASSWORD_RESET_ENABLED": True,
"LOGIN_URL": "/login",
"SIGNUP_URL": "/register",
"LOGIN_REDIRECT_URL": "/",
}
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
DEFAULT_FROM_EMAIL = "noreply@localhost"
In your Reflex app module:
import reflex as rx
from reflex_django.auth import add_auth_pages, login_required, routes
from reflex_django.auth.state import DjangoAuthState
app = rx.App()
add_auth_pages(app)
@rx.page()
@login_required
def dashboard():
return rx.heading("Members only")
Settings (REFLEX_DJANGO_AUTH)
| Key | Default | Purpose |
|---|---|---|
ENABLED |
True |
Master switch for canned pages |
SIGNUP_ENABLED |
True |
Register page at SIGNUP_URL |
PASSWORD_RESET_ENABLED |
True |
Forgot-password flow |
LOGIN_URL |
/login |
Login route (also used by django_login_required) |
SIGNUP_URL |
/register |
Registration route |
PASSWORD_RESET_URL |
/password-reset |
Request reset email |
PASSWORD_RESET_CONFIRM_URL |
/password-reset/confirm/[uid]/[token] |
Set new password |
LOGIN_REDIRECT_URL |
/ |
After successful login |
LOGOUT_REDIRECT_URL |
/login |
After logout |
SIGNUP_REDIRECT_URL |
/login |
After registration (auto sign-in) |
REDIRECT_AUTHENTICATED_USER |
/ |
When visiting login/register while signed in |
EMAIL_REQUIRED |
False |
Require email on signup |
PASSWORD_MIN_LENGTH |
8 |
Minimum password length |
MESSAGES |
(built-in dict) | User-facing copy |
Legacy REFLEX_DJANGO_LOGIN_URL is still read when LOGIN_URL is omitted from the dict.
Security notes
@login_requiredonly redirects in the UI (like reflex-local-auth). Use@django_login_requiredorrequire_login_user()on event handlers that return private data.- Password-reset emails use Django’s token generator; use a stable
SECRET_KEYin production. - Registration creates active users immediately; set
SIGNUP_ENABLED=Falseif only admins should create accounts.
Customization
- Import
reflex_django.auth.pagesand register your own components on the same routes. - Subclass or extend
DjangoAuthState(built from session login + registration + reset mixins). - For hand-built forms, keep using
session_auth_mixin(see below).
Declarative session login (mixins)
reflex_django.mixins.session_auth (re-exported from reflex_django.mixins) builds a Reflex rx.State subclass from a frozen SessionAuthConfig: username/password/error string fields, input setters, an on_load-style handler that refreshes DjangoUserState and optionally redirects already-authenticated users, submit_login (async aauthenticate / alogin on current_request()), optional submit_login_form (same flow using form_data from rx.form.root — avoids stale bound fields on fast submit), and logout (alogout then navigation).
Successful login calls await request.session.asave() then mirrors the new session key into document.cookie via rx.call_script (see reflex_django.session_js) and performs a short deferred full-page navigation. Reflex’s synthetic request path does not run Django’s SessionMiddleware, so rx.redirect alone often leaves the browser without an updated sessionid; logout clears the cookie the same way before navigating.
Requirements. The event bridge must be enabled so current_request() carries the session for each Reflex event. Django 6+ async auth (django.contrib.auth) is used inside handlers.
How it works
- Define
SessionAuthConfigwith redirect paths (for examplepost_login_redirect,post_logout_redirect,redirect_when_authenticatedorNoneto skip). - Call
session_auth_mixin(cfg, base=DjangoUserState)(orbase=your app state that already subclassesDjangoUserState). The returned class is namedSessionAuthStateby default (seestate_class_namein config). - Subclass it if you want a stable app-specific name (for example
LoginState).
state_module= defaults to the caller’s __name__ so the generated class is registered on sys.modules for Reflex pickling.
Example
import reflex as rx
from reflex_django.auth_state import DjangoUserState
from reflex_django.mixins.session_auth import SessionAuthConfig, session_auth_mixin
_LOGIN_CFG = SessionAuthConfig(
post_login_redirect="/notes",
post_logout_redirect="/login",
redirect_when_authenticated="/notes",
)
class LoginState(session_auth_mixin(_LOGIN_CFG, base=DjangoUserState)):
"""Login page; wire ``on_load=LoginState.on_load_login``, etc."""
# app.add_page(login_page, route="/login", on_load=LoginState.on_load_login)
Configurable SessionAuthConfig fields include username_var, password_var, error_var, event names (on_load_event, submit_event, logout_event, optional submit_form_event defaulting to submit_login_form — set to None to omit), form_username_key / form_password_key (HTML field names for the form submit handler, default username / password), message strings session_unavailable_message / invalid_credentials_message, and state_class_name (defaults to SessionAuthState; set a unique value if you generate more than one session-auth state under the same base= parent, since Reflex disallows duplicate substate names).
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
-
You define a frozen
ModelCRUDConfigpointing at yourmodels.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 optionalowner_field(for example"user") so queries and writes are scoped torequire_login_user(). -
You call
crud_mixin(cfg, base=…). It returns a new class named{Model.__name__}CRUDStatewith:- A list of row dicts under
list_var(default serializer usesmodel_to_dictplusid, JSON-friendly datetimes). - String fields
form_<name>andedit_<name>for each entry inform_fields, plusediting_id(-1when not editing). refresh_method: async reload from the ORM (respectsowner_field,ordering).on_load_event: asyncon_loadtarget to call your refresh (login required).add_event/delete_event: create and delete (login required).start_edit,save_edit,cancel_edit: edit lifecycle (cancel_editclears local edit state only and is not login-gated).set_form_<field>/set_edit_<field>:@rx.eventsetters for inputs.
- A list of row dicts under
-
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
Release history Release notifications | RSS feed
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 reflex_django-0.2.3.tar.gz.
File metadata
- Download URL: reflex_django-0.2.3.tar.gz
- Upload date:
- Size: 82.7 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e06b668d7e51a5cdb84b11bf3e630c1b94c232a3a43ae0f8997dfbaaa1ba5444
|
|
| MD5 |
fb9ed1451591af4a17ea4ae562e4e997
|
|
| BLAKE2b-256 |
fffd2ab85688165f599e51d17b2cf5e085deaaf1ec515ac758adbabbdac46fec
|
File details
Details for the file reflex_django-0.2.3-py3-none-any.whl.
File metadata
- Download URL: reflex_django-0.2.3-py3-none-any.whl
- Upload date:
- Size: 76.7 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d291d7b26f1ff4799f95b2dbe0dc7711192acf782c2ae1da6a6335a0ab87f19
|
|
| MD5 |
c07ae569bde978110758864d27948e40
|
|
| BLAKE2b-256 |
307ef6c3ccb6eadff57fe25d0faeba328cf3ac795fe95d356a69ae3ed5706b8f
|