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 three predictable places, depending on which API you call:
  1. As an error outcome (imperative). presentPlacement(key) never rejects for predictable failures. It resolves with { outcome: 'error', error }, where error is an Error (a FlowPilotError when the SDK produced it).
  2. As a rejected promise (declarative). createSession(key) rejects when no presentable flow exists. Wrap it in try/catch or use resolveSession.
  3. 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 outcomes and thrown errors above.

The error type

Errors the SDK produces are a FlowPilotError, an Error subclass with a typed code:
class FlowPilotError extends Error {
  readonly code: ErrorCode;
  readonly underlyingError?: Error;
  readonly context?: Record<string, string>;
}
Because presentPlacement’s error is typed as a plain Error, narrow before reading code:
import { FlowPilot, FlowPilotError } from '@flowpilotjs/react-native-sdk';

const result = await FlowPilot.presentPlacement('paywall_main');
if (result.outcome === 'error') {
  const err = result.error;
  if (err instanceof FlowPilotError) {
    console.warn('FlowPilot error:', err.code, err.message);
  } else {
    console.warn('FlowPilot error:', err?.message);
  }
}

Error codes

ErrorCode is a string union. Each value is the literal string you match against (err.code === 'NETWORK_ERROR'):
CodeCategoryMeaning
INVALID_API_KEYConfigurationAPI key missing or not prefixed fp_.
SDK_NOT_INITIALIZEDConfigurationA present or resolve before configure(...).
NETWORK_ERRORNetworkThe resolve/event request failed at the transport layer.
API_ERRORNetworkThe backend returned a non-2xx status.
TIMEOUTNetworkA resolve exceeded resolveTimeout and nothing was cached.
RATE_LIMITEDNetworkThe backend rate-limited the request.
PLACEMENT_NOT_FOUNDResolutionThe placement id does not exist.
FLOW_NOT_FOUNDResolutionThe placement resolved but no flow matched (no published flow, or targeting excluded the user).
UNSUPPORTED_SCHEMA_VERSIONSchemaThe flow uses a newer major schema than this SDK supports.
INVALID_FLOW_SCHEMASchemaThe resolved flow has no presentable screen.
COMPONENT_RENDER_ERRORRenderingA component failed to render.
CUSTOM_COMPONENT_NOT_FOUNDRenderingA flow referenced a custom component that was not registered.
NAVIGATION_ERRORRuntimeNavigation hit an invalid target.
VARIABLE_ERRORRuntimeA variable operation failed.
ACTION_ERRORRuntimeAn action failed.
ACTION_CHAIN_TIMEOUTRuntimeAn action chain exceeded its timeout.
INTERNAL_ERRORInternalAn unexpected internal failure.
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.
const session = await FlowPilot.resolveSession('onboarding');
return session
  ? <FlowPilotPresenter session={session} onComplete={handleComplete} />
  : <MyNativeOnboarding />;
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/imperative APIs, treat configuration errors differently from transient ones:
import { FlowPilot, FlowPilotError } from '@flowpilotjs/react-native-sdk';

try {
  const session = await FlowPilot.createSession('onboarding');
  session.start();
  // render with <FlowPilotPresenter />
} catch (e) {
  const code = e instanceof FlowPilotError ? e.code : undefined;
  switch (code) {
    case 'FLOW_NOT_FOUND':
      // Expected: no flow for this user. Show your own UI or nothing.
      showNativeOnboarding();
      break;
    case 'PLACEMENT_NOT_FOUND':
    case 'INVALID_API_KEY':
    case 'SDK_NOT_INITIALIZED':
      // Configuration problem; needs a code or dashboard fix.
      console.error('FlowPilot config error', e);
      showNativeOnboarding();
      break;
    case 'TIMEOUT':
    case 'NETWORK_ERROR':
    case 'RATE_LIMITED':
      // Transient. Fall back now; the next launch may succeed.
      showNativeOnboarding();
      break;
    default:
      showNativeOnboarding();
  }
}
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

CodeLikely causeFix
INVALID_API_KEYKey missing or not fp_-prefixedFix the key in Configuration.
SDK_NOT_INITIALIZEDA call before configure(...)Configure once at launch, before any present/resolve.
PLACEMENT_NOT_FOUNDWrong placement idMatch the id to the dashboard exactly.
FLOW_NOT_FOUNDNo published flow, or targeting excluded the userExpected. Publish and attach a flow, or show your own UI.
TIMEOUT / NETWORK_ERRORSlow/no network and nothing cachedUse resolveSession, prefetch at launch, ship a bundled default.
UNSUPPORTED_SCHEMA_VERSIONFlow uses a newer major schema than the SDKUpdate the app’s SDK version.
CUSTOM_COMPONENT_NOT_FOUNDA custom component was not registered, or key/version mismatchRegister the exact key and version before presenting. See Custom components.