Skip to content
You are reading the docs for Backstory 0.2 (private beta). Behavior may change before general availability; the changelog lists every change.

State bridges and OpenTelemetry

State bridges feed the Player’s State panel with actions and diffs so you can time-travel from an action to the replay moment. State capture is opt-in: Backstory.init({ state: … }) configures privacy and size limits, but it cannot discover your application’s store. Attach the appropriate bridge after init.

Backstory.init({
projectKey: "pk_live_…",
state: {
denyPaths: ["/auth/token", "/user/ssn"],
maxDiffBytes: 8192,
snapshotEverySec: 60,
},
});
// Choose the bridges your app uses. Each call returns a detach function.
const detachRedux = Backstory.state.attachRedux(store);
const detachCart = Backstory.state.attachZustand(useCartStore);
const detachQueries = Backstory.state.attachQueryClient(queryClient);
const detachApollo = Backstory.state.attachApollo(apolloClient);
StoreHow it is attached
ReduxattachRedux(store) subscribes without middleware, or install backstoryMiddleware() to preserve action names.
ZustandattachZustand(useStore) subscribes to one store. Call it once per store.
Piniapinia.use(Backstory.state.piniaPlugin()) attaches each Pinia store.
NgRxattachNgrx(store$, actions$) observes the store and optional action stream.
MobXattachMobx({ name, getSnapshot, reaction }) uses your MobX reaction.
TanStack QueryattachQueryClient(queryClient) captures query keys and status; data requires { includeData: true }.
ApolloattachApollo(client) captures cache keys; entities require { includeEntities: true }.
React component treeattachReactDevtoolsHook() captures component names and prop keys when the React DevTools hook is available.
Any storecaptureStore({ store: "custom", storeName, getState, subscribe }).

The bridge emits a masked, size-capped snapshot immediately and every snapshotEverySec, with RFC 6902 diffs between snapshots. All values pass through the same PII detectors as text. Keys such as password, token, ssn, and card are redacted by name.

Storage keys (not values), cookie names (not values), service worker state, and feature flags from LaunchDarkly, Statsig, Split, and Unleash are captured into the Application panel. Values are captured only for keys you allowlist.

The SDK contains a 3 KB OTLP/JSON emitter. It does not bundle the OpenTelemetry web SDK. If your page already runs the OpenTelemetry web SDK, Backstory attaches its attributes to your spans instead of creating its own.

Backstory.init({
projectKey: "pk_live_…",
otel: {
emitSpans: true, // one span per page load and per interaction
propagateTraceContext: ["https://api.example.com"], // inject traceparent into these origins
endpoint: "https://ingest.backstory.io/v1/otlp/traces",
},
});

What you get:

  • traceparent headers on requests to allowlisted origins, so your backend traces carry the frontend trace id.
  • Spans with session.id, session.previous_id, and backstory.event_seq attributes following the OpenTelemetry session semantic conventions.
  • In the Player, the Network panel shows the backend span tree under each request once your backend exports traces to Backstory’s OTLP endpoint or to a collector that forwards spans carrying session.id.

To link backend traces, point an OpenTelemetry Collector exporter at POST /v1/otlp/traces with your API token, filtered to spans that carry session.id. Logs with session.id sent to POST /v1/otlp/logs appear in the Console panel’s server tab.