Clarflow JSON Builder Reference
Build complete quiz funnels with any AI tool and paste them directly into the Clarflow Builder canvas. This document is your complete reference for the JSON format.
How It Works
- Copy this document — click the "Copy Full Markdown" button at the top of this page
- Paste into your AI tool — Claude Code, ChatGPT, or any AI assistant
- Describe your funnel — e.g. "Build me a 10-step quiz funnel for a collagen supplement targeting women 35+"
- Get JSON output — the AI generates valid Clarflow clipboard JSON
- Paste into the Builder — open your funnel in Clarflow, click on the canvas, and press
Ctrl/Cmd + V
The AI uses this reference to produce JSON that the Clarflow Builder accepts natively via clipboard paste. No manual editing needed.
Clipboard Payload Format
When pasting JSON onto the Clarflow canvas, the clipboard must contain a JSON string with this exact structure:
{
"type": "clarflow-canvas-nodes",
"nodes": [ ... ],
"edges": [ ... ],
"timestamp": 1710400000000
}
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | Must be exactly "clarflow-canvas-nodes". Validation key. |
nodes | Node[] | Yes | Array of node objects. |
edges | Edge[] | Yes | Array of edge objects. Can be empty []. |
timestamp | number | Yes | Unix timestamp in milliseconds (e.g. Date.now()). |
The
typefield is a discriminator. If it doesn't match exactly, the paste is silently ignored.
Node Types Overview
Clarflow has 4 node types. Each node in the nodes array follows this base structure:
{
"id": "step_1710400000000_abc123def",
"type": "quizStep",
"position": { "x": 100, "y": 200 },
"data": { ... },
"width": 280,
"height": 200
}
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique node ID. See ID Conventions section. |
type | string | Yes | One of: quizStep, exitNode, ifElseNode, startNode. |
position | { x, y } | Yes | Canvas coordinates (top-left of the node). |
data | object | Yes | Node-specific data. Structure varies by type. |
width | number | Yes | Node width in pixels. Default: 280. |
height | number | Yes | Node height in pixels. Default: 200. Doubled for A/B tests. |
| Node Type | Description | Can Be Pasted? |
|---|---|---|
quizStep | Content step with sub-elements (questions, text, images, etc.) | Yes |
exitNode | Terminal node that redirects to a URL | Yes |
ifElseNode | Conditional branching node | Yes |
startNode | Entry point of the funnel | No (auto-excluded on copy) |
Step Node (quizStep)
The primary content node. Contains sub-elements that the end-user sees.
{
"id": "step_1710400000000_abc123def",
"type": "quizStep",
"position": { "x": 100, "y": 200 },
"data": {
"name": "Step 1",
"subEls": [ ... ],
"size": { "w": 280, "h": 200 },
"buttonText": "Continue",
"hideContinueButton": false,
"isConditional": false,
"conditionalQuestionIndex": 0,
"isABTest": false,
"variantA": null,
"variantB": null,
"variantAWeight": 50
},
"width": 280,
"height": 200
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | No | "New Step" | Internal label in the canvas builder. Not visible to end-users. |
subEls | SubElement[] | Yes | [] | Array of content blocks. See Sub-Elements section. |
size | { w, h } | Yes | { w: 280, h: 200 } | Internal size. w = width, h = height in pixels. |
buttonText | string | No | "Continue" | Custom label for the continue button shown to end-users. |
hideContinueButton | boolean | No | undefined | When true, removes the continue button from this step. Intended for steps holding a Shopify Add to Cart / Checkout block, whose own button is the CTA. Applies to both A/B variants. |
isConditional | boolean | No | undefined | When true, enables conditional routing based on a question's selected option. |
conditionalQuestionIndex | number | No | undefined | 0-based index into subEls pointing to the question used for routing. |
isABTest | boolean | No | undefined | When true, enables A/B test mode. See A/B Testing section. |
variantA | object | No | undefined | Variant A content (replaces subEls for variant A visitors). |
variantB | object | No | undefined | Variant B content. |
variantAWeight | number | No | 50 | Traffic percentage for Variant A (0-100). Variant B gets the remainder. |
Conditional Routing: When isConditional: true, the node renders a separate output handle for each option in the referenced question. Edges connect from handles like option-0, option-1, etc.
Sub-Elements
Sub-elements are the content blocks inside a Step Node's subEls array. Each has a kind field that determines its type.
title
Heading text displayed prominently.
{ "kind": "title", "text": "What is your biggest challenge?" }
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
kind | "title" | Yes | Discriminator. | |
text | string | Yes | The heading text. Plain text or rich text. | |
isRichText | boolean | No | false | Whether text contains rich text formatting. |
color | string | No | undefined | Hex color override (e.g. "#FF0000"). |
contentFormat | string | No | undefined | "markdown" or "html". Format of rich text content. |
text
Body text / paragraph content.
{ "kind": "text", "text": "Select the option that best describes you." }
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
kind | "text" | Yes | Discriminator. | |
text | string | Yes | The body text content. | |
isRichText | boolean | No | false | Whether text contains rich text formatting. |
color | string | No | undefined | Hex color override. |
contentFormat | string | No | undefined | "markdown" or "html". |
Rich text example with HTML:
{
"kind": "text",
"text": "<p style=\"text-align: center;\"><span style=\"font-size: 28px;\">How old are you?</span></p>",
"isRichText": true,
"contentFormat": "html"
}
image
Displays one or more images.
{ "kind": "image", "url": "https://example.com/hero.png", "alt": "Hero image" }
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
kind | "image" | Yes | Discriminator. | |
url | string | Yes | Primary image URL. | |
alt | string | No | undefined | Alt text for accessibility. |
images | Array<{ url, alt? }> | No | undefined | Additional images (for carousels/galleries). |
question
Interactive question with selectable options. The most important sub-element for quiz funnels.
{
"kind": "question",
"kindOf": "single",
"options": [
{ "id": "opt_1", "text": "Focus issues", "emoji": "\ud83c\udfaf" },
{ "id": "opt_2", "text": "Time management", "emoji": "\u23f0" }
],
"layout": "list",
"variableName": "challenge"
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
kind | "question" | Yes | Discriminator. | |
kindOf | "single" or "multi" | Yes | "single" = one selection. "multi" = multiple selections. | |
options | array | Yes | Array of options. Can be plain strings or rich option objects. | |
layout | "list" or "grid" | No | "list" | Visual layout of the options. |
variableName | string | No | undefined | Variable name for storing the selected answer. |
Plain string options (simplest form):
{ "kind": "question", "kindOf": "single", "options": ["Yes", "No", "Maybe"] }
Rich option objects (recommended for most funnels):
{
"id": "opt_abc123",
"text": "Focus issues",
"emoji": "\ud83c\udfaf",
"imageUrl": "https://example.com/focus.png",
"subtext": "Difficulty concentrating on tasks",
"subtextEmoji": "\ud83d\udcad",
"subtextColor": "#6B7280"
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | No* | Auto-generated | Unique option ID. Required for conditional routing & If/Else conditions. |
text | string | Yes | Primary display text. Keep short (1-4 words ideal, 6 max). | |
textHtml | string | No | undefined | Display only. Rich-text version of text (see below). |
emoji | string | No | undefined | Emoji displayed before the text. |
imageUrl | string | No | undefined | Image URL displayed in the option card. |
subtext | string | No | undefined | Secondary text below the main text. |
subtextEmoji | string | No | undefined | Emoji shown to the left of subtext. |
subtextColor | string | No | undefined | Hex color override for subtext. |
Option text tips: Keep
textshort and scannable. Usesubtextfor additional context instead of long option text. Use emojis selectively — they help users scan faster but skip them on age ranges, yes/no, or numbers.
Formatting part of an option (
textHtml): to emphasise part of a label —Best for <strong>fast</strong> results— settextHtmlalongsidetext.textstays the plain source of truth: it is what analytics, response variables, webhooks and CRM payloads receive, so it must matchtextHtmlwith the tags stripped. If the two disagree, the plaintextrenders andtextHtmlis ignored — so when you edit a label, update both or droptextHtml. The editor keeps them in sync for you.Supported tags:
p,br,strong,b,em,i,u,s,del,mark,span. The only attribute isstyle, limited tocolor,background-color,font-weight,font-styleandtext-decoration— anything else is stripped. Font size and alignment are deliberately not supported: option sizing follows the funnel's audience typography setting.
loading
Displays a loading/progress animation. Three styles available.
Bar style (simple progress bar, best for 2-5s transitions):
{ "kind": "loading", "seconds": 4, "style": "bar", "text": "Analyzing your answers..." }
Circle style (circular spinner, best for 3-6s mid-funnel processing):
{ "kind": "loading", "seconds": 5, "style": "circle", "text": "Building your plan..." }
Steps style (multi-step loader, most engaging, best before results, 15-30s):
{
"kind": "loading",
"seconds": 15,
"style": "steps",
"steps": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"loadingText": "Analyzing your profile...",
"completedText": "Profile analyzed",
"seconds": 5,
"modal": null
},
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"loadingText": "Building your plan...",
"completedText": "Plan ready",
"seconds": 5,
"modal": {
"warningText": "To personalize your plan",
"questionText": "Do you prefer morning or evening routines?",
"buttonLabelYes": "Morning",
"buttonLabelNo": "Evening"
}
},
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"loadingText": "Finalizing recommendations...",
"completedText": "Recommendations ready",
"seconds": 5
}
]
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
kind | "loading" | Yes | Discriminator. | |
seconds | number | Yes | Total duration in seconds. | |
style | string | No | "bar" | "bar", "circle", or "steps". |
text | string | No | undefined | Text shown during loading (for bar/circle styles). |
steps | array | No | undefined | Multi-step config. Used when style is "steps". |
Loader step fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique step ID. Use UUID format. |
loadingText | string | Yes | Text shown while this step is loading. |
completedText | string | Yes | Text shown when this step completes (with checkmark). |
seconds | number | Yes | Duration for this step (1-60 seconds). |
modal | object | No | Optional modal that pauses the loader at 50%. |
Modal fields (when present on a loader step):
| Field | Type | Required | Description |
|---|---|---|---|
warningText | string | Yes | Warning/context text. |
questionText | string | Yes | The question to ask. |
buttonLabelYes | string | Yes | Affirmative button label. |
buttonLabelNo | string | Yes | Negative button label. |
Use modals sparingly — 1-2 per multi-step loader max.
custom_html
Renders arbitrary HTML content or a pre-built interactive block.
Inline HTML:
{
"kind": "custom_html",
"html": "<div style='text-align:center; padding:20px;'><h2>Your Results</h2><p>Based on your answers...</p></div>"
}
Pre-built block (uses templateId + templateConfig, html must be empty string):
{
"kind": "custom_html",
"html": "",
"templateId": "reviews1",
"templateConfig": {
"reviews": [
{ "title": "Amazing product", "body": "Really works...", "authorName": "Sarah M." }
],
"starColor": "#F5A623",
"verifiedColor": "#2ECC87",
"autoRotateMs": 4000
}
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
kind | "custom_html" | Yes | Discriminator. | |
html | string | Yes | Raw HTML content. Must be "" when using a pre-built block. | |
templateId | string | No | undefined | Pre-built block identifier. See Pre-Built Blocks section. |
templateConfig | object | No | undefined | Configuration for the pre-built block. |
input
Single-line text input field for collecting user data.
{
"kind": "input",
"label": "Your email",
"placeholder": "you@example.com",
"inputType": "email",
"required": true,
"variableName": "user_email",
"klaviyoEnabled": true
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
kind | "input" | Yes | Discriminator. | |
label | string | No | undefined | Label text above the input. |
placeholder | string | No | undefined | Placeholder text inside the input. |
inputType | string | Yes | One of: "text", "email", "number", "tel", "url", "password". | |
required | boolean | No | undefined | Whether the field must be filled before proceeding. |
minLength | number | No | undefined | Minimum character length. |
maxLength | number | No | undefined | Maximum character length. |
klaviyoEnabled | boolean | No | undefined | Whether to sync this input to Klaviyo. |
variableName | string | No | undefined | Variable name for storing the value. |
dropdown
Dropdown select menu.
{
"kind": "dropdown",
"label": "Your country",
"placeholder": "Select a country",
"options": [
{ "id": "opt_us", "text": "United States", "emoji": "\ud83c\uddfa\ud83c\uddf8" },
{ "id": "opt_uk", "text": "United Kingdom", "emoji": "\ud83c\uddec\ud83c\udde7" }
],
"defaultOptionIndex": 0,
"required": true,
"variableName": "country"
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
kind | "dropdown" | Yes | Discriminator. | |
label | string | No | undefined | Label text above the dropdown. |
placeholder | string | No | undefined | Placeholder text when no option is selected. |
options | array | Yes | Array of dropdown options ({ id?, text, emoji? }). | |
defaultOptionIndex | number | No | undefined | 0-based index of the pre-selected option. |
required | boolean | No | undefined | Whether a selection is required. |
variableName | string | No | undefined | Variable name for storing the value. |
Exit Node
Terminal node that redirects the end-user to an external URL.
{
"id": "exit_1710400000000_abc123def",
"type": "exitNode",
"position": { "x": 900, "y": 200 },
"data": {
"name": "Exit",
"redirectUrl": "https://example.com/thank-you",
"size": { "w": 280, "h": 200 }
},
"width": 280,
"height": 200
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | No | "Exit" | Display name in canvas. |
redirectUrl | string | Yes | "" | URL the end-user is redirected to. Can be empty string. |
size | { w, h } | Yes | { w: 280, h: 200 } | Internal size dimensions. |
If/Else Node
Conditional branching node that routes users based on their previous answers.
{
"id": "ifelse_1710400000000_abc123def",
"type": "ifElseNode",
"position": { "x": 500, "y": 200 },
"data": {
"name": "Age Router",
"conditions": [
{
"id": "cond_young",
"label": "If",
"criteria": [
{ "stepId": "step_q1", "questionIndex": 0, "optionIds": ["opt_under18"] }
],
"handleId": "condition-if",
"type": "if"
},
{
"id": "cond_adult",
"label": "Else If 1",
"criteria": [
{ "stepId": "step_q1", "questionIndex": 0, "optionIds": ["opt_18to35"] }
],
"handleId": "condition-elseif-1",
"type": "elseIf"
},
{
"id": "cond_fallback",
"label": "Else",
"criteria": [],
"handleId": "condition-else",
"type": "else"
}
],
"size": { "w": 240, "h": 180 }
},
"width": 240,
"height": 180
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | No | "If/Else" | Display name in canvas. |
conditions | array | Yes | Array of conditions. | |
size | { w, h } | Yes | { w: 240, h: 180 } | Internal size. |
Condition fields:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Unique condition ID. Used as sourceHandle in edges. |
label | string | Yes | Display label: "If", "Else If 1", "Else If 2", ..., "Else". |
criteria | array | Yes | Array of criteria (AND logic). Empty [] for Else. |
handleId | string | Yes | Internal handle: "condition-if", "condition-elseif-1", "condition-else". |
type | string | Yes | "if", "elseIf", or "else". |
Criteria fields:
| Field | Type | Required | Description |
|---|---|---|---|
stepId | string | Yes | ID of the Step Node containing the question to evaluate. |
questionIndex | number | Yes | 0-based index of the question in that step's subEls array. |
optionIds | string[] | Yes | Option IDs that must be selected. AND logic: all must match. |
Condition ordering rules:
- First condition must be
type: "if" - Last condition must be
type: "else" - Any number of
type: "elseIf"conditions between them - Between criteria in one condition: AND logic (all must be satisfied)
- Between conditions: evaluated top to bottom, first match wins
Edges
Edges connect nodes together. Each edge in the edges array:
{
"id": "edge_1710400000000_abc123def",
"source": "step_1710400000000_node1",
"target": "step_1710400000001_node2",
"sourceHandle": "output",
"targetHandle": "input",
"type": "deletable",
"animated": false,
"style": { "stroke": "#374151", "strokeWidth": 2 }
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | Unique edge ID. | |
source | string | Yes | ID of the source (outgoing) node. | |
target | string | Yes | ID of the target (incoming) node. | |
sourceHandle | string | Yes | "output" | Output handle on the source node. See table below. |
targetHandle | string | Yes | "input" | Always "input". |
type | string | Yes | "deletable" | Always "deletable" for Clarflow edges. |
animated | boolean | No | false | Whether the edge has a flow animation. |
style | object | No | Standard: { "stroke": "#374151", "strokeWidth": 2 }. |
Source handle types:
| Handle Pattern | Used For | Example |
|---|---|---|
"output" | Default (non-conditional) edge | "output" |
"option-{index}" | Conditional routing on step | "option-0", "option-1" |
"variantA-option-{index}" | A/B test variant A conditional | "variantA-option-0" |
"variantB-option-{index}" | A/B test variant B conditional | "variantB-option-1" |
Condition id | If/Else node edges | The condition's id value (e.g. "cond_young") |
Important for If/Else edges: The
sourceHandlemust be the condition'sidfield, NOT thehandleIdpattern. The conditionidis what edges reference.
A/B Testing
When isABTest: true on a Step Node, the node shows two variants to different visitors.
Variant structure:
{
"subEls": [ ... ],
"buttonText": "Continue",
"isConditional": false,
"conditionalQuestionIndex": 0
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
subEls | array | Yes | Sub-elements for this variant. | |
buttonText | string | No | undefined | Custom button text for this variant. |
isConditional | boolean | No | undefined | Enable per-variant conditional routing. |
conditionalQuestionIndex | number | No | undefined | Index of the question in this variant's subEls for routing. |
A/B test node example:
{
"id": "step_abtest_1",
"type": "quizStep",
"position": { "x": 500, "y": 200 },
"data": {
"name": "A/B Test Step",
"subEls": [],
"size": { "w": 280, "h": 200 },
"isABTest": true,
"variantAWeight": 50,
"variantA": {
"subEls": [
{ "kind": "title", "text": "Version A: Do you struggle with focus?" },
{ "kind": "question", "kindOf": "single", "options": ["Yes", "No"] }
],
"buttonText": "Next"
},
"variantB": {
"subEls": [
{ "kind": "title", "text": "Version B: How's your concentration?" },
{ "kind": "question", "kindOf": "single", "options": ["Good", "Could be better"] }
],
"buttonText": "Continue"
}
},
"width": 280,
"height": 400
}
Height rule: For A/B test nodes, set
heighttosize.h * 2(e.g.200 * 2 = 400).
Pre-Built Blocks
Pre-built blocks are config-driven interactive components. When using a pre-built block, set html to an empty string "" and provide templateId + templateConfig.
{
"kind": "custom_html",
"html": "",
"templateId": "reviews1",
"templateConfig": { ... }
}
Available Blocks
| Category | Block ID | Description |
|---|---|---|
| Review | reviews1 | Review carousel with 5-star ratings and verified badges |
| Review | reviews2-trustpilot | Trustpilot-style review carousel |
| Review | reviews3 | Auto-scrolling horizontal review slider (dark/premium aesthetic) |
| Offer | offer | Full offer card with badge, hero image, features, trust badges |
| Offer | offer2 | Simpler product card with pricing and bullet benefits |
| Interactive | before-after | Drag slider to compare two images side by side |
| Interactive | scratch-to-reveal | Scratch card with confetti animation on reveal |
| Interactive | swipe-statements | Tinder-style swipe cards for yes/no statements |
| Graph | graph-goal | Goal progression chart with animated trend line |
| Assessment | noise-profile | Severity assessment card with slider and stat cards |
| Graph | results-by-date | Month-over-month bar chart with projected results |
reviews1 — Review Carousel
Auto-rotating review carousel with 5-star ratings and verified badges.
{
"templateId": "reviews1",
"templateConfig": {
"reviews": [
{ "title": "This actually works", "body": "I was skeptical but after 3 weeks...", "authorName": "Sarah M." },
{ "title": "Best purchase this year", "body": "Noticed results within days...", "authorName": "John G." },
{ "title": "Highly recommend", "body": "My friend recommended this and...", "authorName": "Lisa K." }
],
"starColor": "#F5A623",
"verifiedColor": "#2ECC87",
"autoRotateMs": 4000
}
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
reviews | array | Yes | Array of { title, body, authorName }. 3-5 recommended. | |
starColor | string | No | "#F5A623" | Hex color for star icons. |
verifiedColor | string | No | "#2ECC87" | Hex color for "Verified" badge. |
autoRotateMs | number | No | 4000 | Auto-rotation interval in milliseconds. |
reviews2-trustpilot — Trustpilot Reviews
Trustpilot-style review carousel with green square stars.
{
"templateId": "reviews2-trustpilot",
"templateConfig": {
"reviews": [
{ "title": "Eye-opening information", "body": "Detailed review text...", "authorName": "Patrick N." },
{ "title": "Changed my life", "body": "Another review...", "authorName": "Brian R." }
],
"autoRotateMs": 4000
}
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
reviews | array | Yes | Same structure as reviews1. | |
autoRotateMs | number | No | 4000 | Auto-rotation interval. |
reviews3 — Review Slider
Auto-scrolling horizontal review carousel with overall rating badge. Dark/premium aesthetic.
{
"templateId": "reviews3",
"templateConfig": {
"headerLabel": "Loved by thousands",
"overallRating": "4.8",
"primaryColor": "#CA5839",
"bgGradientStart": "#2e1c18",
"bgGradientEnd": "#3d2520",
"textColor": "#ffffff",
"reviewTextColor": "rgba(255,255,255,0.85)",
"reviewCardBg": "rgba(202,88,57,0.1)",
"reviews": [
{ "text": "Review text without a separate title...", "authorName": "Sarah M." },
{ "text": "Another review...", "authorName": "Mike T." }
]
}
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
headerLabel | string | No | "Loved by thousands" | Header text above reviews. |
overallRating | string | No | "4.8" | Overall rating in badge. |
primaryColor | string | No | "#CA5839" | Accent color. |
bgGradientStart | string | No | "#2e1c18" | Background gradient start. |
bgGradientEnd | string | No | "#3d2520" | Background gradient end. |
textColor | string | No | "#ffffff" | Main text color. |
reviewTextColor | string | No | "rgba(255,255,255,0.85)" | Review body text color. |
reviewCardBg | string | No | "rgba(202,88,57,0.1)" | Review card background. |
reviews | array | Yes | Reviews with { text, authorName } (note: text not body, no title). |
offer — Offer Card
Full-featured offer card with badge, hero image, feature tags, offer box, and trust badges.
{
"templateId": "offer",
"templateConfig": {
"badgeText": "Your Personalized Plan",
"headlineHtml": "<p style=\"text-align: center\"><span style=\"font-size: 22px\">Transform your health — <span style=\"color: #D4488C\">or pay nothing.</span></span></p>",
"descriptionHtml": "<p style=\"text-align: center; font-size: 12px; color: #5C5C5C\">Join 25,000+ people who saw results...</p>",
"imageUrl": "https://example.com/product.jpeg",
"feature1": "Results in 28 days",
"feature2": "Science-backed formula",
"feature3": "Easy daily routine",
"socialProofText": "4.9/5 from 14,207 ratings",
"starColor": "#E88BA0",
"accentColor": "#D4488C",
"offerTextHtml": "<p style=\"font-size: 12px; color: #5C5C5C\"><span style=\"color: #D4488C; font-weight: 700\">QUIZ RESULTS OFFER:</span> Unlock <span style=\"font-weight: 700; color: #1A1A1A\">up to 60% off</span>...</p>",
"trustBadge1": "90-day guarantee",
"trustBadge2": "Clinically tested",
"trustBadge3": "Free shipping"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
badgeText | string | Yes | Top badge/eyebrow text. |
headlineHtml | string | Yes | Rich HTML headline. |
descriptionHtml | string | Yes | Rich HTML description below headline. |
imageUrl | string | Yes | Hero product image URL. |
feature1 | string | Yes | Feature tag 1. |
feature2 | string | Yes | Feature tag 2. |
feature3 | string | Yes | Feature tag 3. |
socialProofText | string | No | Rating line beside the stars. Defaults to "4.9/5 from 14,207 ratings"; set "" to hide the star row. The stars fill to the score stated here — "4.5/5", "4.5 out of 5" and "4.5 stars" all render four and a half (. or , decimals). A line with no score leaves the stars full. |
starRating | number | No | Fills the stars to this value instead. Only needed when socialProofText states no score. |
starColor | string | No | Star fill color (hex). Defaults to "#E88BA0". Unfilled stars are the same color at 25% opacity. |
accentColor | string | Yes | Accent/brand color (hex). |
offerTextHtml | string | Yes | Rich HTML offer details. |
trustBadge1 | string | Yes | Trust badge 1 text. |
trustBadge2 | string | Yes | Trust badge 2 text. |
trustBadge3 | string | Yes | Trust badge 3 text. |
offer2 — Product Card
Simpler product card with image, pricing, features, and benefit bullets.
{
"templateId": "offer2",
"templateConfig": {
"imageUrl": "https://example.com/product.webp",
"badgeText": "RECOMMENDED FOR YOU",
"productName": "Product Name",
"featuresText": "Benefit 1 \u2022 Benefit 2 \u2022 Benefit 3",
"originalPrice": "$89",
"discountedPrice": "$39",
"priceCopyHtml": "<p style=\"font-size: 13px; color: rgba(13,27,42,0.72)\">Your quiz unlocked a <strong>$50 credit</strong>...</p>",
"accentColor": "#5BA4CF",
"whyTitleHtml": "<p style=\"font-size: 16px; font-weight: 900; text-align: center\">Why this fits your profile</p>",
"bullets": [
"First benefit explanation",
"Second benefit explanation",
"Third benefit explanation",
"Fourth benefit explanation"
]
}
}
| Field | Type | Required | Description |
|---|---|---|---|
imageUrl | string | Yes | Product image URL. |
badgeText | string | Yes | Top badge text. |
productName | string | Yes | Product name. |
featuresText | string | Yes | Inline features text (dot-separated). |
originalPrice | string | Yes | Original/strikethrough price. |
discountedPrice | string | Yes | Discounted/current price. |
priceCopyHtml | string | Yes | Rich HTML price explanation. |
accentColor | string | Yes | Accent color (hex). |
whyTitleHtml | string | Yes | Rich HTML "why this fits" title. |
bullets | string[] | Yes | Array of benefit bullet strings (4-6 recommended). |
before-after — Image Comparison Slider
Interactive drag slider to compare two images side by side.
{
"templateId": "before-after",
"templateConfig": {
"beforeImageUrl": "https://example.com/before.jpeg",
"afterImageUrl": "https://example.com/after.jpeg",
"beforeLabel": "Before",
"afterLabel": "After"
}
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
beforeImageUrl | string | Yes | "Before" image URL. | |
afterImageUrl | string | Yes | "After" image URL. | |
beforeLabel | string | No | "Before" | Label for the before side. |
afterLabel | string | No | "After" | Label for the after side. |
scratch-to-reveal — Scratch Card
Interactive scratch card with canvas overlay. User scratches to reveal a reward with confetti animation.
{
"templateId": "scratch-to-reveal",
"templateConfig": {
"titleHtml": "<strong>You've Unlocked a <span style=\"color: #CA5839\">Mystery Reward</span></strong>",
"subtitleText": "Scratch below to reveal your exclusive offer",
"primaryColor": "#CA5839",
"scratchBrushSize": 22,
"revealThreshold": 50,
"canvasHeight": 340,
"rewardEyebrow": "Your Exclusive Reward",
"rewardValue": "FREE",
"rewardItem": "Growth Activation Serum",
"rewardDescription": "$49 value - Added to your order",
"goldColor": "#b8860b",
"instructionText": "Scratch to Reveal",
"instructionSubText": "Use your finger to scratch",
"enableConfetti": true,
"confettiColors": [],
"maxWidth": 340,
"borderRadius": 12
}
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
titleHtml | string | Yes | Rich HTML title above scratch area. | |
subtitleText | string | No | Subtitle below title. | |
primaryColor | string | No | "#CA5839" | Primary accent color. |
scratchBrushSize | number | No | 22 | Scratch brush radius in pixels. |
revealThreshold | number | No | 50 | % of area to scratch before auto-reveal. |
canvasHeight | number | No | 340 | Scratch canvas height in pixels. |
rewardEyebrow | string | Yes | Small text above reward value. | |
rewardValue | string | Yes | Big reward value text (e.g. "FREE", "50% OFF"). | |
rewardItem | string | Yes | Item/product name. | |
rewardDescription | string | Yes | Description below reward. | |
goldColor | string | No | "#b8860b" | Gold accent for reward display. |
instructionText | string | No | "Scratch to Reveal" | Instruction overlay text. |
instructionSubText | string | No | Secondary instruction text. | |
enableConfetti | boolean | No | true | Enable confetti on reveal. |
confettiColors | string[] | No | [] | Custom confetti colors (empty = defaults). |
maxWidth | number | No | 340 | Max width in pixels. |
borderRadius | number | No | 12 | Border radius in pixels. |
swipe-statements — Tinder-Style Cards
Tinder-style swipe cards where users swipe right for yes, left for no.
{
"templateId": "swipe-statements",
"templateConfig": {
"titleText": "Does this sound like you?",
"subtitleText": "Swipe right for yes, left for no",
"primaryColor": "#CA5839",
"yesColor": "#22c55e",
"noColor": "#ef4444",
"cardBgColor": "#ffffff",
"textColor": "#1a1a1a",
"subtextColor": "#555555",
"yesLabel": "Yes",
"noLabel": "Nope",
"hintText": "Swipe or use buttons below",
"completeTitle": "We've got your profile!",
"completeSubtitle": "You matched with {count} common concerns",
"cards": [
{ "emoji": "\ud83e\ude9e", "text": "I've noticed changes recently" },
{ "emoji": "\ud83d\ude29", "text": "I've tried other products with little results" },
{ "emoji": "\ud83c\udf3f", "text": "I prefer natural, science-backed solutions" },
{ "emoji": "\u23f0", "text": "I want to act before it gets worse" },
{ "emoji": "\ud83d\udcaa", "text": "I'm ready to commit to a daily routine" }
],
"maxWidth": 360,
"cardHeight": 320
}
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
titleText | string | Yes | Title above the card stack. | |
subtitleText | string | No | Subtitle/instruction text. | |
primaryColor | string | No | "#CA5839" | Primary accent color. |
yesColor | string | No | "#22c55e" | Color for "yes" swipe/button. |
noColor | string | No | "#ef4444" | Color for "no" swipe/button. |
cardBgColor | string | No | "#ffffff" | Card background color. |
textColor | string | No | "#1a1a1a" | Main text color. |
subtextColor | string | No | "#555555" | Secondary text color. |
yesLabel | string | No | "Yes" | Yes button label. |
noLabel | string | No | "Nope" | No button label. |
hintText | string | No | Hint text below cards. | |
completeTitle | string | No | Title shown after all cards swiped. | |
completeSubtitle | string | No | Subtitle after completion. {count} is replaced with yes-count. | |
cards | array | Yes | Array of { emoji, text } cards (4-8 recommended). | |
maxWidth | number | No | 360 | Max card width in pixels. |
cardHeight | number | No | 320 | Card height in pixels. |
graph-goal — Goal Progression Chart
Animated goal progression chart showing growth/decline over time.
{
"templateId": "graph-goal",
"templateConfig": {
"yAxisLabels": [
"HIGH\nLevels",
"NORMAL\nLevels",
"LOW\nLevels"
],
"xAxisLabels": ["TODAY", "MONTH 1", "MONTH 2", "MONTH 3", "MONTH 4"],
"youAreHereLabel": "YOU ARE HERE",
"goalLabel": "GOAL: Significant Improvement",
"trend": "upward"
}
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
yAxisLabels | string[] | Yes | Y-axis labels (3 items: high, normal, low). Use \n for line breaks. | |
xAxisLabels | string[] | Yes | X-axis labels (time periods, 4-6 items). | |
youAreHereLabel | string | No | "YOU ARE HERE" | Label for the starting point. |
goalLabel | string | No | Label for the goal/target point. | |
trend | string | Yes | "upward" or "downward" — direction of the trend line. |
noise-profile — Severity Assessment Card
Severity assessment display with slider, alert box, and stat cards.
{
"templateId": "noise-profile",
"templateConfig": {
"cardTitle": "Your Assessment Results",
"levelPillText": "Severe",
"levelPillColor": "#C0392B",
"imageUrl": "https://example.com/assessment.webp",
"sliderTooltipText": "Your level",
"sliderLabels": ["Balanced", "Mild", "Moderate", "Severe"],
"sliderEndPosition": 90,
"alertTitle": "SEVERE imbalance detected",
"alertText": "Based on your responses, your levels are critically disrupted...",
"stats": [
{ "emoji": "\ud83e\udda0", "bgColor": "#FEF3E2", "label": "Primary metric", "value": "Critically Low", "isHighlighted": true },
{ "emoji": "\ud83d\udca8", "bgColor": "#E3F2FD", "label": "Secondary metric", "value": "Severe", "isHighlighted": true },
{ "emoji": "\ud83e\udde0", "bgColor": "#FCE4EC", "label": "Tertiary metric", "value": "Elevated", "isHighlighted": true },
{ "emoji": "\u26a1", "bgColor": "#E8EAF6", "label": "Fourth metric", "value": "Below 40%", "isHighlighted": true }
]
}
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
cardTitle | string | Yes | Main card title. | |
levelPillText | string | Yes | Severity level pill text. | |
levelPillColor | string | Yes | Severity pill color (hex). | |
imageUrl | string | No | Optional image URL. | |
sliderTooltipText | string | No | "Your level" | Tooltip on the severity slider. |
sliderLabels | string[] | Yes | Slider scale labels (4 items: low to high). | |
sliderEndPosition | number | Yes | Slider position 0-100. | |
alertTitle | string | Yes | Alert box title. | |
alertText | string | Yes | Alert box description. | |
stats | array | Yes | Array of stat cards (3-4 recommended). | |
stats[].emoji | string | Yes | Stat card emoji. | |
stats[].bgColor | string | Yes | Stat card background color (hex). | |
stats[].label | string | Yes | Stat metric label. | |
stats[].value | string | Yes | Stat value text. | |
stats[].isHighlighted | boolean | No | false | Whether to visually highlight this stat. |
results-by-date — Month-Over-Month Bar Chart
Animated bar chart showing projected results over time.
{
"templateId": "results-by-date",
"templateConfig": {
"titleHtml": "<p style=\"text-align: center; font-size: 28px; font-weight: 700\">The last plan you'll ever need</p>",
"descriptionHtml": "<p style=\"text-align: center; font-size: 16px; color: #555\">Based on your answers, we expect visible results by May 2026</p>",
"trend": "upward",
"startMonth": "February",
"endMonth": "May"
}
}
| Field | Type | Required | Description |
|---|---|---|---|
titleHtml | string | Yes | Rich HTML title (use inline styles). |
descriptionHtml | string | Yes | Rich HTML description. |
trend | string | Yes | "upward" or "downward". |
startMonth | string | Yes | Starting month name (e.g. "February"). |
endMonth | string | Yes | Ending month name (e.g. "May"). |
ID Conventions
IDs use a timestamp + random suffix pattern for uniqueness:
| Entity | Pattern | Example |
|---|---|---|
| Step Node | step_{timestamp}_{random} | step_1710400000000_k7x2m9abc |
| Exit Node | exit_{timestamp}_{random} | exit_1710400000001_p3y8n5def |
| If/Else Node | ifelse_{timestamp}_{random} | ifelse_1710400000002_q1w2e3ghi |
| Start Node | start_{timestamp}_{random} | start_1710400000003_r4t5y6jkl |
| Edge | edge_{timestamp}_{random} | edge_1710400000004_u7i8o9mno |
| Condition | condition_{timestamp}_{random} | condition_1710400000005_a1s2d3pqr |
| Option | opt_{timestamp}_{random} | opt_1710400000006_f4g5h6stu |
| Loader Step | UUID format | a1b2c3d4-e5f6-7890-abcd-ef1234567890 |
{timestamp}=Date.now()(milliseconds since epoch){random}= 9-character random alphanumeric string
When pasting, the system automatically remaps all IDs to new unique values. So the exact IDs don't need to be globally unique — they just need to be internally consistent (e.g., an edge's
sourcemust match a node'sid).
Positioning
Linear flow: Space nodes 400px apart horizontally, constant Y.
position.x = startX + (stepIndex * 400)
position.y = 200
Branching: Offset branches vertically.
Main path: y = 200
Branch A: y = 50
Branch B: y = 350
Variable References
When a quiz captures user input (via variableName on questions, inputs, or dropdowns), you can reference that value in later title/text elements using double-bracket syntax:
{{variable_name}}
For example, if an input stores to variableName: "first_name", a later title can use:
{ "kind": "title", "text": "Great news, {{first_name}}!" }
Validation Rules & Common Pitfalls
The paste deserializer checks:
- The parsed object must have
type === "clarflow-canvas-nodes"(exact match) nodesmust be an arrayedgesmust be an array- If any check fails, the paste is silently ignored (no error shown)
| Pitfall | Symptom | Fix |
|---|---|---|
Missing type: "clarflow-canvas-nodes" | Paste does nothing | Add the type field |
nodes or edges not an array | Paste does nothing | Ensure both are arrays, even if empty |
| Option IDs missing on conditional questions | Edges connect to wrong options | Add explicit id fields to all options |
| A/B test node height not doubled | Node renders incorrectly | Set height to size.h * 2 |
If/Else edge uses handleId instead of condition id | Edge doesn't connect | Use the condition's id as sourceHandle |
Invalid kind on sub-element | Element doesn't render | Must be: title, text, image, question, loading, custom_html, input, dropdown |
kindOf missing on question | Question may not render | Must be "single" or "multi" |
inputType missing on input | Input may not render | Must be: text, email, number, tel, url, password |
Pre-built block with non-empty html | Block may not render correctly | Set html to "" when using templateId |
Complete Examples
Example 1: Minimal Single Step
A single question step with no connections:
{
"type": "clarflow-canvas-nodes",
"nodes": [
{
"id": "step_1_a",
"type": "quizStep",
"position": { "x": 300, "y": 200 },
"data": {
"name": "Quick Question",
"subEls": [
{ "kind": "title", "text": "How did you hear about us?" },
{
"kind": "question",
"kindOf": "single",
"options": ["Google", "Friend", "Social Media", "Other"]
}
],
"size": { "w": 280, "h": 200 }
},
"width": 280,
"height": 200
}
],
"edges": [],
"timestamp": 1710400000000
}
Example 2: Two Connected Steps
Two steps connected by a default edge:
{
"type": "clarflow-canvas-nodes",
"nodes": [
{
"id": "step_1_intro",
"type": "quizStep",
"position": { "x": 100, "y": 200 },
"data": {
"name": "Introduction",
"subEls": [
{ "kind": "title", "text": "Welcome to our assessment" },
{ "kind": "text", "text": "This will take about 2 minutes." }
],
"size": { "w": 280, "h": 200 },
"buttonText": "Let's Go"
},
"width": 280,
"height": 200
},
{
"id": "step_2_question",
"type": "quizStep",
"position": { "x": 500, "y": 200 },
"data": {
"name": "Main Question",
"subEls": [
{ "kind": "title", "text": "What is your primary goal?" },
{
"kind": "question",
"kindOf": "single",
"options": [
{ "id": "opt_focus", "text": "Better focus", "emoji": "\ud83c\udfaf" },
{ "id": "opt_sleep", "text": "Better sleep", "emoji": "\ud83d\ude34" },
{ "id": "opt_stress", "text": "Less stress", "emoji": "\ud83e\uddd8" }
],
"layout": "list"
}
],
"size": { "w": 280, "h": 200 }
},
"width": 280,
"height": 200
}
],
"edges": [
{
"id": "edge_1_2",
"source": "step_1_intro",
"target": "step_2_question",
"sourceHandle": "output",
"targetHandle": "input",
"type": "deletable",
"animated": false,
"style": { "stroke": "#374151", "strokeWidth": 2 }
}
],
"timestamp": 1710400000000
}
Example 3: Conditional Routing
A step with conditional routing that sends users to different nodes based on their answer:
{
"type": "clarflow-canvas-nodes",
"nodes": [
{
"id": "step_q1",
"type": "quizStep",
"position": { "x": 100, "y": 200 },
"data": {
"name": "Router Question",
"subEls": [
{ "kind": "title", "text": "Are you a morning or evening person?" },
{
"kind": "question",
"kindOf": "single",
"options": [
{ "id": "opt_morning", "text": "Morning person", "emoji": "\ud83c\udf05" },
{ "id": "opt_evening", "text": "Evening person", "emoji": "\ud83c\udf19" }
]
}
],
"size": { "w": 280, "h": 200 },
"isConditional": true,
"conditionalQuestionIndex": 0
},
"width": 280,
"height": 200
},
{
"id": "step_morning",
"type": "quizStep",
"position": { "x": 500, "y": 100 },
"data": {
"name": "Morning Path",
"subEls": [
{ "kind": "title", "text": "Great! Early birds get the worm." }
],
"size": { "w": 280, "h": 200 }
},
"width": 280,
"height": 200
},
{
"id": "step_evening",
"type": "quizStep",
"position": { "x": 500, "y": 350 },
"data": {
"name": "Evening Path",
"subEls": [
{ "kind": "title", "text": "Night owls are creative!" }
],
"size": { "w": 280, "h": 200 }
},
"width": 280,
"height": 200
}
],
"edges": [
{
"id": "edge_morning",
"source": "step_q1",
"target": "step_morning",
"sourceHandle": "option-0",
"targetHandle": "input",
"type": "deletable",
"animated": false,
"style": { "stroke": "#374151", "strokeWidth": 2 }
},
{
"id": "edge_evening",
"source": "step_q1",
"target": "step_evening",
"sourceHandle": "option-1",
"targetHandle": "input",
"type": "deletable",
"animated": false,
"style": { "stroke": "#374151", "strokeWidth": 2 }
}
],
"timestamp": 1710400000000
}
option-0maps to the first option ("Morning person"),option-1to the second ("Evening person"). The index corresponds to the option's position in theoptionsarray.
Example 4: Full Flow with Email Capture, Loading, and Exit
A complete flow showcasing multiple sub-element types:
{
"type": "clarflow-canvas-nodes",
"nodes": [
{
"id": "step_welcome",
"type": "quizStep",
"position": { "x": 100, "y": 200 },
"data": {
"name": "Welcome",
"subEls": [
{ "kind": "image", "url": "https://example.com/logo.png", "alt": "Brand logo" },
{ "kind": "title", "text": "Find your perfect routine" },
{ "kind": "text", "text": "Answer a few quick questions and we'll build a personalized plan." }
],
"size": { "w": 280, "h": 200 },
"buttonText": "Start Quiz"
},
"width": 280,
"height": 200
},
{
"id": "step_q1",
"type": "quizStep",
"position": { "x": 500, "y": 200 },
"data": {
"name": "Goal Selection",
"subEls": [
{ "kind": "title", "text": "What's your #1 goal?" },
{
"kind": "question",
"kindOf": "single",
"options": [
{ "id": "opt_focus", "text": "Improve focus", "emoji": "\ud83c\udfaf", "subtext": "Stay on task longer" },
{ "id": "opt_energy", "text": "More energy", "emoji": "\u26a1", "subtext": "Feel less drained" },
{ "id": "opt_calm", "text": "Reduce anxiety", "emoji": "\ud83e\uddd8", "subtext": "Feel more at peace" }
],
"layout": "list",
"variableName": "primary_goal"
}
],
"size": { "w": 280, "h": 200 }
},
"width": 280,
"height": 200
},
{
"id": "step_email",
"type": "quizStep",
"position": { "x": 900, "y": 200 },
"data": {
"name": "Email Capture",
"subEls": [
{ "kind": "title", "text": "Almost done!" },
{ "kind": "text", "text": "Enter your email to get your personalized results." },
{
"kind": "input",
"label": "Email",
"placeholder": "you@example.com",
"inputType": "email",
"required": true,
"klaviyoEnabled": true,
"variableName": "email"
}
],
"size": { "w": 280, "h": 200 },
"buttonText": "Get My Results"
},
"width": 280,
"height": 200
},
{
"id": "step_loading",
"type": "quizStep",
"position": { "x": 1300, "y": 200 },
"data": {
"name": "Processing",
"subEls": [
{ "kind": "title", "text": "Building your plan..." },
{
"kind": "loading",
"seconds": 8,
"style": "steps",
"steps": [
{
"id": "ls_1",
"loadingText": "Analyzing your responses...",
"completedText": "Responses analyzed",
"seconds": 3
},
{
"id": "ls_2",
"loadingText": "Creating personalized plan...",
"completedText": "Plan created",
"seconds": 5
}
]
}
],
"size": { "w": 280, "h": 200 }
},
"width": 280,
"height": 200
},
{
"id": "exit_results",
"type": "exitNode",
"position": { "x": 1700, "y": 200 },
"data": {
"name": "Results Page",
"redirectUrl": "https://example.com/results",
"size": { "w": 280, "h": 200 }
},
"width": 280,
"height": 200
}
],
"edges": [
{
"id": "e1",
"source": "step_welcome",
"target": "step_q1",
"sourceHandle": "output",
"targetHandle": "input",
"type": "deletable",
"animated": false,
"style": { "stroke": "#374151", "strokeWidth": 2 }
},
{
"id": "e2",
"source": "step_q1",
"target": "step_email",
"sourceHandle": "output",
"targetHandle": "input",
"type": "deletable",
"animated": false,
"style": { "stroke": "#374151", "strokeWidth": 2 }
},
{
"id": "e3",
"source": "step_email",
"target": "step_loading",
"sourceHandle": "output",
"targetHandle": "input",
"type": "deletable",
"animated": false,
"style": { "stroke": "#374151", "strokeWidth": 2 }
},
{
"id": "e4",
"source": "step_loading",
"target": "exit_results",
"sourceHandle": "output",
"targetHandle": "input",
"type": "deletable",
"animated": false,
"style": { "stroke": "#374151", "strokeWidth": 2 }
}
],
"timestamp": 1710400000000
}