Skip to content

The form definition

The JSON an assistant edits to build a trulyform form: top-level keys, identifiers, rich text, welcome and ending screens, and form settings.

View as Markdown

All docs pages
On this page

What you need to know

  • A form is one JSON object with the top-level keys below. Every save sends all of them.
  • Every question, choice, row, rule and calculation has an id: a lowercase UUIDv7, unique within the form.
  • Titles and descriptions are small rich-text documents, not strings. Choice, row and button labels are plain strings.
  • Images and logos are uploaded in trulyform, not over MCP. Keep the asset IDs get_form returns, and use null for new questions. See Design and theme.
  • A key the format does not define makes the save fail, wherever it appears. A known setting that a question type does not use is ignored.

Top-level keys

Key Type What it holds
schemaVersion 2 Always 2 for new and edited forms. Keep the value get_form returns.
mediaDefaults object The form-wide image: {assetId, layout, altText}. Keep it as get_form returns it.
welcome object The first screen: {enabled, title, description, button}. title and description are rich text; button is the start button label.
ending object The screen after the last question: {title, description}, both rich text. It can show piped answers.
fields list The questions, in the order respondents see them.
theme object Colors, font, corner radius and spacing. See the theme fields below.
settings object Form-wide behavior: {allowReanswer, hiddenFields, fillLimit}.
rules list Logic rules that show, hide, require or skip questions.
calculations list Named numeric results computed from answers.

A new form, as create_form returns it:

A new form, as create_form returns it

{
    "schemaVersion": 2,
    "mediaDefaults": {
        "assetId": null,
        "layout": "above",
        "altText": ""
    },
    "welcome": {
        "enabled": true,
        "title": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Welcome!"}]}]},
        "description": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"We would love to hear from you."}]}]},
        "button": "Start"
    },
    "ending": {
        "title": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Thank you!"}]}]},
        "description": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Your response has been received."}]}]}
    },
    "fields": [
        {
            "id": "01990000-0000-7000-8000-000000000001",
            "kind": "short_text",
            "title": {"type":"doc","content":[{"type":"paragraph","content":[]}]},
            "description": {"type":"doc","content":[{"type":"paragraph","content":[]}]},
            "required": false,
            "options": [],
            "rows": [],
            "settings": {"nextButton":"Next"},
            "media": {"inherit":true,"assetId":null,"layout":"above"}
        }
    ],
    "theme": {
        "preset": "paper",
        "background": "#ffffff",
        "foreground": "#111827",
        "primary": "#2563eb",
        "buttonText": "#ffffff",
        "font": "Inter",
        "radius": 12,
        "density": "comfortable",
        "logoAssetId": null,
        "backgroundAssetId": null
    },
    "settings": {
        "allowReanswer": true,
        "hiddenFields": [],
        "fillLimit": null
    },
    "rules": [],
    "calculations": []
}

Select the text to copy it.

Identifiers

  • Format: 32 lowercase hexadecimal digits in groups of 8-4-4-4-12. The third group starts with 7 (version 7) and the fourth with 8, 9, a or b. For example 01990000-0000-7000-8000-000000000001.
  • Unique within the form, across all questions, choices, rows, rules and calculations together. Two choices in different questions cannot share an ID.
  • Keep existing IDs when you edit. Responses, logic rules and piped answers refer to them; a question with a new ID is a new question.
  • New IDs: generate a UUIDv7, or call list_field_types with id_count to receive fresh ones. The timestamp part of a UUIDv7 is not checked. The IDs in these docs are readable placeholders; make sure none of them collides with an ID already in the form.
  • The mutation_id of save_form_draft is a UUIDv7 too, new for every save.

Questions

Each entry in fields is one question:

Key Type Notes
id UUIDv7 Unique across every question, choice, row, rule and calculation in the form.
kind string One of the question types.
title rich text The question. Required before publishing.
description rich text Optional help text under the question. Use an empty paragraph when there is none.
required boolean Whether respondents must answer. Always false for statements.
options list Choices as {id, label}, with an optional decimal-string score. Empty for kinds without choices.
rows list Matrix rows as {id, label}. Empty for every other kind.
settings object Kind-specific settings. Include nextButton; leave out settings the kind does not use.
media object {inherit, assetId, layout}. Keep {"inherit": true, "assetId": null, "layout": "above"} unless get_form shows an image.

What each kind accepts is in Question types.

Rich text

Question titles and descriptions, and the titles and descriptions of the welcome and ending screens, are rich-text documents: a doc holding paragraphs, which hold text.

A title with one bold word

{
    "type": "doc",
    "content": [
        {
            "type": "paragraph",
            "content": [
                { "type": "text", "text": "What is your " },
                { "type": "text", "text": "work", "marks": [{ "type": "bold" }] },
                { "type": "text", "text": " email address?" }
            ]
        }
    ]
}

Select the text to copy it.

An empty description is {"type": "doc", "content": [{"type": "paragraph", "content": []}]}.

Node Use
doc The root. Its content is a list of paragraphs.
paragraph A paragraph. Its content holds text, hardBreak and answerToken nodes. An empty paragraph has "content": [].
text A run of text: {"type": "text", "text": "…"}. Optional "marks" style it.
hardBreak A line break inside a paragraph: {"type": "hardBreak"}.
answerToken Pipes in an earlier answer or a calculation: {"type": "answerToken", "attrs": {"source": "answer", "ref": "<question id>", "fallback": "there"}}.

Text marks: bold, italic, underline, strike, written as "marks": [{"type": "bold"}]. Up to 4 marks per text node and 10,000 characters per text node.

Use several paragraphs for several lines of text. There is no link mark: to point people to a page, write its address as text, for example in the description. Do not add a required marker such as an asterisk: trulyform shows one for required questions.

Piped answers

An answerToken node shows an earlier answer or a calculation result inside text, with a fallback for when there is nothing to show. Piped answers explains where tokens may appear.

Welcome and ending

  • welcome: {enabled, title, description, button}. Set enabled to false to start with the first question. When it is enabled, button must not be blank.
  • ending: {title, description}, shown after the last question.

Form settings

Setting Type Notes
allowReanswer boolean Lets respondents go back and change earlier answers.
hiddenFields list of strings URL parameter names to capture with each response, such as "source". Each starts with a letter and uses only letters, digits and underscores, up to 64 characters. They are stored with the response; they cannot be used in logic or piping, and get_response does not return them.
fillLimit integer or null Stop accepting responses after this many. null means no limit.

Theme and images

The theme, mediaDefaults and each question's media are described in Design and theme. Over MCP, keep asset IDs exactly as get_form returns them and give new questions {"inherit": true, "assetId": null, "layout": "above"}.

Ready to build?
Connect your assistant.

Pick your assistant, sign in to trulyform, and ask for the form you need.

Choose your assistant