Skip to main content
To show a flow you give FlowPilot a placement key. The SDK resolves which flow to show (cache, network, experiment variant, audience), and you render it with the <FlowPilotPresenter /> component. On the Expo SDK you always present a flow declaratively: you resolve a FlowSession and render it through the presenter component. There is a non-throwing resolver (resolveSession) for must-not-fail entry points like onboarding, and a throwing variant (createSession) when you want to handle the no-flow case yourself.
FlowPilot.presentPlacement() is deprecated on the Expo SDK and does not render any UI. React Native cannot present UI without a mounted component, and the Expo SDK has no global presentation host, so the call logs a deprecation warning and returns an error result immediately without presenting. Use the declarative pattern on this page. See Deprecated: presentPlacement. (On the iOS SDK, imperative presentPlacement is supported.)
The SDK must be configured first. A resolve before FlowPilot.configure(...) throws. See Configuration.

The presentation APIs at a glance

You render whichever session you get through <FlowPilotPresenter session={...} />.

Present a flow

resolveSession(placementId) is the recommended entry point. It walks the full fail-safe chain (cache, network, bundled default) and returns a ready FlowSession, or null when nothing is presentable, without throwing. Render the session with <FlowPilotPresenter />, and render your own native UI when it is null.
resolveSession returns the session already resolved and started by the presenter when rendered. You do not call session.start() yourself with this pattern; the presenter drives the session. (If you build a session with createSession and render the lower-level FlowPresenter directly, you are responsible for start().)

<FlowPilotPresenter /> props

The component wraps the renderer in a full-screen React Native Modal. Pass session={null} to hide it.
FlowSession | null
required
The session to render. null hides the presenter.
(result: FlowPresentationResult) => void
Called once when the flow completes or is dismissed, with a FlowPresentationResult ({ outcome, error? }). Set session back to null here to close the modal.
{ top: number; bottom: number; left: number; right: number }
Safe-area insets, normally from useSafeAreaInsets(). The renderer lays out around the notch and home indicator with these.
'none' | 'slide' | 'fade'
default:"'slide'"
The modal’s present/dismiss animation (passed straight to React Native’s Modal).
'fullScreen' | 'pageSheet' | 'formSheet' | 'overFullScreen'
default:"'fullScreen'"
The iOS modal presentation style (passed to Modal). Ignored on Android.
'default' | 'light-content' | 'dark-content'
Status bar style applied while the flow is presented.
React.ReactNode | (() => React.ReactNode)
Host UI rendered instead of the loading spinner if the presentation fails before any screen shows. See Host fallback.
(error: Error) => void
Called once if the presentation enters the error state.
For advanced embedding without a modal, the lower-level FlowPresenter component (and its FlowPresenterProps) is also exported. FlowPilotPresenter is FlowPresenter wrapped in a Modal; reach for FlowPresenter only when you need to host the flow inside your own container. Most apps use FlowPilotPresenter.

createSession: the throwing variant

When you would rather handle the no-flow case with a try/catch than a null check, use createSession(placementId). It fetches the placement (using the cache if available) and returns the session unstarted, then throws when no presentable flow exists.
There is no per-present options argument in this build (no additional context, no presentation-style override). To vary targeting, set context in configuration; to control the modal, use the <FlowPilotPresenter /> props above.

Host fallback (must not fail)

For anything user-facing, like onboarding, make sure the user always sees something even when every fail-safe tier misses (offline, no cache, no bundled default). resolveSession already gives you this: it returns null instead of throwing, so you can render your own native UI:
You can also pass a fallback to the presenter. It renders instead of the loading spinner when a presentation fails before any screen shows (for example a navigation dead-end caught by the presentation watchdog):
See the full fail-safe chain in Caching and Offline and bundled flows.

Deprecated: presentPlacement

FlowPilot.presentPlacement(key) is deprecated on the Expo SDK and does not render a flow. React Native cannot present UI without a mounted component, and the Expo SDK has no global presentation host, so the method logs a deprecation warning and resolves immediately with { outcome: 'error' } without presenting anything. (Older builds instead ran the session headlessly, emitting flow_start and the entry screen_view but painting nothing, and never resolved for an interactive flow; either way, it cannot show a flow.) Do not call it. Use resolveSession (or createSession) plus <FlowPilotPresenter />, shown above. (On the iOS SDK, which has a real presenting view controller, imperative presentPlacement is supported.)

Common mistakes

  • Calling presentPlacement on Expo. It is deprecated and renders nothing. Use resolveSession + <FlowPilotPresenter />.
  • Not clearing the session on completion. Set session back to null in onComplete, or the modal stays up.
  • Using createSession on a must-not-fail path with no catch. It rejects when there is no presentable flow. Add a .catch, or use resolveSession, which returns null instead.
  • Expecting a per-present context argument. There is none. Set context at configure time.
  • Rendering FlowPilotPresenter without a SafeAreaProvider. useSafeAreaInsets() returns zeros, so the flow can render under the status bar. Wrap your app in SafeAreaProvider. See Installation.

Troubleshooting

createSession threw, resolveSession returned null, or the presenter entered its error state: See Error handling for every error code.