> ## 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.

# Quickstart

> Build a one-screen flow and render it in your iOS or Expo app in about 15 minutes.

This guide takes you from nothing to a flow rendering in your app. It touches every part of FlowPilot: the dashboard, the Flow Editor, a placement, and a FlowPilot SDK. The dashboard steps are the same for everyone; the SDK steps have tabs for **iOS** (Swift) and **Expo** (React Native), so follow the tab that matches your app. Plan for about 15 minutes.

<Tip>
  New to FlowPilot? Read [Core concepts](/get-started/core-concepts) first to learn the terms used throughout these docs.
</Tip>

## Before you start

You need an app you can run and access to the FlowPilot dashboard:

* **iOS:** an Apple app you can run (a fresh Xcode project is fine).
* **Expo:** a React Native app created with Expo (`npx create-expo-app` is fine), run on a simulator or device.

## Steps

<Steps>
  <Step title="Create a workspace and app">
    Sign in to the dashboard and create a workspace (your organization), then create an **app** for your target inside it (iOS for the iOS SDK, or set the platform to **React Native** for an Expo app). Note the **App ID**. The SDK needs it at launch.

    See [Workspaces and apps](/dashboard/workspaces-and-apps).
  </Step>

  <Step title="Create an SDK API key">
    In the app's settings, create an **SDK API key**. Copy it now, because it is shown only once. This key is different from your dashboard login. The SDK sends it as `Authorization: Bearer <key>` to the `/v1` API.

    See [API keys](/dashboard/api-keys).
  </Step>

  <Step title="Build a flow and publish it">
    Open the Flow Editor, add a screen, and drop in a text component and a button. Give the button a `closeFlow` action so tapping it ends the flow. Then **publish** the flow to create a servable version. A draft is never served.

    See [Your first screen](/editor/first-screen) and [Publishing](/editor/publishing).
  </Step>

  <Step title="Create a placement and attach the flow">
    Create a placement with a key like `onboarding`. Set its default flow version to the one you just published, and make sure the placement targets the platform your app runs on: select **iOS** for an iOS app, and add **Android** too if your Expo app ships there. The device's platform must be in this list for the placement to resolve a flow.

    See [Creating placements](/dashboard/creating-placements).
  </Step>

  <Step title="Add the SDK to your app">
    <Tabs>
      <Tab title="iOS">
        Add the FlowPilot Swift package to your Xcode project with Swift Package Manager.

        See [iOS installation](/ios-sdk/installation).
      </Tab>

      <Tab title="Expo">
        Install the package and its Expo peer dependencies, then add the Reanimated Babel plugin.

        ```bash theme={null}
        npm install @flowpilotjs/react-native-sdk
        ```

        See [Expo installation](/expo-sdk/installation) for the full peer-dependency list and Babel setup.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Configure the SDK at launch">
    Configure the SDK once at app launch with your API key and App ID.

    <Tabs>
      <Tab title="iOS">
        ```swift theme={null}
        import FlowPilotSDK

        // In AppDelegate.application(_:didFinishLaunchingWithOptions:)
        // or your App struct's init().
        FlowPilot.configure(
            FlowPilotConfiguration(
                apiKey: "fp_live_xxxxxxxxxxxxxxxx",
                appId: "your-app-id"
            )
        )
        ```

        The environment defaults to `.production`. The SDK expects the API key to start with `fp_`. If it does not, configuration is skipped and an error is logged. See [iOS configuration](/ios-sdk/configuration).
      </Tab>

      <Tab title="Expo">
        ```tsx theme={null}
        import { FlowPilot } from '@flowpilotjs/react-native-sdk';

        // At module load, or in your root component before any flow is presented.
        FlowPilot.configure({
          apiKey: 'fp_live_xxxxxxxxxxxxxxxx',
          appId: 'your-app-id',
        });
        ```

        The environment defaults to `'production'`. Unlike iOS, `configure` **throws** if the API key does not start with `fp_` or the App ID is empty, so let that surface during development. See [Expo configuration](/expo-sdk/configuration).
      </Tab>
    </Tabs>
  </Step>

  <Step title="Present the placement">
    Ask the SDK to resolve and present your placement. Use the placement key from step 4.

    <Tabs>
      <Tab title="iOS (UIKit)">
        ```swift theme={null}
        import FlowPilotSDK
        import UIKit

        final class HomeViewController: UIViewController {
            func startOnboarding() {
                guard let flowPilot = FlowPilot.shared else { return }
                Task {
                    do {
                        let result = try await flowPilot.presentPlacement("onboarding", from: self)
                        print("Flow finished: \(result.outcome)")
                    } catch {
                        print("FlowPilot could not present a flow: \(error)")
                    }
                }
            }
        }
        ```

        See [iOS: Presenting placements](/ios-sdk/presenting-placements).
      </Tab>

      <Tab title="iOS (SwiftUI)">
        ```swift theme={null}
        import FlowPilotSDK
        import SwiftUI

        struct OnboardingGate: View {
            @State private var session: FlowSession?

            var body: some View {
                Color.clear
                    .flowPresenter(session: $session) { result in
                        print("Flow finished: \(result.outcome)")
                    }
                    .task {
                        session = try? await FlowPilot.shared?.createSession(
                            placementKey: "onboarding"
                        )
                    }
            }
        }
        ```

        See [iOS: SwiftUI](/ios-sdk/swiftui).
      </Tab>

      <Tab title="Expo">
        On Expo you present declaratively: resolve a session, then render it with `<FlowPilotPresenter />`. `resolveSession` returns `null` (instead of throwing) when nothing is presentable.

        ```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(() => {
            FlowPilot.resolveSession('onboarding').then(setSession);
          }, []);

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

        `FlowPilot.presentPlacement()` is deprecated on Expo and renders nothing; always use the presenter. See [Expo: Presenting placements](/expo-sdk/presenting-placements).
      </Tab>
    </Tabs>
  </Step>
</Steps>

Run the app. The flow you published appears, and tapping the button closes it.

## Common mistakes

* **Using a dashboard token instead of an SDK API key.** The SDK authenticates against the `/v1` API with `Authorization: Bearer <sdk-key>`. Your Clerk dashboard session will not work. Create an SDK key in the app settings.
* **App ID mismatch.** The App ID you pass when configuring the SDK must be the app that owns the placement. If they differ, the resolve returns no flow and nothing shows.
* **Placement key typo, paused placement, or no flow attached.** The key in your code must match the placement key exactly. A paused placement, or one with no flow attached, resolves to nothing.
* **Forgetting to publish.** A draft flow is not served. Publish a version and attach that version to the placement.

## Troubleshooting

Nothing appears? Walk this checklist:

1. Is the **API key** valid and an SDK key (it starts with `fp_`)?
2. Is the **App ID** the one that owns the placement?
3. Is the **placement** active (not paused) and targeting your app's platform (iOS, or Android for an Expo app there)?
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 a cache or bundled fallback.

Still stuck? See [iOS troubleshooting](/ios-sdk/troubleshooting) or [Expo troubleshooting](/expo-sdk/troubleshooting).

## Related pages

* [How FlowPilot works](/get-started/how-it-works)
* [Creating placements](/dashboard/creating-placements)
* [iOS SDK quickstart](/ios-sdk/quickstart)
* [Expo SDK quickstart](/expo-sdk/quickstart)
