> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getflowpilot.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Expo quickstart

> Configure the SDK, present a placement, and read the result, in a React Native Expo app.

This is the shortest path from an installed SDK to a flow on screen: configure at launch, present a placement, then read the result. It assumes you already have a published flow attached to a placement (the [Quickstart](/get-started/quickstart) walks through building that end to end).

<Info>
  **Before you start**

  You need:

  * A FlowPilot [workspace](/dashboard/workspaces-and-apps).
  * An [app](/dashboard/workspaces-and-apps) inside that workspace.
  * An [SDK API key](/dashboard/api-keys) for that app.
</Info>

You also need:

* The SDK [installed](/expo-sdk/installation) in your Expo app, with its peer dependencies and the Reanimated Babel plugin.
* A placement key (for example `onboarding`) with a published flow attached.

## Steps

<Steps>
  <Step title="Configure at launch">
    Call `FlowPilot.configure(...)` once, as early as possible, with your API key and App ID. See [Configuration](/expo-sdk/configuration) for every option.

    ```tsx theme={null}
    import { FlowPilot } from '@flowpilotjs/react-native-sdk';

    FlowPilot.configure({
      apiKey: 'fp_live_xxxxxxxxxxxxxxxx',
      appId: 'your-app-id',
    });
    ```
  </Step>

  <Step title="Resolve and render a placement">
    On the Expo SDK you present a flow declaratively: resolve a `FlowSession`, then render it with `<FlowPilotPresenter />`. Use `resolveSession`, which returns `null` (instead of throwing) when there is no presentable flow, so your app can fall back to its own UI.

    ```tsx theme={null}
    import { useEffect, useState } from 'react';
    import { useSafeAreaInsets } from 'react-native-safe-area-context';
    import {
      FlowPilot,
      FlowPilotPresenter,
      type FlowSession,
    } from '@flowpilotjs/react-native-sdk';

    export function OnboardingGate() {
      const [session, setSession] = useState<FlowSession | null>(null);
      const insets = useSafeAreaInsets();

      useEffect(() => {
        // resolveSession is non-throwing: returns null when nothing is presentable.
        FlowPilot.resolveSession('onboarding').then(setSession);
      }, []);

      if (!session) return <MyNativeOnboarding />; // host fallback

      return (
        <FlowPilotPresenter
          session={session}
          safeAreaInsets={insets}
          onComplete={({ outcome }) => {
            console.log('Outcome:', outcome);
            setSession(null);
          }}
          fallback={<MyNativeOnboarding />}
        />
      );
    }
    ```

    <Warning>
      Do not use `FlowPilot.presentPlacement()` on the Expo SDK. It is deprecated and renders nothing (React Native has no global presentation host). See [Presenting placements](/expo-sdk/presenting-placements#deprecated-presentplacement).
    </Warning>
  </Step>

  <Step title="Read the result">
    The presenter's `onComplete` hands you a `FlowPresentationResult` with an `outcome` of `'completed'`, `'dismissed'`, or `'error'`. Switch on it to decide what to do next.

    ```tsx theme={null}
    function handle(result: { outcome: string; error?: Error }) {
      switch (result.outcome) {
        case 'completed':
          // The user finished the flow.
          break;
        case 'dismissed':
          // The user closed the flow before finishing.
          break;
        case 'error':
          // FlowPilot had nothing to present, or rendering failed.
          console.warn(result.error?.message);
          break;
      }
    }
    ```

    See [Results and outcomes](/expo-sdk/results-and-outcomes) for the full shape.
  </Step>
</Steps>

That is the full loop: configure, resolve, render, read the result. For the host fallback, prefetching, custom components, and offline support in depth, see the pages below.

## Common mistakes

* **Using a dashboard token instead of an SDK API key.** The SDK authenticates with an SDK key that starts with `fp_`. Your dashboard login will not work. Create an SDK key in app settings.
* **App ID mismatch.** The `appId` you configure must own the placement, or the resolve returns no flow.
* **Placement key typo, paused placement, or no flow attached.** The key in code must match the placement key exactly, the placement must be active, and a published flow must be attached.
* **Forgetting to publish.** A draft flow is never served. Publish a version and attach it to the placement.
* **Resolving before `configure`.** `resolveSession` / `createSession` before `configure(...)` throws `SDK_NOT_INITIALIZED`. Configure first.

## Troubleshooting

Nothing appears? Walk this checklist:

1. Is the **API key** an SDK key (it starts with `fp_`) and valid for the environment you configured?
2. Is the **App ID** the one that owns the placement?
3. Is the **placement** active (not paused) and targeting your platform?
4. Is a **published** flow version attached to the placement?
5. Is the **placement key** in your code an exact match?
6. Is the device **online**? If it may be offline, configure caching or a [bundled fallback](/expo-sdk/offline-bundled-flows).

Set `logLevel: 'debug'` in `configure` to see each resolve and why a flow was rejected. Still stuck? See [Troubleshooting](/expo-sdk/troubleshooting).

## Related pages

* [Presenting placements](/expo-sdk/presenting-placements)
* [Results and outcomes](/expo-sdk/results-and-outcomes)
* [Prefetching and readiness](/expo-sdk/prefetching)
* [Configuration](/expo-sdk/configuration)
* [Quickstart (end to end)](/get-started/quickstart)
