FAQ¶
Short answers to the questions that come up most often. Each one links to the longer treatment if you want detail.
Getting started¶
Do I need to know Reflex before using this?¶
No. If you're a Django developer, How Reflex works in 5 minutes gives you enough to read the rest of these docs. The official Reflex docs at reflex.dev are excellent for deeper learning.
Do I need to know Django?¶
A little helps. How Django works in 5 minutes is enough for the rest of these docs. If you've used FastAPI or Flask, the concepts are familiar.
Can I use this with an existing Django project?¶
Yes — that's a primary use case. See Add to an existing Django project. You don't change your models or your urls.py structure; you add one line to urls.py and start dropping pages into any app's views.py.
What versions do I need?¶
Python 3.12+, Django 6.0+, Reflex 0.9.2+. Older Reflex versions don't have the plugin hooks we depend on.
Architecture and routing¶
Is this just running two servers behind a reverse proxy?¶
No. It's one Python process listening on one port. Both Django and Reflex's ASGI internals live in the same process. The outer dispatcher decides per-scope whether to send a request to Django or to Reflex's inner ASGI. (Details.)
What about CORS?¶
You don't need it. The SPA, the API, the admin, and the WebSocket all share an origin. The browser sees them as the same site.
Can I still use Django Channels?¶
reflex-django doesn't use Channels. Reflex owns the one WebSocket on /_event. If you need additional WebSocket protocols (chat, multiplayer game state, etc.) and the Reflex event model doesn't fit, Channels is fine to add alongside — but you'll need to route its WebSocket scopes around the outer dispatcher. Most projects don't need this.
Why is there no rxconfig.py?¶
reflex_mount() in urls.py plus REFLEX_DJANGO_* settings provide everything rxconfig.py used to. Keeping the config in urls.py means there's only one place to look. If you have legacy tooling that reads rxconfig.py, set REFLEX_DJANGO_USE_RXCONFIG_FILE = True to merge it in.
Why is there no {app}/{app}.py?¶
The rx.App() instance is built by reflex_django.django_led_app, which auto-discovers pages from {app}/views.py in every entry of INSTALLED_APPS. You write pages; the library writes the boilerplate.
State and the request¶
Why don't I see self.request.user in my handler?¶
Three things to check:
- Does your state subclass
AppState(not plainrx.State)? - Is
SessionMiddlewareinMIDDLEWARE? - Is
AuthenticationMiddlewareinMIDDLEWARE?
Without all three, the bridge can't populate the user. (State Management.)
Can I read request.user from a plain rx.State?¶
Yes — use the module-level proxy:
from reflex_django import request
class FilterState(rx.State):
@rx.event
async def apply(self):
if request.user.is_authenticated:
...
Or the functional helpers (current_user(), current_request()). All return the same per-event request. (Details.)
self.is_authenticated vs self.request.user.is_authenticated — which one?¶
self.is_authenticated is a reactive snapshot, safe for UI rendering. self.request.user.is_authenticated is the live server-side check, the one to use for authorization. Never base security decisions on the snapshot alone. (Live vs snapshot rule.)
Why is self.request None in my test?¶
Outside an event, there's no request. In tests, set one up:
from reflex_django.context import begin_event_request, end_event_request
token = begin_event_request(user=test_user)
try:
await state.my_handler()
finally:
end_event_request(token)
(Testing.)
Why is self.request.user AnonymousUser even though I'm logged in?¶
Most likely: the SPA opened its WebSocket before you logged in, with the anonymous session cookie. After login, the browser has the new cookie, but the WebSocket is still using the old one. Either:
- Use the built-in login flow (
add_auth_pages()) — it handles the cookie sync. - After your custom login, redirect through an HTTP response that carries the fresh
Set-Cookieheader. (Details.)
CRUD¶
ModelState or ModelCRUDView — which one?¶
ModelState for new projects. It's shorter and auto-builds the serializer. Reach for ModelCRUDView when you want explicit serializer classes (e.g. sharing with DRF) or verb-noun handler names (save_post instead of save). (Side by side.)
Can I mix ModelState and manual AppState handlers in the same project?¶
Absolutely. Use ModelState for standard CRUD pages, plain AppState for unusual workflows. They share the same Django context, the same auth, the same everything.
How do I scope a list to the current user?¶
Three options, all in ModelState/ModelCRUDView:
- Override
get_queryset,get_object_lookup,get_create_kwargsmanually. - Add
UserScopedMixinand setMeta.owner_field = "owner". - In a manual handler, filter with
Model.objects.filter(owner=self.request.user).
(Details.)
Why isn't my list refreshing after save?¶
Three common causes:
class Meta: list_var = "..."is missing — the default name isdata.- You're using
ModelCRUDViewbut didn't callon_load=YourState.on_load_<list_var>on the page. - You overrode
savewithout callingsuper()orself.dispatch(ACTION_SAVE).
Forms and validation¶
Where do I add custom validation?¶
Three stages in order: clean_<field> for per-field, validate_state for cross-field, run_model_validation = True for Django's own validators (unique=True, validators=[...]). (Details.)
Why doesn't my form clear after save?¶
You need both Meta.reset_after_save = True (default) and key=YourState.form_reset_key on the <rx.form> element. The key triggers a React remount, which resets uncontrolled input state.
My field-level errors aren't showing¶
Two things:
Meta.structured_errors = True(defaults toTrueonModelState/ModelCRUDView).- Bind to
YourState.<list_var>_field_errors[field_name]— notYourState.error.
Auth¶
Does CSRF protect Reflex events?¶
CSRF is intentionally skipped on Reflex WebSocket events. CSRF protects HTML form submissions where a third-party site could trigger a request with the user's cookies. WebSockets opened by your own SPA don't have that attack shape. For mutations, prefer @login_required, @permission_required, and server-side ownership checks. (Details.)
Can I use django-allauth or OAuth?¶
Yes. reflex-django builds on Django sessions. Once your OAuth flow completes, request.user.is_authenticated is True for both HTTP requests and Reflex events. No special setup is needed — wire up django-allauth (or social-auth-app-django) as you normally would.
Can I use JWT instead of sessions?¶
Not for the SPA. The bridge is session-based — request.user is resolved from the session cookie. You can absolutely have a JWT endpoint alongside (for your mobile app); the SPA just uses the session.
How do I gate a whole page?¶
Wrap the page function:
from reflex_django.auth import login_required
@template(route="/account")
@login_required
def account() -> rx.Component:
...
For permission-based gating, use permission_required("app.codename").
Performance¶
Is the per-event middleware chain slow?¶
The bridge runs your full settings.MIDDLEWARE for each Reflex event. For most apps that's microseconds of overhead per event (session lookup, user resolution). If you have expensive custom middleware that doesn't apply to events, add it to REFLEX_DJANGO_EVENT_MIDDLEWARE_SKIP.
Can I have multiple ASGI workers?¶
Yes. Default rule of thumb: 2 * cores + 1. State is stored per-process by default, so enable sticky session affinity on your load balancer, or point Reflex at Redis for shared state:
Why is my page slow on first load?¶
The compiled SPA might not be on disk yet, so manage.py run_reflex is building it. After the first build, reloads are fast. In production, the SPA is built in CI and shipped in the container.
Development workflow¶
Hot reload doesn't work for Reflex page edits¶
Use python manage.py run_reflex --with-vite for hot module reload on Reflex components. Without it, every save triggers a full uvicorn restart.
Can I edit a Django model without rebuilding the SPA?¶
Yes — use --skip-rebuild:
The uvicorn server still restarts on Python changes, but the slow SPA export step is skipped.
Where does the compiled SPA live?¶
STATIC_ROOT/_reflex/ in production. In development, manage.py run_reflex builds and stages it there automatically. The source bundle lives in .web/ (gitignored).
The SPA loads but crashes with TypeError: t is not a function / d is not a function¶
You're hitting a Vite/Rolldown bundler regression — usually a CJS-interop bug in Vite 8.0.x that emits var r=r(), t=t(), n=n(), i=i(); inside memoized factory wrappers. Pin the last Rollup-based Vite from your Django settings:
Then wipe .web/ and STATIC_ROOT/_reflex/ and rebuild. See REFLEX_DJANGO_VITE_VERSION for the full rundown.
Deployment¶
One container or two?¶
One. The whole point of reflex-django is single-process: Django and Reflex live in the same uvicorn worker. Your deploy is one ASGI app.
Can I deploy to Heroku / Railway / Fly / ECS / Cloud Run?¶
Yes — anywhere that supports WebSockets and an ASGI server (uvicorn / granian / hypercorn / gunicorn-uvicorn-worker). See platform-specific notes in the Deployment guide.
Does the SPA need to be rebuilt for every deploy?¶
Yes — but only once, in CI, not at every container boot:
python manage.py export_reflex --frontend-only --no-zip --stage-to-static-root
python manage.py collectstatic --noinput
Bake those into your image build. The runtime container then serves the pre-built bundle.
How long should the WebSocket idle timeout be on my proxy?¶
At least 300 seconds. The SPA holds the WebSocket open for the user's whole session. Default Nginx 60s timeout drops the connection too aggressively.
Errors and debugging¶
AppRegistryNotReady¶
You're touching a Django model at module import time (in a class-level default, a module-level query, or the top of a file imported very early). Move the model import inside an event handler:
SynchronousOnlyOperation¶
You used a sync ORM call in an async event handler. Replace with aget, acreate, asave, adelete, async for. (Database integration.)
ModuleNotFoundError: shop.shop¶
A leftover rxconfig.py is pointing at the old layout. Delete rxconfig.py. reflex_mount() in urls.py is the only configuration you need.
Could not find compiled SPA¶
The SPA hasn't been built yet. Run python manage.py export_reflex --frontend-only --no-zip --stage-to-static-root, or just python manage.py run_reflex and let it build automatically.
Browser console: dispatch is not a function / h[M] is not a function (page stuck on loading skeleton)¶
The compiled SPA's state dispatcher map is missing an entry for a substate the running backend is sending deltas for. Two distinct causes:
Cause 1 — drift between build and runtime. .web/ was generated against an older set of Python imports than the process is now serving (added/renamed a substate, switched branches, edited a class XxxState(rx.State)). Recovery: stop the server, delete .web/ (or .web/build/ + .web/utils/state.js), restart python manage.py run_reflex, hard-refresh the browser (Ctrl+Shift+R).
Cause 2 — runtime-attached substates that the frontend codegen missed. Some reflex_django state classes are exposed via :pep:562 lazy attribute access on the package (notably reflex_django.reflex_context.DjangoContextState) and were historically first imported by middleware on the first WebSocket event — after the SPA had already been compiled. Since 0.5.x, reflex_django.app_factory.prepare_pages_for_compile (and ensure_django_led_app_ready) eagerly imports these classes before Reflex walks the state tree, so both the bundle and the runtime see the same set of substates. If you previously hit h[<...>django_context_state] is not a function, a clean rebuild (rm -rf .web && python manage.py run_reflex) fixes it.
Defensive fallback. As of 0.5.x reflex-django also patches Reflex's utils/state.js template at boot so the WebSocket event handler tolerates unknown substates: instead of throwing and freezing the page, it logs
…and continues rendering. If you ever see that warning after a clean rebuild, share the substate name — that's a real registration-timing bug we can fix upstream by extending the eager-import list in _ensure_runtime_state_classes_registered.
Tasks/middleware silently doesn't run on events¶
You probably set REFLEX_DJANGO_RUN_MIDDLEWARE_CHAIN = False somewhere (or your middleware is in REFLEX_DJANGO_EVENT_MIDDLEWARE_SKIP). Remove the override.
Comparisons¶
How is this different from running Django + a React SPA?¶
You don't write React. You don't write JSX. You don't run a Node build server. Pages, state, and event handlers are all in Python. The SPA still exists — it's just compiled for you.
How is this different from htmx / Unpoly?¶
htmx and Unpoly add reactivity to server-rendered HTML by intercepting form submissions and link clicks. reflex-django ships a real React SPA backed by a persistent WebSocket. Different trade-off: htmx is lighter and works with any backend; reflex-django is heavier but gives you a fully reactive client without writing JavaScript.
How is this different from Django Channels + a separate SPA?¶
Channels gives you WebSocket plumbing, but you still write the SPA yourself. reflex-django gives you the SPA, the WebSocket, the state management, and the bridge — all in Python.
Why not just use django-htmx or live-reload?¶
If those fit your needs, use them. They're great for adding selective interactivity to a server-rendered Django app. reflex-django makes sense when you want a full SPA but don't want to write React.
Compatibility¶
Does this work with Django 5? Django 4?¶
The library targets Django 6.0+. Django 5 may work but isn't officially supported. Django 4 won't work — we depend on async ORM features added in later versions.
Does this work with PyPy?¶
Untested. CPython 3.12+ is what we test against.
Does this work with SQLite?¶
For development, yes. For production, prefer Postgres (or MySQL). SQLite's locking model becomes a bottleneck under any concurrent load.
Where to learn more¶
If you can't find your question here:
- Glossary — definitions of every term in these docs.
- Public API at a glance — every importable symbol.
- Architecture overview — the full plumbing picture.
- GitHub — issues and discussions.
Next: Glossary →