Reflex
Open Source

Upgrading Reflex Apps from 0.8 to 0.9

Upgrading your Reflex app to v0.9.0: the optional database extra, single-port production builds, the auto-setter change, and removed APIs.

Image for blog post: Upgrading Reflex Apps from 0.8 to 0.9

Upgrading Reflex Apps from 0.8 to 0.9

v0.9.0 moves event processing to the backend, makes database support an optional extra, collapses production into a single port, and turns off auto-generated state setters by default. Here's what to change.

TL;DR

ChangeWhat to do
Database deps are now an extrapip install 'reflex[db]~=0.9.0' and set db_url explicitly
state_auto_setters defaults to FalseDefine set_* event handlers explicitly, or opt back in temporarily
Production runs on a single portAccess --env prod apps on --frontend-port (default 3000)
App.overlay_component removedUse extra_app_wraps
rx.Base and PydanticV1 removedMigrate to dataclasses or Pydantic V2
App.process_background removedUse app.event_processor
codeblock in rx.markdown removedUse pre
reflex-enterprise usersUpgrade to >= 0.7.0.post1

1. Install with the right extras

pydantic, sqlmodel, and alembic are no longer base dependencies. If you use rx.Model or any database helpers, install the db extra:

pip install 'reflex[db]~=0.9.0'

Or in pyproject.toml:

dependencies = [
    "reflex[db]~=0.9.0",
]

2. Set db_url explicitly

db_url no longer defaults to a local SQLite file. Declare it in rxconfig.py:

import reflex as rx

config = rx.Config(
    app_name="my_app",
    db_url="sqlite:///reflex.db",
)

3. Define your own setters

state_auto_setters defaults to False, so Reflex no longer generates set_count for you.

Before:

class MyState(rx.State):
    count: int = 0

After:

class MyState(rx.State):
    count: int = 0

    @rx.event
    def set_count(self, value: int):
        self.count = value

If a state field has many call sites, you can opt back in temporarily:

config = rx.Config(
    app_name="my_app",
    state_auto_setters=True,  # deprecated, will be removed before 1.0
)

This is deprecated and emits a warning. Don't reach for it as a long-term solution — define explicit handlers and remove the flag before v1.0.

4. Production runs on a single port

The --single-port flag is gone — --env prod now always serves frontend and backend on one port.

# Before
reflex run --env prod --single-port

# After
reflex run --env prod

The frontend is served by Starlette StaticFiles. Default ports depend on the running mode:

  • Fullstack (frontend + backend, the default): both share --frontend-port (3000)
  • Frontend only: --frontend-port (3000)
  • Backend only: --backend-port (8000)

Update any reverse proxy or load balancer that split traffic across separate frontend and backend ports.

If you call prerequisites.check_running_mode() in tooling, it now returns a single RunningMode enum (FRONTEND_ONLY, BACKEND_ONLY, FULLSTACK) instead of a (frontend, backend) tuple.

5. Replace removed APIs

App.overlay_component → extra_app_wraps:

extra_app_wraps is a dict[tuple[int, str], Callable[[bool], Component | None]] — keyed by a (priority, name) tuple, with a callable that receives a stateful: bool and returns the wrapper component (or None). Wrappers must render their children, so use rx._x.memo to declare one:

import reflex as rx

@rx._x.memo
def my_overlay(children: rx.Var[rx.Component]) -> rx.Component:
    return rx.container(
        rx.heading("Common header"),
        children,
    )

app = rx.App(extra_app_wraps={(1, "CommonHeader"): lambda s: my_overlay()})

rx.Base and PydanticV1 are gone. Migrate model classes to dataclasses or Pydantic V2. rx.State is unaffected — it moved off Pydantic in 0.8.

codeblock in rx.markdown → pre:

# Before
rx.markdown(text, component_map={"codeblock": my_codeblock})

# After
rx.markdown(text, component_map={"pre": my_codeblock})

App.process_background → app.event_processor. App._background_tasks becomes App.event_processor._tasks.

_substate_key / _split_substate_key are deprecated. Use rx.BaseStateToken to address state from outside an event handler — for example with app.modify_state:

async with app.modify_state(rx.BaseStateToken(ident=client_token, cls=MyState)) as state:
    state.count += 1

Other internal renames:

  • State.class_substates → State.get_substates()
  • Event.token removed — the token lives on EventContext
  • Event.substate_token → Event.state_cls
  • StateManager.create(state=...) no longer takes a state= kwarg
  • fix_events() and get_hydrate_event() removed
  • AppHarness lost state_manager, get_state(), set_state(), modify_state(), poll_for_clients(), _reset_backend_state_manager() — drive state through events in tests instead

6. Bump reflex-enterprise

pip install 'reflex-enterprise>=0.7.0.post1'

7. New APIs worth a look

  • rx.upload_files_chunk (#6190) — streams chunks instead of buffering whole files
  • @rx._x.memo (#6192, experimental) — JS-level memoization for components and pure functions
  • backend_path config (#6338) — prefix backend routes for path-based proxies
  • In-memory state expiration (#6201) and a Redis state-expiry event (#6194)
  • Recharts Defs (#6322) and dropdown cells in the data editor (#6139)

See the roadmap for what's coming next.

Written by

Build the applications you used to buy.