# reflex-django — LLM and agent guide

This file orients coding agents and LLMs: how reflex-django fits Reflex + Django, what to import, how to configure it safely, and common mistakes. For tutorials and longer examples, read `README.md` in this directory.

---

## Metadata

| Item | Value |
|------|--------|
| Package | `reflex-django` (import: `reflex_django`) |
| Purpose | Run a Django ASGI app (ORM, admin, sessions, auth, i18n) alongside a Reflex app in **one process** under `reflex run`. |
| Python | `>=3.12,<4.0` |
| Django | `>=6.0,<7.0` |
| Reflex | `>=0.9.2,<1.0` |
| License | Apache-2.0 (see `LICENSE`) |

---

## What it is / what it is not

**Is:** A Reflex **plugin** (`ReflexDjangoPlugin`) that (1) initializes Django early from `rxconfig.py`, (2) wraps Reflex’s ASGI stack with a **path-prefix dispatcher** so selected HTTP paths go to Django and the rest to Reflex, and (3) optionally installs **Reflex event middleware** (`DjangoEventBridge`) that builds a synthetic `HttpRequest` per Socket.IO event and binds it in **context variables** so helpers like `current_user()` work inside `@rx.event` handlers.

**Is not:** A single merged URL router. Reflex UI traffic and `/_event/…` are **not** ordinary Django views. Django HTTP middleware does **not** run for Reflex events unless you rely on the **event bridge** (default on), which reconstructs session, user, and optional locale for that event only.

---

## Agent setup checklist

Do these in order:

1. Add dependencies: `uv add reflex reflex-django` (or equivalent).
2. Scaffold Reflex: `uv run reflex init frontend` (app name/layout per Reflex CLI).
3. Create a Django project (e.g. `uv run django-admin startproject backend .`) so you have `manage.py` and a settings module (e.g. `backend.settings`).
4. In **`INSTALLED_APPS`**, include Django contrib apps you need (`auth`, `sessions`, `admin`, `staticfiles`, …) and **`reflex_django`** when you use bundled helpers (states, admin registration, etc.).
5. In **`rxconfig.py`**, add the plugin with your settings module:

   ```python
   import reflex as rx
   from reflex_django import ReflexDjangoPlugin

   config = rx.Config(
       app_name="myapp",
       plugins=[ReflexDjangoPlugin(settings_module="backend.settings")],
   )
   ```

6. Align **`ROOT_URLCONF`** (and `urlpatterns`) with plugin prefixes: `backend_prefix`, `admin_prefix` (default `"/admin"`), and `extra_prefixes`. Mismatched prefixes are a frequent bug.
7. Run: `uv run reflex run`.

---

## Architecture (compact)

```text
Browser
  │ HTTP matching admin / api / static path prefixes…
  ├──────────────────────────────► Django ASGI (same process)
  │
  │ Reflex pages, assets, Socket.IO /_event/…
  └──────────────────────────────► Reflex ASGI
                                         │
                                         ▼
                             Reflex event → DjangoEventBridge
                                         → contextvars (current_request, …)
                                         → your @rx.event handlers
```

- **HTTP bridge:** Implemented via `ReflexDjangoPlugin.post_compile` appending an `api_transformer` that wraps the inner app (`src/reflex_django/plugin.py`, `src/reflex_django/asgi.py`).
- **Event bridge:** `DjangoEventBridge` Reflex middleware (`src/reflex_django/middleware.py`) calls `begin_event_request` / `end_event_request` (`src/reflex_django/context.py`).
- **ASGI lifespan:** Owned by Reflex, not duplicated for Django in the same way as a standalone Django-only deploy—see README “Architecture” for nuance.

---

## Public API (package `reflex_django`)

These names match `__all__` in `src/reflex_django/__init__.py`. Many are **lazy-loaded** (see next section).

| Area | Symbols |
|------|---------|
| Plugin | `ReflexDjangoPlugin` |
| Django init | `configure_django` |
| ASGI | `build_django_asgi`, `make_dispatcher` |
| Per-event context | `current_request`, `current_user`, `current_session`, `current_language`, `begin_event_request`, `end_event_request` |
| Event middleware | `DjangoEventBridge` |
| Reflex state helpers | `AppState`, `ModelState`, `DjangoUserState`, `DjangoI18nState`, `DjangoContextState` |
| Reflex context dicts | `collect_reflex_context`, `builtin_user_context`, `builtin_i18n_context` |
| Auth shortcuts | `require_login_user`, `auser_has_perm`, `ReflexDjangoAuthError` (`reflex_django.auth.shortcuts`) |
| Auth decorators | `login_required` (pages and event handlers; `reflex_django.auth.decorators`) |
| Canned auth UI | `add_auth_pages`, `register_login_page`, `register_register_page`, `register_password_reset_page`, `register_password_reset_confirm_page`, `LoginPage`, `RegisterPage`, `PasswordResetPage`, `PasswordResetConfirmPage`, `autoload`, `DjangoAuthState`, `get_auth_settings`, `AuthSettings`, `auth_pages`, `auth_routes` (subpackage `reflex_django.auth`) |
| User JSON snapshot | `user_snapshot` (dict for display, processors, tests—not authorization) |
| ORM helper | `Model` (abstract Django model base + Reflex serializer) |
| Model serializers | `ReflexDjangoModelSerializer` (`Meta.fields` / `exclude` / `read_only_fields`, `.data`, `.adata`, queryset `many=True`) |
| Declarative model CRUD state | `ModelCRUDView` / `ModelState`, `ModelListView` (`reflex_django.state`; see **ModelCRUDView** section below) |
| Admin | `register_admin` |
| Session / cookies (JS helpers) | `session_cookie_set_js`, `session_cookie_clear_js`, `session_cookie_name_and_suffix` |
| CLI (programmatic) | `django_cli` |

**Submodule extras (not re-exported on the package root):** `reflex_django.reflex_context` also exposes `template_context_processor_paths`, `reflex_context_processor_paths`, and `reflex_context_processors_use_template_sanitization` for tooling or advanced use (`src/reflex_django/reflex_context.py`). **`reflex_django.mixins`** exposes **`SessionAuthConfig` / `session_auth_mixin`** (Django session login/logout in Reflex events); see `README.md`. **`reflex_django.auth`** provides canned pages as **`BaseAuthPage`** subclasses (**`LoginPage`**, **`RegisterPage`**, …). Override hook methods (`heading_text`, `form_fields`, …), **`state_cls`**, or **`render()`**; copy via **`MESSAGES`** in **`REFLEX_DJANGO_AUTH`**. Register with **`app.add_page`**, **`register_*_page`** (uses **`default_on_load`**), or **`add_auth_pages(app)`**.

---

## Critical rule: lazy imports on `reflex_django`

Many public attributes are resolved via **PEP 562** `__getattr__` so submodules that touch the Django ORM, admin, or HTTP stack are not imported until **after** `django.setup()`-compatible initialization.

**Do:** Import `from reflex_django import …` in application code after `rxconfig` / plugin construction in normal Reflex startup. Prefer the package root for documented names.

**Avoid:** Import patterns that pull Django models or heavy `reflex_django` submodules at **import time before** settings and `configure_django()` have run (e.g. circular imports from models imported at the top of modules that load before `rxconfig`). If tests need a request binding, use `begin_event_request` / `end_event_request` carefully (see tests under `reflex_django_tests/`).

---

## `ReflexDjangoPlugin` configuration

| Argument | Role |
|----------|------|
| `settings_module` | Dotted path (e.g. `"backend.settings"`). Sets `DJANGO_SETTINGS_MODULE` via `setdefault` and runs `configure_django`. |
| `backend_prefix` | Optional prefix for **your** Django HTTP routes (e.g. `"/api"`). Exported to env as `REFLEX_DJANGO_API_PREFIX` when non-empty. |
| `admin_prefix` | Django admin mount (default `"/admin"`). Sets `REFLEX_DJANGO_ADMIN_PREFIX`. |
| `extra_prefixes` | Additional path prefixes forwarded to Django. |
| `install_event_bridge` | Default `True`: register `DjangoEventBridge`. Set `False` if you do not need session/user on Reflex events (then `current_user()` etc. will not be populated by the bridge). |
| `install_auth_pages` | Default `False`. When `True`, attempts `reflex_django.auth.autoload()` (prefer explicit `add_auth_pages(app)` in the app module). |

**Static files:** If `django.contrib.staticfiles` is in `INSTALLED_APPS` and `STATIC_URL` is a **path** (not `://` CDN URL), the plugin adds that URL as a forwarded prefix so Django can serve statics (`_static_prefixes` in `plugin.py`).

---

## Django settings (`REFLEX_DJANGO_*` and related)

Consolidated from `src/reflex_django/default_settings.py` and related modules:

| Setting | Meaning |
|---------|---------|
| `REFLEX_DJANGO_AUTO_SETTINGS` | `True` in bundled defaults; plugin **warns** that you should use your own settings + stable `SECRET_KEY` for production. |
| `REFLEX_DJANGO_ADMIN_PREFIX` | Admin URL prefix; kept in sync with plugin env (`default_settings`, `urls.py`). |
| `REFLEX_DJANGO_CONTEXT_PROCESSORS` | Non-empty tuple of dotted callables `(request) -> dict` or async; used **exclusively** by `collect_reflex_context`. You must return **JSON-serializable** dicts. |
| `REFLEX_DJANGO_USE_TEMPLATE_CONTEXT_PROCESSORS` | When `REFLEX_DJANGO_CONTEXT_PROCESSORS` is empty and this is `True`, run `TEMPLATES[*].OPTIONS["context_processors"]` with sanitization (see `reflex_context.py`). |
| `REFLEX_DJANGO_LOGIN_URL` | Redirect target for `login_required` on event handlers when anonymous. Legacy fallback for `REFLEX_DJANGO_AUTH["LOGIN_URL"]`. |
| `REFLEX_DJANGO_AUTH` | Dict: `ENABLED`, `SIGNUP_ENABLED`, `PASSWORD_RESET_ENABLED`, route URLs, redirects, `EMAIL_REQUIRED`, `PASSWORD_MIN_LENGTH`, `MESSAGES`. See `reflex_django.auth.settings`. |
| `EMAIL_BACKEND` / `DEFAULT_FROM_EMAIL` | Required for password-reset emails (console backend OK in dev). |
| `REFLEX_DJANGO_SITE_ORIGIN` | Optional absolute origin for reset links when no request is bound. |
| `REFLEX_DJANGO_USER_SNAPSHOT_INCLUDE_GROUPS` | When `True`, user snapshots / `DjangoUserState` may include group names. |
| `REFLEX_DJANGO_I18N_EVENT_BRIDGE` | When `True` and `USE_I18N`, event bridge aligns locale with Django-style negotiation on the synthetic request (`middleware.py`). |
| `REFLEX_DJANGO_DATABASE_URL` | Optional env override for DB URL when using defaults (`default_settings._resolve_db_url`). |

Default settings also honor env vars such as `REFLEX_DJANGO_STATIC_URL`, `REFLEX_DJANGO_STATIC_ROOT`, `REFLEX_DJANGO_SECRET_KEY`, `REFLEX_DJANGO_DEBUG`, `REFLEX_DJANGO_ALLOWED_HOSTS`, `REFLEX_DJANGO_URLCONF`. Database URL can fall back from Reflex config `db_url` to a local SQLite file.

---

## Security and best practices

1. **Authorize on the server inside event handlers** using `current_user()`, `require_login_user()`, or `await auser_has_perm(user, "app.codename")`. Never trust `DjangoUserState` fields alone for permission or ownership checks—they are a **UI snapshot** synchronized to the client.
2. Use **`@login_required`** on event handlers to redirect anonymous users (`rx.redirect` to `REFLEX_DJANGO_LOGIN_URL` or `login_url=...`).
3. **`user_snapshot(user)`** (`from reflex_django import user_snapshot`) is for display, logging, or custom processors—not a substitute for live `User` checks on mutations.
4. Anything assigned to Reflex **`rx.State`** fields that sync to the browser must be **JSON-serializable**. Custom `REFLEX_DJANGO_CONTEXT_PROCESSORS` must not leak secrets or non-JSON objects.
5. Prefer **`async def`** event handlers when calling Django’s async APIs (`aget_user` is already used in the bridge for `request.user`).

---

## Reflex + Django ORM

- Optional abstract base **`Model`** (`src/reflex_django/model.py`): subclass for tables; run migrations through the CLI below. Importing `reflex_django.model` calls `configure_django()`.
- Registered **serializer** turns Django `Model` instances into JSON-friendly dicts (includes PK) for Reflex wire format.

---

## ModelCRUDView — declarative model CRUD (agent reference)

**Import:** `from reflex_django.state import AppState, ModelCRUDView, ModelState, ModelListView`  
**Alias:** `ModelState` = `ModelCRUDView`  
**Requires:** event bridge on; `@login_required` on most generated handlers by default.

### Pattern

```python
class MyState(AppState, ModelCRUDView):
    serializer_class = MySerializer   # or Meta.serializer = MySerializer

    class Meta:
        list_var = "items"            # optional; default pluralizes model name
        state_fields = ("title",)   # optional; default = writable serializer fields
        read_only_fields = ("user",)  # extra read-only (not on state vars)
```

Assembly (via `AppStateMeta`) declares vars + handlers unless the subclass already defines the same name in its class body.

### Generated members (example: model `Note`, `list_var="notes"`)

| Member | Role |
|--------|------|
| `notes` | `list[dict]` serialized rows |
| `notes_error` | `str` validation / error message |
| `editing_id` | `int`, `-1` = create mode, `>=0` = editing pk |
| `title`, `content`, … | flat editable state vars (not `form_*` prefixes) |
| `set_title`, … | `@rx.event` setters |
| `_load_notes` | internal reload |
| `on_load_notes` | page `on_load` target |
| `save_note` | create or update from flat vars + `editing_id` |
| `start_edit(item_id)` | populate vars, set `editing_id` |
| `delete_note(item_id)` | delete + refresh list |
| `cancel_edit` | `reset_state_fields()` |
| `reset_state_fields` | clear editable vars, `editing_id=-1`, bump `form_reset_key` |
| `form_reset_key` | int; bind to `rx.form(..., key=...)` so DOM clears after save |
| `save_{model}_form` | optional when `Meta.use_form_submit = True`; reads `form_data` then `dispatch(save)` |

### Event flow

```text
on_load_notes  → dispatch("load_list")  → get_queryset → filter_queryset → serialize → notes
save_note      → dispatch("save")       → validate_state → create|update → on_save_success → reset_state_fields? → refresh list
start_edit(id) → dispatch("start_edit") → get_object → populate state vars
```

### Per-event `self.request` / `self.django_request`

Bound at the start of each **`dispatch`** (and list-only **`_load_*`**):

| Attr | Value |
|------|--------|
| `self.django_request` | `current_request()` — synthetic `HttpRequest` |
| `self.request` | `DjangoStateRequest` wrapper |
| `self.request.user` | Live `request.user` (use for ORM filters) |
| `self.request.SOME_KEY` | Merged context-processor output |
| `self.request.context` | Full processor dict |

```python
def get_queryset(self):
    return Note.objects.filter(user=self.request.user)
```

`Meta.load_context_processors = False` skips `collect_reflex_context` but still binds the HTTP request.

### Scoping (no default owner field)

Framework does **not** auto-filter by user. Prefer **`self.request.user`** in hooks (available after bind):

```python
def get_queryset(self):
    return Note.objects.filter(user=self.request.user)

def get_object_lookup(self, pk: int) -> dict:
    return {"pk": pk, "user": self.request.user}

def get_create_kwargs(self, state_data: dict) -> dict:
    return {**state_data, "user": self.request.user}
```

**Shortcut:** `class NotesState(AppState, ModelCRUDView, UserScopedMixin):` with `scope_field = "user_id"`. Assembly injects the three hooks (Reflex MRO cannot prioritize plain mixins).

### Page wiring example

```python
def notes_page() -> rx.Component:
    return rx.vstack(
        rx.cond(NotesState.notes_error != "", rx.callout(NotesState.notes_error)),
        rx.input(value=NotesState.title, on_change=NotesState.set_title),
        rx.button("Save", on_click=NotesState.save_note),
        rx.foreach(
            NotesState.notes,
            lambda n: rx.button("Edit", on_click=NotesState.start_edit(n["id"])),
        ),
    )

# app.add_page(notes_page, route="/notes", on_load=NotesState.on_load_notes)
```

### Validation hooks (state-centric names — not `form_*`)

| Hook | When |
|------|------|
| `get_state_data()` | Read vars → dict |
| `validate_state(ctx, data)` | Cross-field errors → `dict[str, str]` |
| `clean_<field>(value)` | Per-field; return error string |
| `clean_state(data)` | Normalize before save |
| `on_state_invalid(ctx, errors)` | Set `error_var` / `field_errors` |
| `on_state_valid(ctx, state_data)` | Before create/update |
| `perform_create(ctx, state_data)` | ORM create |
| `perform_update(ctx, instance, state_data)` | ORM update |

Set `run_model_validation = True` to call Django `full_clean()`. Set `structured_errors = True` for `{list_var}_field_errors`.

### Read-only lists

```python
class LogState(AppState, ModelListView):
    serializer_class = LogSerializer
```

### Permissions

```python
from reflex_django.state.mixins import IsAuthenticated, PermissionMixin

permission_classes = (IsAuthenticated,)  # on ModelCRUDView subclass Meta or class attr
```

### Do not use

- **`crud_mixin` / `ModelCRUDConfig`** — removed; use `ModelCRUDView`.
- **`owner_field`** on serializer — removed; use `get_queryset` / `UserScopedMixin`.
- **`Meta.form_fields`** — use **`Meta.state_fields`**.
- **`_clear_form`** — use **`reset_state_fields`** (alias **`_reset_state_fields`**).
- After save, bind **`rx.form(..., key=State.form_reset_key)`** if inputs use **`name=`** only; or use **`value=` + `on_change=`** on each field.

### Submodule map

`reflex_django/state/` — `views/crud.py` (`ModelCRUDView`), `assembly.py`, `options.py`, `mixins/` (`dispatch`, `state_fields`, `queryset`, `scoping`, …), `backends/django.py`.

---

## CLI

- **Via Reflex:** `uv run reflex django <subcommand>` — e.g. `migrate`, `makemigrations`, `createsuperuser`, `shell`, `collectstatic`, `help`. Loads `rxconfig` first so `ReflexDjangoPlugin` sets `DJANGO_SETTINGS_MODULE` and prefixes, then `configure_django()`.
- **Standalone:** `uv run reflex-django <subcommand>` — forwards to Django’s `execute_from_command_line` the same way (see `src/reflex_django/cli.py`).
- **`reflex django init`:** Exists for scaffolding but is **beta**; README recommends the **manual** setup checklist for production-minded projects.

---

## Testing and advanced use

- Use **`begin_event_request(request)`** / **`end_event_request()`** to bind a synthetic request in tests or advanced flows (normally the bridge does this). Examples: `reflex_django_tests/`.
- **`collect_reflex_context(request)`** is async; pass `current_request()` inside a bridged event, or `None` for an empty dict.

---

## Common mistakes

| Mistake | Why it fails |
|---------|----------------|
| Plugin prefixes do not match `ROOT_URLCONF` | Django returns 404 or Reflex handles paths unexpectedly. |
| Using `DjangoUserState` for authorization | Client-visible; can be stale or tampered with—use `current_user()` / permissions on the server. |
| Putting non-JSON values into Reflex state or explicit context processors | Serialization errors or subtle runtime failures. |
| `install_event_bridge=False` but still expecting `current_user()` from sessions | No synthetic request / session binding from the bridge. |
| Heavy imports before Django setup / `rxconfig` | Breaks lazy-loading assumptions; circular import issues. |
| Relying on Django HTTP middleware for Reflex-only actions | Events are not full HTTP requests; use the event bridge and explicit checks. |
| Expecting `crud_mixin` or `owner_field` on ModelState | Removed; use `ModelCRUDView` + `get_queryset` / `UserScopedMixin`. |
| Using `form_title` / `Meta.form_fields` | Use flat `title` vars and `Meta.state_fields`. |

---

## Pointers for deeper reading

- `README.md` — full architecture, examples for states, bridges, context processors.
- `CHANGELOG.md` — version-to-version changes.
- `RELEASING.md` — maintainer release process.
- Source: `src/reflex_django/` — especially `plugin.py`, `middleware.py`, `context.py`, `conf.py`, `asgi.py`, `auth/`, `auth_state.py`, `i18n_state.py`, `reflex_context.py`, `model.py`, `state/` (ModelCRUDView), `cli.py`.
