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

bash
pip install reflex

Output: (none — exits 0 on success)

bash
uv add reflex

Output: resolved + added to pyproject.toml

bash
poetry add reflex

Output: updated lockfile + virtualenv install

bash
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.x series approaching the 1.0 boundary as of late 2025. APIs have churned across 0.x minor releases — pin tightly.
  • Supports Python 3.10+. Earlier Python versions are unsupported.
  • The PyneconeReflex rename in mid-2023 also moved the PyPI package name from pynecone to reflex — older tutorials reference the dead package.
  • Pre-1.0 semver — every minor release may rename APIs. The 0.50.6 and 0.60.7 jumps 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 to reflex in 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 server
  • sqlmodel, sqlalchemy, alembic — built-in ORM and migrations
  • pydantic — state and event-payload validation
  • typer, rich — the reflex CLI
  • httpx — internal HTTP client
  • redis (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 first reflex init if not on PATH. You can opt in to a system Node with REFLEX_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

PackageTrade-off
streamlitLinear top-to-bottom script model. Faster to prototype; harder to scale to real apps with routing/components.
niceguiVue.js-based component tree in Python. No build step (uses Vue at runtime); smaller install.
solaraReact-based with Jupyter-friendly model. Strong for data scientists already in notebooks.
fastapi + React/VueMaximum flexibility; you write TypeScript. Use when team has frontend depth.
htmy / htmx + FastAPIServer-rendered HTML with progressive enhancement. Skip the React build entirely.
djangoBattle-tested classic full-stack framework, server-rendered templates.

Common gotchas

  1. First reflex init requires internet access to download Bun (~50 MB) and Node.js into ~/.local/share/reflex/. Offline environments must pre-stage those binaries.
  2. 0.x API churn. Pin to an exact version in production. Migrations between minor versions are usually documented but are not codemod-able yet.
  3. 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.
  4. The built-in ORM is sqlmodel (SQL only). No NoSQL story out of the box; integrate motor / beanie manually.
  5. Hot-reload restarts both backend and frontend. Save in state.py and you'll see a 2–5 s reload during which rx.toast etc. are unavailable. Quality-of-life improves over the 0.x series.
  6. Deploys. Self-hosting works (the project produces a static frontend + a FastAPI backend), but the smooth path is reflex deploy to the official hosted platform. Self-host docs assume you can host both halves and run Redis if you want multi-worker.
  7. PyPI name vs project name. Older tutorials and posts reference pip install pynecone; the package was renamed to reflex in 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

python
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

python
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)

python
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

python
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:

python
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

bash
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 /_event and /_upload WebSocket/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.
nginx
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

dockerfile
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:

python
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 pyneconepip install reflex.
  • Import: import pynecone as pcimport reflex as rx.
  • CLI: pc initreflex init, etc.
  • All pc.* symbols are gone; tutorials referencing pc.button are stale.

0.5 → 0.6

  • Event handlers consolidated under @rx.event. The older un-decorated state-method pattern is deprecated.
  • rx.Chakra* became rx.chakra.* and then opt-in via pip install reflex-chakra. Radix UI primitives (rx.radix.*) became the default theme.
  • rx.Var API refined; some helpers renamed.

0.6 → 0.7

  • Background tasks API stabilised under @rx.event(background=True).
  • The rx.State router shape changed; router.page.params is the canonical path-param accessor.
  • Several CLI subcommands moved (reflex db init consolidated under reflex 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:

  1. reflex-enterprise — paid Reflex add-on includes auth components.
  2. Reverse-proxy auth — cleanest for production. Cloudflare Access, oauth2-proxy, Authelia in front of the whole app.
  3. 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

LayerRecent stableFloor
Python3.133.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.xany 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-chakra then import reflex_chakra as rc.
  • Tailwind CSS — configure via rxconfig.py's tailwind={} block; classes work on any component via class_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