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.placementName, plus anything the app passed alongside it).
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.placementName === "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 — 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 Checkout
Sell the same paywall on the web with one config key — Stripe payment in a sheet, Apple Pay, or a hosted checkout page, with purchase() unchanged.
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.