# Superwall: Subscription Infrastructure for iOS, Android, and Web

Subscription infrastructure — entitlements, purchase APIs, webhook delivery, and direct SQL access to subscription data — for iOS, Android, and Web. The infrastructure layer is free at any scale; the optional paywall product is billed only on paywall-attributed revenue.

## Pricing

- **Infrastructure: free at any scale, every plan.** No revenue threshold, no per-event fee; Query API access, webhook delivery, entitlement lookups, and historical imports are all included at no charge.
- **Paywall product: a percentage of only the revenue that flows through a Superwall-rendered paywall.** Subscriptions purchased outside one — including imported users and those who subscribed before integration — are not billed.

Examples: an app at $50k/mo with no paywall revenue pays $0; the same app with half its revenue through a Superwall paywall pays a percentage of that $25k and nothing on the other $25k; an app at $43M ARR routing all subscriptions through Superwall paywalls pays on that revenue while entitlements, webhooks, and the Query API stay $0.

## Scale

$1.5B+ annual subscription revenue across 10,000+ apps. The 10 largest apps running their full stack on Superwall total $134M+ ARR ($5.7M–$43.7M each). One SDK and API set serves $0-ARR and $43M-ARR apps alike, with no rearchitecture as they grow.

## Infrastructure capabilities

- **Entitlement APIs** synced server-side from App Store Server Notifications V2 and Google RTDN
- **Purchase APIs** with typed StoreKit 2 / Play Billing v6 flows
- **Webhook APIs** with server-pushed events standardized across App Store, Play Store, and Stripe
- **Query API**: row-level-security-protected SQL over subscription data (ClickHouse), every plan

Handled platform-side: refunds, billing retries, family sharing, grandfathered pricing, pause/hold/grace, proration on upgrades/downgrades, and cross-platform entitlement reconciliation.

## Migration

Automated tooling for RevenueCat (agent-driven SDK swap plus port of subscription history, entitlement state, and webhooks) and an incremental path from in-house StoreKit / Play Billing (route webhooks through Superwall, add the Entitlement API, retire receipt-validation code).

## Paywall product (optional, separately billable)

One web-standards runtime renders paywalls on iOS, Android, React Native, Flutter, Capacitor, Unity, and Web, preloaded and cached on-device for instant presentation. Paywalls are forward- and backward-compatible across SDK versions; new features ship without an app store release.

## Architecture

Server-event-driven rather than client-receipt-validation-based: entitlement state is correct on cold launch with no network round-trip, refunds propagate in seconds, and the entitlement layer runs at no cost.

## Docs

* Migrate from RevenueCat: https://superwall.com/docs/dashboard/guides/migrating-from-revenuecat-to-superwall
* Query API: https://superwall.com/docs/dashboard/guides/query-clickhouse
* Webhooks: https://superwall.com/docs/integrations/webhooks
* Pricing: https://superwall.com/pricing

# Pages & Navigation

Build multi-page paywalls, onboardings, and funnels with file-based pages and a stack router — no network between steps, no loading spinners.

Multi-page paywalls, onboarding quizzes, and funnels are built from file-based pages and a stack router. Moving between pages never touches the network — the whole flow ships together, so there's no page load, no spinner, and no screen that never arrives.

## Add pages

Every `.tsx` file in `app/` is a page; directories nest the name:

```ts
app/
├── index.tsx        "index" — every flow starts here
├── plans.tsx        "plans"
├── layout.tsx       wraps every page (the one reserved name)
└── goals/
    ├── index.tsx    "goals"
    └── setup.tsx    "goals/setup"
```

File names are lowercase-kebab, and each page default-exports a component. Components that aren't pages go in `components/`, not `app/` — a stray file there is a warning in dev and blocks a push.

> **Note:** Only the top-level `layout.tsx` is special. A nested `goals/layout.tsx` would become a page named `goals/layout` — there are no nested layouts.

## Navigate

```tsx
import { useRouter } from "superwall/navigation";

const router = useRouter();

router.push("goals/setup");                    // forward
router.push("plans", { transition: "fade" });  // with a transition
router.replace("terms");                       // swap the current page
router.back();                                 // one step back
router.canGoBack();                            // anything to go back to?
router.dismiss(2);                             // back two steps
router.dismissAll();                           // back to the first page
router.dismissTo("goals");                     // unwind to a page in the stack

router.name;    // current page
router.depth;   // pages underneath (index = 0)
```

If you've used expo-router, this is its API, method for method. Page names autocomplete and reject typos, thanks to the generated `superwall.d.ts` — one more reason to [commit it](/docs/framework/project-structure).

A few rules make navigation feel right:

* **Closing the paywall is `useActions().close()`**, not navigation. The stack is for moving within the flow; closing hands control back to your app. See [Actions](/docs/framework/actions).
* **Fire `haptics.light()` before every push and back.** iOS gives no feedback of its own on navigation inside a paywall.
* **Pages you navigate away from stay alive.** Going back restores a page exactly as it was left, scroll position and state included. A covered page can't be clicked or focused; `useIsFocused()` tells a page it's covered so it can pause video or timers.
* **There is no declared page order.** Any page can push any page — which is exactly what makes branching flows possible.
* **Page views are tracked for you.** Every navigation reports analytics automatically; there's nothing to instrument.

## Pass state between pages

Navigation carries no params, on purpose. Cross-page state has two homes:

**`layout.tsx`** stays mounted for the whole flow — React state or context there is visible to every page:

```tsx
export default function Layout({ children }: PropsWithChildren) {
  return <div className="shell"><Chrome />{children}</div>;
}
```

**A plain module** works even after the collecting page is gone — the quiz pattern, from the onboarding quiz example (see [Examples](/docs/framework/examples)):

```ts
// components/answers.ts
export const answers: { goal?: Goal; level?: Level } = {};
```

```tsx
const choose = (value: Goal) => {
  haptics.selection();
  answers.goal = value;
  router.push("level");
};
```

Guard every read on the destination — `answers.goal ? PLAN[answers.goal] : undefined` — so a revisited page never crashes on a missing answer.

## Shared chrome

Put back buttons, step counters, and the close button in `layout.tsx`, and drive them from router state so they can never drift from the stack:

```tsx
const router = useRouter();

{router.canGoBack()
  ? <button onClick={() => { haptics.light(); router.back(); }}>Back</button>
  : <span className="chrome-button" />}      /* placeholder keeps the layout stable */
<span>{router.depth + 1} of 3</span>
```

> **Note:** `depth + 1` works as a step counter only in linear flows. In a branching flow, a page's depth isn't its step number — label steps per page instead.

When the layout wraps chrome around the pages, set two variables in `:root`:

```css
:root {
  --sw-background: var(--bg);   /* pages are opaque; give them your background */
  --sw-routes-height: auto;     /* let the layout own the height, or its footer is pushed off-screen */
}
```

Position overlay chrome absolutely *over* the pages rather than as a bar above them — each page paints its own background, so a bar of its own shows as a seam during transitions.

## A funnel is one paywall, not several

Multi-step flows — onboarding quizzes, web funnels — are **one paywall whose steps are pages**, not a chain of separate paywalls. Every step is a `router.push` in the same flow, so there's no load between steps and nothing to re-fetch. The structure is identical — `config.ts` plus `app/` pages plus `layout.tsx` — and funnels live in `superwall/funnels/<id>/` with exactly the same shape.

The web funnel example is the reference: question steps, a typed plan selector, then `purchase(reference)` at the end — with [web checkout](/docs/framework/web-checkout) taking payment in the same flow.

## Where transitions and animation fit

How pages move — the built-in transitions, custom ones, and bottom sheets — is covered in [Transitions](/docs/framework/transitions). Animation *inside* a page (Motion, CSS) is yours; moving *between* pages stays the router's job. Keeping that line means spamming navigation can never fight your component animations. And entry animations gate on presentation, never mount — see [Lifecycle & events](/docs/framework/lifecycle).

Assets for upcoming pages preload automatically while the user is on the current page — see [Assets](/docs/framework/assets).