SDK guides

Expo & React Native

@fixback/expo is the capture SDK for Expo / React Native apps: shake the device to open a feedback composer, and every report ships with trace Evidence — console output, network metadata, screen changes — plus an optional screenshot. Uncaught JS errors report themselves, exactly like the web SDK.

Install

shell
npm install @fixback/exponpx expo install @react-native-async-storage/async-storage expo-secure-store expo-sensors expo-web-browser react-native-view-shot

The Expo-ecosystem packages are peer dependencies — they carry the native modules for the accelerometer, screenshots, id persistence, the in-app browser Connect opens, and the secure store it keeps the Reporter session in. All of them work in Expo Go, and so does every feature below except session replay, which is native code and needs a development build. That list is generated from the SDK's own peerDependencies, so it is always the full set.

Quick start

Wrap your app once:

App.tsx
import { FixbackProvider } from "@fixback/expo";export default function App() {  return (    <FixbackProvider options={{ key: "pk_…" }}>      <RootNavigator />    </FixbackProvider>  );}

Shake the device → the composer opens → write a comment, review (or remove) the screenshot, send. There's no Kind to pick — Fixback's analysis classifies the Issue server-side.

The origin option is optional

There is no origin to configure and nothing to allowlist. Fixback checks a request's Origin header against the Project's allowed origins when one is present. A browser sends that header itself and page script cannot override it, so the check is a real control there. A native app sends none, which means any origin it sent would be a value the app made up — so ingest does not ask for one. What guards a mobile submission is the Project's Gate and the Reporter identity behind it.

Set an origin only when you want mobile reports to carry one, so the queue can be filtered by surface and the report's url reads <origin>/<screen> instead of the screen alone:

tsx
<FixbackProvider  options={{    key: "pk_…",    origin: "https://com.acme.myapp", // must be allowlisted on the Project  }}>

An origin you do present is still checked, so add the same value to the Project's allowed origins. In the dashboard, open the Allowed origins card, switch to Mobile origin, and paste your app's bundle id (iOS) or package name (Android). The card shows the exact origin it will store — com.acme.myapp becomes https://com.acme.myapp, lowercased, because the allowlist compares by exact string equality — and that stored string is what you pass as origin. Any allowlisted origin works, so reusing the web origin of the site this app fronts is equally valid.

Signing in — and gated Projects on mobile

A Reporter is a Fixback Account, and an invite admits an Account, not a device. That is what makes an Invited-gated Project work on a phone: the tester claims the invite once, and Connect carries that claim into the app. Both doors into it — an invite, and signIn() from your own UI — come back through one link your app handles, so they need the same one piece of setup.

1. Give your app a link

Declare a scheme in your app.json:

app.json
{  "expo": {    "scheme": "myapp"  }}

Then tell Fixback which link to come back to:

tsx
<FixbackProvider  options={{    key: "pk_…",    // Where sign-in and invites come back to. Allowlist this link on the Project.    returnUrl: "myapp://fixback-connect",  }}>

A custom scheme is fine — you do not need an https universal link, and no associated-domains entitlement or apple-app-site-association file is involved. Use whatever Linking.createURL() gives you; a universal link works too if you already have one. Without a returnUrl, signIn() is a no-op and an invite has no way back into the app.

2. Allowlist it

Fixback refuses to mint a Connect code for a return URL whose origin is not allowlisted — that is what stops a stolen publishable key redirecting a sign-in somewhere else. So add it: in the dashboard open the Allowed origins card, switch to App link, and paste the whole link. The card shows the exact origin it stores before you add it — myapp://fixback-connect/done is stored as myapp://fixback-connect, because an origin carries no path.

3. Invite a tester

Invite someone from the Project's Reporter invites card and the email takes them to a Fixback claim page, where they sign in and claim in one tap — then Fixback sends them back to your app through the link above, and the SDK signs them in as they arrive. Nothing to wire up on your side: it reads the code off the link your app was opened with. If the Project also lists a website, the claim page offers that too, and the tester picks the one for the device they are on.

4. Or call signIn from your own UI

tsx
const { signIn, identity } = useFixback();// e.g. on your settings screen:<Button title="Sign in to Fixback" onPress={signIn} />;

signIn() opens Fixback's connect page in an in-app auth session and returns through the same link; the SDK exchanges the one-time code and re-boots as the signed-in Account, so a gated Project arms its launcher on the spot. identity() reports who is connected, and signOut() drops the session. This is the door for a tester who did not arrive from an invite, or who signed out.

Telling a tester whether they can report yet

On a gated Project the app knows nothing until boot answers. Ask the handle rather than inferring it from the tier — the Gate rule is the server's, and mirroring it in app code is how the two drift apart.

tsx
const { status, canSubmit, signIn, identity, onIdentity } = useFixback();// Keep a settings row current — signing in does not always change `status`:useEffect(() => onIdentity(setWho), [onIdentity]);// Ask the server, never re-derive the Gate rule in app code:const armed = canSubmit(); // or: status === "ready"

Triggers

  • Shake (default): tuned to a deliberate shake — threshold, peak count, window, and cooldown are configurable via options.shake; shake: { enabled: false } turns the gesture off.
  • Programmatic: useFixback().present() in a component, or the module-level Fixback.present() anywhere.

Screens in reports

React Native has no URL, so tell Fixback where the reporter is:

tsx
const { trackScreen } = useFixback();// e.g. in your navigation container's state-change handler:trackScreen(routeName);

Each change records a navigation trace crumb, and the active screen names the report's URL: <origin>/<screen> when an origin is configured, otherwise the screen on its own.

Releases & code-area pointers

Set release to the build identifier you uploaded sourcemaps under (npx fixback sourcemaps upload --release <v>) and every capture carries it, so an auto-captured crash arrives with a symbolicated code-area pointer instead of a minified frame — the same flow as the web SDK. Optional: without it (or without uploaded sourcemaps) nothing changes.

tsx
<FixbackProvider  options={{    key: "pk_…",    release: "1.4.2",          // the value `fixback sourcemaps upload --release` used    environment: "production", // the deploy environment stamped on every capture  }}>

Privacy

The same client-side scrubbing as the web SDK. One difference is deliberate: mobile screenshots are not auto-masked — so a screenshot only ever ships from the composer, where the reporter sees exactly what will be sent and can remove it. Automatic error reports never attach a screenshot. See Privacy & data.

What's not here yet

Element annotation / drawing, session replay, unhandled-rejection and native-crash capture. An invite now lands in the app itself, so redemption is no longer on this list. The reference has the full options table.