State bridges and OpenTelemetry
State bridges
Section titled “State bridges”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);| Store | How it is attached |
|---|---|
| Redux | attachRedux(store) subscribes without middleware, or install backstoryMiddleware() to preserve action names. |
| Zustand | attachZustand(useStore) subscribes to one store. Call it once per store. |
| Pinia | pinia.use(Backstory.state.piniaPlugin()) attaches each Pinia store. |
| NgRx | attachNgrx(store$, actions$) observes the store and optional action stream. |
| MobX | attachMobx({ name, getSnapshot, reaction }) uses your MobX reaction. |
| TanStack Query | attachQueryClient(queryClient) captures query keys and status; data requires { includeData: true }. |
| Apollo | attachApollo(client) captures cache keys; entities require { includeEntities: true }. |
| React component tree | attachReactDevtoolsHook() captures component names and prop keys when the React DevTools hook is available. |
| Any store | captureStore({ 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.
Application collector
Section titled “Application collector”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.
OpenTelemetry
Section titled “OpenTelemetry”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:
traceparentheaders on requests to allowlisted origins, so your backend traces carry the frontend trace id.- Spans with
session.id,session.previous_id, andbackstory.event_seqattributes 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.