Skip to content

Expo SDK — API reference

All public exports from @reprojs/expo.

<ReproProvider>

The provider normalizes config, starts all collectors, manages the offline queue, and renders the wizard modal when triggered.

tsx
import { ReproProvider } from "@reprojs/expo"

<ReproProvider config={config}>
  <App />
</ReproProvider>

config: ReproConfigInput

FieldTypeDefaultDescription
projectKeystringYour project's public key (rp_pk_…). Empty string = silent disable.
intakeUrlstringhttps://your-dashboard.example.com/api/intake. Empty = silent disable.
reporter{ userId?: string, email?: string, name?: string }nullPre-fill the reporter identity. useRepro().identify(...) overrides at runtime.
collectors.consolebooleantruePatch console.* and buffer the last 200 entries.
collectors.network.enabledbooleantruePatch globalThis.fetch and buffer the last 100 calls.
collectors.network.captureBodiesbooleanfalseReserved for v1.1; currently ignored.
collectors.breadcrumbsbooleantrueEnable the useRepro().log(event, data) buffer.
collectors.systemInfobooleantrueAttach device + env info at submit time.
queue.maxReportsnumber5Max queued reports before oldest is evicted.
queue.maxBytesnumber10_485_760 (10 MB)Byte cap across all queued attachments.
queue.backoffMsnumber[][1000, 5000, 30000, 120000]Retry delays per attempt.
redact.headerDenyliststring[]["authorization", "cookie", "x-api-key"]Case-insensitive header names that get [redacted] in the network entry.
redact.bodyRedactKeysstring[]["password", "token", "secret"]Reserved for v1.1 body capture.
theme.accentstring"#6366f1"Reserved; v1 UI uses the flame accent internally.
theme.mode"auto" | "light" | "dark""auto"Reserved for v1.1.
metadataRecord<string, string | number | boolean>{}Attached to every report.

<ReproLauncher>

Opt-in floating bug-report button. Tap opens the wizard; drag relocates to any of the four corners.

tsx
<ReproLauncher
  position="bottom-right"
  offset={{ bottom: 24, right: 24 }}
  icon={<CustomBugIcon />}
  hideWhen={() => someAtom.value}
  draggable
/>
PropTypeDefaultDescription
position"bottom-right" | "bottom-left" | "top-right" | "top-left""bottom-right"Initial corner. Overridden by the persisted choice if the user has dragged.
offset{ top?, bottom?, left?, right? }24 eachDistance from the edge.
iconReact.ReactNode🐞Replace the default emoji.
hideWhen() => booleanCalled on every render; return true to hide (e.g. on specific routes).
draggablebooleantrueWhen false, pinned to position and ignores the persisted choice.

The launcher returns null when the provider is in silent-disable mode, so it's always safe to render unconditionally.

useRepro()

React hook returning a ReproHandle for the nearest provider.

ts
interface ReproHandle {
  disabled: boolean
  open: (opts?: { initialTitle?: string; initialDescription?: string }) => void
  close: () => void
  identify: (reporter: ReporterIdentity | null) => void
  log: (event: string, data?: Record<string, string | number | boolean | null>) => void
  setMetadata: (patch: Record<string, string | number | boolean>) => void
  queue: {
    pending: number
    lastError: string | null
    flush: () => Promise<void>
  }
}

disabled: boolean

true when the provider rendered but config was empty (silent disable). All other methods are no-ops in that state. Use for branching UI:

tsx
const repro = useRepro()
if (!repro.disabled) {
  // feature-gate a "Report a bug" menu item
}

open(opts?) / close()

Open / dismiss the wizard programmatically. initialTitle / initialDescription pre-fill the form step.

identify(reporter)

Set or clear the reporter's identity. null clears.

tsx
repro.identify({ userId: user.id, email: user.email, name: user.name })

log(event, data?)

Emit a breadcrumb. The last 50 breadcrumbs ship with the next submitted report.

tsx
repro.log("checkout.failed", { cartId: "abc", total: 42.50 })

setMetadata(patch)

Shallow-merge host-supplied metadata that ships with every report.

tsx
repro.setMetadata({ appVersion: "1.2.3", buildChannel: "staging" })

queue

Read-only view of the offline queue + a manual flush trigger.

tsx
if (repro.queue.pending > 0) {
  await repro.queue.flush()
}

Repro singleton

Non-React callsites (analytics wrappers, error handlers, class components) can use the module-level singleton.

ts
import { Repro } from "@reprojs/expo"

Repro.open()
Repro.log("purchase.clicked", { sku: "pro-annual" })
Repro.identify({ userId: "u_123" })
Repro.close()
await Repro.flush()

It proxies to the currently-mounted provider. When no provider is mounted (or the provider is silently disabled), every call is a no-op — safe to sprinkle anywhere.

Type exports

ts
import type { ReproConfigInput, ReproHandle } from "@reprojs/expo"

ReproConfigInput matches the config shape accepted by <ReproProvider>. ReproHandle matches what useRepro() returns. ReporterIdentity is re-exported from @reprojs/shared.

Imperative config plugin (advanced)

The @reprojs/expo config plugin is loaded via app.json's plugins array. It's a no-op in v1 but participates in the Expo build chain so future releases can add Info.plist / AndroidManifest patches without requiring consumers to rewire their config.

If you want to manually compose multiple plugins without relying on the plugins array:

ts
// app.config.ts
import withRepro from "@reprojs/expo/app.plugin.js"

export default ({ config }) => withRepro(config)