Skip to Content
BuildApp Download

App Download

Your funnel’s last screen sends the visitor to your app. <DownloadButton> picks the right store for the device they’re holding, so the finish page is one button instead of a row of badges they have to choose between.

Full signatures are in the components reference and hooks reference.

One button, both stores

pages/download.tsx
import { definePage, DownloadButton } from '@appfunnel-dev/sdk' export default definePage(function Download() { return ( <section> <h1 className="text-3xl font-bold">You're all set</h1> <p className="mt-2">Get the app and pick up where you left off.</p> <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 mt-6" > Download the app </DownloadButton> </section> ) })

iPhones and iPads get ios, Android phones get android, and desktop — plus anything unrecognised — gets fallback. A good fallback is a page with a QR code or “we’ll text you the link”, since a desktop visitor can’t install anything from the browser they’re in.

Every field is optional. If one is missing, the button uses the next best URL rather than rendering a dead link, so a visitor on the page they just paid on always has somewhere to go. A URL without https:// gets one.

fallback on its own is also how you use a single link that does its own routing — a Branch, Adjust or AppsFlyer OneLink URL — since it’s used for every platform when the others are absent.

Matching the label to the store

usePlatform() gives you 'ios' | 'android' | 'other', so your copy and artwork can agree with where the button actually goes.

import { usePlatform, DownloadButton } from '@appfunnel-dev/sdk' function GetTheApp() { const platform = usePlatform() return ( <DownloadButton ios={appStoreUrl} android={playUrl} fallback={qrPageUrl}> {platform === 'android' ? 'Get it on Google Play' : 'Download on the App Store'} </DownloadButton> ) }

Two badges instead of one button

If you’d rather show both store badges side by side, use the component twice with your own badge markup as its children.

<div className="flex gap-3"> <DownloadButton ios={appStoreUrl} fallback={appStoreUrl} target="_blank"> <AppStoreBadge /> </DownloadButton> <DownloadButton android={playUrl} fallback={playUrl} target="_blank"> <GooglePlayBadge /> </DownloadButton> </div>

Connecting the purchase to the app

The button’s job ends at the store. Connecting the person who just paid to the account they open in your app is a separate thing, and it does not travel on the store link.

Store links don’t carry data. The App Store discards query parameters entirely, and Google Play carries only its own install-referrer value. Anything you append to a store URL will not arrive in your app.

What does work:

  • Have them sign in with the email they paid with. Your funnel already collected it at checkout, so the account exists before the app is installed. This is the most reliable option and needs nothing on the funnel side.
  • Use a deferred deep-link provider (AppsFlyer, Branch, Adjust). Put their link in fallback and their SDK in your app; it matches the install back to the click and hands your app the context. Matching is best-effort rather than guaranteed, especially on iOS.
  • Use a universal link in ios/android if the visitor is likely to already have the app — those carry everything, but only when the app is installed.

If you use RevenueCat, entitlements from a web purchase are granted to the customer profile before the app is ever opened, so the app only has to identify the right customer — it doesn’t need anything passed through the store link.

Behavior worth knowing

  • In test mode the button doesn’t navigate. It logs the store URL to the console instead, so previewing a funnel never walks you out to the App Store. Open the console to check where a tap would go.
  • <a> all the way. A real link — focusable, styleable through className/style like any anchor, and target="_blank" opens a new tab. If your own onClick calls preventDefault(), the navigation is skipped.
  • Every tap is tracked as download.click with the store URL and the platform. Pass event="got_the_app" to rename it, or event={false} for none.

Do it with an agent: “On the download page, send iPhones to the App Store and Android to Play, with a QR page for desktop.” The assistant wires <DownloadButton> with the right destinations for you.

Last updated on