This tool works with both Expo and React Native CLI projects. Just install and go.
The Redux DevTools extension is the reason most people can debug Redux at all — and on React Native it is the one thing you cannot have on the device where the bug is. You get a remote-debugger bridge that changes the timing you were trying to measure, or you get nothing.
Buoy runs the same workflow inside the app: every dispatched action with its payload and duration, the state tree after it, a diff of exactly what changed, and JUMP to put the store back. No middleware and no wrapper — it hooks the store at creation through the official Redux DevTools integration point, so actions are captured from the very first dispatch, thunk-internal and RTK Query actions included. And it works in a TestFlight build, where the extension cannot reach.
A checkout session dispatches, a thunk comes back declined, you read the error and the diff, jump the store back, and replay the failure.
Not a video — this is the shipped tool, and the checkout above is a real Redux store. Every action lands with its slice, its duration and what it changed.
npm install @buoy-gg/reduxThat's it. The Redux DevTools auto-detects your store and appears in your FloatingDevTools menu.
Zero config required — Just install the package. Your existing Redux store works as-is with no middleware or wrapper needed. Buoy hooks the store at creation via the official Redux DevTools integration point (Redux Toolkit enables it by default), so actions are captured from your app's very first dispatch — including thunk-internal and RTK Query actions — with no UI interaction required.
Guarantee full capture — the store-creation hook needs
@buoy-gg/reduxto load before your store module. That's usually automatic; to make it a guarantee, putimport '@buoy-gg/redux';as the first import of your app entry. If Buoy loads too late, it still binds your store automatically at app mount (top-level dispatches only — the tool tells you when it's in that mode).
BUOY brings the power of Redux DevTools to mobile — with features designed for React Native workflows.
| Feature | BUOY | Chrome Extension |
|---|---|---|
| On-device debugging | ✅ | ❌ |
| Works in production | ✅ | ❌ |
| QA/support can use it | ✅ | ❌ |
| No desktop app required | ✅ | ❌ |
| Zero configuration | ✅ | ❌ |
| Action logging | ✅ | ✅ |
| State inspection | ✅ | ✅ |
| State diff view | ✅ | ✅ |
| Time-travel (Jump to state) | ✅ | ✅ |
| Action replay | ✅ | ✅ |
| Action filtering & search | ✅ | ✅ |
| Performance timing | ✅ | ✅ |
| Async thunk linking | ✅ | ✅ |
| Export history | ✅ | ✅ |
| RTK Query support | ✅ | ✅ |
| Skip/toggle actions | 🔜 | ✅ |
| Dispatch custom actions | 🔜 | ✅ |
| Import state | 🔜 | ✅ |
| Persist across reloads | 🔜 | ✅ |
| Stack traces | 🔜 | ✅ |
Why on-device matters: Debug Redux on real devices, in TestFlight, or in production. No USB cable, no desktop app, no "it works on the simulator" moments.
Every dispatched action is captured with rich metadata:
Tap any action to see three detailed tabs:
View the complete action payload, meta information, and error details for failed actions. Interactive JSON tree for exploring nested data.
Explore the full state tree after this action with a collapsible data viewer. Navigate deeply nested state with ease.
Side-by-side comparison showing exactly what changed — additions (green), removals (red), and modifications (yellow) clearly highlighted. Choose between tree view or split view.
Jump to any point in your app's history:
Note: Jumping to a past state needs a reducer that can serve it, which is a separate piece of wiring from action capture — middleware sits above your reducer and cannot replace what it returns. You get it automatically when
@buoy-gg/reduxis imported before your store module (Buoy becomes the store enhancer); otherwise add the reducer wrapper from Advanced Configuration. The JUMP button tells you which you have: it is disabled and labelled "Time travel not wired" when the store cannot serve a jump, rather than doing nothing when pressed.
Full support for Redux Toolkit async thunks with intelligent linking:
Catch performance issues before they impact users:
Find actions instantly by type, or filter to show only actions that changed state.
Export action data or payloads for debugging, bug reports, or test fixtures.
Pause action capture when you need to focus, resume when ready.
Download your complete action history as JSON for sharing with teammates or creating test data.
For most apps, zero-config is all you need. But if you want more control:
To enable jumping to past states (not just viewing them), wrap your reducer:
import { configureStore } from '@reduxjs/toolkit';
import { withBuoyDevTools } from '@buoy-gg/redux';
const store = configureStore({
reducer: withBuoyDevTools(rootReducer),
});
import { configureStore } from '@reduxjs/toolkit';
import { withBuoyDevTools } from '@buoy-gg/redux';
const store = configureStore({
reducer: withBuoyDevTools(rootReducer),
});
For fine-grained control over what gets captured:
import { createBuoyReduxMiddleware, withBuoyDevTools } from '@buoy-gg/redux';
const customMiddleware = createBuoyReduxMiddleware({
maxActions: 500, // History size (default: 200)
ignoreActions: [ // Actions to skip
'persist/PERSIST',
'persist/REHYDRATE',
],
});
const store = configureStore({
reducer: withBuoyDevTools(rootReducer),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(customMiddleware),
});
import { createBuoyReduxMiddleware, withBuoyDevTools } from '@buoy-gg/redux';
const customMiddleware = createBuoyReduxMiddleware({
maxActions: 500, // History size (default: 200)
ignoreActions: [ // Actions to skip
'persist/PERSIST',
'persist/REHYDRATE',
],
});
const store = configureStore({
reducer: withBuoyDevTools(rootReducer),
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware().concat(customMiddleware),
});
When to use manual middleware: If you need to ignore specific actions, increase history size, or you simply prefer explicit wiring — the middleware path is also a guaranteed-full-capture alternative to the import-order note above. Everything (action log, desktop sync, MCP
get_redux_state/redux_dispatch) works the same on either path.
No conflicts: If you configure middleware manually, the auto-instrumentation automatically detects this and defers to your configuration. You'll never get duplicate action entries.
import {
// Auto-instrumentation (used internally, rarely needed)
instrumentStore,
isStoreInstrumented,
// Manual middleware (optional, for advanced config)
buoyReduxMiddleware,
createBuoyReduxMiddleware,
// Time-travel (optional)
withBuoyDevTools,
jumpToState,
replayAction,
// Hooks
useReduxActions,
useAutoInstrumentRedux,
// History adapter (for custom integrations)
reduxHistoryAdapter,
createReduxHistoryAdapter,
} from '@buoy-gg/redux';
import {
// Auto-instrumentation (used internally, rarely needed)
instrumentStore,
isStoreInstrumented,
// Manual middleware (optional, for advanced config)
buoyReduxMiddleware,
createBuoyReduxMiddleware,
// Time-travel (optional)
withBuoyDevTools,
jumpToState,
replayAction,
// Hooks
useReduxActions,
useAutoInstrumentRedux,
// History adapter (for custom integrations)
reduxHistoryAdapter,
createReduxHistoryAdapter,
} from '@buoy-gg/redux';
JUMP only reaches the 25 most recent actions. Every retained action pins its own copy of the state tree, and on an app that replaces large slices wholesale — a store switch, a rehydration — a few dozen of those are enough to exhaust memory. Older actions keep their row, their diff summary and their payload; they just no longer have a tree to restore, so JUMP is disabled on them.
It reads the store, it doesn't replay it. Jumping sets state directly. It does not re-run your reducers, re-fire thunks, or reissue the network calls an action originally triggered — so a jump puts the data back, not the side effects.
Install @buoy-gg/redux — the action stream, state diffs, and time-travel controls run inside the app on the device. Flipper (deprecated since RN 0.73) is not involved.
Yes — JUMP restores the store to the state after any recorded action, and REPLAY re-dispatches an action, directly from the in-app panel.
REPLAY works on every setup. JUMP needs a reducer that handles the jump, which you get either by importing @buoy-gg/redux before your store module (Buoy becomes the store enhancer) or by wrapping your root reducer with withBuoyDevTools. If neither applies, the JUMP button is disabled and says so — it never silently does nothing.
JUMP is also disabled on older actions whose raw state has been released. Buoy keeps the before/after state trees of the 25 most recent actions only: every retained action pins its own copy of the tree, and on an app that replaces large slices wholesale (a store switch, a rehydration) a few dozen of those are enough to exhaust memory. Older actions keep their row, their diff summary and their payload — just not a tree to restore.