Skip to main content
Showing a flow depends on the network, your configuration, and a valid flow schema, so it can fail. FlowPilot is built so a failure never crashes your app or leaves a blank screen, but you still decide what the user sees when there is no flow. This page covers the error type, the codes, and where errors actually surface.

Where errors surface

The Expo SDK surfaces errors in predictable places, depending on which API you call:
  1. As a null session (recommended). resolveSession(key) never throws for predictable failures (network error, missing placement, no flow). It returns null when nothing is presentable, so you render your own UI instead.
  2. As a rejected promise. createSession(key) rejects when no presentable flow exists. Wrap it in try/catch, or use resolveSession.
  3. From the presenter’s onError. <FlowPilotPresenter onError={...} /> fires once if a presentation fails after a session was handed to it (for example a navigation dead-end caught by the watchdog). Pass a fallback to render host UI in that case, and onComplete still fires with an error outcome.
  4. Thrown synchronously from configure. An invalid API key or empty App ID throws immediately from FlowPilot.configure(...).
There is no error callback in this build (the SDK does not expose a setErrorCallback). Handle errors through the null/thrown resolve results and the presenter props above.
The deprecated FlowPilot.presentPlacement() does not render on the Expo SDK: it returns an error result immediately without presenting. Resolve with resolveSession / createSession and render <FlowPilotPresenter /> instead. See Presenting placements.

The error type

Errors the SDK produces are a FlowPilotError, an Error subclass with a typed code:
A rejection from createSession (or a presenter onError) is typed as a plain Error, so narrow before reading code:

Error codes

ErrorCode is a string union. Each value is the literal string you match against (err.code === 'NETWORK_ERROR'): A few worth calling out:
  • FLOW_NOT_FOUND is a normal, expected outcome (no flow for this user), not a bug. Show your own UI or nothing.
  • PLACEMENT_NOT_FOUND is a configuration mistake; check the id against the dashboard.
  • TIMEOUT only reaches you when the resolve exceeded resolveTimeout and there was nothing cached or bundled to fall back to.
configure(...) validation throws a plain Error (message Invalid API key. Must start with "fp_". or App ID is required.), not a FlowPilotError. The SDK_NOT_INITIALIZED and resolution codes above come from the present/resolve path.

The resilient path

For anything user-facing, do not depend on catching the right code. Let the SDK walk its fail-safe chain and fall back to your own UI:
  • resolveSession(key) returns a ready session or null, never throws. Render your native UI when it is null.
  • <FlowPilotPresenter fallback={...} /> renders your fallback if a presentation fails before any screen shows.
With this, a network failure cannot block onboarding: the user gets a cached flow, a bundled default, or your own screen. See Caching and Offline and bundled flows.

Example: branch on the code

If you use the throwing createSession API, treat configuration errors differently from transient ones:
The cleaner option is resolveSession, which does this fallback for you.

Common mistakes

  • Reading result.error.code without narrowing. result.error is typed Error. Use instanceof FlowPilotError before reading code.
  • Treating FLOW_NOT_FOUND as a crash. It is a normal no-flow result. Always have something to show.
  • Surfacing raw message to users. It is for your logs. Map codes to user-facing behavior.
  • Using createSession on a critical path with no fallback. It rejects when there is no flow. Use resolveSession for must-not-fail entry points.
  • Looking for an error callback. There is none. Use the error outcome and thrown errors.

Troubleshooting

Internal error reporting

When the SDK hits one of its own internal failures — a flow that fails to resolve after every fallback, a schema it can’t decode, or a component that throws while rendering — it sends a small scoped report to FlowPilot so we can find and fix it. This is not a crash reporter:
  • It installs no global error handlers and never intercepts your app’s crashes. It only forwards errors the SDK itself caught.
  • It does not touch or conflict with your own Sentry / Crashlytics.
  • It is fire-and-forget and best-effort: it never throws, blocks, retries aggressively, or changes what the user sees. Reports are de-duplicated and capped per app launch.
What’s sent: the error code and message, the SDK version, the platform / OS version, the anonymous SDK user and session ids, and flow / placement identifiers for context. No end-user PII beyond the anonymous ids the SDK already generates. Opt out: set disableErrorReporting: true in your configuration and nothing is sent.