Getting started / Quickstart
Quickstart
From a fresh account to autopilot's first set of flows — written for your app, in your theme, waiting for you to look at them. About ten minutes of it is yours; the rest happens without you.
Create an app
Sign up at app.upliftfunnel.com and add your app with its bundle ID and App Store link. The listing is read straight away into a draft app profile; the bundle ID is what locks your public key to your app. No listing yet is fine — the profile fills from the interview instead.Copy your public key
Settings ▸ SDK & API. Public keys start withfnl_pk_and are safe to ship inside a binary — they can read flows and report events, nothing else. The full key is shown once, at creation. More on keys →Add the SDK
Package.swift // Xcode: File ▸ Add Package Dependencies… // or, in Package.swift: // registerPresenter arrived in 0.10.0. Older releases // cannot complete setup — the gate measures a presenter that did not exist. .package( url: "https://github.com/uplift-funnel/uplift-funnel-swift", from: "0.10.1" )Terminal flutter pub add uplift_funnel_flutter # pins ^0.10.1 — anything older has no registerPresenterConfigure it once, at startup
MyApp.swift import SwiftUI import UpliftFunnel @main struct MyApp: App { init() { Task { await UpliftFunnel.configure(apiKey: "fnl_pk_…") } } var body: some Scene { WindowGroup { ContentView() } } }main.dart import 'package:flutter/material.dart'; import 'package:uplift_funnel_flutter/uplift_funnel_flutter.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await UpliftFunnel.configure(apiKey: 'fnl_pk_…'); runApp(const MyApp()); }Register a presenter
This is the step that makes autopilot possible: your app stops naming flows and instead offers to show whatever the server decides on. Until a presenter is registered the SDK never even asks.
AppRoot.swift // Somewhere that owns your root view. The SDK holds this weakly. UpliftFunnel.registerPresenter(self) extension AppRoot: UpliftPresenter { func presentFullscreen(flowKey: String, dismissible: Bool) async -> Bool { guard canInterrupt else { return false } // your call, always coverFlowKey = flowKey // drives a .fullScreenCover return true } func presentSheet(flowKey: String, dismissible: Bool) async -> Bool { guard canInterrupt else { return false } sheetFlowKey = flowKey // drives a .sheet return true } }main.dart UpliftFunnel.registerPresenter((request) async { if (!mounted || !canInterrupt) return false; // your call, always await showModalBottomSheet<void>( context: context, builder: (_) => UpliftFunnelFlow(request.flowKey), ); return true; });Returning
falseis always allowed and costs nothing — the frequency cap is not spent on a screen nobody saw. See Triggers & presentation.Hand over purchases, and publish your products
A paywall screen draws the plans and the button. It does not charge the card — your app does, through handlers you register before a flow is presented.
swift UpliftFunnel.registerPurchaseHandler { request in await myBilling.buy(request.productId) ? .purchased : .cancelled } UpliftFunnel.registerRestoreHandler { await myBilling.restore() } UpliftFunnel.setProducts([ UpliftFunnelProduct(id: "yearly_pro", price: "$59.99", period: .year) ])dart UpliftFunnel.registerPurchaseHandler((request) async { return await myBilling.buy(request.productId) ? PurchaseResult.purchased : PurchaseResult.cancelled; }); UpliftFunnel.registerRestoreHandler(() => myBilling.restore()); UpliftFunnel.setProducts(const [ UpliftFunnelProduct( id: 'yearly_pro', price: r'$59.99', period: ProductPeriod.year, ), ]);setProductsis also one of the three signals the server watches for when it verifies your integration, so do it even if no paywall exists yet.Connect revenue
A RevenueCat or Adapty webhook, or App Store Server Notifications V2. No keys are asked for. Revenue integrations →Answer five questions
In Autopilot ▸ Setup, press Answer for me — the system fills the five in from your listing, your screenshots and early behaviour — then correct what is wrong. Saving an answer promotes it from a guess to a statement. This is the step that unlocks I'm ready. Setup in detail →Press “I’m ready”
At the bottom of the same page. It stays disabled until four facts are on record — what the app does, who it is for, its aha moment, and what the copy may say — and the panel above it names whichever one is missing.
Then stop
Autopilot builds your profile, plans which flows you are missing, writes them, renders them across devices and languages, repairs what breaks, and stops at the approval gate. You do nothing in between, and nothing reaches a user until you press the button.
If you would rather drive
You can still name a flow and present it yourself — the editor and the SDK have not gone anywhere. This is the escape hatch, not the main road.
// The escape hatch: present a flow you named yourself.
UpliftFunnelFlowView(flowKey: "welcome") { result in
save(result.variables) // every answer the user gave
}// The escape hatch: present a flow you named yourself.
UpliftFunnelFlow(
'welcome',
onCompleted: (result) => save(result.variables),
)Next
- Autopilot overview — the whole state machine, and where it stops to ask.
- Safety & guardrails — what stands between a generated screen and your users.
- Native handlers (Swift) or Native handlers (Flutter) — all six handoffs, and what each one does without you.