Open Survey Format

An open, JSON-based format for defining surveys and forms. Used by YourOpinion.is and free for anyone to implement.

Current version: 1.0 (Published: 2025-11-15)

Download Specification

  • JSON Schema - Machine-readable format definition
  • Markdown - Human-readable documentation (optimized for LLMs)

Introduction

The Open Survey Format provides a standardized way to define surveys, questionnaires, and forms as JSON objects. This format enables:

  • Portability - Move surveys between different platforms and tools
  • Version control - Track survey changes using standard diff tools
  • Programmatic generation - Create surveys dynamically from code
  • LLM compatibility - AI assistants can easily create and modify surveys

Core Concepts

Collections (Pages)

Surveys are organized into collections, which function as pages or sections. Each survey must contain at least one collection.

Elements

Individual components within a collection, such as:

  • Questions (text input, multiple choice, ratings, etc.)
  • Content blocks (markdown text, images)
  • Control flow elements (conditional logic, page jumps)

Assets

Reusable components that can be referenced by multiple questions:

  • Option lists (for select questions)
  • Rating scales
  • Shared resources

Document Structure

A survey is represented as a JSON object with this structure:

{
    "$schema": "https://youropinion.is/json-schema/1.0",
    "$readme": "https://youropinion.is/docs/survey-format.md",
    "collections": {
        "collection-id-1": {
            "name": "Collection Name",
            "elements": {
                "element-id-1": {
                    "type": "Markdown",
                    "data": {
                        /* Element-specific data */
                    }
                }
            },
            "displayOrder": ["element-id-1"]
        }
    },
    "displayOrder": ["collection-id-1"],
    "assets": {
        "asset-id-1": {
            "type": "options",
            "data": {
                /* Asset-specific data */
            }
        }
    }
}

Top-Level Properties

$schema (optional)

URL to the JSON Schema definition for validation:

"$schema": "https://youropinion.is/json-schema/1.0"

$readme (optional)

URL to human-readable documentation:

"$readme": "https://youropinion.is/docs/survey-format.md"

collections (required)

Object containing survey pages/sections. Each key is a unique collection ID:

"collections": {
    "welcome": {
        "name": "Welcome Page",
        "elements": { /* ... */ },
        "displayOrder": ["intro", "consent"]
    },
    "questions": {
        "name": "Main Questions",
        "elements": { /* ... */ },
        "displayOrder": ["q1", "q2", "q3"]
    }
}

Collection Properties:

  • name (string, optional) - Human-readable collection name (not displayed to users, used for reference)
  • condition (object, optional) - Conditional logic to determine if this collection should be shown (see Conditional Logic section)
  • elements (object) - Contains the elements for this collection. Each key is a unique element ID
  • displayOrder (array) - Array of element IDs defining display order

Important: The displayOrder array acts as both a filter and sequencer:

  • Only elements listed in displayOrder will be shown
  • Elements not in displayOrder remain in the definition but are hidden
  • Including a non-existent element ID will cause an error

displayOrder (required)

Array specifying the order of collections:

"displayOrder": ["welcome", "questions", "thank-you"]

Same filtering rules apply: only collections listed here will be shown.

assets (optional)

Reusable components referenced by multiple elements. Assets reduce duplication and make surveys easier to maintain.

Asset Types:

  1. options - Reusable option lists for SelectOne/SelectMany questions
  2. ordinal-scale - Reusable labeled scales for OrdinalScale questions
  3. interval-scale - Reusable numeric scales for IntervalScale questions

Example - Options asset:

"assets": {
    "color-options": {
        "type": "options",
        "name": "Color Options",
        "data": {
            "options": {
                "red": { "label": "Red" },
                "blue": { "label": "Blue" },
                "green": { "label": "Green" },
                "yellow": { "label": "Yellow" }
            },
            "displayOrder": ["red", "blue", "green", "yellow"]
        }
    },
    "satisfaction-scale": {
        "type": "ordinal-scale",
        "name": "Standard Satisfaction Scale",
        "data": {
            "labels": {
                "1": "Very Dissatisfied",
                "2": "Dissatisfied",
                "3": "Neutral",
                "4": "Satisfied",
                "5": "Very Satisfied"
            }
        }
    },
    "nps-scale": {
        "type": "interval-scale",
        "name": "Net Promoter Score Scale",
        "data": {
            "start": 0,
            "end": 10,
            "labels": {
                "start": "Not likely",
                "end": "Very likely"
            }
        }
    }
}

All asset types require:

  • type - Asset type: "options", "ordinal-scale", or "interval-scale"
  • name - Human-readable name for documentation purposes
  • data - Type-specific data structure

Assets can be referenced using JSON references (see JSON References section).


Element Types

Elements are the building blocks of your survey. Each element has a type and data property. All elements can optionally include an extensions object for custom metadata (not used by the standard renderer).

Available element types:

TypeCategoryDescription
MarkdownContentDisplay formatted text and content
FlowControlControlConditional logic and navigation
StringQuestionText input (single or multi-line)
NumberQuestionNumeric input
DateQuestionDate picker
BooleanQuestionYes/No checkbox
SelectOneQuestionSingle choice from options (radio buttons)
SelectManyQuestionMultiple choices from options (checkboxes)
IntervalScaleQuestionNumeric rating scale (e.g., 0-10 for NPS, 1-5 for satisfaction)
OrdinalScaleQuestionLabeled rating scale (e.g., Strongly Disagree to Strongly Agree)
PaymentQuestionPayment processing element

Content Elements

Markdown

Display formatted text, headings, lists, and other content using Markdown syntax.

{
    "type": "Markdown",
    "data": {
        "markdown": "# Welcome!\n\nThank you for taking our survey.\n\n- Please answer honestly\n- All responses are anonymous"
    }
}

Supported Markdown:

  • Headings (#, ##, ###)
  • Lists (ordered and unordered)
  • Bold (**text**) and italic (*text*)
  • Links ([text](url))
  • Code blocks

FlowControl

Control survey flow with conditional logic and navigation.

{
    "type": "FlowControl",
    "data": {
        "condition": {
            /* Optional - See Conditional Logic section */
        },
        "action": {
            "type": "survey-finish" // Options: "survey-finish" or "page-finish"
        }
    }
}

Action Types:

  • survey-finish - Complete the survey immediately
  • page-finish - End current page and move to next

For detailed information on condition structure and examples, see the Conditional Logic section below.


Question Types

All questions share these common properties:

{
    "type": "QuestionType",
    "data": {
        "label": "Your question text here",
        "required": "yes", // Optional: "yes", "no", or "suggested" (default is "no")
        "markdown": "Help text" // Optional: additional context in Markdown
    }
}

Common properties:

  • label (string, required) - The question text shown to users
  • required (string, optional) - Whether answer is required: "yes", "no", or "suggested" (shows as optional but encouraged)
  • markdown (string, optional) - Additional help text or description in Markdown format
  • defaultValue (any, optional) - Pre-filled value for the question

String

Single or multi-line text input.

{
    "type": "String",
    "data": {
        "label": "What is your name?",
        "placeholder": "Enter your full name", // Optional
        "multiline": false, // Optional: true for textarea
        "required": "yes"
    }
}

Number

Numeric input with optional validation.

{
    "type": "Number",
    "data": {
        "label": "How many employees?",
        "min": 1, // Optional: minimum value
        "max": 1000, // Optional: maximum value
        "step": 1, // Optional: increment size (default: 1)
        "required": "yes"
    }
}

Properties:

  • min (number, optional) - Minimum allowed value
  • max (number, optional) - Maximum allowed value
  • step (number, optional) - Increment size for input validation (default: 1)

Date

Date picker input with optional accuracy level and min/max constraints.

{
    "type": "Date",
    "data": {
        "label": "When did you join?",
        "required": "suggested",
        "accuracy": "day", // Optional: "day", "month", or "year" (default: "day")
        "min": "2020-01-01", // Optional: earliest selectable date
        "max": "2025-12-31" // Optional: latest selectable date
    }
}

Properties:

  • accuracy (string, optional) - Date precision level:
    • "day" - Full date picker with calendar (default)
    • "month" - Month and year dropdown selection
    • "year" - Year-only dropdown selection
  • min (string, optional) - Earliest selectable date. Can be:
    • ISO date string (e.g., "2020-01-01")
    • Relative time string (e.g., "+ 3 months", "now", "- 1 year")
  • max (string, optional) - Latest selectable date. Same format options as min

Boolean

Yes/No checkbox. Default value is false (unchecked).

{
    "type": "Boolean",
    "data": {
        "label": "Terms and Conditions",
        "description": "I agree to the terms and conditions" // Shown next to checkbox
    }
}

SelectOne

Multiple choice question (radio buttons) - users select one option.

{
    "type": "SelectOne",
    "data": {
        "label": "What is your favorite color?",
        "required": "yes",
        "options": {
            "options": {
                "red": { "label": "Red" },
                "blue": { "label": "Blue" },
                "green": { "label": "Green" }
            },
            "displayOrder": ["red", "blue", "green"]
        }
    }
}

Using asset references:

{
    "type": "SelectOne",
    "data": {
        "label": "Pick a color",
        "options": { "$ref": "#/assets/color-options/data" }
    }
}

SelectMany

Multiple choice question (checkboxes) - users can select multiple options.

{
    "type": "SelectMany",
    "data": {
        "label": "Which operating systems do you use?",
        "required": "yes",
        "options": {
            "options": {
                "windows": { "label": "Windows" },
                "macos": { "label": "macOS" },
                "linux": { "label": "Linux" }
            },
            "displayOrder": ["windows", "macos", "linux"]
        },
        "other": true, // Optional: add "Other" option with text input
        "minSelections": 1, // Optional: minimum number of selections required
        "maxSelections": 3 // Optional: maximum number of selections allowed
    }
}

IntervalScale

Numeric rating scale with a defined range (e.g., 0-10 for NPS, 1-5 for satisfaction). Commonly used for:

  • Net Promoter Score (NPS): 0-10 scale
  • Satisfaction ratings: 1-5 or 1-7 scale
  • Likelihood questions: 0-10 scale
{
    "type": "IntervalScale",
    "data": {
        "label": "How likely are you to recommend us to a friend?",
        "required": "yes",
        "scale": {
            "start": 0, // Starting number of the scale
            "end": 10, // Ending number of the scale
            "labels": {
                // Labels for start and end points (required)
                "start": "Not at all likely",
                "end": "Extremely likely"
            }
        }
    }
}

Common use cases:

// Net Promoter Score (NPS)
{
    "type": "IntervalScale",
    "data": {
        "label": "How likely are you to recommend us?",
        "scale": {
            "start": 0,
            "end": 10,
            "labels": {
                "start": "Not likely",
                "end": "Very likely"
            }
        }
    }
}

// 5-point satisfaction scale
{
    "type": "IntervalScale",
    "data": {
        "label": "How satisfied are you with our service?",
        "scale": {
            "start": 1,
            "end": 5,
            "labels": {
                "start": "Very dissatisfied",
                "end": "Very satisfied"
            }
        }
    }
}

Display: Shows a horizontal scale with clickable numbers. Labels appear below the start and end points.

OrdinalScale

Labeled rating scale where each point has a custom label. Ideal for:

  • Likert scales with specific wording for each point
  • Agreement scales (Strongly Disagree → Strongly Agree)
  • Frequency scales (Never → Always)
  • Custom rating scales with meaningful labels
{
    "type": "OrdinalScale",
    "data": {
        "label": "How satisfied are you with your position?",
        "required": "yes",
        "scale": {
            "labels": {
                "1": "Very Dissatisfied",
                "2": "Dissatisfied",
                "3": "Neutral",
                "4": "Satisfied",
                "5": "Very Satisfied"
            }
        }
    }
}

Common use cases:

// Likert agreement scale
{
    "type": "OrdinalScale",
    "data": {
        "label": "The product met my expectations",
        "scale": {
            "labels": {
                "1": "Strongly Disagree",
                "2": "Disagree",
                "3": "Neither Agree nor Disagree",
                "4": "Agree",
                "5": "Strongly Agree"
            }
        }
    }
}

// Frequency scale
{
    "type": "OrdinalScale",
    "data": {
        "label": "How often do you use our product?",
        "scale": {
            "labels": {
                "1": "Never",
                "2": "Rarely",
                "3": "Sometimes",
                "4": "Often",
                "5": "Always"
            }
        }
    }
}

Display: Shows labeled buttons or options. Each label is displayed fully, making the scale self-explanatory.

IntervalScale vs OrdinalScale:

  • Use IntervalScale when the numbers themselves have meaning (0-10, 1-5)
  • Use OrdinalScale when you need custom labels for each point
  • IntervalScale is more compact; OrdinalScale is more descriptive

Payment

Collect payment information and process transactions. Integrates with payment providers configured in your survey settings.

{
    "type": "Payment",
    "data": {
        "label": "Payment",
        "required": "yes",
        "amount": {
            "value": 29.99,
            "currency": "USD"
        },
        "captureMethod": "automatic" // Optional: "immediate", "manual", or "automatic"
    }
}

Properties:

  • amount (object, required) - Payment amount with value (number) and currency (3-letter code, e.g., “USD”, “EUR”, “GBP”)
  • captureMethod (string, optional) - When to capture payment:
    • "immediate" - Capture payment immediately when survey is submitted
    • "manual" - Require manual capture through your payment dashboard
    • "automatic" - Automatically capture when survey is completed (default)

Note: Payment processing requires a payment provider to be configured in your channel settings. Supported currencies and payment methods depend on your provider configuration.


JSON References

Reduce duplication by referencing reusable components. References use this format:

{ "$ref": "[<location>]#<path>" }
  • location - URL to source document (empty = current document)
  • path - Path from document root using / separators

Example - Inline duplication:

{
    "question-1": {
        "type": "SelectOne",
        "data": {
            "label": "Favorite color?",
            "options": {
                "options": {
                    "red": { "label": "Red" },
                    "blue": { "label": "Blue" }
                },
                "displayOrder": ["red", "blue"]
            }
        }
    },
    "question-2": {
        "type": "SelectOne",
        "data": {
            "label": "Least favorite color?",
            "options": {
                "options": {
                    "red": { "label": "Red" },
                    "blue": { "label": "Blue" }
                },
                "displayOrder": ["red", "blue"]
            }
        }
    }
}

Better - Using assets:

{
    "collections": {
        "main": {
            "elements": {
                "question-1": {
                    "type": "SelectOne",
                    "data": {
                        "label": "Favorite color?",
                        "options": { "$ref": "#/assets/colors/data" }
                    }
                },
                "question-2": {
                    "type": "SelectOne",
                    "data": {
                        "label": "Least favorite color?",
                        "options": { "$ref": "#/assets/colors/data" }
                    }
                }
            },
            "displayOrder": ["question-1", "question-2"]
        }
    },
    "assets": {
        "colors": {
            "type": "options",
            "data": {
                "options": {
                    "red": { "label": "Red" },
                    "blue": { "label": "Blue" }
                },
                "displayOrder": ["red", "blue"]
            }
        }
    }
}

Benefits:

  • Single source of truth
  • Easier maintenance
  • Better version control diffs
  • Smaller file size

Conditional Logic

Conditional logic allows you to create dynamic surveys that adapt based on user responses. Conditions can be used in two ways:

1. Collection-Level Conditions

Show or hide entire pages based on conditions. If a collection’s condition evaluates to false, the entire page is skipped and execution moves to the next collection.

{
    "collections": {
        "follow-up": {
            "name": "Follow-up Questions",
            "condition": {
                "type": "condition",
                "fact": "questions/nps-score",
                "operator": "lt",
                "compare": { "value": 7 }
            }
            "elements": {
                /* ... */
            },
            "displayOrder": ["question-1"]
        }
    }
}

In this example, the “follow-up” page only shows if the NPS score is less than 7.

2. FlowControl Elements

Control survey flow within a page using FlowControl elements. When the condition evaluates to true, the specified action is executed.

Available actions:

  • survey-finish - Complete the survey immediately (skip all remaining pages)
  • page-finish - End the current page and move to the next (skip remaining elements on current page)
{
    "type": "FlowControl",
    "data": {
        "condition": {
            "type": "condition",
            "fact": "satisfaction",
            "operator": "eq",
            "compare": { "value": "very-satisfied" }
        },
        "action": {
            "type": "page-finish"
        }
    }
}

Note: If no condition is provided, the action always executes when the element is reached.

Condition Structure

Conditions are defined as a binary tree with two node types:

1. Comparison Conditions

Compare a fact (survey response) against a value or another fact.

{
    "type": "condition",
    "fact": "questions/age",
    "operator": "gt",
    "compare": { "value": 18 }
}

Properties:

  • type - Always "condition" for comparisons
  • fact (string) - The element reference in the format ‘collection-id/element-id’ to read the value from
  • operator (string) - Comparison operator (see operators below)
  • compare (object, optional) - What to compare against:
    • { "value": <any> } - Compare to a literal value
    • { "fact": "<collection-id/element-id>" } - Compare to another element’s value
    • Not required for operators like "exists" or "true"
  • not (boolean, optional) - Invert the result (default: false)

Supported operators by element type:

Number, IntervalScale, OrdinalScale, Date:

  • "eq" - Equal to
  • "gt" - Greater than
  • "gte" - Greater than or equal to
  • "lt" - Less than
  • "lte" - Less than or equal to
  • "exists" - Has a value (no compare needed)

String:

  • "eq" - Equal to
  • "contains" - String contains value
  • "exists" - Has a value (no compare needed)

Boolean:

  • "true" - Value is true (no compare needed)
  • "exists" - Has a value (no compare needed)

SelectOne:

  • "eq" - Equal to specific option
  • "in" - Is one of multiple options (compare value should be SelectMany format)
  • "exists" - Has a value (no compare needed)

SelectMany:

  • "eq" - Exactly matches set of options
  • "exists" - Has at least one value (no compare needed)

2. Chain Conditions

Combine multiple conditions using logical operators.

{
    "type": "all",
    "items": [
        {
            "type": "condition",
            "fact": "questions/age",
            "operator": "gt",
            "compare": { "value": 18 }
        },
        {
            "type": "condition",
            "fact": "questions/country",
            "operator": "eq",
            "compare": { "value": "US" }
        }
    ]
}

Properties:

  • type - Logical operator:
    • "all" - All conditions must be true (AND logic)
    • "any" - At least one condition must be true (OR logic)
  • items (array) - Array of conditions (can be comparisons or nested chains)
  • not (boolean, optional) - Invert the entire chain result
  • name (string, optional) - Human-readable label for documentation

Examples

Skip survey for satisfied customers

{
    "type": "FlowControl",
    "data": {
        "condition": {
            "type": "condition",
            "fact": "questions/nps",
            "operator": "gte",
            "compare": { "value": 9 }
        },
        "action": {
            "type": "survey-finish"
        }
    }
}

Show page only for specific age range

{
    "collections": {
        "teen-questions": {
            "name": "Questions for Teens",
            "condition": {
                "type": "all",
                "items": [
                    {
                        "type": "condition",
                        "fact": "questions/age",
                        "operator": "gte",
                        "compare": { "value": 13 }
                    },
                    {
                        "type": "condition",
                        "fact": "questions/age",
                        "operator": "lt",
                        "compare": { "value": 20 }
                    }
                ]
            },
            "elements": {
                /* ... */
            }
        }
    }
}

Complex nested conditions

{
    "type": "any",
    "name": "Premium users or high spenders",
    "items": [
        {
            "type": "condition",
            "fact": "questions/membership",
            "operator": "eq",
            "compare": { "value": "premium" }
        },
        {
            "type": "all",
            "items": [
                {
                    "type": "condition",
                    "fact": "questions/total-spent",
                    "operator": "gt",
                    "compare": { "value": 1000 }
                },
                {
                    "type": "condition",
                    "fact": "questions/active-months",
                    "operator": "gte",
                    "compare": { "value": 6 }
                }
            ]
        }
    ]
}

Comparing two facts

{
    "type": "condition",
    "fact": "questions/current-salary",
    "operator": "gt",
    "compare": { "fact": "questions/desired-salary" }
}

Using the not property

{
    "type": "condition",
    "fact": "questions/email-consent",
    "operator": "true",
    "not": true // Inverts result: true when email-consent is NOT true
}

Version History

VersionDateChanges
1.02025-11-15Payment element
0.52025-01-24Initial public release