Skip to main content
A survey definition is a JSON document. The dashboard editor, the REST API, the MCP server and Kai all read and write this format, and Gleap validates it the same way everywhere.

Top level

2
Always 2.
'card' | 'full'
required
card (compact card, bottom sheet on phones) or full (Typeform-style full screen). The legacy values survey and survey_full are deprecated aliases of card and full.
string
required
Locale of the default texts, for example en.
Welcome
Optional welcome screen before the first question — see Welcome screen.
Block[]
required
The questions in display order. 1–60 blocks.
Ending[]
required
Thank-you screens. 1–10 endings.
HiddenField[]
Hidden fields: values passed in by the SDK, URL parameters or personal links.
boolean
Show the progress bar.
boolean
Full screen: allow going back to earlier questions.
Theme
The survey’s design — see Theme. Every field is optional; unset fields use your widget settings.
object
{ userId? } — the teammate shown in the card’s sender row. Defaults to the project’s default sender.

Localized texts

Every text (title, description, choice label, …) is a map of locale to text:
Titles may contain tokens that are replaced for each respondent: {{contact.first_name}}, {{field.<hidden field key>}} and {{answer.<earlier answer key>}}.

Theme

theme sets the survey’s design. Every field is optional: a field you leave out uses your project’s widget settings (widget colour, widget logo, widget light/dark setting), so a survey without theme looks like your widget.
hex
Buttons, selected choices and the progress bar. Default: the widget colour.
hex
Text on brand-filled buttons and selected choices. Default: black or white, whichever contrasts better with brandColor.
'auto' | 'light' | 'dark'
Light or dark surfaces. auto (default) follows the widget’s light/dark setting.
'tint' | 'plain' | 'color' | 'image'
The backdrop of the full-screen format and the survey page. tint (default) is a light tint of the brand colour, plain has no tint, color uses backgroundColor, image uses backgroundImage. The card and the bottom sheet always stay a solid light or dark surface.
hex
Backdrop colour for background: "color".
string
Backdrop image for background: "image". Must be an https URL without quotes, parentheses or whitespace.
number
0–0.8. Black darkening over a colour or image backdrop so text stays readable. Default 0.3 for images, 0 for colours.
hex
Text on the full-screen and survey page backdrop. Default: picked by contrast — light text on dark colours and images.
'sharp' | 'rounded' | 'pill'
Corner radius of choices, buttons and inputs. Default rounded.
string
https URL of the logo. Default: the widget logo.
Show the logo — top left in the full-screen format and in the survey page header. Default true.
Where each field applies: A brand colour, a photo backdrop darkened for legibility and pill-shaped choices:
Colours are hex (#0f766e, #fff, or with alpha). An invalid backgroundImage or backgroundColor falls back to tint.

Resolved theme

The SDK and the survey page load a survey through GET /surveys/{id} and GET /surveys/page/{slug}. Both return theme with the widget defaults already filled in:

Blocks

string
required
Stable id, unique across blocks and endings (letters, digits, _, -; max 40). Jumps point to it.
string
required
Stable answer key, unique, ^[a-z][a-z0-9_]{1,40}$ — for example nps_score. Webhooks, exports, the API and filters use it. Don’t rename it on a live survey.
string
required
One of the types below.
Localized
required
The question.
Localized
Help text under the question.
boolean
required
Whether the question must be answered. statement blocks are never required; a required consent must be ticked.
string | number | boolean | string[]
Preselected answer the question starts with: a choice id (single), choice ids (multi), a scale value, true / false (yesno) or text. The respondent still confirms it; nothing is saved before. Must be a valid answer of the block (invalid_default otherwise).
Jump[]
required
Logic rules, checked top to bottom; the first match wins. Max 20. [] for none.
string | null
required
”All other cases go to”: a later block id or an ending id. null = the next block in order (after the last block, the first ending).
Type-specific options: Choices are { "id": "too_expensive", "label": { "en": "Too expensive" } }. Choice ids are unique within the block; answers and conditions use the id, never the label. Any block can also carry media: { type: 'image' | 'video', url }.

Logic: jumps and conditions

A jump is { "if": <condition>, "go": "<block id or ending id>" }. go must point to a later block or to an ending — jumps never go backwards, so there are no loops. Where a person goes after answering:
  1. the first jump whose condition matches,
  2. otherwise next,
  3. otherwise the next block in order,
  4. after the last block, the first ending.

Conditions on the answer

Conditions on the person

The editor lists person conditions before answer conditions; put them first when you write definitions yourself.

Welcome screen

welcome adds an intro screen before the first question, like Typeform’s. It is not a block: it has no answer key, no logic and does not count as a question. In full screen and on the survey page it shows centred, with the optional image, a large title, the description, the Start button (“press Enter ↵”) and the time estimate. The card and the phone bottom sheet show a compact version (title, description, Start). People who come back to finish a response they started skip it.
boolean
required
true shows the welcome screen. false keeps the texts without showing them.
Localized
required
The heading. Required when enabled is true; may contain tokens like {{contact.first_name}}.
Localized
One or two sentences under the title.
Localized
Start button text. Default “Start”.
object
{ type: "image", url } — an image above the title (full screen and survey page). https only.
boolean
Show “Takes about N minutes” (about 15 seconds per question). Default true.
Use the welcome screen rather than a leading statement block: question numbers, the step funnel and “starts” (first answer) stay the same, and the analytics report how many people pressed Start.

Endings

string
required
Unique across blocks and endings.
Localized
required
Thank-you text.
Localized
object
{ label: Localized, url } — a call to action.
string
Survey page: redirect after this ending.
number
In the app: close after N seconds.
string
Tag added to the contact who reaches this ending.

Hidden fields

Values come from showSurvey(id, { fields }), URL parameters on the survey page (?plan=pro) and personal links. They are stored on the response under fields.

Validation

Gleap rejects a definition at publish time when:
  • ids or keys are missing, malformed or duplicated (ids are unique across blocks and endings; choice ids within a block),
  • a go or next points to an unknown id or to an earlier block (loop),
  • a block can’t be reached from the first block, or a path doesn’t end in an ending,
  • a condition doesn’t fit its block type (for example includes on a single block, between: [0, 11] on NPS, or a value condition on a consent),
  • scaleStyle: 'compact' is set on anything but NPS, or a defaultValue is not a valid answer of its block,
  • a limit is exceeded: 60 blocks, 10 endings, 25 choices per block, 20 jumps per block.
Saving a draft accepts invalid logic and returns the problems as warnings; publishing returns 422 with the same list. Use POST /validate to check a definition without saving. Errors look like:

Example: NPS with follow-ups

Detractors (0–6), passives (7–8) and promoters (9–10) each get their own follow-up question.

Example: cancellation survey

People who pick “Too expensive” are asked what price would have worked; everyone else goes straight to “What could we have done better?”. People who agree to be contacted are tagged.

Responses

Answers are stored keyed by the block’s key:
channel is one of web, ios, android, page, email, embed, conversation, api.