Where errors surface
The Expo SDK surfaces errors in predictable places, depending on which API you call:- As a
nullsession (recommended).resolveSession(key)never throws for predictable failures (network error, missing placement, no flow). It returnsnullwhen nothing is presentable, so you render your own UI instead. - As a rejected promise.
createSession(key)rejects when no presentable flow exists. Wrap it intry/catch, or useresolveSession. - 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 afallbackto render host UI in that case, andonCompletestill fires with anerroroutcome. - Thrown synchronously from
configure. An invalid API key or empty App ID throws immediately fromFlowPilot.configure(...).
setErrorCallback). Handle errors through the null/thrown resolve results and the presenter props above.
The error type
Errors the SDK produces are aFlowPilotError, an Error subclass with a typed code:
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_FOUNDis a normal, expected outcome (no flow for this user), not a bug. Show your own UI or nothing.PLACEMENT_NOT_FOUNDis a configuration mistake; check the id against the dashboard.TIMEOUTonly reaches you when the resolve exceededresolveTimeoutand 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 ornull, never throws. Render your native UI when it isnull.<FlowPilotPresenter fallback={...} />renders your fallback if a presentation fails before any screen shows.
Example: branch on the code
If you use the throwingcreateSession API, treat configuration errors differently from transient ones:
resolveSession, which does this fallback for you.
Common mistakes
- Reading
result.error.codewithout narrowing.result.erroris typedError. Useinstanceof FlowPilotErrorbefore readingcode. - Treating
FLOW_NOT_FOUNDas a crash. It is a normal no-flow result. Always have something to show. - Surfacing raw
messageto users. It is for your logs. Map codes to user-facing behavior. - Using
createSessionon a critical path with no fallback. It rejects when there is no flow. UseresolveSessionfor must-not-fail entry points. - Looking for an error callback. There is none. Use the
erroroutcome 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.
disableErrorReporting: true in your configuration and nothing is sent.