Getting Started
This guide walks you through creating your first headless funnel project, from installation to deployment.
Prerequisites
- Node.js 18+ — download here if you don’t have it.
- An AppFunnel account with at least one project created. Sign in at appfunnel.net and create a project if you haven’t already.
Setup
Install the CLI
Install the AppFunnel CLI globally:
npm install -g appfunnelLogin
Authenticate with your AppFunnel account:
appfunnel loginThis opens your browser for OAuth authentication.
Create a project
Scaffold a new funnel project:
appfunnel initThe CLI walks you through interactive prompts to:
- Select the project to link to
- Choose a starter template
- Optionally configure products (select a store, pick prices, and set trial settings)
Start the dev server
Navigate into the project and start developing:
cd my-funnel
appfunnel devThis launches a Vite dev server with hot module replacement on port 5173.
Open in browser
Visit http://localhost:5173 to see your funnel running locally. Edits to your source files are reflected instantly.
Project Structure
After running appfunnel init, your project looks like this:
my-funnel/
├── appfunnel.config.ts # Funnel configuration
├── tsconfig.json
├── src/
│ ├── funnel.tsx # Layout wrapper (rendered around every page)
│ ├── app.css # Global styles (Tailwind)
│ └── pages/
│ ├── index.tsx # First page
│ ├── loading.tsx # Loading page
│ └── result.tsx # Result page
└── locales/
└── en.json # Translationsappfunnel.config.ts— Defines which project and funnel this code belongs to, along with variables, products, and settings. See Configuration.src/funnel.tsx— A layout wrapper rendered around every page. This is where you add global UI (headers, progress bars, fonts) or your own React context providers.src/pages/*.tsx— Each file is a funnel page. The file name (without extension) becomes the page slug used in routing. Use lowercase with dashes (e.g.choose-goal.tsx,loading.tsx). Nested folders are not supported — all pages must be directly insrc/pages/.locales/en.json— Translation strings in{ "key": "value" }format. Use{{name}}for interpolation. Access via theuseTranslationhook.
funnel.tsx
The funnel wrapper is a plain React component. The SDK handles FunnelProvider and page rendering automatically — you just control the layout:
import './app.css'
export default function Funnel({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen">
{children}
</div>
)
}Add shared UI here — a progress bar, a back button, background styling, or any context providers your pages need. The children prop is the current page component.
You do not need to set up FunnelProvider, Vite, or React — the CLI handles all of that. Just edit funnel.tsx for layout and src/pages/*.tsx for page content.
locales/en.json
Translation files are flat JSON with {{variable}} interpolation:
{
"welcome": "Welcome to the quiz",
"greeting": "Hello, {{name}}!",
"step_counter": "Step {{current}} of {{total}}"
}const { t } = useTranslation()
t('greeting', { name: 'John' }) // "Hello, John!"Your First Page
Every page exports a page definition and a default React component. Here is a minimal example:
import { definePage } from '@appfunnel-dev/sdk'
import { useNavigation, useResponse } from '@appfunnel-dev/sdk'
export const page = definePage({
name: 'Welcome',
routes: [{ to: 'loading' }],
})
export default function Welcome() {
const { goToNextPage } = useNavigation()
const [name, setName] = useResponse<string>('name')
return (
<div>
<h1>What's your name?</h1>
<input
value={name ?? ''}
onChange={(e) => setName(e.target.value)}
/>
<button onClick={goToNextPage}>Continue</button>
</div>
)
}definePage— Declares the page name and its outgoing routes. Thetovalue matches a file name insrc/pages/.useNavigation— ProvidesgoToNextPage,goToPage, and other routing helpers.useResponse— Reads and writes a variable scoped to this page. The value is automatically persisted to the session.
Routes are validated at build time. If you reference a page slug that
doesn’t exist as a file in src/pages/, the build will fail with a clear
error message.
Deploy
When you’re ready to go live, build and publish in two steps:
Build
appfunnel buildThis validates all routes and variables, then produces an optimized bundle in dist/.
Publish
appfunnel publishUploads the build to AppFunnel hosting. The first publish creates the funnel record and writes the funnelId back to your appfunnel.config.ts so subsequent publishes update the same funnel.
After publishing, link the funnel to a campaign in the AppFunnel dashboard to make it accessible to visitors.
Next Steps
- Configuration — Customize
appfunnel.config.tswith products, locales, and build options. - Hooks — Full reference for
useVariable,useNavigation,useProducts, and more. - Elements — Pre-built interactive components like
SingleSelect,Dialog, andSpinnerWheel. - Payments — Integrate Stripe or Paddle checkout into your funnel.