Skip to Content
Headless SDKConfiguration

Configuration

Every headless funnel has two configuration layers: a project-level config file (appfunnel.config.ts) that declares variables, products, and settings, and per-page exports that define routing and metadata. Both use type-safe helper functions provided by the SDK.

defineConfig

The defineConfig function is used in appfunnel.config.ts at the root of your project. It provides full type checking and autocompletion for all configuration options.

import { defineConfig } from '@appfunnel-dev/sdk' export default defineConfig({ projectId: 'proj_abc123', name: 'My Funnel', responses: { goal: { type: 'string' }, experience: { type: 'string' }, interests: { type: 'stringArray' }, }, data: { showDiscount: { type: 'boolean', default: false }, }, queryParams: { utm_source: { type: 'string' }, ref: { type: 'string' }, }, products: { items: [ { id: 'monthly', name: 'Monthly', storePriceId: 'price_xxx' }, { id: 'yearly', name: 'Yearly', storePriceId: 'price_yyy', trialDays: 7 }, ], defaultId: 'yearly', }, })

Config properties

type AppFunnelConfig = { // Required projectId: string // Project identifier from the dashboard name: string // Funnel display name shown in the dashboard // Required initialPageKey: string // Page key of the first page (e.g. 'welcome'). Filename without .tsx. // Optional funnelId?: string // Auto-set after first publish — do not set manually defaultLocale?: string // Default i18n locale code (e.g. "en") // Variables responses?: Record<string, VariableConfig> // answers.* variables (user responses) data?: Record<string, VariableConfig> // data.* variables (internal state) queryParams?: Record<string, VariableConfig> // query.* variables (from URL params) // Payments & integrations products?: ProductsConfig integrations?: Record<string, Record<string, unknown>> settings?: FunnelSettings }

FunnelSettings

type FunnelSettings = { disableToasts?: boolean // Disable SDK's automatic error toasts (default: false) }

VariableConfig

Each variable declaration specifies a type and optional defaults.

type VariableConfig = { type: 'string' | 'number' | 'boolean' | 'stringArray' default?: any // Initial value persist?: boolean // Whether to persist across sessions }

All variables must be declared here. The three variable namespaces — responses, data, and queryParams — map to answers.*, data.*, and query.* prefixes in hooks like useVariable. Variables that are not declared in this config will be ignored at runtime — hooks like useResponse, useData, and useQueryParam will not read or write undeclared keys. The only exception is the five default UTM query parameters (utm_source, utm_medium, utm_campaign, utm_content, utm_term), which are included automatically.

ProductsConfig

type ProductsConfig = { items: ProductConfig[] // Array of product definitions defaultId?: string // Product ID selected by default } type ProductConfig = { id: string // Unique identifier within the funnel name: string // Display name storePriceId: string // Price ID from your payment provider (e.g. Stripe price_*) trialDays?: number // Free trial days before the first charge }

definePage

Each file in src/pages/ exports a page constant created with definePage. This sets the page metadata and routing rules.

Page files must be directly in src/pages/ — nested folders are not supported. File names should be lowercase with dashes (e.g. choose-goal.tsx, checkout.tsx). The file name (without extension) is the page key, used in routes, initialPageKey, and URL paths. index.tsx is not supported — use a descriptive name instead (e.g. intro.tsx, welcome.tsx).

import { definePage } from '@appfunnel-dev/sdk' export const page = definePage({ name: 'Choose Your Goal', type: 'default', routes: [ { to: 'next-page' }, ], }) export default function ChooseYourGoal() { return <div>Page content here</div> }

Page properties

PropertyTypeRequiredDescription
namestringYesDisplay name shown in the dashboard and analytics.
typePageTypeNoCategorizes the page for analytics grouping.
slugstringNoURL slug. Defaults to the filename without the extension.
routesRouteConfig[]NoOrdered list of navigation routes. The first matching route is used.

PageType

The type field accepts one of the following values:

'default' | 'checkout' | 'finish' | 'auth' | 'paywall' | 'upsell'

Setting the correct type allows the dashboard to group pages in analytics views. For example, pages with type 'paywall' appear in conversion funnels automatically.

RouteConfig

Routes are evaluated in order. The first route whose when condition is met (or has no condition) determines where useNavigation().next() sends the user.

PropertyTypeRequiredDescription
tostringYesTarget page slug (the filename without the extension, e.g. 'checkout' for checkout.tsx).
whenConditionConfig | ConditionGroupConfigNoCondition that must be satisfied for this route to activate. If omitted, the route always matches.

ConditionConfig

A single condition that checks one variable against one operator.

type ConditionConfig = { variable: string // Variable to check (e.g. 'answers.goal', 'device.isMobile') equals?: VariableValue // Loose equality (==). For arrays, compares length. notEquals?: VariableValue // Loose inequality (!=). For arrays, compares length. contains?: string // String contains substring greaterThan?: number // Numeric > comparison. For arrays, compares length. lessThan?: number // Numeric < comparison. For arrays, compares length. exists?: boolean // Value is defined, not null, and not empty string isEmpty?: boolean // Empty array, blank string, or falsy value includes?: string // Array includes this value }

ConditionGroupConfig

Combine multiple conditions with AND/OR logic. Groups can be nested.

type ConditionGroupConfig = { operator: 'AND' | 'OR' rules: (ConditionConfig | ConditionGroupConfig)[] }

Conditional routing examples

Routes are evaluated top to bottom. Place specific conditions first and a catch-all route last.

export const page = definePage({ name: 'Experience Level', routes: [ { to: 'advanced-tips', when: { variable: 'answers.experience', equals: 'advanced' } }, { to: 'beginner-guide' }, // default fallback ], })

Multiple conditions with AND/OR:

export const page = definePage({ name: 'Pricing', routes: [ { to: 'premium-checkout', when: { operator: 'AND', rules: [ { variable: 'answers.budget', greaterThan: 50 }, { variable: 'answers.interests', greaterThan: 2 }, // array length > 2 ], }, }, { to: 'basic-checkout' }, ], })

Check array membership:

{ to: 'yoga-plan', when: { variable: 'answers.interests', includes: 'yoga' } }

Always include a fallback route (one without a when condition) as the last entry. If no route matches, navigation will not proceed.

Last updated on