DSL Reference
AgentWallie's paywall schema includes a rich DSL (Domain-Specific Language) for conditionals, expressions, gradients, and animations. These features enable dynamic, personalized, and visually engaging paywalls without writing native code.
Conditionals
Conditionals control whether a component is rendered based on runtime data. Add a condition property to any component to gate its visibility.
Syntax
{
"type": "text",
"id": "trial_text",
"props": { "content": "Start your free trial today" },
"condition": {
"field": "products.selected.has_trial",
"operator": "is",
"value": true
}
}Operators
| Operator | Description | Example |
|---|---|---|
is | Exact equality | { "field": "user.plan", "operator": "is", "value": "free" } |
is_not | Not equal | { "field": "user.plan", "operator": "is_not", "value": "premium" } |
contains | String contains | { "field": "user.email", "operator": "contains", "value": "@company.com" } |
gt | Greater than | { "field": "device.session_count", "operator": "gt", "value": 5 } |
gte | Greater than or equal | { "field": "device.session_count", "operator": "gte", "value": 3 } |
lt | Less than | { "field": "user.age", "operator": "lt", "value": 18 } |
lte | Less than or equal | { "field": "user.age", "operator": "lte", "value": 65 } |
in | Value in array | { "field": "device.platform", "operator": "in", "value": ["ios", "macos"] } |
not_in | Value not in array | { "field": "user.country", "operator": "not_in", "value": ["CN", "RU"] } |
exists | Field exists | { "field": "user.referral_code", "operator": "exists", "value": true } |
not_exists | Field does not exist | { "field": "user.referral_code", "operator": "not_exists", "value": true } |
Field Paths
Conditions can reference these field namespaces:
products.*-- Product data:products.selected.has_trial,products.selected.price,products.primary.savings_percentageuser.*-- User attributes set via SDK:user.plan,user.session_count,user.onboarding_completedevice.*-- Device info:device.platform,device.session_count,device.localetheme.*-- Theme tokens:theme.primary,theme.background
Examples
Show a trial CTA only when the selected product has a trial:
{
"type": "cta_button",
"id": "trial_cta",
"props": { "text": "Start Free Trial", "action": "purchase" },
"condition": { "field": "products.selected.has_trial", "operator": "is", "value": true }
}Show a special message for returning users:
{
"type": "text",
"id": "welcome_back",
"props": { "content": "Welcome back! Here's a special offer.", "text_style": "headline" },
"condition": { "field": "device.session_count", "operator": "gte", "value": 5 }
}Expressions
Expressions use double-brace syntax ({{ }}) to insert dynamic values into text content, style values, and other string properties. Values are resolved at render time.
Syntax
{
"type": "text",
"id": "price_display",
"props": {
"content": "{{ products.selected.price }}/{{ products.selected.period }}"
}
}Available Variables
| Variable | Description |
|---|---|
theme.* | Any theme token: {{ theme.primary }}, {{ theme.background }} |
products.selected.price | Display price of currently selected product |
products.selected.period | Billing period of selected product |
products.selected.trial.period | Free trial period (e.g., "7 days") |
products.<slot>.price | Price of a specific slot: {{ products.primary.price }} |
products.<slot>.savings_percentage | Savings vs monthly price |
user.<attribute> | User attribute: {{ user.name }} |
device.platform | Device platform (ios or android) |
Examples in Text
{
"props": {
"content": "{{ products.selected.trial.period }} free, then {{ products.selected.price }}/{{ products.selected.period }}"
}
}Examples in CTA Buttons
{
"type": "cta_button",
"props": {
"text": "Subscribe for {{ products.selected.price }}",
"subtitle": "Save {{ products.primary.savings_percentage }}%"
}
}Complete Expression Path Reference
All available expression paths, their example resolved values, and descriptions:
| Path | Example | Description |
|---|---|---|
products.selected.price | $49.99 | Display price of the currently selected product |
products.selected.period | year | Billing period unit (year, month, week, lifetime) |
products.selected.period_label | /yr | Short period label for display |
products.selected.price_per_month | $4.17 | Monthly equivalent price |
products.selected.trial_period | 7 days | Trial duration text |
products.selected.trial_price | Free | Price during the trial period |
products.selected.savings_percentage | 58 | Percentage savings vs monthly billing |
products.selected.has_trial | true | Whether the product has a free trial |
products.selected.label | Annual | Product display label |
products.selected.slot | primary | Product slot identifier |
products.{slot}.price | $49.99 | Price of a specific slot (e.g., products.primary.price) |
products.{slot}.period | year | Period of a specific slot |
products.{slot}.period_label | /yr | Period label of a specific slot |
products.{slot}.price_per_month | $4.17 | Monthly equivalent for a specific slot |
products.{slot}.trial_period | 7 days | Trial duration for a specific slot |
products.{slot}.trial_price | Free | Trial price for a specific slot |
products.{slot}.savings_percentage | 58 | Savings percentage for a specific slot |
products.{slot}.has_trial | true | Whether a specific slot has a trial |
products.{slot}.label | Annual | Label for a specific slot |
user.{key} | free | User attribute value (e.g., user.plan, user.session_count) |
theme.{key} | #007AFF | Theme token value (e.g., theme.primary, theme.corner_radius) |
Replace {slot} with a slot name defined in your schema (e.g., primary, secondary, tertiary). Replace {key} with any key defined in user attributes or the theme object.
Expression paths always use snake_case (e.g., price_per_month, has_trial). If you are using mock data with the Paywall Dev Kit, note that mock data files use camelCase -- the CLI maps between the two formats automatically.
Examples in Style Values
{
"style": {
"background_color": "{{ theme.primary }}",
"corner_radius": "{{ theme.corner_radius }}"
}
}Gradients
Background gradients add visual depth to components. Use the background_gradient property inside style to apply a gradient behind component content.
Syntax
{
"style": {
"background_gradient": {
"colors": ["#FF6B6B", "#4ECDC4"],
"direction": "vertical"
}
}
}Properties
| Property | Type | Required | Description |
|---|---|---|---|
colors | string[] | Yes | Array of 2+ colors (hex or theme refs) |
direction | string | No | Gradient direction (default: "vertical") |
Directions
| Direction | Description |
|---|---|
vertical | Top to bottom |
horizontal | Left to right |
diagonal_down | Top-left to bottom-right |
diagonal_up | Bottom-left to top-right |
Using Theme Colors
{
"style": {
"background_gradient": {
"colors": ["{{ theme.primary }}", "{{ theme.secondary }}"],
"direction": "horizontal"
}
}
}When both background_color and background_gradient are set, the gradient takes precedence.
Animations
Animations bring components to life when they appear on screen. Add an animation property to any component to animate its entrance.
Syntax
{
"type": "text",
"id": "title",
"props": { "content": "Welcome" },
"animation": {
"type": "fade_in",
"duration_ms": 300,
"delay_ms": 100
}
}Properties
| Property | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Animation type (see below) |
duration_ms | number | No | Duration in milliseconds (default: 300) |
delay_ms | number | No | Delay before animation starts (default: 0) |
Animation Types
| Type | Description | Best For |
|---|---|---|
fade_in | Fades from transparent to opaque | Titles, subtitles, general content |
slide_up | Slides up from below while fading in | Feature lists, cards |
slide_in_left | Slides in from the left | Sequential content reveals |
scale_up | Scales from small to full size | CTA buttons, badges |
bounce | Bounces into position | Attention-grabbing elements |
pulse | Continuously pulses (scale oscillation) | Countdown timers, urgency indicators |
shake | Horizontal shake | Error states, attention alerts |
shimmer | Translucent highlight sweeps across the element repeatedly | CTA buttons, promotional cards |
Shimmer Animation
The shimmer animation overlays a moving translucent gradient (transparent to white at 12% opacity to transparent) that sweeps horizontally across the element on a repeating loop. It is commonly used on CTA buttons to draw attention without being as aggressive as pulse.
{
"animation": { "type": "shimmer", "duration_ms": 3000 }
}Best Practices
- Use sparingly. Animate 2-3 key elements per paywall, not everything.
- Keep durations short. 200-500ms for entrance animations. Longer durations feel sluggish.
- Stagger with delays. Use incrementing
delay_msvalues (0, 200, 400) for sequential reveals. - Match the purpose.
fade_infor content,scale_upfor CTAs,pulsefor urgency. - Don't animate legal links. Footer content should appear immediately.
Example: Staggered Reveal
[
{
"type": "text",
"id": "title",
"props": { "content": "Go Premium" },
"animation": { "type": "fade_in", "duration_ms": 300 }
},
{
"type": "feature_list",
"id": "features",
"props": { "items": [...] },
"animation": { "type": "slide_up", "duration_ms": 400, "delay_ms": 200 }
},
{
"type": "cta_button",
"id": "cta",
"props": { "text": "Subscribe Now" },
"animation": { "type": "scale_up", "duration_ms": 300, "delay_ms": 500 }
}
]Additional Style Properties
glow_color
Adds a colored drop shadow beneath a component, creating a neon glow effect. Most commonly used on cta_button components. The SDK renders this as .shadow(color: glow_color, radius: 15, y: 4) on iOS.
{
"style": {
"background_color": "#ff0080",
"glow_color": "#61FF0080"
}
}letter_spacing
Controls character tracking (spacing between letters). Commonly used on uppercase eyebrow labels or section headers for a wide, spaced-out appearance. The SDK applies .tracking(letterSpacing) on iOS.
{
"style": {
"letter_spacing": 4,
"font_size": 11
}
}Paywall Settings Extensions
background_gradient
Apply a gradient background to the entire paywall view instead of a solid background_color. When both are set, the gradient takes precedence.
{
"settings": {
"background_gradient": {
"colors": ["#06060f", "#1a0a20"],
"direction": "vertical"
}
}
}The gradient object uses the same format as component-level background_gradient in style (see Gradients above).
close_button_style
Controls how the close/dismiss button is rendered. Defaults to "icon".
| Value | Description |
|---|---|
"icon" | Filled circle with an X icon (SF Symbol xmark). This is the default. |
"text" | Renders as plain text "x Close" in the top-right corner, with no circle background. |
{
"settings": {
"close_button": true,
"close_button_style": "text"
}
}Next Steps
- Component Types -- All component types with props and examples
- iOS SDK - Custom Views -- Register native views for use in schemas