Skip to content
Join our Slack

Form Blueprint

The Form Blueprint is the JSON document that describes a whole form: its name, Hidden Values, logic rules and Calculated values, pages and their nodes, and the form-level settings, properties, access, and styles blocks.

One shape serves both directions. The document you send is the Draft Document; the document you read back, with IDs and defaults filled in, is the Canonical Blueprint. A document you pull can be pushed back unchanged.

This page covers the rules for the whole document. Pages, Nodes, and Settings, properties, access, and styles cover each part.

Every example in this section comes from one fixture, the Docs Example Form: a form named “Event registration” with the public ID Ev3ntReg01, owned by the organization “Acme Events”. It has an intro page, one question page, and one ending, and declares one Hidden Value:

The Docs Example Form draft document
{
"public_id": "Ev3ntReg01",
"name": "Event registration",
"is_template": false,
"hidden_values": [
{
"key": "utm_source",
"default_value": null
}
],
"logic_rules": [],
"calculated_values": [],
"pages": [
{
"public_id": "pIn1",
"name": "Welcome",
"type": "intro",
"is_hidden": false,
"is_entrypoint": true,
"is_auto_jump_next_page": false,
"nodes": [
{
"public_id": "nWlc",
"parent_node_id": null,
"column_index": null,
"sort_order": 1,
"is_hidden": false,
"type": "text",
"meta": {
"content": "<p>Join us for the Acme Events community meetup.</p>"
}
},
{
"public_id": "nSt1",
"parent_node_id": null,
"column_index": null,
"sort_order": 2,
"is_hidden": false,
"type": "actions",
"meta": {
"label": "Start",
"is_page_actions_override": false,
"back_button_label": "Back"
}
}
]
},
{
"public_id": "pQ01",
"name": "Your details",
"type": "normal",
"is_hidden": false,
"is_entrypoint": false,
"is_auto_jump_next_page": false,
"nodes": [
{
"public_id": "nNam",
"parent_node_id": null,
"column_index": null,
"sort_order": 1,
"is_hidden": false,
"type": "text_input_short",
"meta": {
"question": "<span>Full name</span>",
"placeholder": "Jane Doe",
"validation": {
"required": true
}
}
},
{
"public_id": "nEml",
"parent_node_id": null,
"column_index": null,
"sort_order": 2,
"is_hidden": false,
"type": "text_input_email",
"meta": {
"question": "<span>Email address</span>",
"placeholder": "[email protected]",
"validation": {
"required": true
}
}
},
{
"public_id": "nTck",
"parent_node_id": null,
"column_index": null,
"sort_order": 3,
"is_hidden": false,
"type": "multiple_choice",
"meta": {
"question": "<span>Ticket type</span>",
"select_options_data": [
{
"value": "general",
"label": "General",
"label_formatted": "<span>General</span>"
},
{
"value": "student",
"label": "Student",
"label_formatted": "<span>Student</span>"
},
{
"value": "speaker",
"label": "Speaker",
"label_formatted": "<span>Speaker</span>"
}
],
"select_options_randomize": false,
"select_options_selection": "single",
"multiple_choice_variant": "alphabet",
"validation": {
"required": true
}
}
},
{
"public_id": "nDie",
"parent_node_id": null,
"column_index": null,
"sort_order": 4,
"is_hidden": false,
"type": "checkbox_choice",
"meta": {
"question": "<span>Dietary needs</span>",
"select_options_data": [
{
"value": "vegetarian",
"label": "Vegetarian",
"label_formatted": "<span>Vegetarian</span>"
},
{
"value": "vegan",
"label": "Vegan",
"label_formatted": "<span>Vegan</span>"
},
{
"value": "gluten_free",
"label": "Gluten-free",
"label_formatted": "<span>Gluten-free</span>"
}
],
"select_options_randomize": false,
"select_options_selection": "single",
"select_options_style": "checkbox",
"validation": {
"required": false
}
}
},
{
"public_id": "nCmp",
"parent_node_id": null,
"column_index": null,
"sort_order": 5,
"is_hidden": true,
"type": "text_input_short",
"meta": {
"question": "<span>Company</span>",
"validation": {
"required": false
}
}
},
{
"public_id": "nSub",
"parent_node_id": null,
"column_index": null,
"sort_order": 6,
"is_hidden": false,
"type": "actions",
"meta": {
"label": "Register",
"is_page_actions_override": false,
"back_button_label": "Back"
}
}
]
},
{
"public_id": "pFn1",
"name": "Thank you",
"type": "final",
"is_hidden": false,
"is_entrypoint": false,
"is_auto_jump_next_page": false,
"nodes": [
{
"public_id": "nThx",
"parent_node_id": null,
"column_index": null,
"sort_order": 1,
"is_hidden": false,
"type": "thank_you",
"meta": {
"thank_you_title": "<h1 style=\"text-align: center\">See you there!</h1>",
"thank_you_description": "<p style=\"text-align: center\">We sent a confirmation to your email address.</p>",
"thank_you_icon": "check_animated"
}
}
]
}
],
"settings": {
"email_notifications_enabled_owner": 1,
"email_notifications_enabled_respondent": 0,
"email_notifications_recipients_owner": [
],
"email_notifications_recipient_respondent": null
},
"properties": {
"show_branding": 1,
"auto_jump_next_page": 1,
"allow_back_navigation": 1,
"meta_title": null,
"meta_description": null,
"meta_index": 1,
"meta_favicon_url": null,
"meta_ogimage_url": null,
"partial_response_capture_enabled": 0
},
"access": {
"is_closed": 0,
"close_at": null,
"open_at": null,
"duplicate_response_rule": null
},
"styles": {
"theme_id": null,
"override_properties": null
}
}

Every top-level field is listed in the request body of Replace the draft document.

Every form, page, and node has a public ID from the alphabet A-Z, a-z, 0-9, _, and -. Form IDs are 10 characters long; page and node IDs are 4.

  • Page and node IDs are optional on write. A supplied ID is kept and must be unique within the form; a missing ID is minted by the server.
  • IDs never change once assigned, and every read returns every ID.
  • Reference fields such as parent_node_id use public IDs. Database IDs never appear in the document.
  • Logic rules, their conditions and actions, and Calculated values carry IDs you choose: 1 to 22 characters from the same alphabet. The server never mints these.
  • The form’s public_id is assigned on create and is read-only afterwards.

A draft write matches pages and nodes by ID, so persist the IDs on your side. Submissions record which page and node each answer belongs to, and keeping an ID through a write keeps a question’s answer history attached to it.

What you sendWhat happens
An ID the draft holdsThe item is updated in place
No IDThe item is created, and the minted ID comes back in the response
An ID the draft removed earlierThe removed item is restored under that ID
An ID the form has never heldThe item is created under that ID

A minted ID never reuses an ID the form has held, including removed items, so it cannot collide with an item a later write restores.

An omitted optional key that has a product default receives it. The server stores the default at write time and every read returns it. A key without a default, such as a question’s placeholder, stays absent until you set it. Values you write explicitly are never rewritten. settings and properties keys are the exception to defaulting on a draft write: a key you leave out keeps its stored value, and only a create fills in the default. Defaults can change between Contract Revisions; each change is announced in the API changelog and reaches only documents that omit the key, on their next write.

The server resolves three page and node fields itself:

  • sort_order is renumbered from array order.
  • is_entrypoint is always the first page. Sending true on any other page is refused, as described in Pages.
  • is_auto_jump_next_page is derived from each page’s nodes and ignored on write, as described in Pages.

Every read, and the response to every successful write, returns the Canonical Blueprint:

  • Every page and node carries its public_id.
  • Sort orders are renumbered from 1 within each parent and column.
  • Every node meta includes its defaulted keys.
  • Every page carries is_hidden, is_entrypoint, and is_auto_jump_next_page.
  • Read-only fields are present and hidden fields are absent.

A draft can describe a form that cannot be published yet. That is not an error: call validate to see what still blocks publishing, reported as Publish Issues.

Booleans inside settings, properties, and access read as 1, 0, or null, and accept true, false, 1, or 0 on write. Page and node flags (is_hidden, is_entrypoint, is_auto_jump_next_page) and the form’s is_template are plain JSON booleans in both directions.

Read-only fields appear on every read. On write they are accepted and ignored, so a pulled document stays pushable without edits.

  • The form public_id.
  • is_template, which is honored only when a form is created.
  • The whole access block, owned by the close, reopen, and access operations. It is honored on create.
  • styles.theme_name and styles.override_properties. styles.theme_id is writable; see Styles.
  • properties.show_branding for Free organizations, where branding is always shown.
  • Each page’s is_auto_jump_next_page.

These fields never appear in the document, and sending one is rejected as an unknown key:

  • source_template_id
  • settings.email_notifications_data_owner and settings.email_notifications_data_respondent
  • settings.email_notifications_custom_template_owner and settings.email_notifications_custom_template_respondent
  • styles.override_properties.metadata
  • every database ID

The server is strict. An unknown key, node type, or enum value is rejected with 400 INVALID_INPUT, naming the JSON path of the value and, for node types and enum values, the accepted values.

Your client should be tolerant: keep fields you do not recognize and send them back, because a later Contract Revision may add them. The API changelog describes how the contract evolves.

hidden_values declares the keys respondents can supply through URL parameters. Each declaration has a key and an optional default_value of up to 500 characters. A form declares at most 50. The Docs Example Form declares utm_source.

The declaration belongs to the Form Blueprint; the captured value belongs to the submission. GET /v1/forms/{form_public_id}/submissions/{submission_public_id} returns captured values in hidden_values, keyed by key:

hidden_values on a submission
{
"hidden_values": {
"utm_source": "conference-newsletter"
}
}

A captured value records what applied when the response began; later changes to the declaration do not rewrite it. Submissions covers the response and export shapes, and Hidden values explains how respondents supply them.

logic_rules is an ordered list of rules, and calculated_values declares the values those rules compute. Conditional logic and Calculated values describe what respondents experience.

Each rule has a public_id, conditions, and actions.

Conditions. conditions is { "match": "all" | "any", "items": [...] }. An item is a condition, { "kind": "condition", "id", "subject", "operator", "value" }, or a group, { "kind": "group", "id", "match", "conditions" }. Groups do not nest, and a rule with no items always applies.

A condition’s subject is one of:

typeTestsExtra field
answerA question’s answernode_public_id
hidden_valueA Hidden Valuekey
calculated_valueA Calculated valuecalculated_value_public_id
pageWhether the respondent visited a pagepage_public_id
endingWhich ending the respondent reached; value is the ending’s page IDnone

Choice answers compare option values, never labels. The operators a condition accepts depend on its subject.

Actions. Actions run in order:

typeFieldsEffect
show, hidetarget: { "kind": "node", "node_public_id" } or { "kind": "page", "page_public_id" }Shows or hides a node or question page. A show target starts hidden.
require, optionalnode_public_idMakes a question required or optional.
jumpto_page_public_idSkips ahead to a later question page or an ending. Needs at least one condition.
calculatecalculated_value_public_id, formulaSets a number Calculated value from a formula.
update_textcalculated_value_public_id, operation (replace, append, prepend), textUpdates a text Calculated value.
redirecturlSends the respondent to a URL after an ending. Needs a condition on the ending subject. Pro feature.

The topmost matching rule decides each show or hide target, the first matching jump from a page wins, and every matching calculation applies in order. A rule holds at most one jump or redirect.

Calculated values. Each has a public_id, a name of 1 to 50 characters that is unique within the form, a type of number or text, and an optional initial_value string that defaults to 0 or empty text.

Example. Add the Yes/No example question “Will you join the speakers’ dinner?” (nDnr) to the Docs Example Form’s “Your details” page. This rule then shows “Dietary needs” (nDie) only to respondents who answer yes:

{
"logic_rules": [
{
"public_id": "showDiet",
"conditions": {
"match": "all",
"items": [
{
"kind": "condition",
"id": "c1",
"subject": { "type": "answer", "node_public_id": "nDnr" },
"operator": "is",
"value": "yes"
}
]
},
"actions": [{ "id": "a1", "type": "show", "target": { "kind": "node", "node_public_id": "nDie" } }]
}
]
}

Writing logic. Unlike the rest of the document, these two lists are kept when you leave them out of a draft write. Send an empty array to remove every item; a list you send replaces the stored one. Draft Write Validation checks the shape of rules and Calculated values and the uniqueness of their IDs and names. A rule that references something missing or hidden, or breaks one of the rules above, does not block publishing: it is skipped when the form runs.

A page or node with is_hidden: true stays in the draft: you can read it, edit it, and write it back. At publish it is stripped: Publish Validation skips it, it never appears in the published document, and sort orders are renumbered without it. Use hiding to stage unfinished work on a live form. The editor’s view is Hide and show pages and components.

In the Docs Example Form, the “Company” question (nCmp) is hidden in the draft. In the published document the “Your details” page loses it, and the Actions node nSub moves from sort_order 6 to 5:

The Your details page in the published document
{
"public_id": "pQ01",
"name": "Your details",
"type": "normal",
"is_hidden": false,
"is_entrypoint": false,
"is_auto_jump_next_page": false,
"nodes": [
{
"public_id": "nNam",
"parent_node_id": null,
"column_index": null,
"sort_order": 1,
"is_hidden": false,
"type": "text_input_short",
"meta": {
"question": "<span>Full name</span>",
"placeholder": "Jane Doe",
"validation": {
"required": true
}
}
},
{
"public_id": "nEml",
"parent_node_id": null,
"column_index": null,
"sort_order": 2,
"is_hidden": false,
"type": "text_input_email",
"meta": {
"question": "<span>Email address</span>",
"placeholder": "[email protected]",
"validation": {
"required": true
}
}
},
{
"public_id": "nTck",
"parent_node_id": null,
"column_index": null,
"sort_order": 3,
"is_hidden": false,
"type": "multiple_choice",
"meta": {
"question": "<span>Ticket type</span>",
"select_options_data": [
{
"value": "general",
"label": "General",
"label_formatted": "<span>General</span>"
},
{
"value": "student",
"label": "Student",
"label_formatted": "<span>Student</span>"
},
{
"value": "speaker",
"label": "Speaker",
"label_formatted": "<span>Speaker</span>"
}
],
"select_options_randomize": false,
"select_options_selection": "single",
"multiple_choice_variant": "alphabet",
"validation": {
"required": true
}
}
},
{
"public_id": "nDie",
"parent_node_id": null,
"column_index": null,
"sort_order": 4,
"is_hidden": false,
"type": "checkbox_choice",
"meta": {
"question": "<span>Dietary needs</span>",
"select_options_data": [
{
"value": "vegetarian",
"label": "Vegetarian",
"label_formatted": "<span>Vegetarian</span>"
},
{
"value": "vegan",
"label": "Vegan",
"label_formatted": "<span>Vegan</span>"
},
{
"value": "gluten_free",
"label": "Gluten-free",
"label_formatted": "<span>Gluten-free</span>"
}
],
"select_options_randomize": false,
"select_options_selection": "single",
"select_options_style": "checkbox",
"validation": {
"required": false
}
}
},
{
"public_id": "nSub",
"parent_node_id": null,
"column_index": null,
"sort_order": 5,
"is_hidden": false,
"type": "actions",
"meta": {
"label": "Register",
"is_page_actions_override": false,
"back_button_label": "Back"
}
}
]
}

POST /v1/forms makes a new form from the same document. It differs from a draft write in these ways:

  • is_template and access are honored, so you choose whether you create a form or a Private Template, and whether it starts closed.
  • styles.theme_id picks the theme, and a form sent without one gets the default theme. Any access.duplicate_response_rule must name a question of the blueprint.
  • Every minted ID comes back in the response under draft.

Create runs the same Draft Write Validation as a draft write. Creating a form covers dry runs and safe retries.

PUT /v1/forms/{form_public_id}/draft replaces the whole document. The server matches pages and nodes by public ID, updates what changed, soft-deletes what you left out, and restores removed items whose IDs you send again. Array order is canonical: pages and nodes are renumbered from it, and the first page becomes the entrypoint. A settings or properties key you leave out keeps its stored value, as do logic_rules and calculated_values when you leave them out. An identical write changes nothing and returns the ETag you already had.

The server never adds structure for you. It does not add an ending or an Actions node, and a document without an ending is refused. Empty pages are allowed in a draft. Replacing the draft covers the request, the ETag, and dry runs.

Two validators guard a form:

  • Draft Write Validation runs on every create and draft write. A failure refuses the whole write with 400 INVALID_INPUT, or 402 PRO_FEATURE_REQUIRED for a plan gate.
  • Publish Validation runs when you publish or call validate. It reports Publish Issues and never blocks a draft write.
  • Strict keys and the document shape, including every node meta.
  • Public IDs unique within the form.
  • Layout: parent_node_id and column_index set together, a parent on the same page, column eligibility, and no nested rows. See Layout rows and columns.
  • The limits on pages, nodes, Hidden Values, logic rules, and Calculated values.
  • Page structure: 1 to 10 endings, all after every other page; the first page is not an ending; at most one intro page, which must be the first page and holds no questions; no hidden ending.
  • Ending content: at least one node besides a progress bar; only text, image, media, divider, layout_row, progress_bar, and at most one thank_you. A thank_you node appears nowhere else.
  • Form Title: on normal pages only, at most one per page.
  • Media: every media_url resolves to a supported service. See Media.
  • Logic rules and Calculated values: valid shape and unique IDs and names.
  • Plan gates on settings and properties, listed in Settings, properties, access, and styles.
  • At least one question on a visible normal page.
  • Options on every choice question, and rows and columns on every matrix question.
  • Number settings that fit together on every number_input: min no greater than max, a default value inside the range, and a whole-number step when whole_only is on.
  • No more than 1,000 options per question, 30 columns and 50 rows per matrix, and 5,000 options and matrix rows across the form.
  • No dangling answer pipes.
  • No hidden ending, and at least one visible page besides the endings.
  • An intro page only as the first page.
  • Pro features on a Free organization: more than one ending or an ending with content beyond one Thank You node (custom_endings, path pages), a redirect action (redirect_on_completion, path logic_rules), and search-engine properties (meta_tags, path properties/meta_title).

Publishing also checks your account, for example that your email address is verified. Errors lists every Publish Issue code.

LimitValue
Pages per form1 to 100
Nodes per page100, not counting the progress bar
Nodes per form1,000, not counting progress bars
Endings (final pages) per form1 to 10
Hidden Values per form50
Logic rules per form100
Conditions per rule20
Actions per rule20
Calculated values per form50
Form name1 to 100 characters
Page name1 to 255 characters
Options per question1,000
Matrix columns30
Matrix rows50
Options and matrix rows per form, at publish5,000

Your next form could be the best-looking thing on your site.

Free forever. Unlimited responses. No signup to start.