rozenite-zustand-manager
v0.0.4
Published
Rozenite DevTools plugin to inspect, edit, and time-travel Zustand stores in React Native apps.
Maintainers
Readme

A Rozenite plugin that brings live inspection, time-travel, and type-aware action invocation to Zustand stores in React Native.
The Rozenite Zustand Manager Plugin provides real-time store inspection, time-travel debugging, snapshot capture, and type-aware action invocation for every Zustand store in your React Native app — without per-store wiring.
This plugin is a community contribution to the Rozenite DevTools ecosystem.
🚧 Status:
0.0.1— alpha. API is stable, the panel UI is still being polished. Feedback and PRs welcome.

Features
- Metro Autodiscovery: Babel transformer wraps your Metro config and registers every module-level
create<T>()(...)Zustand store automatically. Your app source stays untouched. - Live Inspector: Tree, JSON, and table views with type-aware inline editing, path/value copy, search, and sensitive-key redaction.
- Timeline: Every mutation captured as a diff event with real
+/−line counts, kind filters (edit/action/replace/reset), restore-before, and side-by-side compare. - Snapshots: Capture, import/export JSON, restore-all or per-store, compare current state vs any snapshot in a side-by-side diff modal.
- Type-aware Action Runner: Every action exposed as a button. Arity-0 actions fire immediately; parameterized actions open a form with per-arg type pills (
string/number/boolean/object/array/null/undefined). - Safe by Default: Functions stripped before crossing the bridge, sensitive keys (
token,password,email,secret, …) redacted server-side, mutations applied as top-level partials so your store actions are never wiped.
Installation
Install the plugin as a dev dependency:
npm install --save-dev rozenite-zustand-managerNote: This plugin requires zustand (>= 5.0) as a peer dependency, plus Rozenite and @rozenite/metro wired into your project.
Quick Start
1. Install the Plugin
npm install --save-dev rozenite-zustand-manager2. Wrap Your Metro Config
// metro.config.js
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const { withRozenite } = require('@rozenite/metro');
const { withZustandManager } = require('rozenite-zustand-manager/metro');
const config = mergeConfig(getDefaultConfig(__dirname), {});
module.exports = withZustandManager(
withRozenite(config, {
enabled: process.env.WITH_ROZENITE !== 'false',
}),
);No
includeoption needed — Rozenite auto-discovers any installed dependency that ships adist/rozenite.jsonmanifest.
3. Define Stores Normally
// src/stores/useAuthStore.ts
import { create } from 'zustand';
export const useAuthStore = create<AuthState>()((set) => ({
user: null,
signIn: (user) => set({ user }),
signOut: () => set({ user: null }),
}));The Metro transformer detects this declaration and injects a dev-only registerZustandStore({ name, file, store }) call. Your source stays untouched.
4. Access DevTools
Start your development server with Rozenite enabled and open React Native DevTools. You'll find the Zustand Manager panel in the DevTools interface.
WITH_ROZENITE=true npm startManual Registration
For stores the autodiscovery can't reach — factory functions, runtime-created stores, re-exports through a wrapper — register them explicitly:
import { registerZustandStore } from 'rozenite-zustand-manager';
import { createBottomSheetStore } from './createBottomSheetStore';
const sheet = createBottomSheetStore({ visible: false });
export const useSheetStore = sheet.useStore;
if (__DEV__) {
registerZustandStore({
name: 'useSheetStore',
file: 'features/ui/sheets/useSheetStore.ts',
store: useSheetStore,
});
}registerZustandStore returns a cleanup function. There is also a hook form for component-scoped registration:
import { useZustandManager } from 'rozenite-zustand-manager';
function App() {
useZustandManager([
{ name: 'useAuthStore', store: useAuthStore },
{ name: 'useCartStore', store: useCartStore, redactPattern: /token|password/i },
]);
return <YourApp />;
}Configuration
withZustandManager(metroConfig, options?)
type WithZustandManagerOptions = {
enabled?: boolean; // default true. Set to false in production builds.
};StoreEntry
| Field | Type | Description |
| --------------- | ------------- | ------------------------------------------------------------------------------------------------- |
| name | string | Display name in the panel. Required. |
| store | StoreApi<T> | The Zustand store (the value returned by create). Required. |
| id | string | Stable id for diffing. Defaults to name. |
| file | string | Source path, shown in the inspector header. Optional. |
| actions | string[] | Whitelist of action names to expose. Defaults to all functions in state. |
| editable | boolean | When false, panel disables Edit / Replace / Delete — store becomes read-only. |
| redactPattern | RegExp | Custom regex for sensitive keys. Defaults to password\|token\|secret\|email\|apiKey\|authorization. |
Usage
Once you've configured the plugin, it provides:
- Inspector tab: Sidebar of stores, central tree / JSON / table view of state, right-side detail pane with type-aware inline editing, path/value copy, exposed actions, and danger zone (reset / replace).
- Timeline tab: Stream of every mutation with diff highlights and
+/−counts. Filter by store and kind (edit/action/replace/reset), restore the state from before any event, or compare before/after side-by-side. - Snapshots tab: Manual capture or import JSON, restore one store or all, compare a snapshot vs current state in a side-by-side diff modal, export to JSON.
- Action runner: Arity-0 actions fire on click; parameterized actions open a typed form modal where you pick the kind per parameter (
string/number/boolean/object/array/null/undefined). - Sensitive-key redaction: Keys matching the redact pattern (default or custom) are masked in the panel and never crossed the bridge raw.
How It Works
The package ships three artifacts that talk through the typed Rozenite bridge:
- DevTools panel (
dist/devtools/) — Vite-built React app shown in Chrome DevTools. - React Native runtime (
dist/react-native/) — singleton onglobalThis.__ROZENITE_ZUSTAND_MANAGER_RUNTIME__. Subscribes to each store, sanitizes snapshots, applies remote mutations as top-level partials so actions survive every edit. - Metro transformer (
dist/metro/) — wraps@react-native/metro-babel-transformer. For files that importzustandand define module-levelcreate<T>()(...)declarations, appends a__rozeniteZustandRegisterStore({ name, file, store })call. Factory functions and indentedconstdeclarations are deliberately skipped to avoidReferenceErroron closure-scoped variables.
All mutation paths use setState(partial, false) so action functions are never wiped. Replace-state uses the explicit setState(value, true) form. Function values are stripped, and configurable redaction runs before any data crosses the bridge.
Limitations
- The Metro transformer is regex-based. Only module-level
const x = create<...>(...)declarations fromzustand/zustand/vanillaare caught. Factories, namespace imports (import * as Z from 'zustand'), and dynamic helpers (createWithEqualityFn,combine, …) need manualregisterZustandStore. - Action arguments are introspected via
fn.lengthand a best-effortfn.toString()parse. Hermes-optimized builds fall back to genericarg0,arg1names — the form still works, you just lose the original parameter names. - TypeScript type information is not available at runtime; the per-arg form uses kind pills for the user to pick the correct type.
- Class instances,
Date,Map,Setare coerced to JSON-friendly values before crossing the bridge; round-tripping back to the original instance is on you.
Plugin Development
pnpm install
pnpm dev # vite + rozenite live reload of the panel
pnpm build # typecheck → tests → vite build → rozenite build
pnpm test # vitest
pnpm typecheck # tsc --noEmit
pnpm example:start # boot the RN example with Rozenite enabledThe example app under example/ reproduces the full flow with useAuthStore, useCartStore, and useUiStore.
Contributing
PRs and issues welcome. See CONTRIBUTING.md for the workflow and project layout. By participating, you agree to abide by the Code of Conduct.
Release notes live in CHANGELOG.md.
Made with ❤️ for the React Native community
rozenite-zustand-manager is a community plugin built on top of Rozenite, the modular DevTools framework crafted by the team at Callstack. Huge ⭐ to them for making the plugin model possible — go give the main repo some love.
If this plugin saves you time, please star the repo and join the Rozenite Discord to share what you build.
