cheat sheet
reflex
Package-level reference for reflex on PyPI — install variants, Node.js requirement, version policy, and alternatives.
reflex
What it is
Reflex (formerly Pynecone) is a full-stack Python web framework that compiles a Python component tree to a Next.js / React frontend and runs a FastAPI backend, with state synchronised over WebSockets. The project was founded in 2022 by ex-MIT researchers and is developed by Reflex Inc., with an open-core model (Apache-2.0 core; hosted deployment is a paid product).
Reach for reflex when you want a real SPA — proper URL routing, components, typed state, forms — without writing TypeScript or React. Reach for streamlit when you want a top-to-bottom dashboard script with the least ceremony; reach for fastapi + a JS frontend when your team is fluent in both.
Install
pip install reflex
Output: (none — exits 0 on success)
uv add reflex
Output: resolved + added to pyproject.toml
poetry add reflex
Output: updated lockfile + virtualenv install
reflex init # scaffold a project (downloads Node.js if missing)
reflex run # dev server on http://localhost:3000 (frontend) + :8000 (backend)
Output: Reflex auto-installs Bun and Node.js into ~/.local/share/reflex/ on first run; expect a 30–60s download.
Versioning & Python support
- Current stable line is the
0.xseries approaching the1.0boundary as of late 2025. APIs have churned across0.xminor releases — pin tightly. - Supports Python 3.10+. Earlier Python versions are unsupported.
- The
Pynecone→Reflexrename in mid-2023 also moved the PyPI package name frompyneconetoreflex— older tutorials reference the dead package. - Pre-
1.0semver — every minor release may rename APIs. The0.5→0.6and0.6→0.7jumps each had non-trivial migrations.
Package metadata
- Maintainer: Reflex Inc. (Nikhil Rao et al.)
- Project home: github.com/reflex-dev/reflex
- Docs: reflex.dev/docs
- PyPI: pypi.org/project/reflex
- License: Apache-2.0 (core)
- Governance: VC-funded commercial company with open-core OSS model
- First released: 2022 (as
pynecone); renamed toreflexin 2023 - Downloads: hundreds of thousands per month — growing quickly
Optional dependencies & extras
Reflex auto-pulls a large dependency tree because it bundles both backend and frontend tooling:
fastapi,uvicorn,starlette— backend HTTP + WebSocket serversqlmodel,sqlalchemy,alembic— built-in ORM and migrationspydantic— state and event-payload validationtyper,rich— thereflexCLIhttpx— internal HTTP clientredis(optional) — for multi-worker state-backend
Non-PyPI runtime requirements:
- Node.js + Bun — required to build the frontend bundle. Reflex downloads Bun (~50 MB) and Node.js into
~/.local/share/reflex/on firstreflex initif not on PATH. You can opt in to a system Node withREFLEX_USE_SYSTEM_NODE=1. - A C compiler is not required — wheels exist for all major platforms.
Common companions:
reflex-chakra— Chakra UI v2 component library (the original built-in)- Radix UI primitives ship in core under
rx.radix.* reflex-enterprise— paid optional features (auth, advanced components)
Alternatives
| Package | Trade-off |
|---|---|
streamlit | Linear top-to-bottom script model. Faster to prototype; harder to scale to real apps with routing/components. |
nicegui | Vue.js-based component tree in Python. No build step (uses Vue at runtime); smaller install. |
solara | React-based with Jupyter-friendly model. Strong for data scientists already in notebooks. |
fastapi + React/Vue | Maximum flexibility; you write TypeScript. Use when team has frontend depth. |
htmy / htmx + FastAPI | Server-rendered HTML with progressive enhancement. Skip the React build entirely. |
django | Battle-tested classic full-stack framework, server-rendered templates. |
Common gotchas
- First
reflex initrequires internet access to download Bun (~50 MB) and Node.js into~/.local/share/reflex/. Offline environments must pre-stage those binaries. 0.xAPI churn. Pin to an exact version in production. Migrations between minor versions are usually documented but are not codemod-able yet.- State is server-side, synchronised over WebSocket. Every state mutation round-trips to the backend. For chatty UIs, batch updates inside a single event handler — don't trigger 10 state changes per keystroke.
- The built-in ORM is
sqlmodel(SQL only). No NoSQL story out of the box; integratemotor/beaniemanually. - Hot-reload restarts both backend and frontend. Save in
state.pyand you'll see a 2–5 s reload during whichrx.toastetc. are unavailable. Quality-of-life improves over the0.xseries. - Deploys. Self-hosting works (the project produces a static frontend + a FastAPI backend), but the smooth path is
reflex deployto the official hosted platform. Self-host docs assume you can host both halves and run Redis if you want multi-worker. - PyPI name vs project name. Older tutorials and posts reference
pip install pynecone; the package was renamed toreflexin 2023. Tooling that still mentions Pynecone is stale.
Real-world recipes
Package-level recipes — install, build, and deploy patterns. For the component/state API see the companion Python article.
Recipe 1 — minimal counter with state + event handler
import reflex as rx
class State(rx.State):
count: int = 0
@rx.event
def increment(self):
self.count += 1
def index():
return rx.vstack(
rx.heading(State.count),
rx.button("+1", on_click=State.increment),
spacing="4",
)
app = rx.App()
app.add_page(index, route="/")
Output: browser shows the counter; clicking the button round-trips through the WebSocket, updates State.count, and re-renders. The server-side state model is the defining trait — every interaction touches Python.
Recipe 2 — form submission
import reflex as rx
class FormState(rx.State):
submitted: dict[str, str] = {}
@rx.event
def handle_submit(self, form_data: dict):
self.submitted = form_data
def index():
return rx.form(
rx.vstack(
rx.input(placeholder="Name", name="name"),
rx.input(placeholder="Email", name="email"),
rx.button("Submit", type="submit"),
),
on_submit=FormState.handle_submit,
reset_on_submit=True,
)
app = rx.App()
app.add_page(index)
Output: submitting the form sends {"name": "...", "email": "..."} to FormState.handle_submit. The state mutation triggers a re-render.
Recipe 3 — streaming event handler (async generator)
import asyncio
import reflex as rx
class ChatState(rx.State):
response: str = ""
@rx.event
async def stream(self):
self.response = ""
for chunk in ["Hello", " ", "world", "!"]:
self.response += chunk
yield # flush state to client mid-handler
await asyncio.sleep(0.2)
def index():
return rx.vstack(
rx.text(ChatState.response),
rx.button("Stream", on_click=ChatState.stream),
)
app = rx.App()
app.add_page(index)
Output: clicking "Stream" causes the text to grow incrementally — yield flushes the current state to the client mid-handler. This is the standard pattern for LLM token streaming.
Recipe 4 — multi-page routing with dynamic segments
import reflex as rx
def home():
return rx.heading("Home")
def post_page():
slug = rx.State.router.page.params.get("slug", "")
return rx.heading(f"Post: {slug}")
app = rx.App()
app.add_page(home, route="/")
app.add_page(post_page, route="/post/[slug]")
Output: /post/hello-world renders "Post: hello-world". The bracket syntax matches Next.js's dynamic-segment convention because the compiled frontend is Next.js.
Recipe 5 — connecting to an existing FastAPI backend
Reflex's backend is FastAPI — app.api exposes the underlying FastAPI app for custom routes:
import reflex as rx
from fastapi import APIRouter
router = APIRouter()
@router.get("/api/health")
def health():
return {"status": "ok"}
app = rx.App()
app.api.include_router(router)
Output: the Reflex frontend AND a GET /api/health endpoint share the same process. Useful for webhook receivers, REST APIs alongside the UI, or auth callbacks.
Production deployment
Reflex produces two artifacts: a static frontend bundle (Next.js export) and a Python backend (FastAPI + Uvicorn). Deployment means hosting both halves.
Build for production
reflex export # produces ./frontend.zip (static) and ./backend.zip
# OR
reflex export --frontend-only --no-zip # ./.web/_static
reflex export --backend-only --no-zip # backend ready to package separately
Output: static assets on disk; Reflex's CLI bundles them, but you can also manually zip and ship. The frontend is plain Next.js static export — any static host works (Vercel, Netlify, Cloudflare Pages, S3+CloudFront).
Self-hosted reference architecture
- Frontend — static export served by Nginx/Caddy or a CDN.
- Backend — FastAPI + Uvicorn (or Gunicorn-with-Uvicorn workers) on port 8000.
- Reverse proxy — Nginx in front of both. Routes
/_eventand/_uploadWebSocket/HTTP traffic to the backend; everything else to the static bundle. - Redis (optional) — required if you run more than one backend worker; the state backend uses Redis as a cross-worker store.
upstream reflex_backend { server 127.0.0.1:8000; }
server {
listen 80;
server_name app.example.com;
# WebSocket events
location /_event {
proxy_pass http://reflex_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /_upload {
proxy_pass http://reflex_backend;
client_max_body_size 100M;
}
# Static frontend
location / {
root /var/www/reflex-frontend;
try_files $uri $uri.html /index.html;
}
}
Output: the browser loads static HTML/JS from the CDN/Nginx, then opens a WebSocket to /_event for state sync.
Reflex Cloud (managed)
reflex deploy ships to Reflex Inc.'s hosted platform — easiest path, paid past the free tier. Provides:
- Auto-scaling backend
- Managed Redis
- Custom domain + TLS
- Centralised logs
For prototypes and demos this is the most efficient option. For production with strict data residency, self-hosting is required.
Containerisation
FROM python:3.12-slim AS base
RUN apt-get update && apt-get install -y curl unzip nodejs npm && \
npm install -g bun && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY pyproject.toml requirements.txt ./
RUN pip install -r requirements.txt
COPY . .
RUN reflex init && reflex export --no-zip
FROM nginx:alpine AS frontend
COPY --from=base /app/.web/_static /usr/share/nginx/html
FROM base AS backend
EXPOSE 8000
CMD ["reflex", "run", "--env", "prod", "--backend-only"]
Output: two-stage build; one container serves the static frontend, another runs the FastAPI backend. The bun install is non-trivial — the build stage is large (~1 GB) before pruning.
Multi-worker state
Default state backend is in-memory — a single worker only. For horizontal scaling:
config = rx.Config(
app_name="myapp",
redis_url="redis://redis-host:6379",
)
Output: state synchronises across backend workers via Redis pub/sub. Required for any deployment with >1 Uvicorn worker.
Version migration guide
Reflex is pre-1.0. Pin to an exact version. Each minor release across 0.5 → 0.6 → 0.7 has had non-trivial breaking changes.
Pynecone → Reflex (mid-2023)
- PyPI package renamed:
pip install pynecone→pip install reflex. - Import:
import pynecone as pc→import reflex as rx. - CLI:
pc init→reflex init, etc. - All
pc.*symbols are gone; tutorials referencingpc.buttonare stale.
0.5 → 0.6
- Event handlers consolidated under
@rx.event. The older un-decorated state-method pattern is deprecated. rx.Chakra*becamerx.chakra.*and then opt-in viapip install reflex-chakra. Radix UI primitives (rx.radix.*) became the default theme.rx.VarAPI refined; some helpers renamed.
0.6 → 0.7
- Background tasks API stabilised under
@rx.event(background=True). - The
rx.Stateroutershape changed;router.page.paramsis the canonical path-param accessor. - Several CLI subcommands moved (
reflex db initconsolidated underreflex db).
Approaching 1.0
Reflex is signalling stability over the 0.7+ line. Expect the major rename phase to be over by 1.0, with backward-compat shims for at least one release. Until 1.0, always pin in requirements.txt.
Security considerations
State is server-side — every mutation hits the network
This is by design but has implications:
- Untrusted clients can replay event payloads. Validate inside event handlers; never trust client-supplied state.
- WebSocket DoS — a malicious client can spam events. Rate-limit at the proxy layer.
Authentication
Reflex has no built-in auth as of late 2025. Patterns:
reflex-enterprise— paid Reflex add-on includes auth components.- Reverse-proxy auth — cleanest for production. Cloudflare Access, oauth2-proxy, Authelia in front of the whole app.
- Custom OAuth via FastAPI mount — see Recipe 5; mount OAuth callbacks as FastAPI routes alongside Reflex.
WebSocket origin
By default Reflex accepts WebSocket connections from any origin. For production restrict via config.cors_allowed_origins.
Secret handling
.env files are not loaded automatically — use python-dotenv or rely on the deployment environment. Never embed secrets in rxconfig.py (committed) or in rx.State class attributes (synced to client).
XSS via custom components
rx.html("...") and rx.component_html evaluate raw HTML. Treat user input as hostile — sanitise or escape before passing through. Default Reflex components (text, heading, input) escape their content.
Compatibility matrix
| Layer | Recent stable | Floor |
|---|---|---|
| Python | 3.13 | 3.10 |
| Node.js (auto-downloaded) | 22.x | ~18.x — auto-managed by Reflex |
| Bun (auto-downloaded) | 1.x | ~1.x — auto-managed by Reflex |
| FastAPI (transitive) | 0.115+ | matches Reflex's pin |
| SQLModel (transitive) | 0.0.20+ | matches Reflex's pin |
| Redis (optional, multi-worker) | 7.x | any modern Redis |
Notable platform notes:
- Apple Silicon (M-series) — fully supported; Reflex downloads ARM-native Bun.
- Alpine Linux — Bun needs glibc; use
python:3.12-slim(Debian-based), not Alpine. - Windows native — works but the Node/Bun dance is more fragile; WSL2 is recommended.
Ecosystem integrations
- Radix UI primitives — built into core under
rx.radix.*. Accessible by default. - Chakra UI v2 — original built-in; now an opt-in via
pip install reflex-chakrathenimport reflex_chakra as rc. - Tailwind CSS — configure via
rxconfig.py'stailwind={}block; classes work on any component viaclass_name="...". - Recharts (
rx.recharts.*) — line, bar, pie, area charts. - react-plotly wrapping exists via custom components.
reflex-chakra-pro/reflex-enterprise— paid component libraries.sqlmodel+alembic— built into the framework as the ORM and migrations story.reflex-spline,reflex-leaflet,reflex-monaco— community-built wrappers for 3D scenes, maps, and code editors.- MCP / AI features — Reflex's CLI has experimental commands for generating components from prompts; the API surface is evolving.
When NOT to use this
- Sites with predominantly static content. Reflex is overkill for marketing pages or docs sites — every page is a Next.js + WebSocket-bound Python app. Use Astro, Hugo, or Next.js directly.
- Mobile-app-style offline UX. State is server-side; WebSocket loss = app loss. Use a real SPA with a service worker.
- High-concurrency simple read APIs. Reflex's per-user WebSocket model doesn't fit "10k concurrent read-only viewers". Streamlit or a CDN-fronted static dashboard scales better at the read-heavy extreme.
- Strict no-Node-on-disk environments. The build step requires Bun + Node. Pre-built artifacts can ship without them, but development cannot.
- Pre-1.0 production tolerance is low. API churn is real — every minor release has rename-class migrations. If you can't afford to follow the upgrade treadmill, wait for 1.0 or stick to Streamlit.
- Teams comfortable with React. If your team already writes React, FastAPI + a hand-built React frontend gives more flexibility for the same effort.
See also
- Python: reflex — components, state, events, deployment
- Packages: pip-streamlit — the lighter dashboard-style alternative
- Concept: HTTP — the protocol Reflex bridges Python state across