State
Every state export of @appfunnel-dev/sdk, generated from the SDK source. Signatures and
descriptions are exact; for how these fit together, see the guides.
useClickIds
hook · state/namespaces.ts
useClickIds(): FunnelContext["clickIds"]useClickIds() — per-network ad click-ids captured on entry (read-only).
useContextValue
hook · state/namespaces.ts
useContextValue(path: string): unknownRead a dotted path out of context, e.g. useContextValue('device.isMobile').
useData
hook · state/namespaces.ts
useData(key: string): [VariableValue, (value: VariableValue) => void]const [score, setScore] = useData('score') — working/scratch state. Persisted
(survives reload/return) but kept out of responses aggregation.
useDevice
hook · state/namespaces.ts
useDevice(): FunnelContext["device"]useDevice() — viewport, device type, pixel ratio (read-only).
useField
hook · state/namespaces.ts
useField(path: string): { value: VariableValue; set: (value: VariableValue) => void; }The headless field hook — useField('responses.goal') — the building block
the styleable components (<Choice>) wrap. Takes a namespaced path and
returns { value, set }. Accepts only writable namespaces.
useFunnel
hook · state/namespaces.ts
useFunnel(): FunnelThe imperative handle. Does not subscribe — use it in event handlers and
effects (useFunnel().responses.set({...})). For reactive reads use the hooks.
useLocale
hook · state/namespaces.ts
useLocale(): FunnelContext["locale"]useLocale() — resolved locale/language/region/timezone (read-only).
useMarketingConsent
hook · state/namespaces.ts
useMarketingConsent(): [boolean, (granted: boolean) => void]const [consent, setConsent] = useMarketingConsent() — the visitor’s marketing
opt-in as a plain boolean. Writing true records the answer AND fires the
platform marketing.consent_given event, which sets the customer’s consent
flag and forwards marketing_consent: true to marketing integrations
(Customer.io). Writing false records the answer only — consent is one-way;
there is no un-consent event today. Prefer this over useResponse('marketingConsent'),
which records the variable but never drives the consent pipeline.
useMeasurementSystem
hook · state/measurementSystem.ts
useMeasurementSystem(): MeasurementSystemSeed a funnel’s unit toggle from the visitor’s likely-expected measurement system.
SSR-safe: 'metric' on first paint (server + hydration), resolved post-mount. See
the module JSDoc for the rule, the UK decision, and the detection priority.
useResponse
hook · state/namespaces.ts
useResponse(key: string): [VariableValue, (value: VariableValue) => void]const [goal, setGoal] = useResponse('goal') — one funnel answer,
useState-shaped and per-key reactive. Writing persists + (later) feeds
analytics aggregation.
useSystem
hook · state/namespaces.ts
useSystem(): FunnelContext["system"]useSystem() — funnel id, test/live mode, build clock (read-only).
useUserAttribute
hook · state/namespaces.ts
useUserAttribute(key: string): [VariableValue, (value: VariableValue) => void]const [height, setHeight] = useUserAttribute('height') — any field on the
user. The platform routes known fields → typed column, custom → attribute;
the author doesn’t specify which. email is a plain attribute like any other —
writing it does NOT identify; complete identity explicitly on submit with
useFunnel().completeUserRegistration().
useUtm
hook · state/namespaces.ts
useUtm(): FunnelContext["utm"]useUtm() — the 5 utm params captured on entry (read-only).
FunnelProvider
component · state/namespaces.ts
FunnelProvider({ config, sessionValues, deferSessionValues, tracker, context: contextOpts, offerings, experiments, checkout, messages, locale: localeOverride, toaster, store, children, }: FunnelProviderProps): ReactNodeMounts a funnel: builds the writable VariableStore + the read-only
FunnelContext (once) and provides the runtime to the tree via hooks.
buildAcquisition
function · state/context.ts
buildAcquisition(context: FunnelContext): AcquisitionAssemble the full Acquisition snapshot from the context + document
(cookies, referrer, path) — captured once on entry, forwarded to the tracker for
first-class persistence (session + first-touch Customer) + CAPI.
buildContext
function · state/context.ts
buildContext(opts?: BuildContextOptions): FunnelContextBuild the read-only FunnelContext once, at mount. page starts at the
first page and is replaced per navigation when assembling a snapshot.
createFunnelStore
function · state/store.ts
createFunnelStore(config?: FunnelStoreConfig, sessionValues?: Record<string, VariableValue>): VariableStoreSeed a store from a funnel’s declared responses/data defaults, then layer
server / session-restored values on top (highest priority).
measurementSystemForCountry
function · state/measurementSystem.ts
measurementSystemForCountry(country: string | null | undefined): MeasurementSystem | nullPure rule: map a country/region code to a measurement system. Returns null when
the input is absent or not a 2-letter code (so the caller can fall through to the
next source); a known metric country returns 'metric', not null.
resolveMeasurementSystem
function · state/measurementSystem.ts
resolveMeasurementSystem(contextRegion?: string | null): MeasurementSystemPure resolver over the priority chain: context region → navigator.language
region → 'metric'. Reads navigator (client-only) — call it in an effect, not
during render (see the hook). Exported for direct/unit use.
Namespace
class · state/namespaces.ts
Namespace: typeof NamespaceA typed view over one writable namespace’s slice of the store.
VariableStore
class · state/variableStore.ts
VariableStore: typeof VariableStoreAcquisition
interface · state/context.ts
export interface Acquisition {
utm: UtmContext
clickIds: ClickIdContext
cookies: { fbp?: string; fbc?: string }
referrer: string
landingPath: string
}The full acquisition snapshot captured once on entry — the funnel reads utm /
clickIds off FunnelContext; the SDK forwards this whole thing to the
tracker (setAcquisition) so the platform persists it first-class: per-visit
on the session + first-touch (write-once) on the Customer, and the click-ids /
_fbp/_fbc cookies feed CAPI. Not stored as a generic attribute/answer.
BrowserContext
interface · state/context.ts
export interface BrowserContext {
name: string
version: string
userAgent: string
language: string
cookieEnabled: boolean
online: boolean
}BuildContextOptions
interface · state/context.ts
export interface BuildContextOptions {
funnelId?: string
campaignId?: string
mode?: 'test' | 'live'
now?: number
sessionId?: string | null
customerId?: string | null
visitorId?: string | null
overrides?: Partial<FunnelContext>
}ClickIdContext
interface · state/context.ts
export interface ClickIdContext {
fbclid?: string
gclid?: string
gbraid?: string
wbraid?: string
msclkid?: string
ttclid?: string
twclid?: string
sccid?: string
li_fat_id?: string
epik?: string
rdt_cid?: string
[key: string]: string | undefined
}Per-network ad click-ids captured from the entry URL — the matching signals
the server CAPI needs. Named per network (not a generic bag) so the platform +
each integration map them precisely; [key] catches any others.
DeviceContext
interface · state/context.ts
export interface DeviceContext {
type: 'mobile' | 'tablet' | 'desktop'
isMobile: boolean
isTablet: boolean
screenWidth: number
screenHeight: number
viewportWidth: number
viewportHeight: number
colorDepth: number
pixelRatio: number
}v2 context — the read-only, computed namespace.
Everything the funnel can read but never write: the device, the visitor’s
locale, the current page, acquisition params (UTM), the session, and system
facts (funnel id, test/live mode, clock). It supersedes the v0 flat
page.*/device.*/browser.*/os.*/query.*/metadata.* system variables
(see ./systemVariables) by folding them into one nested object.
Why it’s not in the ./variableStore: the store holds writable
state (user/responses/data) that persists + re-renders per key. Context
is derived and read-only, so it lives as a plain object on a React context —
no subscriptions, no churn. The one moving part (page) is merged into the
snapshot from navigation state at read time.
Authors read it through hooks (useDevice(), useLocale(), usePage(),
useUtm()) or inside routing/condition predicates as s.context.*.
Funnel
interface · state/namespaces.ts
export interface Funnel {
user: Namespace
responses: Namespace
data: Namespace
readonly context: FunnelContext
readonly experiments: RuntimeExperiment[]
track: Tracker['track']
completeUserRegistration(email?: string): boolean
snapshot(): FunnelSnapshot
}The imperative funnel handle (useFunnel()). Non-reactive — for event
handlers and effects. Reactive render reads go through the hooks instead.
FunnelContext
interface · state/context.ts
export interface FunnelContext {
device: DeviceContext
browser: BrowserContext
os: OsContext
locale: LocaleContext
page: PageContext
utm: UtmContext
clickIds: ClickIdContext
session: SessionContext
identity: IdentityContext
system: SystemContext
}The whole read-only computed namespace, as read in predicates: s.context.*.
FunnelProviderProps
interface · state/namespaces.ts
export interface FunnelProviderProps {
config?: FunnelDefinition
sessionValues?: Record<string, VariableValue>
deferSessionValues?: boolean
tracker?: Tracker
context?: Parameters<typeof buildContext>[0]
offerings?: OfferingInput[]
experiments?: RuntimeExperiment[]
checkout?: CheckoutDriver
messages?: MessageCatalog
locale?: string
toaster?: boolean | ToasterProps
store?: VariableStore
children: ReactNode
}IdentityContext
interface · state/context.ts
export interface IdentityContext {
customerId: string | null
visitorId: string | null
}Durable identity for stable experiment bucketing (phase-4 §4.2). The
preferred seed is the signed anonymous visitorId (stable from the first
pageload, matching the server-side FUNNEL split in resolve.ts); customerId is
only the fallback for cookieless SDK embeds. visitorId-FIRST is deliberate — the
anon customerId is minted server-side AFTER the first tracked event, so seeding
on it would flip a returning visitor’s arm between visit 1 and 2 (see
../flow/experiments.bucketingSeed). It is deliberately not the
session id or fingerprint — a returning visitor (e.g. via winback) must keep
their variant across sessions (landmine #4). Both null = unidentifiable traffic,
and the experiment layer falls back to control rather than bucket on nothing.
LocaleContext
interface · state/context.ts
export interface LocaleContext {
locale: string
language: string
region: string
timeZone: string
}OsContext
interface · state/context.ts
export interface OsContext {
name: string
timezone: string
}PageContext
interface · state/context.ts
export interface PageContext {
key: string
slug: string
group?: string
index: number
total: number
progressPercentage: number
startedAt: number
}SessionContext
interface · state/context.ts
export interface SessionContext {
id: string | null
startedAt: number
referrer: string
landingPath: string
}SystemContext
interface · state/context.ts
export interface SystemContext {
funnelId: string
campaignId: string
mode: 'test' | 'live'
now: number
}UtmContext
interface · state/context.ts
export interface UtmContext {
source?: string
medium?: string
campaign?: string
content?: string
term?: string
[key: string]: string | undefined
}Acquisition / ad params captured on entry (the old query.* utm slice).
MeasurementSystem
type · state/measurementSystem.ts
export type MeasurementSystem = 'imperial' | 'metric'