Variables & Personalization
React to user attributes, device state, and placement parameters, and write paywalls the dashboard can experiment on without a rebuild.
Everything your app and the SDK tell a paywall about the presentation arrives through useVariables(): who the user is, what device they're on, and what the placement was called with. Read these defensively and a single paywall can greet a returning user by name, adapt to platform, or react to any parameter your app passes, all without a rebuild.
useVariables()
import { useVariables } from "superwall/hooks";
const { device, user, params } = useVariables();Three records, three sources:
device, filled in by the SDK:platform,deviceModel,osVersion,appVersion,deviceLocale,regionCode,deviceCurrencyCode,subscriptionStatus,activeEntitlements,daysSinceInstall,totalPaywallViews, and more.user, whatever your app set viasetUserAttributes(user.firstName,user.plan, …).params, whatever the placement was called with (params.event_nameis the placement's name;$-prefixed keys are SDK-set, and anything the app passed alongside comes through unprefixed).
const name = typeof user.firstName === "string" ? user.firstName : undefined;
<h1>{name ? `Welcome back, ${name}` : "Go Pro"}</h1>
<span>{device.platform ?? "—"}</span>Guard every read
All three records are filled in by the host, your paywall controls none of them, so every read needs a fallback:
- For
devicefields,?? "—"(or any sensible default) suffices. The SDK guarantees the shape, just not that a value has arrived yet. - For
userandparams, the host controls the type too, so check it before using it:typeof params.event_name === "string". An attribute your app sets as a number today might be a string tomorrow, and the paywall must not crash either way.
device.isSandbox is a string, not a boolean. Compare it as one.
While previewing, every one of these values is editable live in the studio's Variables panel: user attributes, device properties, placement params, and per-product variables, all seeded from your app's real sample data. Change a value and watch the paywall react. See The studio.
useUser()
Shorthand for when you only need the user record:
import { useUser } from "superwall/hooks";
const user = useUser();Identical to useVariables().user, reach for it when the device and params records aren't needed.
useDevice()
The same device record as useVariables().device, plus orientation:
import { useDevice } from "superwall/hooks";
const { orientation, platform, deviceModel } = useDevice();orientation is "portrait" | "landscape", measured in the page itself, it updates the moment the device turns, so you can build layouts that answer to rotation. The orientation example reflows to a two-column grid in landscape rather than shrinking the portrait layout; see Examples.
Built to be experimented on
Notice what's missing: variables are never declared in code. What the paywall reads, user attributes, device state, placement params, product variables, trial eligibility, is supplied by the app and the store at runtime, and the studio overrides all of it live while previewing.
Write every read defensively, guarded, typed, with a designed fallback, and every one of those values becomes a knob the dashboard can turn without a rebuild. A paywall that renders sensibly for any combination of inputs can be A/B tested freely.
The personalization example shows the full doctrine in one project: ?? "—" for SDK-guaranteed device fields, typeof checks for host-controlled user and params reads, and designed fallbacks for every string. See Examples.
How is this guide?
Web Funnels
Quizzes and checkout funnels on the web: one paywall whose steps are pages, every answer kept in the URL so the flow survives any browser hand-off, and payment at the end.
Actions
Close the paywall, restore purchases, open links, request OS permissions, and call back into your app, everything a paywall asks its host to do.