Skip to main content
A placement is a named trigger point in your app, for example onboarding or paywall. Your app calls the SDK with a placement key, and FlowPilot decides which flow (if any) to show at that point based on the placement’s default flow, its audience rules, and any active experiment. Placements are the bridge between the dashboard and the SDK. They are the most important dashboard concept for anyone integrating the SDK, because they decouple “where in my app” from “which flow runs there”. You can swap the flow a placement serves at any time, without shipping a new app release.

The mental model

There are two sides to every placement, and they are linked by a single string, the placement key.
  • In your app code, you trigger a placement by its key:
    try await FlowPilot.shared?.presentPlacement("onboarding", from: viewController)
    
  • In the dashboard, a placement with the matching key holds the configuration that decides what "onboarding" resolves to.
The key is the contract. As long as the string in your code matches the placement key in the dashboard (and they belong to the same app), you control the experience entirely from the dashboard.
The dashboard’s SDK Integration card shows a short snippet like FlowPilot.trigger("onboarding"). That is shorthand for the real iOS call, which is presentPlacement(_:from:). See Presenting placements for the actual API.

What a placement holds

FieldWhat it is
Placement ID (key)The string your app passes to the SDK. Lowercase letters, numbers, and underscores. Unique within an app.
PlatformsThe platforms this placement applies to: iOS, Android, Web. The device platform must be in this list to resolve a flow.
Default flowThe published flow version served when nothing else takes priority. Stored as a default_flow_version_id (an exact published version, not “the latest”).
Audience filter (optional)Rules that decide whether a user qualifies. No filter means everyone qualifies. See Audience targeting.
Active experiment (optional)A running A/B test bound to the placement. When present and running, it can serve a variant flow instead of the default.
Statusactive or paused. A paused placement resolves to nothing.
Description (optional)A note for your team. Not shown to users.

How resolution works

When the SDK asks about a placement key, the backend runs this sequence. The first step that fails returns no flow.
1

Find the placement

Look up the placement by app and key. If it does not exist, or its status is paused, resolution stops and no flow is returned.
2

Match the platform

The device’s platform (ios, android, or web) must be in the placement’s platforms list. If it is not, no flow is returned.
3

Check the audience filter

If the placement has an audience filter, the user’s attributes must match it. If they do not, no flow is returned. No filter means everyone passes this step.
4

Apply an active experiment

If a running experiment is bound to the placement, the user is assigned to a variant (sticky, the same user always gets the same variant). That variant’s flow version is served. The experiment can have its own audience filter that gates enrollment.
5

Fall back to the default flow

If no experiment applies, the placement’s default flow version is served.
6

Return the flow

The backend returns the flow schema along with the media base URL, any custom fonts, and a cache TTL. If the resolved flow version is missing, no flow is returned.
When no flow is returned, the SDK shows nothing for that placement, and the backend records a resolve_no_flow event. For the exact request and response contract, see SDK REST API.
A placement returns no flow in any of these cases: it is paused, the device platform is not in its platforms list, the user does not match the audience filter, or it has no default flow and no matching experiment. Plan a native fallback in your app for the “no flow” case. The non-throwing presentPlacement(_:from:fallback:) API exists for exactly this. See Presenting placements.

The Placements list

Open Placements from the sidebar. The page is titled Placements with the subtitle “Manage how flows and experiments are triggered in your app”. The list is scoped to the app you currently have selected.
  • New Placement: the button in the page header opens the create wizard. See Creating a placement.
  • Search: the Search placements by ID or description box filters by a case-insensitive match on the placement ID or description.
  • Status tabs: All, Active, Paused.
  • Platform filter: All Platforms, iOS, Android, Web. This matches placements that include the chosen platform.
The table has four columns: Placement ID (a mono link to the detail page, with the description below it), Status, Platforms, and Actions. The Status column shows one of three badges:
  • Testing (with a flask icon) when an experiment is bound to the placement.
  • Active when the placement is active and has no experiment.
  • Paused when the placement is paused.
Each row’s More actions (...) menu has View Details, Edit (opens the placement in edit mode), and Delete. Clicking the placement ID also opens the detail page.
TODO: screenshot of the Placements list (header, filter bar, status badges, and the actions menu).

Common mistakes

  • Key mismatch. The placement key in your code must exactly match the placement ID in the dashboard. onboarding and Onboarding are different. The dashboard lowercases IDs for you, so match that in code.
  • Wrong app. Placements are app-scoped. If your app’s SDK is configured with a different appId than the app that holds the placement, it will not resolve. Confirm the app switcher shows the right app.
  • Platform not included. A placement with only Android selected returns no flow to an iOS device. Make sure iOS is in the platforms list.
  • Paused placement. A paused placement always resolves to nothing. Re-activate it from the detail page when you are ready.