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 IDdisplayOrder(array) - Array of element IDs defining display order
Important: The displayOrder array acts as both a filter and sequencer:
- Only elements listed in
displayOrderwill be shown - Elements not in
displayOrderremain 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:
options- Reusable option lists for SelectOne/SelectMany questionsordinal-scale- Reusable labeled scales for OrdinalScale questionsinterval-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 purposesdata- 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:
| Type | Category | Description |
|---|---|---|
Markdown | Content | Display formatted text and content |
FlowControl | Control | Conditional logic and navigation |
String | Question | Text input (single or multi-line) |
Number | Question | Numeric input |
Date | Question | Date picker |
Boolean | Question | Yes/No checkbox |
SelectOne | Question | Single choice from options (radio buttons) |
SelectMany | Question | Multiple choices from options (checkboxes) |
IntervalScale | Question | Numeric rating scale (e.g., 0-10 for NPS, 1-5 for satisfaction) |
OrdinalScale | Question | Labeled rating scale (e.g., Strongly Disagree to Strongly Agree) |
Payment | Question | Payment 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 immediatelypage-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 usersrequired(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 formatdefaultValue(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 valuemax(number, optional) - Maximum allowed valuestep(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")
- ISO date string (e.g.,
max(string, optional) - Latest selectable date. Same format options asmin
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 withvalue(number) andcurrency(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 comparisonsfact(string) - The element reference in the format ‘collection-id/element-id’ to read the value fromoperator(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 (nocompareneeded)
String:
"eq"- Equal to"contains"- String contains value"exists"- Has a value (nocompareneeded)
Boolean:
"true"- Value is true (nocompareneeded)"exists"- Has a value (nocompareneeded)
SelectOne:
"eq"- Equal to specific option"in"- Is one of multiple options (compare value should be SelectMany format)"exists"- Has a value (nocompareneeded)
SelectMany:
"eq"- Exactly matches set of options"exists"- Has at least one value (nocompareneeded)
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 resultname(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
| Version | Date | Changes |
|---|---|---|
| 1.0 | 2025-11-15 | Payment element |
| 0.5 | 2025-01-24 | Initial public release |