AppState — your bridge to Django¶
AppState is the most important class in reflex-django. It's a regular Reflex State that also knows about your Django session. If you only learn one new thing from these docs, learn this.
This page explains what AppState gives you, how it does it, and how to choose between AppState, plain rx.State, and the higher-level ModelState.
What AppState is¶
It's a rx.State subclass. It adds two things:
- Django context attributes on
self— inside any@rx.eventhandler, you can readself.request,self.user,self.session,self.messages,self.csrf_token,self.response. They're filled in byreflex-djangobefore your handler runs, using your real Django middleware. - Reactive snapshot variables for the UI — things like
self.is_authenticated,self.username,self.email. These are normal Reflex variables you can bind in components.
You use it exactly like a regular Reflex state — just subclass AppState instead of rx.State:
from reflex_django.state import AppState
class CartState(AppState):
items: list[dict] = []
@rx.event
async def add(self, product_id: int):
if not self.request.user.is_authenticated:
return rx.redirect("/login")
await Cart.objects.acreate(owner=self.request.user, product_id=product_id)
That's the whole API surface. The rest of this page is detail.
What lives on self¶
Inside any @rx.event async def method on an AppState subclass:
Per-event Django context¶
| Attribute | What it is |
|---|---|
self.request |
A synthetic HttpRequest, fully populated by your settings.MIDDLEWARE. |
self.user |
request.user, already resolved (no SynchronousOnlyOperation). |
self.session |
The session, async-safe (await self.session.aget(...), asave()). |
self.messages |
Snapshot of django.contrib.messages (JSON-safe list). |
self.csrf_token |
The CSRF token for the current request. |
self.response |
The HttpResponse produced by middleware (200 unless a middleware short-circuited). |
self.resolver_match |
ResolverMatch if the path resolved to a Django view. |
self.django_request |
The raw HttpRequest (same object as self.request, just a different alias). |
self.django_response |
The raw HttpResponse. |
self.django_context |
Dict of context-processor keys, if REFLEX_DJANGO_AUTO_LOAD_CONTEXT is on. |
Reactive variables (also bindable in components)¶
| Variable | What it reflects |
|---|---|
self.is_authenticated |
request.user.is_authenticated |
self.username |
request.user.get_username() |
self.email |
request.user.email |
self.is_staff, self.is_superuser |
Mirror Django user flags |
self.user_id |
The user's primary key |
self.group_names |
List of group names the user belongs to |
self.perms |
JSON-safe list of permissions ({app}.{codename} strings) |
You use the per-event context (self.user, self.session) inside handlers — for authorization, queries, mutations. You use the reactive variables (self.is_authenticated, self.username) in components — for rx.cond, text bindings, conditional rendering.
class DashboardState(AppState):
@rx.event
async def delete_account(self):
# Per-event context — live, authoritative, NOT sent to the browser
if not self.user.is_authenticated:
return rx.redirect("/login")
if self.user.is_superuser:
return # don't let admins delete themselves accidentally
await self.user.adelete()
def dashboard_ui():
return rx.vstack(
rx.cond(
DashboardState.is_authenticated, # reactive — used in UI
rx.text(f"Welcome, {DashboardState.username}"),
rx.link("Log in", href="/login"),
),
)
The security rule: never make authorization decisions based on the reactive snapshot variables alone. Always check
self.user/self.request.userin the handler. The reactive ones are sent to the browser and can be spoofed; the per-event ones are computed server-side every time.
How it works (briefly)¶
Every Reflex event is a WebSocket frame on /_event. By default, that doesn't go through Django's HTTP pipeline. reflex-django inserts a small piece called the DjangoEventBridge before your handler runs:
- The bridge reads
router_datafrom the event (cookies, path, query string, headers). - It builds a synthetic
HttpRequestfrom that data. - It runs your full
settings.MIDDLEWAREchain on the request.SessionMiddlewareloads the session.AuthenticationMiddlewareresolves the user. Your custom middleware runs too. - It eagerly resolves
request.userusing Django's asyncaget_user(so you don't getSynchronousOnlyOperationlater). - It binds the result onto your
AppStateinstance:self.request,self.user,self.session, etc. - Then your handler runs.
If any middleware short-circuits with a 3xx — e.g. LoginRequiredMiddleware returning a redirect — the bridge converts that into a Reflex rx.redirect(...) and skips your handler. You can opt out with REFLEX_DJANGO_AUTO_REDIRECT_FROM_MIDDLEWARE = False.
For the full plumbing, see The WebSocket event pipeline.
When to use AppState vs plain rx.State vs ModelState¶
You have three choices. They stack:
| Class | Inherits from | Use it when |
|---|---|---|
rx.State |
(Reflex) | The page doesn't need Django context. Pure UI state — counters, filters, modals. |
AppState |
rx.State + the Django bridge |
The page reads request.user or session, or runs custom Django middleware logic. Default choice for anything user-aware. |
ModelState |
AppState + CRUD machinery |
The page is mostly "list, edit, save, delete" rows from one Django model. |
# Pure UI state — no Django needed
class FilterState(rx.State):
query: str = ""
sort: str = "newest"
# User-aware state — uses self.user, self.session
class CartState(AppState):
@rx.event
async def add(self, product_id: int):
user = self.request.user
...
# CRUD on a model — uses ModelState (which is itself an AppState)
class ProductState(ModelState):
model = Product
fields = ["name", "price"]
# auto-generates load/save/delete handlers and a `data` list
A page can use several states at once. Use rx.State for the filter bar and AppState for the user-specific data. Use AppState for one screen and ModelState for another. Mix and match freely.
Reading the request¶
Three equivalent styles. They all read the same per-event request:
# Style A — self.request (on AppState / ModelState handlers)
class CatalogState(AppState):
@rx.event
async def search(self, q: str):
page = self.request.GET.get("page", "1")
theme = self.request.COOKIES.get("theme", "light")
if self.request.user.is_authenticated:
...
# Style B — module-level request proxy (works in any rx.State too)
from reflex_django import request
class FilterState(rx.State):
@rx.event
async def apply(self):
q = request.GET.get("q", "")
...
# Style C — functional helpers (explicit, no inheritance needed)
from reflex_django import current_request, current_user
class FilterState(rx.State):
@rx.event
async def apply(self):
req = current_request()
user = current_user()
...
All three return the same HttpRequest for the current event. Inside an AppState subclass, self.request is the most natural — that's what we use throughout these docs.
Outside an event (at import time, in a background thread), there's no request. self.request will be None and the helpers return anonymous defaults.
Writing to the session¶
class PreferencesState(AppState):
@rx.event
async def set_theme(self, theme: str):
self.session["theme"] = theme
await self.session.asave()
Use await self.session.asave() to persist. The next event for the same user will see the new value.
Adding flash messages¶
from django.contrib import messages
class CheckoutState(AppState):
@rx.event
async def submit(self):
try:
...
messages.success(self.request, "Order placed!")
except Exception:
messages.error(self.request, "Something went wrong.")
Then in your UI, bind to DjangoUserState.messages:
A full example — a small profile page¶
This is what a typical AppState page looks like: per-event Django context for the work, reactive variables for the rendering.
# accounts/views.py
import reflex as rx
from reflex_django import template
from reflex_django.state import AppState
class ProfileState(AppState):
bio: str = ""
saved: bool = False
@rx.event
async def on_load(self):
if not self.request.user.is_authenticated:
return rx.redirect("/login")
self.bio = self.request.user.profile.bio or ""
@rx.event
async def save(self):
user = self.request.user
if not user.is_authenticated:
return
user.profile.bio = self.bio.strip()
await user.profile.asave()
self.saved = True
@template(route="/profile", title="Profile", on_load=ProfileState.on_load)
def profile() -> rx.Component:
return rx.vstack(
rx.heading(f"Hi, {ProfileState.username}"),
rx.text_area(
value=ProfileState.bio,
on_change=ProfileState.set_bio,
placeholder="Tell us about yourself",
),
rx.button("Save", on_click=ProfileState.save),
rx.cond(ProfileState.saved, rx.callout("Saved!", color_scheme="green")),
spacing="3",
)
Five things to notice:
ProfileState(AppState)— that one swap is what gives usself.request.user.on_loadgates by login. Returningrx.redirect("/login")from a handler navigates the browser.self.request.user.profile.bio— we use the real Django user object. Same auth as/admin/.await user.profile.asave()— async ORM for non-blocking writes.ProfileState.usernamein the UI — the reactive snapshot, auto-refreshed on each event.
Avoid storing model instances in state fields¶
State fields are serialized to JSON and shipped to the browser. Django model instances aren't JSON-serializable. Always convert them to dicts (or use a serializer) before assigning:
# wrong — will crash on the JSON roundtrip
self.product = product
# right — primitives only
self.product = {"id": product.id, "name": product.name, "price": str(product.price)}
The same applies to Decimal, datetime, date objects — convert to strings first.
Don't read request.user at class definition time¶
Class-level defaults run at import time, when Django's app registry might not be ready and there's certainly no live request. So this is wrong:
# wrong — runs once, at import, with no request
class HomeState(AppState):
greeting: str = f"Hi, {request.user}"
This is right:
class HomeState(AppState):
greeting: str = "Hi"
@rx.event
async def on_load(self):
if self.request.user.is_authenticated:
self.greeting = f"Hi, {self.request.user.get_username()}"
Class defaults are values, not snapshots of the current user.
A note on DjangoUserState¶
reflex-django also ships a smaller class called DjangoUserState. It exposes the same reactive variables (is_authenticated, username, etc.) without the per-event self.request and self.user. Use it when you only need the UI snapshot — for example, a navbar that shows the username:
from reflex_django import DjangoUserState
def navbar():
return rx.hstack(
rx.cond(
DjangoUserState.is_authenticated,
rx.text(f"Hi, {DjangoUserState.username}"),
rx.link("Log in", href="/login"),
),
)
If your page also needs to perform authenticated actions, use AppState instead — it includes everything DjangoUserState has plus the live request.
Summary¶
- Subclass
AppStatewhenever a page needs Django context inside event handlers. - Use
self.request,self.user,self.sessionin handlers — they're real, fresh, server-side. - Use
AppState.is_authenticated,AppState.username, etc. in components — they're reactive snapshots. - Never base security decisions on the reactive snapshot alone. Check the live
self.userin the handler. - Avoid storing model instances in state fields. Convert to dicts or use a serializer.