App download
Every download export of @appfunnel-dev/sdk, generated from the SDK source. Signatures and
descriptions are exact; for how these fit together, see the guides.
useAppDownload
hook · download/DownloadButton.tsx
useAppDownload(stores: AppStores & DownloadTracking): UseAppDownloadResultThe headless download — everything DownloadButton does, without the markup. Reach for it
when the handoff isn’t a link: a redirect after a countdown, a QR code’s payload, a button that
has to do other things first.
const app = useAppDownload({
ios: 'https://apps.apple.com/app/id1234567890',
android: 'https://play.google.com/store/apps/details?id=com.acme.app',
})
return <button onClick={() => app.open()}>Download the app</button>usePlatform
hook · download/DownloadButton.tsx
usePlatform(): AppPlatformusePlatform() — 'ios' | 'android' | 'other' for the current visitor. The store decision on
its own, so copy and artwork can agree with where the button goes
(platform === 'android' ? 'Get it on Google Play' : 'Download on the App Store').
DownloadButton
component · download/DownloadButton.tsx
DownloadButton({ ios, android, fallback, event, target, children, onClick, ...rest }: DownloadButtonProps): ReactNodeA link to the app store, picked from the visitor’s device.
iPhones and iPads get ios, Android phones get android, everyone else gets fallback — so
the finish page is one button rather than a row of badges the visitor has to choose between.
Renders a plain <a href="{store url}">: real link semantics, keyboard-focusable, and styleable
entirely through className/style like any anchor.
Calling preventDefault() in your own onClick cancels the navigation, matching <Next>/<Back>.
<DownloadButton
ios="https://apps.apple.com/app/id1234567890"
android="https://play.google.com/store/apps/details?id=com.acme.app"
fallback="https://acme.com/get-the-app"
className="btn-primary"
>
Download the app
</DownloadButton>platformFor
function · download/store.ts
platformFor(context: FunnelContext): AppPlatformThe platform for a funnel context. Reads context.os.name rather than sniffing the user-agent
again: the SDK has exactly one detector (../state/context.osFromUserAgent) and a second
one drifting alongside it is how a funnel ends up sending iPhones to Google Play.
That detector was wrong until 2026-07-31 — every iOS user-agent contains “like Mac OS X”, so the mac test matched first and reported every iPhone as macOS. Replaying all 144,938 stored sessions put 85.7% of them in the wrong bucket. This function is only as good as that fix, which is why the fix has its own test file pinned against real user-agents.
resolveStore
function · download/store.ts
resolveStore(stores: AppStores, platform: AppPlatform): stringPick the store URL for a platform, specific → generic.
The other chain ends in a store URL rather than nothing on purpose: a desktop visitor landing
on the App Store’s web listing is a mildly wrong destination, whereas a dead button on the page
they just paid on is a refund.
withScheme
function · download/store.ts
withScheme(url: string): stringNormalise a destination: a bare host/path gets https://. Already-schemed URLs,
protocol-relative (//cdn…), absolute paths (/thanks) and bare ?/# are returned as given.
AppStores
interface · download/store.ts
export interface AppStores {
ios?: string
android?: string
fallback?: string
}Where each platform goes. Every field optional — give the stores you actually have.
DownloadButtonProps
interface · download/DownloadButton.tsx
export interface DownloadButtonProps
extends AppStores,
DownloadTracking,
Omit<AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'target'> {
children?: ReactNode
}DownloadTracking
interface · download/DownloadButton.tsx
export interface DownloadTracking {
event?: string | false
target?: DownloadTarget
}What useAppDownload accepts on top of the store URLs.
UseAppDownloadResult
interface · download/DownloadButton.tsx
export interface UseAppDownloadResult {
platform: AppPlatform
url: string
open: (target?: DownloadTarget) => string
}AppPlatform
type · download/store.ts
export type AppPlatform = 'ios' | 'android' | 'other'The store platform, collapsed to what a download button can act on. other is desktop and
anything unrecognised — a real destination in its own right (a QR page, the marketing site),
not an error state.
DownloadTarget
type · download/DownloadButton.tsx
export type DownloadTarget = '_self' | '_blank'Where the store page opens. Same values as the HTML attribute, and passed through as one.