How to define paywalls as JSON schemas

Your paywall is a document, not a screen. Treat it like one and everything else — version control, validation, programmatic generation — follows naturally.

Most paywall platforms store your paywall configuration in their database, behind a visual editor. You drag elements around, pick colors, type copy into form fields, and hit publish. The paywall exists as opaque state in someone else's system.

There's a better model: define your paywall as a JSON document. A structured schema that describes what the paywall contains — its layout, copy, products, features, and behavior — without prescribing how it renders. The native SDK reads the schema and produces platform-appropriate UI. The schema is the source of truth.

Why JSON, not a visual editor

A visual editor optimizes for one workflow: a single person making one-off changes to one paywall. That's fine for small teams running one or two paywalls. But the moment you need to do anything programmatic — generate localized variants, diff changes between versions, run automated validation, or let an AI agent create paywalls — the visual editor becomes a bottleneck.

JSON schemas give you:

None of this is possible when your paywall lives as drag-and-drop state in a vendor's database.

Anatomy of a paywall schema

A paywall schema has a consistent structure. Here's a complete example:

{
  "id": "premium_onboarding_v3",
  "name": "Premium onboarding",
  "version": 3,
  "type": "full_screen",
  "metadata": {
    "created_at": "2026-03-10T09:00:00Z",
    "updated_at": "2026-03-15T14:30:00Z",
    "tags": ["onboarding", "premium"]
  },
  "header": {
    "title": "Start creating without limits",
    "subtitle": "Join 2 million creators who've gone premium",
    "image": "premium_hero.png"
  },
  "features": [
    { "icon": "infinity", "text": "Unlimited exports" },
    { "icon": "wand", "text": "AI-powered editing" },
    { "icon": "cloud", "text": "50 GB cloud storage" },
    { "icon": "users", "text": "Real-time collaboration" }
  ],
  "products": [
    {
      "id": "annual_premium",
      "label": "Annual",
      "badge": "Save 40%",
      "highlighted": true
    },
    {
      "id": "monthly_premium",
      "label": "Monthly"
    }
  ],
  "cta": {
    "text": "Start free trial",
    "subtext": "7 days free, then $49.99/year"
  },
  "legal": {
    "terms_url": "/terms",
    "privacy_url": "/privacy",
    "restore_text": "Restore purchases"
  }
}

Let's break down each section.

Metadata

The top-level fields — id, name, version, type — identify the paywall and track its evolution. The type field tells the SDK which layout to use (full screen, modal, bottom sheet, inline). The metadata object holds timestamps and tags for organizational purposes.

Versioning matters. When you update a paywall, you increment the version number. The SDK can use this to cache intelligently and the API can use it for conflict detection.

Header

The header object defines the top section of the paywall — the first thing users see. The title should be benefit-focused, not feature-focused. "Start creating without limits" converts better than "Unlock Premium Features" because it describes what the user gains, not what the product has.

The subtitle is where social proof lives. The optional image field references an asset that the SDK resolves and renders at the appropriate resolution for the device.

Features

The features array lists what the user gets. Each feature has an icon identifier (mapped to platform-native icons by the SDK) and a text description. Keep these short — they should be scannable, not readable.

Products

The products array references in-app purchase product IDs. The schema doesn't contain prices — those come from the App Store or Play Store at runtime, ensuring they're always accurate and properly localized. The badge and highlighted fields control visual emphasis.

CTA

The cta object defines the primary action button. The text is the button label, and subtext provides pricing context or trial information. Clear, specific CTAs outperform vague ones — "Start free trial" converts better than "Continue."

Legal

The legal object handles compliance requirements — links to terms and privacy policy, and a restore purchases option (required by Apple for apps with auto-renewable subscriptions).

Schema validation

One of the biggest advantages of JSON schemas is validation. You can catch errors before they reach production. Here's what validation looks like in practice:

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "type": "object",
  "required": ["id", "name", "type", "header", "products", "cta"],
  "properties": {
    "id": {
      "type": "string",
      "pattern": "^[a-z0-9_]+$"
    },
    "type": {
      "enum": ["full_screen", "modal", "bottom_sheet", "inline"]
    },
    "products": {
      "type": "array",
      "minItems": 1,
      "maxItems": 4,
      "items": {
        "type": "object",
        "required": ["id", "label"],
        "properties": {
          "id": { "type": "string" },
          "label": { "type": "string" },
          "badge": { "type": "string" },
          "highlighted": { "type": "boolean" }
        }
      }
    }
  }
}

This schema enforces constraints: the id must be lowercase alphanumeric with underscores, the type must be one of four valid layouts, products must have between 1 and 4 items, and every product must have an id and label.

Run validation in CI and you'll never deploy a paywall with a missing CTA, an invalid product reference, or a typo in the layout type. Errors caught at build time don't reach users.

Version control and diffs

When your paywall is a JSON file, every change produces a readable diff:

 {
   "id": "premium_onboarding_v3",
-  "version": 3,
+  "version": 4,
   "header": {
-    "title": "Unlock Premium Features",
+    "title": "Start creating without limits",
     "subtitle": "Join 2 million creators who've gone premium"
   },
   "products": [
     {
       "id": "annual_premium",
       "label": "Annual",
-      "badge": "Best Value",
+      "badge": "Save 40%",
       "highlighted": true
     }
   ]
 }

This diff tells you exactly what changed: the version was bumped, the headline was rewritten from feature-focused to benefit-focused, and the badge copy was updated. A reviewer can understand the change in seconds. Compare that to a visual editor where the only record of a change is "Updated paywall on March 15."

You can also use git history to answer questions that visual editors can't: when did we change the headline? What was the paywall configuration during last month's A/B test? Who approved the change that broke conversion on March 12th?

Programmatic generation

Because paywall schemas are just data, you can generate them programmatically. This unlocks workflows that are impractical with visual editors:

Localization at scale. Take your base paywall schema, translate the copy fields into 30 languages, and generate 30 localized variants. An AI agent can do this in minutes, and each variant is a valid schema that can be validated and reviewed.

A/B test variant creation. Write a script that takes your control paywall and produces variants with different headlines, feature orderings, or product configurations. Each variant is a self-contained schema that can be independently validated and deployed.

Template-based generation. Define paywall templates with placeholder values and generate concrete paywalls by filling in the blanks. Useful when you have multiple products or markets that share a common layout but differ in copy and pricing.

AI agent creation. An MCP-connected agent can create paywalls from natural language descriptions. "Create a full-screen paywall for the onboarding flow, highlight the annual plan, and emphasize the collaboration features" becomes a valid JSON schema with the right structure and fields.

Native rendering

A JSON schema is platform-agnostic — it describes what the paywall contains, not how it looks. The native SDK handles rendering:

Platform Rendering approach Advantages
iOS SwiftUI views mapped to schema fields Native animations, accessibility, Dynamic Type support
Android Jetpack Compose components Material Design compliance, native performance
React Native Native modules bridging to platform UI Consistent with the rest of the RN app
Flutter Widget tree generated from schema Single rendering path across platforms

The SDK reads the schema, resolves product prices from the app store, maps icon identifiers to platform-native icons, and renders a paywall that looks and feels native. No web views, no iframes, no runtime JavaScript interpretation. The result is a paywall that matches the platform's design language and performs like any other native screen.

When this doesn't work

Schema-driven paywalls aren't the right fit for every situation:

Highly custom designs. If your paywall has a unique visual design that doesn't map to a standard layout — animated 3D elements, custom illustration compositions, non-standard interaction patterns — a schema can't capture that. You'll need custom native code.

Non-technical teams. If the person managing paywalls is a product manager who doesn't read JSON, a visual editor is a better interface. The schema can still be the underlying representation, but it needs a GUI layer on top.

Simple, stable paywalls. If you have one paywall that changes twice a year, the overhead of schema validation, version control, and programmatic generation isn't worth it. Use whatever tool lets you get the paywall out the door fastest.

For everyone else — teams running multiple paywalls across markets, teams iterating frequently on monetization, teams that want AI agents to help manage paywalls — JSON schemas are the foundation everything else builds on. Your paywall is a document. Treat it like one.

Try AgentWallie

API-first paywall platform. Paywalls as JSON schemas. MCP as a first-class interface.

Read the Docs