Reflex Changelog
v0.10.0 (2026-10-08)
Breaking Changes
Python and dependencies
- Drop support for Python 3.10, which reaches end of life in October 2026. Reflex now requires Python 3.11 or newer. (#7449)
reflex[db]allows SQLModel 0.0.45 and later, which stores a plaindatetimefield as UTC: writes and filters need timezone-aware values and reads return aware ones. To keep naive datetimes, declare the field withsa_type=DateTime(timezone=False)or pinsqlmodel<0.0.45, as described in Datetimes and SQLModel upgrades. (#7462)
State
- Reading a backend var on a state class (
State._items) now returns itsFielddescriptor instead of the var's default value. Passing it to a component, which used to bake the default into the frontend, now raisesChildrenTypeErroras a child orTypeError: Unsupported type <class 'reflex_base.vars.base.Field'> for LiteralVar.as a prop. To keep baking in the default, passState._items.default_value()instead; to make the value reactive, declare a frontend var or computed var and pass that. See Upgrading to Reflex 0.10. (#7312) - Assigning a state var through its state class, such as
State.count = 10, raisesTypeErrorinstead of replacing the var; so do pytest'smonkeypatch.setattrandunittest.mock.patch.objecton a var. Change a default withState.__fields__["count"].set_default(10), patch the field in tests, and declare class-level configuration asClassVar. See Upgrading to Reflex 0.10. (#7516) - Mutable values provided as state var defaults are deep-copied when assigned at class creation time or passed to
set_default, so later changes to the original object no longer reach new sessions. A module-level list filled in after theclassstatement, such asoptions: list[str] = OPTIONSfollowed byOPTIONS.append(...), now starts every session empty: fill it before the class is defined, or declaredefault_factory=lambda: list(OPTIONS). (#7519) - In a background task, calling a state-modifying handler inherited from a parent state, or writing an inherited var, outside
async with selfnow raisesImmutableStateErrorlike any other state change. See Upgrading to Reflex 0.10. (#7312) - Assigning an undeclared state attribute still raises
SetUndefinedStateVarErroroutside of prod mode, but no longer in prod. The internal class mapsbackend_vars,inherited_varsandinherited_backend_vars,get_skip_vars()and the instance_backend_varsare removed: useget_fields(), whose fields know the state they belong to. (#7312) - Instances of Reflex 0.9 and 0.10 can no longer share a Redis or disk state store. A 0.10 instance loads state saved by 0.9, but a 0.9 instance discards state saved by 0.10, so a rolling deploy that runs both versions, or a rollback to 0.9 against the same store, resets the sessions that reach the older instance. Upgrade every instance of an app together, and clear the store (or start a fresh one) when rolling back. (#7494)
CLI
- Remove the
reflex componentCLI (init,build,share,install) and theCustomComponentsconstants; runningreflex componentnow points to the replacements. Wrap React libraries directly in your app as described in the wrapping React docs, and start reusable component packages from the component template, which builds, tests, and publishes them with standard Python tooling. (#6425, #7497)
Deprecations
- Deprecate
State.router.headers.cookieandState.router.headers["cookie"]in components; both now render an empty string. Userx.Cookiefor cookies that need to be accessible to the frontend. (#7360)
Features
- A substate may now declare a var or computed var with the same name as an inherited var: it gets an independent one of its own, instead of raising
BaseVarShadowsInheritedVarErrororComputedVarShadowsBaseVarsError. A dynamic route arg only conflicts with a var of the state it is installed on. (#7312) - The duration settings read by the app and the state managers take a unit suffix:
SQLALCHEMY_POOL_TIMEOUT,REFLEX_SOCKET_INTERVALandREFLEX_SOCKET_TIMEOUTaccept values such as2m, andREFLEX_AUTO_RELOAD_COOLDOWN,REFLEX_OPLOCK_HOLD_TIMEandREFLEX_STATE_MANAGER_DISK_DEBOUNCEreplace the_MS/_SECONDSnames, which still work with a deprecation warning until 1.0. A bare number is read as seconds. (#7138) - Set
REFLEX_REDIS_MAX_CONNECTIONSto cap each Redis client's connection pool (the state manager, the token manager and the health check each use their own client). Once a pool reaches the cap, requests wait up toREFLEX_REDIS_POOL_TIMEOUT(default 2s, which must be above 0 and below the configured state-lock lifetime) for a free connection instead of opening new ones. (#7179)
Bug Fixes
State and events
- Stop sending request cookies, including HttpOnly cookies, and standard authorization, Cloudflare Access, OAuth2 Proxy, AWS ALB, and Google IAP credential headers to frontend router data. On-load events no longer copy request router metadata to the frontend; server-side access to request headers is unchanged. (#7360)
- Defaults are no longer part of the saved-state schema, so changing one keeps state saved by this release or later loadable. A browser storage var annotated with a storage type, such as
rx.Field[rx.LocalStorage], and declared with adefault_factorycompiles with the storage options the factory produces, andreset()restores the factory's value. (#7461) - Assigning a double-underscore private attribute on a state (
self.__counter = 1) from a mixin, a base, or a class whose name starts with an underscore no longer raisesSetUndefinedStateVarErrorin dev mode. Private names not explicitly declared withrx.field()are plain Python attributes: vars do not update in response to them. (#7465) - In a background task on a substate, in-place changes to a mutable var inherited from a parent state (like
self.items.append(...)) are now sent to the client and persisted. (#7312) - A page URL with a
selfquery parameter (e.g./post?self=1), or a request header namedself, no longer crashes router data parsing and leaves the page unhydrated. (#7324) - A state stored in Redis that can no longer be unpickled, for example because a deploy moved or deleted a class held in a state var, is now replaced with a fresh state like a schema mismatch, instead of failing every event from that tab until the Redis key expires. (#7329)
- The memory and disk state managers now free expired session states right away instead of waiting for a garbage collection pass, and the disk state manager no longer keeps a lock for every expired session. (#7318)
- Fix a race with
REFLEX_OPLOCK_ENABLEDwhere an instance could take an opportunistic lease before its Redis lock notifications were active, making other instances wait out the full hold time for the same token. (#7372) - Fix
TypeError: refs._client_state_set... is not a functionwhen a component sets a globalrx._x.client_statevalue before any component reading.valuehas mounted, such as when the reader sits behind anrx.cond. (#7286) - Preserve ID-based form controls through automatic memoization while excluding IDs on non-controls from submissions. (#7227)
Database
- The
dbextra now installsgreenlet, which SQLAlchemy 2.1 no longer pulls in on its own, sorx.Model,rx.session()and thereflex dbcommands work on a freshpip install reflex[db]instead of failing withImportError: The SQLAlchemy asyncio module requires that the Python 'greenlet' library is installed. (#7466) - Fixed
reflex db makemigrations/migratecrashing withCompileErrorwhen autogenerating a migration that adds a column with a callable default (e.g.default_factory=datetime.nowordefault=uuid.uuid4) to an existing table; callable defaults are now evaluated before being carried as a SQLserver_default. (#6706) reflex dbcommands only requiresqlalchemyandalembic, so apps that use plain SQLAlchemy models withoutsqlmodelcan run migrations again. (#7322)reflex dbcommands run without thedbextra installed now exit with the "pip install reflex[db]" message instead of a raw traceback. (#7259)
CLI, build and serving
- Serve valid dynamic-route URLs (e.g.
/articles/7) with HTTP 200 instead of 404 when loaded directly in self-hosted prod static serving, reserving 404 for genuinely unknown paths. (#6996) reflex runnow stops its frontend on SIGTERM and SIGINT without a TTY, while keeping frontend workers in the CLI process group so a hard kill also stops them. (#7328)reflex run --jsonnow emits every output line as a JSON record:print()output from the app, subprocess output, and worker tracebacks (as one record with anexceptionfield) no longer break the JSON-lines stream. (#7350)- Fix stylesheet edits in
assets/not applying in dev mode until a manual reload. (#7317) - Avoid reinstalling frontend packages on every compile or hot reload when bun or npm only changes the formatting of
package.json. (#7236) - When a project keeps using npm because
reflex.lock/only haspackage-lock.json(for example after a run withREFLEX_USE_NPM=1), Reflex now logs why and how to switch back to bun withREFLEX_USE_NPM=0. (#7093) - Reflex now checks the Node.js version before running npm, so an unsupported Node.js no longer leaves npm lockfiles behind that switch later runs to npm. (#7210)
- An editable install (
uv sync,pip install -e .) no longer overwrites.pyistubs that the checkout already has. A checkout missing any of them still gets them generated. (#7303) - On Windows, the development backend no longer closes its listening socket twice when it releases the port, which could close another socket that had reused the handle and make it fail with
OSError: [WinError 10038]. (#7348) - Stop logging
asyncio.CancelledError: lifespan_cleanupas an error when a running coroutine lifespan task is cancelled at backend shutdown or hot reload. (#7392)
Testing
AppHarnessnow forgets the states of every module of the app's package when it stops, not only those of the app module, so a state defined elsewhere in the app (for example one usingrx.dynamic) no longer breaks the next app started in the same test process. (#7359)
Performance
- Speed up first page loads by combining hydration with the websocket connect and sending only values that differ from compiled defaults. Reduce Redis state-tree read/write overhead and avoid repeated class metadata computation in apps with many states. (#7064)
- Reading a state var is about 4x faster, and setting one about 7x faster (15x in prod mode): vars, computed vars and event handlers are now descriptors on the state class that declares them, instead of every attribute access going through
BaseState.__getattribute__. (#7312) - Process events with less CPU on the backend: iterating, sorting and reading list and dataclass state values costs 25–75% less, and the redis state manager writes the changed states of a session in one round trip. (#7370)
- Apps that define an
rx.SharedStateno longer pay extra per-event work for events that change no shared state. (#7237) - Compile memoized components faster by reusing unchanged memo bodies instead of rendering them again. (#7123)
Documentation
- Add an Upgrading to Reflex 0.10 guide covering the changes most likely to break an existing app, with the fix for each. (#7496)
- Document the dict form for
rx.toastactionandcancelprops instead of referencing the non-publicToastActionclass. (#7327)
Miscellaneous
- Allow wrapt 2.4 and 2.5. (#7424)
v0.9.12 (2026-09-21)
Breaking Changes
- The root state gained five base vars holding the router data:
rx_router_session,rx_router_headers,rx_router_page,rx_router_urlandrx_router_route_id. A substate that declares one of these names now raisesBaseVarShadowsInheritedVarError, the same error any other shadowed inherited var raises, and must rename its field.State.routeritself is unchanged. (#7068) - Declaring a substate var that shadows a var inherited from a parent state now raises
BaseVarShadowsInheritedVarErrorat class creation. Such a declaration was silently ignored — reads and writes resolved to the parent's var and class-level access returned the raw default instead of a reactiveVar. Rename the substate var to fix the error. (#7077) - State vars, event handlers, and dynamic route arguments now reject names reserved by framework methods and bookkeeping before registration. Rename conflicting members. (#7136)
state.dict()and the state deltas no longer carry a singlerouterentry: the root state now serializesrx_router_session,rx_router_headers,rx_router_page,rx_router_urlandrx_router_route_idinstead (each with the usual field-marker suffix). Code that read or rewrote therouterentry of a state dict or delta — for example to redact request headers before returning state over an API — must read those five entries instead.State.routeritself is unchanged for app code. (#7215)
Deprecations
- Declaring a computed var dependency on the
routervar (deps=["router"]) is deprecated; depend on the router Var instead, e.g.deps=[State.router.url]for a single field ordeps=[State.router]to keep tracking all of them. (#7068)
Bug Fixes
- Fix stateful inputs under
rx.form.control(..., as_child=True)so they receive the parent form's attributes and their values appear in submitted form data. (#6850) - Allow State Vars for page titles and descriptions in
@rx.pageand compiled metadata. (#6923) - Stopping
reflex runwith SIGTERM no longer reports "Starting frontend failed with exit code 143" and now exits cleanly. (#6981) - Preserve relationship serialization and database usage accounting for apps that use SQLModel directly, without loading unused database integrations. (#7049)
- Preserve prerendered pages when asset directories collide with routes under
frontend_path, and compress the final merged output. (#7078) - Generated
.pyistubs now type a prop declared as a union —content: Var[str] | Component, say — as optional, matching theNonedefault thatcreate()gives every prop. Type checkers previously reported the generated signature itself as an error. (#7080) - Subclassing
rx.Model(e.g.class Item(rx.Model, table=True)) without thedbextra installed now raises the guided "pip install reflex[db]"ImportErrorinstead of a bareTypeErrorfrom__init_subclass__. (#7083) - Backend-only development runs no longer leave a compile-skip marker that can cause the next full run to skip frontend compilation. (#7089)
- Persist bundled-library metadata for backend-only workers so state hydration can serialize values that reference libraries included in the frontend build. (#7096)
- Keep the development backend port open while hot reload restarts the worker, so requests made during a reload wait for the new worker instead of being refused. (#7114)
- Fixed local package specifiers such as
@masenf/hello-react@../hello-reactand@masenf/hello-react@../hello-react.tgzbeing truncated at the first slash (to@masenf/hello-react@..) before reaching the package manager, so wrapping a React package from a local directory or archive now installs correctly. (#7117) - Switching between bun and npm (
REFLEX_USE_NPM) no longer leavesreflex.lock/in a state that makes the next run fail withbun install --frozen-lockfile: lockfile had changes. Only the lockfile of the package manager that actually ran is kept. (#7129) - Keep saving state to disk and Redis when a state defines a var named
_get_was_touched. (#7132) - Apps no longer crash at startup with
AttributeError: 'method' object attribute '__call__' is read-onlywhen ASGI instrumentation that wraps middleware is active, such as sentry-sdk's Starlette integration. (#7139) - Fix backend startup crashes from concurrent or truncated stateful-page marker writes. Markers are replaced atomically, remain readable by separate backend users, and are rebuilt when missing or corrupt; dry-run compilation leaves them unchanged. (#7142)
- Match routes that start with the
frontend_pathtext, such as/appleunderfrontend_path="/app", instead of treating them as 404. (#7153) - Flush OpenTelemetry compile spans before the isolated initial development compile worker exits. (#7155)
- Fixed
StateManagerDisk.set_stateto persist and cache state instances that were not obtained fromget_state, and debounced writes now flush the latest supplied value instead of the first one queued. (#7159) - Fix
@rx.memocomponents dropping the app wraps their body requires. Providers requested by a nested child, or through var data asrx.upload'sUploadFilesProvideris, now reach the app root — so a provider-backed component behaves the same inside a memo as inlined into the page. (#7176) - Reuse one long-lived Redis client for the
/_healthendpoint instead of opening and closing a new TCP connection on every probe. (#7187) - Emit Granian lifecycle logs as JSON records when Reflex JSON logging is enabled, keeping
reflex run --jsonstdout valid JSON lines. (#7193) - Avoid crash when node is not installed (
error: restartWithMergedOptions() was called, but the process has already been restarted.). (#7202) type(rx.State)isreflex.vars.BaseStateMetaagain, so a state declared with its own metaclass derived fromBaseStateMeta(class MyState(rx.State, metaclass=MyMeta)) no longer raisesTypeError: metaclass conflict. The reserved-state-name validation is unchanged: it now runs fromBaseStateMetaitself for every subclass ofrx.State. (#7215)- An
@rx.var(cache=False)value that a downstreamget_deltaoverride keeps out of the delta is now delivered as soon as the override stops withholding it, instead of being deduplicated away until the value changes again. Uncached var values only count as sent to the client once the delta that carries them is actually delivered. (#7216) - Release the development backend port again when no worker can serve it, so requests fail fast while the app module raises on import and after the server shuts down, instead of waiting in the accept backlog until the client times out. (#7217)
- Keep app wraps registered below the "Built with Reflex" badge in the rendered
page. In production builds with the badge on, the badge swallowed every
lower-priority app wrap, so
rx.data_editor's<div id="portal" />never reached the DOM and its overlay cell editors — including the new image preview — could not open. (#7218) - Fix nested router mutations bypassing background-task locks and read-only state proxies. Writes through
self.routernow enforce the same mutation guards as direct state-field access. (#7230)
Performance
@rx.var(cache=False)vars now remember what they last sent to the frontend. They are still recomputed on every state update, but the value is only included in the delta when it actually changed, so an uncached var whose value stays the same no longer causes needless network traffic and re-renders. (#6946)- Reduce development startup and reload time and memory by deferring unused database, admin, and compiler imports in the backend launcher and state mutation tracking, and by avoiding redundant app preloads in spawned Granian supervisors. (#7049)
- Store router data in separate base vars (session, headers, page, url, route_id) so a navigation delta only re-sends the fields that changed instead of the whole router, and gather the connection-scoped router data (headers, client IP, session id) once at connect time rather than on every event.
State.routeris unchanged for app code. - The page URL is also persisted as the URL itself rather than as its parsed pieces:
ReflexURLandURLDatare-split on the way out of the state store instead of writing scheme, netloc, origin, path, query, query parameters and fragment alongside the href on every state write. (#7068) - Preload the global stylesheet so browsers can discover render-blocking CSS alongside early resource hints. (#7078)
- Honor
frontend_lazy_bundled_librarieswhen compiling the app root so optional dynamic-component namespaces do not force their full exports into every page's initial bundle. (#7078) - Reduce
reflex runandreflex exportmemory: the vite/react-router processes no longer keep their dependency pre-bundling arena resident (MIMALLOC_ARENA_EAGER_COMMIT=0, overridable from the environment), and error telemetry is sent throughurllibso backend workers never importhttpx. (#7112) - Speed up compilation by reading only the props a component sets, caching literal Var dispatch by value type, and trimming render and app-wrap bookkeeping. (#7121)
- Share one event chain per handler and trigger across call sites, and reuse memoized event wrappers by chain identity during compilation. (#7122)
Documentation
- Refresh the
docker-exampledeployments for current Reflex and consolidate them intoproduction,production-compose, andapp-platform-backend, with smaller images, clean SIGTERM shutdown, and no build tooling at runtime. The two-port example is gone since prod mode now serves the frontend and backend on one port; the self-hosting docs are updated to match. (#7140)
v0.9.11 (2026-09-11)
Breaking Changes
- State deltas may emit state entries and variable keys in a different order. Values are unchanged, but downstream snapshots or tests comparing serialized deltas as text may need updating; compare parsed JSON objects or normalize key order instead. (#7087)
Features
- Propagate a frontend
traceparentinto event spans, count websocket connections and message sizes, and wrap the ASGI app when thereflex-otelinstrumentor is active. (#6227)
Bug Fixes
- A backend var whose name is annotated on a state now takes the default declared by a
field()on a base class instead of silently becomingNone, and an error raised by that field'sdefault_factorysurfaces instead of being swallowed. (#6812) - State classes no longer resolve descriptors while being constructed, so a hybrid property's frontend var is no longer built against a half-built class. (#6812)
- Assigning to a state attribute backed by a property (including a
hybrid_property) now runs its setter instead of raisingSetUndefinedStateVarError. (#6812) - Telemetry events are now collected under the submitting thread's registration context, so the background worker reuses the config the app already loaded instead of re-importing
rxconfig.py(and mutatingsys.path) off-thread. (#6960) - Make
reflex.testingimportable without test-only dependencies and provide atestingextra forAppHarness. (#6974) - Mutable proxies over dataclass state values now carry the wrapped type's
__dataclass_params__and__match_args__on their class alongside__dataclass_fields__, so code that inspects a dataclass through the class — reading thefrozen/eqflags or the positional field names afterdataclasses.is_dataclass— no longer raisesAttributeErroron a proxied value. (#7014) AdminDashnow works with starlette-admin 1.0, which renamed the SQLAlchemyAdmin(engine=...)argument tosession_provider. Both starlette-admin 0.x and 1.x are supported. (#7019)- Compiling an app from several processes against one working directory — pytest-xdist workers, parallel builds, or containers sharing a bind mount — no longer aborts with
FileNotFoundErrororFileExistsErrorwhile linking arx.asset(shared=True)file intoassets/external/. A shared asset whose link already points at a different file is repointed at the asset rather than left alone. (#7039) reflex run --env prodandreflex exportno longer fail withFileNotFoundErrorwhenfrontend_pathis set and route prerendering is disabled (REFLEX_SSR=false), and no longer fail on Windows withcannot instantiate 'PosixPath'wheneverfrontend_pathis set. (#7044)- Generate the frontend context module as
utils/context.jsxsoreflex runhot updates keep the state providers mounted; a staleutils/context.jsis removed on the next compile. (#7071) - Fix
rx.AdminDashpages failing withNoMatchFoundby preserving named route lookup through the application's context middleware. (#7107) - Give forked backend workers distinct socket-owner identities so Redis can deliver backend-initiated state updates to clients connected to another worker. (#7108)
- Preserve explicit
bundle_library()registrations through frontend compilation and automatically bundle imports used by initial-state components. Explicitly registered component subpaths can first appear after an event, and initial components such as Lucide icons no longer need a separate registration. (#7109)
Performance
- Clear auto-memoization naming caches after compiling app. (#6947)
- New opt-in dev-server knobs:
REFLEX_DEV_PROD_REACT=1serves React's production build under the Vite dev server (navigation CPU on a large app 54 → 36 ms, prod build 24 ms; edits become a full reload since Fast Refresh needs dev React), andREFLEX_VITE_WARMUP_ROUTES=1pre-transforms route modules at startup so the first visit to a page no longer waits on Vite (105–131 → 43–69 ms, or 20–32 ms with both). (#7021) - Trimmed the framework overhead around every event handler: the state fast-paths its own bookkeeping attributes, foreground handler tasks start eagerly on Python 3.12+, the computed-var expiry check only looks at interval vars, route matching is memoized per path, and socket.io handlers run inline. About 28% less CPU per trivial event and 20% more events per second per worker under concurrent load. (#7025)
- Reduce CLI startup time by loading component and cloud command implementations only when invoked, and avoid frontend package reinstalls after backend-only config changes. (#7050)
- Avoid repeated PyPI requests by caching successful latest-version checks for 24 hours and throttling failed checks for one hour. (#7050)
Miscellaneous
- Allow
wrapt2.2 and 2.3. (#7019)
v0.9.10 (2026-09-01)
Bug Fixes
- Shared state updates now reach linked clients connected to other backend instances — the fan-out previously skipped any client whose websocket was not connected to the instance processing the event, so with redis and multiple workers only same-instance clients received live updates. (#6934)
- Allow static IDs on document-root head components without generating React hooks. (#7005)
v0.9.9 (2026-08-28)
Breaking Changes
pip install reflexno longer installspydantic; pydantic model support activates when it is installed. Use the newreflex[pydantic]extra (orreflex[db]) to keep it. (#6786)- The compiled frontend now targets React Router 8.3.0 (from 7.18.2), and Reflex requires Node 22.22.0 or newer as a result. Apps on the default generated setup need no
rxconfig.pyor app code changes. One change is required if you wrote a custom component againstreact-router-dom: that package no longer exists upstream and is no longer installed, solibrary = "react-router-dom"must becomereact-router(orreact-router/domforRouterProvider/HydratedRouter). (#6854) - A
RegistrationContextcan only be associated with a singleAppinstance, so creating a second barerx.App()in one process now raisesReflexRuntimeError(0.9.8 allowed it); use a freshRegistrationContext(e.g.RegistrationContext.fork()) to create multiple apps. (#6382)
Deprecations
reflex.components.dynamic.bundled_librariesandDEFAULT_BUNDLED_LIBRARIESare deprecated (removal in 1.0) but keep working, resolving against the activeRegistrationContext. UseRegistrationContext.ensure_context().bundled_librariesto read the list, orbundle_library()/reset_bundled_libraries()to modify it. (#6967)reflex.page.DECORATED_PAGESis deprecated (removal in 1.0) but keeps working, resolving to a mapping of the app name to the activeRegistrationContext's page registrations. UseRegistrationContext.ensure_context().decorated_pagesinstead. (#6985)
Features
- The current
App, the loadedConfig,@rx.pageregistrations, and the bundled-library registry are now scoped to the activeRegistrationContextinstead of module-level globals, so multiple apps (and test harnesses) can coexist in one process without leaking registrations into each other. (#6382) - Report state deltas the frontend cannot process back to the backend via a new
client_errorsocket event, logging an actionable error in the terminal instead of failing silently. A frontend/backend state mismatch is fatal for the session: further events stop until the page is reloaded after the frontend is rebuilt orapi_urlis corrected. (#6827) - Framework logging now flows through standard python
loggingwith per-module loggers (reflex_base.utils.log, re-exported asreflex.utils.log), bootstrapped onimport reflex. Rich colored output is preserved, andREFLEX_LOG_JSONemits machine-readable JSON-lines records.--loglevel criticalno longer prints the system-info banner. (#6863) - The reflex CLI accepts
--json(equivalent toREFLEX_LOG_JSON) to emit machine-readable JSON-lines logs. (#6865) reflex deployaccepts--min-instancesand--max-instancesto set the autoscaling bounds of an app deployed to Google Cloud. Omitted bounds are left unchanged. (#6884)reflex deploygains--gcp-connection, to pick which of your organization's connected GCP accounts an app deploys through;--full-deploy, to serve the frontend from the provider's own container instead of Reflex's CDN; and--strategy, which was previously only settable in the config file. (#6908)- Compiled components are now named for React DevTools: memoized components take a
displayNamefrom the Python class or@rx.memofunction they came from instead of showing asAnonymous, generated contexts are named (StateContext(reflex___state____state.my_state).Providerrather than an unlabelledContext.Provider), pages are labelled with their route (Component(blog/[slug])), and client-only (NoSSRComponent) wrappers render asClientSide(<Tag>). (#6945)
Bug Fixes
- Stale
on_loadwork no longer blocks or outlives a page navigation: a newer navigation for the same client now cancels the previous page's unfinishedon_loadevent chain, includingon_loadhandlers that are background tasks (@rx.event(background=True)), which 0.9.8 let run to completion. Background tasks started from other events are unaffected. (#6593) - A
[[...splat]]catchall route no longer matches paths that merely share its prefix —posts/[[...splat]]matched/postsomethingas well as/postsand its descendants, so the wrong page'son_loadevents could fire. (#6790) - Ensure state manager instances use isolated internal locks instead of sharing one lock across instances. (#6830)
- Qualify
dictannotations onBaseStatethat were shadowed byBaseState.dict, so type checkers resolve them to the builtin. (#6846) reflex runnow pre-enables thedevelopmentexport condition for the dev server viaNODE_OPTIONS/BUN_OPTIONS, fixing the dev server exiting withrestartWithMergedOptions() was called, but the process has already been restartedon installs without node, where react-router 8's CLI re-executes itself to set the condition. (#6857)- An
AppHarnessProdno longer leaksREFLEX_ENV_MODE=prodto devAppHarnessinstances created later in the same process, which made them compile with route prerendering enabled and drop events dispatched during hydration recovery. (#6857) - Cache event handler annotations before runtime state-class patches can shadow builtin names on Python 3.14. (#6890)
rx.scripthead updates now flush synchronously instead of via react-helmet's requestAnimationFrame batching, fixing intermittently missing script tags after hydration (flaky "scripts not loaded" failures). (#6905)- Fixed a race where a finishing background task could silently discard state updates made by a concurrently running event handler before they reached the frontend, leaving the UI stale until the next write. Background handlers that never enter
async with selfstill emit their delta, now computed under the state lock. (#6920) AppHarnessstarts the frontend dev server with thedevelopmentexport condition enabled, fixing "Frontend did not start" on node-less (bun-only) installs where react-router's dev CLI restart guard trips. (#6931)- Adding a page no longer raises a spurious
RouteValueErrorwhen a static segment lines up with another route's dynamic segment (e.g./posts/all/[x]alongside/posts/[id]). React Router resolves such siblings in favor of the static one, so only two differently named dynamic segments at the same position conflict. The check was also order-dependent: it only tripped when the bracket-carrying route was added second. (#6953) - Reduce published wheel and sdist size by removing misplaced generated artifacts. (#6966)
- A
client_errorsocket emit with no payload no longer raises an unhandledTypeErrorinside python-socketio's dispatch, which let any connected socket — even one without a valid token — spam asyncio tracebacks into the backend logs past the handler's rate limits. (#6984) - Console warnings and errors no longer print literal backslash-escaped brackets (e.g.
dict\[str, str]). The rich-markup escapes were left over from the legacy console helpers, but the logging pipeline renders messages with markup disabled, so bracketed type names now print verbatim.VarAttributeErrormessages drop the same escapes. (#6989) reflex runno longer hangs forever when a fatal error (e.g. the node minimum-version check on the npm path) exits the frontend worker thread while the backend blocks the main thread; the failure now interrupts the main thread and the CLI exits promptly with the original error. (#6990, #6994)
Performance
- Remove the per-update
asyncio.create_taskwrapper inEventNamespace.emit_update, cutting scheduling overhead roughly in half for every outgoing state update. (#6734) - Dev mode no longer pays for React's per-element owner-stack capture: navigation clicks in a large app dropped from ~350ms to ~83ms of main-thread CPU (5.6x prod down to ~1.3x). In exchange
React.captureOwnerStack()returns no owner frames in dev, which affects React DevTools' owner-stack view and custom error overlays built on that API; setREFLEX_REACT_OWNER_STACKS=1to restore them. (#6905) @rx.memocomponents with props bound to state are now auto-memoized at the call site: the state hooks those props need compile into a generated wrapper component instead of the page module. A state change re-renders that wrapper rather than the whole page, and React'smemostops there unless one of the prop values actually changed. (#6949)- The generated
vite.config.jsnow declares a hook filter on the plugin that redirectsreact-dom/servertoreact-dom/server.node, so the bundler no longer calls into it for every import in the module graph — on the Reflex docs site that was ~15,800 calls per build to rewrite a single specifier. (#6959)
Documentation
- Documented the
provider,gcp_connectionandfull_deploycloud config settings, including which settings a Google Cloud target ignores and whyfull_deployis left unset rather than false by default. (#6908)
Miscellaneous
- The generated
package.jsonno longer carries a framework-ownedpostcssoverride; the pinnedpostcssdev dependency already forces a single resolved copy for every transitive requirer. Projects that already installed 0.9.8 keep an inert"postcss": "8.5.23"override inreflex.lock/package.json; it matches the dev-dependency pin, so it changes nothing today and can be deleted by hand. (#6854) - Upgrade the locked dev tooling:
ruff0.15.12 -> 0.16.2,pyright1.1.408 -> 1.1.411,typer0.25.1 -> 0.27.1. (#6893) - The
reflex deploycommand implementation moved out of thereflexpackage intoreflex-hosting-cli, so cloud code is no longer shipped inside the framework. Flags and behavior are unchanged, andreflex-hosting-cliremains a dependency ofreflex, soreflex deployandreflex cloudstay available out of the box. If the package is not installed, these commands now report which package to install instead of failing with a missing-command error. (#6924)
v0.9.8.post1 (2026-08-18)
Features
reflex deployaccepts--min-instancesand--max-instancesto set the autoscaling bounds of an app deployed to Google Cloud. Omitted bounds are left unchanged. (#6884)reflex deploygains--gcp-connection, to pick which of your organization's connected GCP accounts an app deploys through;--full-deploy, to serve the frontend from the provider's own container instead of Reflex's CDN; and--strategy, which was previously only settable in the config file. (#6908)
Documentation
- Documented the
provider,gcp_connectionandfull_deploycloud config settings, including which settings a Google Cloud target ignores and whyfull_deployis left unset rather than false by default. (#6908)
v0.9.8 (2026-08-04)
Features
- Added content-hash cache busting to
rx.assetURLs. (#6550) - Add a
previewrun mode (reflex run --env preview) that hot reloads likedevbut serves a freshly built, un-minified frontend bundle mounted into the backend instead of running the Vite dev server. Minification, CSS minification, autoprefixer, and sourcemaps are disabled by default for faster rebuilds and readable output (each overridable viaVITE_MINIFY,REFLEX_NO_AUTOPREFIXER, andVITE_SOURCEMAP). (#6663) - Support using mutable state proxies as async context managers. (#6689)
- Run plugins' staged
register_routehooks once per app before page evaluation so plugins can contribute pages atomically, and invalidate the cached route resolver when a page is added after it was first built. (#6728) reflex deploynow accepts--provider(deploy to Reflex Cloud or a GCP account connected to your organization) and--description(record an optional changelog note on the deployment, shown inreflex cloud apps history).
Bug Fixes
- Fix
reflex component buildcrashing withAttributeErroron Python 3.10 and 3.11 by delegating recursive stub generation to the Python 3.10-compatiblePyiGeneratorscanner. (#6760) - Fixed
reflex renamecorrupting or failing on source files on non-UTF-8 platform locales while preserving declared Python source encodings and line endings. (#6761) - Fixed nested/subfolder stylesheets failing to load on Windows because the generated CSS
@importused backslash path separators (which CSS treats as escape sequences); the import URL is now always POSIX-normalized. (#6762) - Process persisted package.json files before mirroring them into the web directory. (#6765)
- Fix production frontend hydration on Windows when the system MIME registry maps JavaScript files to
text/plain. (#6831) - Fixed
reflex runfailing witherror: lockfile had changes, but lockfile is frozenafter upgrading to a Reflex version that adds apackage.jsonoverride. Overrides are now applied after the lockfile saved inreflex.lock/has been installed, so it is no longer treated as out of date. (#6844)
Miscellaneous
- Update dev pin Pillow==12.3.0 to avoid various CVE reports (#6836)
- Update locked
aiohttp==3.14.3andcryptography==50.0.0, clearing CVE-2026-59881, CVE-2026-69243, CVE-2026-69244 and CVE-2026-69247. Both are transitive development dependencies of the docs app and are not installed with Reflex. (#6837)
v0.9.7 (2026-07-15)
Features
- Added
default_color_modetorx.Config("system","light", or"dark", also settable viaREFLEX_DEFAULT_COLOR_MODE), so apps can set the initial color mode — and use the built-in color mode switcher andrx.color_mode_cond— without pulling in the large Radix themes CSS. The value drives both the compiledThemeProviderdefault and the pre-hydration preload script, so there is no flash of the wrong theme on first paint. An explicitrx.theme(appearance=...)still takes precedence. (#6716) @rx.memocomponents now compile with a configurable JS wrapper: React'smemoremains the default,wrapper=swaps in a custom functionVarwhose imports ride along into the generated module, andwrapper=Noneemits the bare function component. (#6730)- The new
frozen_lockfileconfig option is now honored during frontend package installation: when enabled (the default), bun's initial install runs with--frozen-lockfileso a lockfile out of sync withpackage.jsonfails fast. Setfrozen_lockfile=Falseto let the lockfile update in place instead. npm has no equivalent install flag today, so the option is a no-op there. (#6763)
Bug Fixes
- Fix stateful pages being evaluated twice in one process (forked prod workers and same-process export+serve), which created duplicate
ComponentStateclasses and broke frontend hydration (TypeError: d is not a function). (#6710) - Reset the disk state manager write queue task after close. (#6715)
- Close the
RedisTokenManagerredis client and cancel its pub/sub background tasks on app shutdown, fixing leaked redis connections (ResourceWarning: unclosed Connection) when the server stops. (#6724) - Event handlers and computed vars inherited from a state mixin now preserve the source function's custom attributes and keyword-only defaults. (#6725)
Performance
- Run anonymous telemetry collection and delivery on a dedicated single-worker background thread instead of inline on the asyncio event loop. The blocking syscalls, subprocess calls and synchronous HTTP request used to gather and post an event no longer stall the event loop — notably when reporting backend errors at a high rate. Delivery is best-effort and any failure is suppressed, so telemetry can never affect the running app. (#6626)
- Event chaining (
yield OtherState.handler(rows)) no longer deep-copies payload values that are not attached to any state: only state-boundMutableProxysubtrees are copied, making proxy-free payloads ~5x faster to chain. (#6739) Var.to()andVar.guess_type()resolve their target Var subclass through cached registry lookups instead of scanning the full registry withsafe_issubclasson every call. (#6742)
v0.9.6 (2026-06-25)
Features
- Auto-memoized (
rx.memo) components now compile to.web/app_components/output paths that mirror their defining Python source module (using the real package name, including framework packages) instead of being bundled into a single sharedcomponents.jsx. The compiler's auto-memo registry is scoped per source module, so identical-rendering subtrees in different modules each emit their own output instead of one silently overwriting another, hot-reloads of a module refresh the correct output, and stale memo files are cleaned up when their source changes. Memos whose module can't be mirrored (__main__, unsafe names) fall back to one file per memo at.web/utils/components/<name>.jsx. Each mirrored memo's generated export name also carries a stable per-module suffix, so two memos that share a name in different modules compile to distinct symbols and can be used together on one page without colliding. (#6457) rx._x.hybrid_propertynow works on dataclasses, pydantic models and SQLAlchemy models, not justStateclasses. Accessing the property through an object var on the frontend (e.g.State.info.a_b) renders it as a var, using the same code you already use on the backend. (#6617)reflex initnow writes a Reflex-managed section intoAGENTS.md(fetched from the canonical source and delimited by markers that preserve surrounding user content), and bridges it for Claude Code by creating aCLAUDE.mdimporting@AGENTS.md— or, if aCLAUDE.mdexists without the import, managing the section there directly. (#6620)rx._x.hybrid_propertynow raises a clear error when its frontend logic reads a backend (underscore-prefixed) state var, instead of silently baking the var's server-side default into the frontend. Reference a regular var, or provide a separate frontend implementation with@<name>.var. (#6621)
Bug Fixes
- Sync
reflex.lock/package.jsonto.web/package.jsonbefore installing packages to ensure lock file and package.json are aligned. (#6658) - Avoid re-entering config loading when a
Statesubclass is defined inrxconfig.py. (#6662) - Raise minimum dependency versions to pull in security fixes:
starlette>=1.3.1(Host-header path poisoning,request.form()DoS, and UNC-path SSRF),python-multipart>=0.0.32(quadratic-time querystring DoS, unbounded header field size, and negativeContent-Lengthbuffering inparse_form), andgranian>=2.7.4(WSGI and WebSocket header-panic DoS). (#6665) - Fixed
modify_stateto rebindEventContext.tokento the token being modified, so delta resolution and computed vars inside shared-state fan-out tasks observe the correct client token rather than the triggering event's inherited context. (#6673)
v0.9.5.post2 (2026-06-10)
Bug Fixes
- Allow access to State from
app_wrapcomponents (#6651)
v0.9.5.post1 (2026-06-10)
Bug Fixes
- Bumped minimum
reflex-components-coredependency to 0.9.5 for compatibility.
v0.9.5 (2026-06-10)
Features
rx.formon_submithandlers can now annotate their form-data parameter with aTypedDict(includingtyping_extensions.NotRequiredfields). The submitted mapping is accepted by the event-argument type checker, and at component build time the form statically validates that its controls supply every requiredTypedDictfield, raisingEventHandlerValueError— with the missing and present field names — when a required field has no control with a matching staticname/id. Validation is skipped when the form sets anid(controls may be associated externally via the HTMLformattribute) or when any control identifier is a dynamicVar. (#6301)- Event handlers attached to JSX literals built outside a component's render scope — such as an
ErrorBoundary'sonError— can now dispatch events.addEventsis reached through a module-level import thatEventLoopProviderpopulates on each render, so dispatch no longer depends on auseContexthook being hoisted into the calling scope. The state and event-loop providers, previously hard-coded in the layout template, are now injected around the app root by the compiler from theapp_wrapsdeclared on theVars that use them. (#6447) - Added
App.hydrate_fallback, a component rendered during the page's hydration window (React Router'sHydrateFallback) instead of a blank white page. It can also be configured without code through thehydrate_fallbackconfig — a dotted import path to a no-arg callable returning a component, settable via theREFLEX_HYDRATE_FALLBACKenvironment variable — with theAppargument taking precedence. Note that the fallback only covers the hydration window after the JS bundle has loaded, not the initial bundle download. (#6630) - Added the
REFLEX_HOT_RELOAD_OVERRIDE_PATHSenvironment variable, a colon-separated list of paths that, when set, fully replaces the paths watched for hot reload in dev mode — taking precedence over the config-derived defaults as well asREFLEX_HOT_RELOAD_INCLUDE_PATHSandREFLEX_HOT_RELOAD_EXCLUDE_PATHS. (#6639)
Bug Fixes
- Anonymous telemetry now reports the installation and project identifiers as UUID strings rather than 128-bit integers. PostHog coerced the large integers to floats, discarding all but ~16 significant digits and risking distinct installs or apps being correlated as one. Each identifier is re-encoded to the same value (a UUID carries the same 128 bits), and a one-time PostHog
$create_aliaslinks an installation's pre-existing history to its new identifier so continuity is preserved. (#6611) scripts/make_pyi.pyis now a proper CLI for maintainingpyi_hashes.json:--forceregenerates every default target (ignoring the incremental markers), explicit targets are merged into the registry instead of pruning it, and an unreachable last-run commit (after a branch switch or rebase) triggers a full regeneration. A new--checkmode, wired into the pre-commit CI job, fails when apyi_hashes.jsonentry no longer has a matching.pysource. (#6614)State.get_var_value()no longer silently returns a wrong value when passed a Var operation — an arithmetic/concatenation expression such asState.a + State.b, or an indexed/item access such asState.items[0]. Previously it resolved the state and field of the operation's first operand and returned that field's value instead of the operation's result. It now raisesUnretrievableVarValueError, consistent with how it already handled vars not associated with any state. Plain field and computed-var references continue to resolve as before. (#6633)
Performance
- Speed up reading mutable state vars (lists, dicts, dataclasses) through
MutableProxy. The per-element check that detectsdataclasses.asdict/astuplerecursion now readsframe.f_code.co_filenamedirectly instead of callinginspect.getfile(), cutting proxy read overhead by roughly 3-4x on large containers without changing behavior. (#6600)
Miscellaneous
- Report the versions of the first-party Reflex subpackages shipped with Reflex (
reflex-base, thereflex-components-*family andreflex-hosting-cli) in anonymous telemetry via a newreflex_package_versionfield. The set is derived from Reflex's own declared dependencies, so unrelated third-partyreflex-*packages are never reported. Now that Reflex is split across many independently-versioned packages, the singlereflex_versionfield no longer reflects the full install. (#6610)
v0.9.4 (2026-06-03)
Deprecations
rx._x.memois deprecated in favor ofrx.memo. The old name remains a working alias for now; update imports to userx.memodirectly. (#6517)@rx.memonow expects each parameter to be annotated asrx.Var[...](orrx.RestProp/rx.EventHandler) and the function to declare anrx.Componentorrx.Var[...]return type. Memos that still use bare Python types (e.g.name: str) or omit the return annotation keep working — the values are coerced torx.Var[...]/rx.Componentand a deprecation warning points at the parameters and return type that need explicit annotations — but this fallback will be removed in 1.0. (#6598)
Features
- Added
rx._x.hybrid_property, a property decorator usable on State classes that works like a normal Python property for backend access while also rendering on the frontend at class level. Use the same method for both, or register a separate frontend implementation with@<name>.var. (#3806) - Promoted the component memo system to a first-class
rx.memoAPI. Memo-decorated components now acceptrx.EventHandlerparameters and carry annotated return types so they type-check correctly at call sites. (#6517) - Added
rx.EMPTY_VAR_COMPONENT, an empty-componentrx.Var[rx.Component]sentinel for use as a default on@rx.memochildrenslots (and anyrx.Var[rx.Component]prop) — the component counterpart torx.EMPTY_VAR_STRandrx.EMPTY_VAR_INT. (#6598) @rx.memonow evaluates the decorated function body lazily — on first use (component instantiation) or at compile time — instead of at import time. This speeds up startup and lets a memo reference modules that aren't fully imported yet, sidestepping circular-import errors during decoration. Body-dependent errors (e.g. a var-returning memo that uses hooks or non-bundled imports) now surface when the memo is first used or compiled rather than at import. (#6598)
Miscellaneous
- Introduced towncrier-based changelog management. Each PR that changes package source now adds a fragment under the affected package's
news/directory; fragments are assembled intoCHANGELOG.mdat release time. See CONTRIBUTING.md for the full workflow. (#6350) - Removed the "choose templates" option from
reflex init. The interactive prompt now offers only a blank app or the AI builder, and no longer opens the open-source templates page. (#6592)