Paywall Schema
Components

Component Types

AgentWallie supports 19 component types for building paywalls. Every component supports the following top-level properties: type, id, props, style, condition, and animation. Container components (stack, drawer, carousel, slides) also support a top-level children array.

Common Properties

Every component shares this structure:

{
  "type": "component_type",
  "id": "unique_id",
  "props": { ... },
  "style": { ... },
  "condition": { "field": "...", "operator": "...", "value": "..." },
  "animation": { "type": "fade_in", "duration_ms": 300, "delay_ms": 0 }
}
PropertyTypeRequiredDescription
typestringYesComponent type identifier
idstringYesUnique identifier for this component
propsobjectYesComponent-specific properties (see each type below)
styleobjectNoVisual styling (see Common Style Properties below)
conditionobjectNoConditional visibility rule. See DSL Reference.
animationobjectNoEntrance animation. See DSL Reference.

Common Style Properties

All components support these style properties:

PropertyTypeDescription
widthstring | numberWidth ("100%" or fixed points)
heightstring | numberHeight
margin_topnumberTop margin in points
margin_bottomnumberBottom margin
margin_horizontalnumberLeft + right margin
margin_leftnumberLeft margin
margin_rightnumberRight margin
padding_topnumberTop padding
padding_bottomnumberBottom padding
padding_horizontalnumberLeft + right padding
padding_verticalnumberTop + bottom padding
padding_leftnumberLeft padding
padding_rightnumberRight padding
background_colorstringBackground color
colorstringText/foreground color
text_colorstringText color (alias)
corner_radiusstring | numberCorner radius
font_sizenumberFont size in points
alignmentstringContent alignment
opacitynumberOpacity (0-1)
border_widthnumberBorder width
border_colorstringBorder color

text

Any text block. Supports Liquid templates for dynamic values and markdown bold for inline emphasis.

Props

PropTypeRequiredDescription
contentstringYesText content (supports Liquid: {{ theme.primary }}). Wrap text in **double asterisks** to render it bold.
text_stylestringNoPredefined style: title1, title2, title3, headline, body, callout, subheadline, footnote, caption
alignmentstringNoleft, center, right

Markdown Bold

Text content supports markdown-style bold via **text**. The SDK parses these patterns and renders them with a bold font weight using AttributedString. This is useful for highlighting prices or key phrases inline:

{
  "props": {
    "content": "Free for 3 days. **$29.99/yr after.** Cancel anytime."
  }
}

Style: letter_spacing

The text component supports a letter_spacing style property for wide tracking, commonly used on uppercase eyebrow labels:

{
  "style": { "letter_spacing": 4, "font_size": 11 }
}

Example

{
  "type": "text",
  "id": "title",
  "props": {
    "content": "Unlock Everything",
    "text_style": "title1",
    "alignment": "center"
  },
  "style": {
    "color": "{{ theme.text_primary }}",
    "margin_bottom": 8
  }
}

image

Static image from a URL or local resource reference.

Props

PropTypeRequiredDescription
srcstringYesImage URL
altstringNoAlt text for accessibility
aspect_ratiostringNoAspect ratio (e.g., "16:9", "1:1")
fitstringNocover, contain, or fill

Example

{
  "type": "image",
  "id": "hero",
  "props": {
    "src": "https://cdn.example.com/hero.png",
    "alt": "App preview",
    "aspect_ratio": "16:9",
    "fit": "cover"
  },
  "style": { "width": "100%", "margin_bottom": 16 }
}

video

Video or animated content.

Props

PropTypeRequiredDescription
srcstringYesVideo URL
autoplaybooleanNoAuto-play on load
loopbooleanNoLoop playback
mutedbooleanNoMute audio
aspect_ratiostringNoAspect ratio

Example

{
  "type": "video",
  "id": "demo",
  "props": {
    "src": "https://cdn.example.com/demo.mp4",
    "autoplay": true,
    "loop": true,
    "muted": true,
    "aspect_ratio": "16:9"
  }
}

cta_button

Purchase or action button. The primary interactive element.

Props

PropTypeRequiredDescription
textstringYesButton label
subtitlestringNoSecondary text (e.g., pricing details)
actionTapBehaviorYesAction to perform (see Tap Behaviors)
productstringNoProduct slot (for purchase action): "primary", "selected", etc.
urlstringNoURL (for open_url action)
action_namestringNoCustom action name (for custom_action)
placement_namestringNoPlacement name (for custom_placement)

Style: glow_color

Set glow_color in the button's style to add a neon glow effect beneath the button. The SDK renders this as a colored drop shadow (.shadow(color: glow_color, radius: 15, y: 4) on iOS).

{
  "style": {
    "background_color": "#ff0080",
    "glow_color": "#61FF0080"
  }
}

Example

{
  "type": "cta_button",
  "id": "subscribe",
  "props": {
    "text": "Start Free Trial",
    "subtitle": "{{ products.selected.trial.period }} free, then {{ products.selected.price }}/{{ products.selected.period }}",
    "action": "purchase",
    "product": "selected"
  },
  "style": {
    "background_color": "{{ theme.primary }}",
    "glow_color": "#61FF0080",
    "text_color": "#FFFFFF",
    "corner_radius": 12,
    "height": 56,
    "margin_horizontal": 16
  }
}

product_picker

Product selection UI. Displays available product slots for the user to choose between.

Props

PropTypeRequiredDescription
layoutstringYeshorizontal, vertical, cards, or toggle
show_savings_badgebooleanNoShow savings percentage badge
savings_textstringNoCustom badge text (e.g., "BEST VALUE"). Supports Liquid templates.
show_pricebooleanNoShow the product price on each option. Default: true.
selected_border_colorstringNoBorder color for selected product

Layout: "cards"

The "cards" layout renders each product as a tall card displaying:

  • Plan label (e.g., "WEEKLY", "ANNUAL") from the product slot label
  • Price in large text (e.g., "$4.99") pulled from StoreKit / Google Play product data
  • Period subtext (e.g., "/week", "/year")
  • Selected card: highlighted border via selected_border_color with an optional glow shadow
  • Savings badge: when show_savings_badge is true, a pill badge overlaps the top edge of the best-value card
  • Savings text: when savings_text is set, displays custom text (e.g., "Save 88%") below the price in the accent color

This layout is recommended for paywalls where pricing visibility is critical for conversion.

Example (cards layout)

{
  "type": "product_picker",
  "id": "products",
  "props": {
    "layout": "cards",
    "show_price": true,
    "show_savings_badge": true,
    "savings_text": "BEST VALUE",
    "selected_border_color": "#ff0080"
  },
  "style": { "margin_bottom": 16, "padding_horizontal": 16 }
}

Example (horizontal layout)

{
  "type": "product_picker",
  "id": "products",
  "props": {
    "layout": "horizontal",
    "show_savings_badge": true,
    "savings_text": "Save {{ products.primary.savings_percentage }}%",
    "selected_border_color": "{{ theme.primary }}"
  },
  "style": { "margin_bottom": 16, "padding_horizontal": 16 }
}

feature_list

Icon + text list for displaying product features or benefits.

Props

PropTypeRequiredDescription
itemsarrayYesArray of { icon, text } objects
items[].iconstringYesSF Symbol name (iOS) or Material icon
items[].textstringYesFeature text
icon_colorstringNoIcon tint color

Per-Row Card Styling

Style properties on feature_list are applied to each individual row, not just the outer container. This lets you create a card effect per feature row:

  • background_color + corner_radius + padding_* create a card look on each row
  • border_color renders as a leading-edge accent bar (a 3px vertical line on the left side of each row) -- a common paywall design pattern for visually anchoring benefit rows

Example (card-styled rows)

{
  "type": "feature_list",
  "id": "features",
  "props": {
    "items": [
      { "icon": "checkmark.circle.fill", "text": "Unlimited workouts" },
      { "icon": "checkmark.circle.fill", "text": "Advanced analytics" },
      { "icon": "checkmark.circle.fill", "text": "Personal coaching" }
    ],
    "icon_color": "{{ theme.accent }}"
  },
  "style": {
    "background_color": "#0d0d1a",
    "corner_radius": 5,
    "padding_vertical": 8,
    "padding_horizontal": 12,
    "border_color": "#1FFF0080"
  }
}

Example (basic)

{
  "type": "feature_list",
  "id": "features",
  "props": {
    "items": [
      { "icon": "checkmark.circle.fill", "text": "Unlimited workouts" },
      { "icon": "checkmark.circle.fill", "text": "Advanced analytics" },
      { "icon": "checkmark.circle.fill", "text": "Personal coaching" }
    ],
    "icon_color": "{{ theme.accent }}"
  },
  "style": { "margin_bottom": 24, "padding_horizontal": 24 }
}

link_row

Row of text links, typically used for legal links and restore purchases.

Props

PropTypeRequiredDescription
linksarrayYesArray of link objects
links[].textstringYesLink text
links[].actionTapBehaviorYesAction on tap
links[].urlstringNoURL (for open_url action)
separatorstringNoSeparator between links (e.g., " · ")

Example

{
  "type": "link_row",
  "id": "legal",
  "props": {
    "links": [
      { "text": "Restore Purchases", "action": "restore" },
      { "text": "Terms", "action": "open_url", "url": "https://example.com/terms" },
      { "text": "Privacy", "action": "open_url", "url": "https://example.com/privacy" }
    ],
    "separator": " · "
  },
  "style": { "font_size": 12, "alignment": "center" }
}

stack

Container component for horizontal, vertical, or z-axis (overlay) layouts. Can nest any other components.

Props

PropTypeRequiredDescription
directionstringYeshorizontal, vertical, or z
spacingnumberNoSpacing between children (points, ignored for z)
alignmentstringNoCross-axis alignment: leading, center, trailing, top, bottom

Top-Level Fields

FieldTypeRequiredDescription
childrenarrayNoArray of child components

When direction is "z", children are layered on top of each other (like SwiftUI ZStack). The first child is the bottommost layer. Use this for text overlays on images, badges on cards, etc.

Example

{
  "type": "stack",
  "id": "header",
  "props": {
    "direction": "horizontal",
    "spacing": 12,
    "alignment": "center"
  },
  "children": [
    { "type": "image", "id": "icon", "props": { "src": "https://...", "fit": "contain" }, "style": { "width": 48, "height": 48 } },
    { "type": "text", "id": "app_name", "props": { "content": "My App", "text_style": "headline" } }
  ]
}

drawer

Bottom sheet or expandable section. Contains child components that can be toggled.

Props

PropTypeRequiredDescription
titlestringNoDrawer header text
expandedbooleanNoStart expanded. Default: false

Top-Level Fields

FieldTypeRequiredDescription
childrenarrayNoArray of child components

Example

{
  "type": "drawer",
  "id": "details",
  "props": {
    "title": "Plan Details",
    "expanded": false
  },
  "children": [
    { "type": "text", "id": "detail_text", "props": { "content": "Your subscription includes..." } }
  ]
}

carousel

Swipeable content carousel with optional auto-scroll.

Props

PropTypeRequiredDescription
auto_scrollbooleanNoEnable auto-scrolling
auto_scroll_interval_msnumberNoAuto-scroll interval in milliseconds
show_indicatorsbooleanNoShow page indicators

Top-Level Fields

FieldTypeRequiredDescription
childrenarrayNoArray of child components (one per page)

Example

{
  "type": "carousel",
  "id": "testimonials",
  "props": {
    "auto_scroll": true,
    "auto_scroll_interval_ms": 3000,
    "show_indicators": true
  },
  "children": [
    { "type": "text", "id": "quote1", "props": { "content": "Best app ever! - Sarah", "alignment": "center" } },
    { "type": "text", "id": "quote2", "props": { "content": "Changed my life! - Mike", "alignment": "center" } }
  ]
}

slides

Multi-page content with page dots. Similar to carousel but typically used for onboarding-style flows.

Props

PropTypeRequiredDescription
show_page_dotsbooleanNoShow page indicator dots

Top-Level Fields

FieldTypeRequiredDescription
childrenarrayNoArray of child components (one per page)

Example

{
  "type": "slides",
  "id": "onboarding",
  "props": {
    "show_page_dots": true
  },
  "children": [
    { "type": "text", "id": "slide1", "props": { "content": "Track your workouts", "text_style": "title1", "alignment": "center" } },
    { "type": "text", "id": "slide2", "props": { "content": "Get personalized plans", "text_style": "title1", "alignment": "center" } },
    { "type": "text", "id": "slide3", "props": { "content": "Reach your goals", "text_style": "title1", "alignment": "center" } }
  ]
}

Unlike carousel, slides does not auto-scroll. Users swipe manually between pages, making it ideal for onboarding flows where each page requires user attention.


spacer

Flexible spacing between components.

Props

PropTypeRequiredDescription
heightnumberYesFixed height in points

Example

{
  "type": "spacer",
  "id": "gap1",
  "props": { "height": 24 }
}

divider

Horizontal line separator.

Props

PropTypeRequiredDescription
colorstringNoLine color
thicknessnumberNoLine thickness in points

Example

{
  "type": "divider",
  "id": "sep1",
  "props": { "color": "#E5E7EB", "thickness": 1 }
}

countdown_timer

Urgency timer that counts down to zero or a specific time.

Props

PropTypeRequiredDescription
duration_secondsnumberYesCountdown duration
formatstringNoDisplay format (e.g., "mm:ss", "hh:mm:ss")
expires_atstringNoISO 8601 timestamp to count down to (overrides duration)

Example

{
  "type": "countdown_timer",
  "id": "timer",
  "props": {
    "duration_seconds": 900,
    "format": "mm:ss"
  },
  "style": { "font_size": 32, "alignment": "center", "color": "{{ theme.primary }}" }
}

toggle

Switch control for product selection, consent checkboxes, or other boolean inputs.

Props

PropTypeRequiredDescription
labelstringYesToggle label text
default_valuebooleanNoInitial state. Default: false
linked_productstringNoProduct slot to select when toggled on

Example

{
  "type": "toggle",
  "id": "annual_toggle",
  "props": {
    "label": "Pay annually (save 40%)",
    "default_value": true,
    "linked_product": "primary"
  }
}

survey

Inline survey questions for gathering user feedback.

Props

PropTypeRequiredDescription
questionstringYesSurvey question text
optionsstring[]YesArray of answer options
allow_multiplebooleanNoAllow multiple selections. Default: false

Example

{
  "type": "survey",
  "id": "exit_survey",
  "props": {
    "question": "Why are you not subscribing?",
    "options": ["Too expensive", "Missing features", "Just browsing", "Other"],
    "allow_multiple": false
  }
}

custom_view

Escape hatch for rendering a native SwiftUI or Jetpack Compose view by name. Register the view in your app code.

Props

PropTypeRequiredDescription
view_namestringYesRegistered native view name
parametersobjectNoKey-value parameters passed to the view
custom_dataobjectNoArbitrary data passed to the view via CustomViewContext

Example

{
  "type": "custom_view",
  "id": "video_preview",
  "props": {
    "view_name": "WorkoutPreviewView",
    "parameters": { "workoutId": "w_123" },
    "custom_data": {
      "accent_color": "{{ theme.accent }}",
      "show_controls": true
    }
  }
}

Custom views must be registered in your app before they can be referenced. See the Custom Views guide for registration API and best practices.


badge

Pill-shaped label for highlighting offers, tiers, or status. Renders as a compact colored tag.

Props

PropTypeRequiredDescription
textstringYesBadge text (e.g., "BEST VALUE", "SAVE 40%")
variantstringNofilled (default), outlined, or soft

Example

{
  "type": "badge",
  "id": "offer_badge",
  "props": {
    "text": "BEST VALUE",
    "variant": "filled"
  },
  "style": {
    "background_color": "{{ theme.accent }}"
  }
}

Variants:

  • filled -- Solid background with white text
  • outlined -- Transparent background with colored border and text
  • soft -- Lightly tinted background with colored text

rating

Star rating display with optional count and label. Useful for showing app store ratings or testimonials.

Props

PropTypeRequiredDescription
valuenumberYesRating value (e.g., 4.8)
countnumberNoNumber of ratings (e.g., 12500)
labelstringNoLabel text (e.g., "App Store Rating")
max_starsnumberNoMaximum star count (default: 5)

Example

{
  "type": "rating",
  "id": "app_rating",
  "props": {
    "value": 4.8,
    "count": 12500,
    "label": "App Store Rating"
  },
  "style": {
    "margin_horizontal": 24,
    "margin_bottom": 16
  }
}