Paywalls

Configure the screen, product bundles, macros, and custom JSON shown to users on a paywall.

Paywalls are used to display products on your in-app purchase screens.

A paywall is a reusable purchase configuration: an ordered product bundle list, an optional visual screen with optional macros (dynamic text placeholders), and optional custom JSON.
It is a configuration object, not a rendered screen — the SDK receives the resolved paywall at runtime, and your app decides how to present it.

One paywall can be assigned to multiple placements and audiences. Paywalls live in Mission Control → Paywalls.

For how a paywall reaches a user — see how targeting works in Mission Control.
For the bundles that paywalls reference, see Product Hub.
For the Figma-built layouts that render paywalls, see Screens.

Paywalls - List in Mission Control

The Paywalls tab of the Mission control page has a paywalls list. The list shows columns: Paywall ID, Product bundles, Placement IDs (where the paywall is currently assigned via targetings), Screen, and Custom JSON (green check if any JSON is set).

What a paywall contains

ElementWhat it is
Paywall IDUnique identifier used in the SDK and reports. Only letters, numbers, dash (-), and underscore (_) are allowed.
Product bundlesOrdered list of bundles offered on the paywall. References bundles in Product Hub.
ScreenOptional. The Figma-built visual layout that renders the paywall. Selected from your library of registered screens.
MacrosOptional. Dynamic text placeholders for the screen, filled per product and per language.
Custom JSONOptional JSON your app reads alongside the paywall.

The paywall editor exposes tabs that map to these elements: Screen & products, Macros, Custom JSON — plus context-dependent tabs that appear based on the products on the paywall: for instance, Commitment plans (iOS App Store products only) and Introductory offer (web-payment products — Stripe / Paddle — only).

Create a paywall

Paywalls - Create a new paywall

To create a paywall:

  1. Open Mission Control → Paywalls.
  2. Click + New paywall.
  3. Enter a Paywall ID — only letters, numbers, dash, and underscore.
  4. In the Screen & products tab, select a Screen and add Product bundles — see Screen & products.
  5. (Optional) In the Macros tab, fill in dynamic text placeholders for each product and language — see Macros.
  6. (Optional, iOS) In the Commitment plans tab, opt selected App Store products into Apple's commitment plan — see Commitment plans.
  7. (Optional) In the Custom JSON tab, add parameters your app reads alongside the paywall — see Custom JSON.
  8. Click Create paywall.

The paywall is added to the Paywalls list and becomes available for use in targetings and placements.

📘

Creating a paywall in context

You can also create a paywall directly:

  • from the targeting configuration flow when overriding a placement;
  • from the new placement creation flow.

The result is identical — the paywall is added to the global Paywalls list and is reusable.
See Targetings → Create new paywall.

Save as new is also the way to create a paywall based on an existing one. For example, start from a three-product paywall, add a fourth product, and click Save as new to get a new paywall without altering the original.
This "create from existing" capability is available wherever you assign a paywall in context — the targeting Configure flow and the placement fallback-paywall step.

The standalone Paywalls tab currently has no duplicate or "Save as" actions.

Screen & products

This tab defines what the user sees and what they can buy.

Paywalls - Paywall with screen and product bundles

Screen

A paywall's screen is the visual layout — built in Figma using Apphud templates and registered in Mission Control → Screens. Select an existing screen from the dropdown.

If you haven't built a screen yet, see Screens for the Figma workflow.

A screen is optional. Without one, the paywall serves as a remote configuration layer — product bundles plus custom JSON — and your app renders its own UI.
A screen is only needed for Apphud-rendered Figma paywalls. A screenless paywall shows "No screen data" in the dashboard preview, which is expected. [TBD - check if preview will be hidden if no screen and correct]

Product bundle

The product bundles offered on this paywall, in display order.

To add bundles:

  1. Click + Add product bundle (the button reads Edit product bundles once the paywall already has bundles).
  2. In the Add products dialog, search or scroll the list. Each row shows the bundle name with store icons (App Store, Google Play, Stripe, Paddle, Xiaomi).
  3. Check the bundles to include.
  4. Click Save.

Selected bundles appear in the order you add them. Bundle order determines how they appear on the paywall screen — for example, the first bundle is rendered as product 0, the second as product 1, and so on. Bundle order also drives macro indices (see Macros).

To reorder bundles, drag a bundle by the drag handle (the ⠿ icon on the left of its row) and drop it into the position you want. Reordering changes both the bundles display order on the paywall and the macro product indices.

Bundles themselves — their product IDs, type, duration, permission group — are managed in Product Hub. Open Product Hub from the link in this section to edit a bundle.

Macros

Macros are placeholders for dynamic text content that get automatically replaced with real values when the paywall screen is shown to a user. Typical macros render the product price, a discount percentage, a trial duration, or an app-specific label.

Paywalls - Screen Macros Editing

How macros are defined and filled

Macros are declared in the Figma screen (the screen template specifies which macros it uses) and filled in the paywall — for each product and each language.

For example, an onboarding paywall screen might declare {{0#full-price}}, {{0#duration}}, and {{0#custom-1}} for the first product. When configuring the paywall, you supply localized values for every supported language.

Macro naming

Macros follow the pattern {{N#name}}, where:

  • N is the product index0 for the first product bundle in the paywall, 1 for the second, etc. The product index matches the order you set in Screen & products.
  • name is the macro key declared in the Figma template.

Standard macro keys:

MacroCommon use
{{N#full-price}}Full product price (e.g., $9.99)
{{N#old-price}}Crossed-out original price for discount paywalls
{{N#new-price}}Discounted price
{{N#discount}}Discount percentage (e.g., 50%)
{{N#duration}}Subscription duration label (e.g., "Weekly")
{{N#custom-1}} through {{N#custom-5}}App-specific custom values declared in the Figma template

Fill macros for a paywall

  1. Open the paywall and go to the Macros tab.
  2. Use the Select product dropdown at the top to choose which product index to fill macros for.
  3. The table lists each macro as a row, with one column per supported language.
  4. Type a value into the cell for each language. You can mix static text and SDK tokens.
  5. To insert an SDK token (for example, product.formatted_price), click the button on the right of the cell.
  6. To copy a value into every language column at once, click the icon next to the cell. For string values, it will automatically translate the text to all the app localizations. Mind that verification of the automatic AI translation is required.
  7. Save the paywall.

A macro value can combine static text and tokens — for example, First 3d free, then product.formatted_price /week.

📘

Macros require a Figma screen to render

Macros only affect the rendered paywall if a Figma screen with matching macro declarations is attached. The Macros tab accepts entries regardless, but values are not used until a screen with the same macro keys is selected.

Custom JSON

Use the Custom JSON tab to add parameters your app reads alongside the paywall — titles, descriptions, localizations, fonts, colors, media URLs, and similar paywall-specific values.

Paywalls - Custom JSON Editing

Apphud does not interpret these values — they reach the SDK exactly as configured, and your app decides how to use them. For example:

{
  "hero_image": "https://cdn.example.com/bf.png",
  "show_close_button_after_sec": 3,
  "theme": "black_friday",
  "cta_style": "large"
}
📘

App-level parameters belong elsewhere

Parameters that apply to all users matching an audience — feature flags, app-wide config — should be set on the targeting's App remote config instead.
Custom JSON on a paywall is for paywall-specific parameters only.

Preview

[TBD] Preview is available only if a paywall has a Screen specified.

The right side of the editor shows a Preview & edit pane with a phone mockup. The Store country and Localization indicators at the top show the rendering context. Click Refresh to reload after changes.

The preview is a simulation. It renders using the selected localization and store country but may not reflect real-device conditions such as introductory-offer eligibility, store product availability, or runtime app state.
Validate on a real device before release.

Important behavior

Non-obvious behavior worth knowing before you ship:

  • Screenless paywalls are valid. Without a screen, a paywall returns product bundles and custom JSON only; your app renders the UI. See Screen.
  • Macros require a matching screen. A macro renders only if the attached Figma screen declares the same macro key. See Macros.
  • Product index follows bundle order. {{0#…}} targets the first bundle, {{1#…}} the second. A macro for an index with no matching product won't render.
  • Editing a paywall affects every targeting that uses it. The paywall is a shared resource. See Edit a paywall.
  • Missing localization — the paywall falls back to the app's default locale, then en, then the first available locale (Paywall Screens use the default_url). It never renders blank.
  • Deleted screen or bundle still referenced by a paywall — the paywall keeps working. A deleted bundle drops those products from the paywall; a deleted screen sets the paywall's screen_id to null, so it renders without a screen (handle the UI via SDK callbacks).

Find a paywall

The Paywalls tab supports search:

  • Search by paywall ID — use the search field at the top of the tab to find a paywall by its ID.

Edit a paywall

To edit a paywall from the Paywalls tab:

  1. Open Mission Control → Paywalls.
  2. Find the paywall (search by ID if the list is long).
  3. Click the menu on the paywall row and choose Edit.
  4. Modify any of Screen & products, Macros, or Custom JSON. Adjust product bundles order if needed.
  5. Click Save changes.
❗️

Important note on paywalls editing

Changes apply to every targeting and placement that uses this paywall — the paywall is a shared resource.

If you edited product bundles (reordered or added a new bundle) for a paywall with Screen configured, ensure to check and adjust if needed macros settings.

Editing a paywall in context

When you pick Use existing paywall inside a targeting's Configure flow or a placement's fallback-paywall step, the paywall preview opens in read-only mode. An info banner appears on the right:

Want to customize this paywall? Edit the selected paywall directly, or use Save as new to create a copy with your changes.

The banner has an Edit paywall button. Click it to unlock the paywall fields for editing. Two save options replace the banner:

  • Apply changes — updates the existing paywall itself. Changes propagate to every targeting and placement using this paywall.
  • Save as new — creates a new paywall with your edits and assigns it to this (targeting, placement) pair only. The original paywall stays unchanged; the new paywall is added to the global Paywalls list and is reusable.

All edited fields carry over to the new paywall when you click Save as new — Screen, Product bundles, Macros, Custom JSON, and any context-dependent tabs (Commitment plans / Introductory offer).

❗️

Apply changes affects every targeting using the paywall

If you only want the change for one targeting, use Save as new instead. Apply changes modifies the paywall globally and cannot be reverted. The Edit paywall read-only-first flow exists to prevent accidental global edits.

See Targetings → Use existing paywall for the full Configure flow.

Delete a paywall

  1. In Mission Control → Paywalls, click the menu on the paywall row.
  2. Click Delete (red).
  3. Confirm.

Deleting a paywall is actually an archive (soft-delete) and is blocked while the paywall is still in use — bound to any targeting or placement (App::Scheme::Item) or referenced in an integration filter. You'll see "Cannot archive paywall. It is currently used in schemes." There's no auto-revert to the Inherited / Default paywall — unbind the paywall first (swap it out everywhere it's used), then archive.

Where paywalls are used

Once configured, paywalls can be:

  • Assigned to placements via targetings in Mission Control
  • Compared in A/B tests — a single test can vary multiple paywalls across placements
  • Triggered by Rules — present a paywall from an event-driven flow
  • Embedded in Flows — multi-step purchase Web journeys

FAQ

Is a paywall the same as what the user sees?

No. A paywall is a configuration object. The rendered experience depends on the attached screen, your app's implementation, SDK integration, and runtime conditions. The SDK receives the resolved paywall; your app decides how to present it.

Can one paywall be used in multiple placements or targetings?

Yes. Paywalls are reusable. Assign the same paywall to multiple placements and audiences via targetings.

Can two paywalls share the same Paywall ID?

No. Paywall IDs must be unique within the app.

Do I need a screen to use a paywall?

No. A paywall without a screen works as a remote-configuration layer — product bundles plus custom JSON — and your app renders its own UI. A screen is only required for Apphud-rendered Figma paywalls.

What's the difference between custom JSON on a paywall and App remote config on a targeting?

  • Custom JSON (paywall) — parameters specific to this paywall: titles, colors, copy, media for this exact paywall config.
  • App remote config (targeting) — parameters that apply to all users matching the targeting regardless of which paywall they see: feature flags, app-wide values, segment-level config.

Use the paywall's Custom JSON for visuals and copy. Use the targeting's App remote config for behavior and feature gating.

Why don't my macros render?

Common causes:

  • No Figma screen is attached, or the screen doesn't declare the macro keys you filled in.
  • The product index doesn't match — {{0#...}} is the first product in your bundle list, {{1#...}} is the second. If you only have one product, {{1#...}} won't render.
  • The paywall hasn't been refreshed in the preview — click Refresh to fetch the latest state.

Can macros use SDK-provided tokens like product.formatted_price?

Yes. Click the button next to a macro cell to insert a token. The token is replaced at runtime with the real value. Tokens can be mixed with static text — for example, First 3d free, then product.formatted_price/week.

How do I localize a paywall?

Fill macros per language in the Macros tab. The languages shown depend on your app's localization configuration. The language list combines two sources: screen-side, the keys of the urls JSON (one URL per locale, plus a default_url fallback); and app-side, apps.locales + the default_locale, managed on App settings → Localization (Apple language codes such as en, de, ja).

Can I duplicate a paywall?

There's no standalone Duplicate action on the Paywalls list. The equivalent is Save as new inside the in-context paywall editor:

  1. Go to a targeting's Configure flow (or a placement's fallback-paywall step) and pick the paywall you want to duplicate via Use existing paywall.
  2. Click Edit paywall in the info banner.
  3. (Optional) Adjust any fields — Screen, Product bundles, Macros, Custom JSON, Commitment plans, Introductory offer. Or skip to step 4 to get an exact copy.
  4. Click Save as new (instead of Apply changes).

A new paywall is created with all current fields carried over. It's assigned to this (targeting, placement) pair only; the original paywall stays untouched and unaffected.

See Editing a paywall in context.

What happens if I delete a paywall that's currently assigned to a placement via a targeting?

You can't — deletion is an archive and is blocked while the paywall is bound to any targeting or placement (or used in an integration filter). You'll see "Cannot archive paywall. It is currently used in schemes." Unbind it everywhere first, then archive. It does not auto-revert to the Inherited / Default paywall.

Why should I configure Commitment offer per paywall?

You may want to enable the commitment plan on one paywalls for the product, while disable this option for another. thus in Apphud the functionality is moved to a paywall level.