What you need to know
- Find out what the form is for, who will fill it in and what happens with the answers.
create_form(orget_formfor an existing form) returns the whole definition and itsdraft_revision.- Edit the complete definition. Save it with
save_form_draft, passing the current revision asbase_revisionand a new UUIDv7 asmutation_id. - Read the
issuesin the reply. Fix them and save again until the list is empty. - Show the person what you built. Call
publish_formonly when they ask, with the revision they reviewed.
Before you build
Ask, or work out from the conversation:
- The goal. What decision or task do the answers feed? Leave out every question that does not serve it.
- The respondent. A customer on a phone, a colleague, a guest? It sets the tone and the length.
- What is required. Require only the answers the person cannot do without.
- What happens next. The ending screen should tell respondents what to expect.
For a long or sensitive form, list the questions in the chat and let the person confirm them before you save.
Create the form
Call create_form with a name. The reply's form holds the id, draft_revision (1 for a new form), the definition and its current issues.
A new form has a welcome screen, one untitled short text question, a thank-you ending and a white theme. Its issues ask for the missing question title. Replace that starter question with your own, or reuse its ID for your first question; nothing depends on it yet.
create_form is not idempotent. If the reply is lost, call list_forms and look for the form before you create another one.
To change an existing form instead, find it with list_forms and read it with get_form.
Edit the definition
- Start from the definition you just read. Keep every top-level key, including
schemaVersion,mediaDefaults,themeandsettings, and keep every existing ID. See The form definition. - Write the questions in
fields, in the order respondents will see them. Start each one from its template in Question types or fromlist_field_types, then give it a title. - Give every new question, choice, row, rule and calculation a fresh ID: a lowercase UUIDv7, unique within the form. If you cannot generate one, call
list_field_typeswithid_countand use the IDs it returns. Details are in Identifiers. - Titles and descriptions are rich text, not plain strings. See Rich text.
- To change a question's type, replace the question with a new one that has a new ID, then update any rules, calculations or piped answers that pointed at the old ID.
- Label the last question's button "Submit" or similar with its
nextButtonsetting. There is no separate submit button.
A complete, publishable definition:
Contact request. The smallest useful form: three required questions, and an ending that greets the person by the name they typed.
It shows:
- required questions
- placeholder and maxLength settings
- an answer piped into the ending
Contact request definition
{
"schemaVersion": 2,
"mediaDefaults": {
"assetId": null,
"layout": "above",
"altText": ""
},
"welcome": {
"enabled": true,
"title": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Get in touch"}]}]},
"description": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Three quick questions. We reply within one working day."}]}]},
"button": "Start"
},
"ending": {
"title": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"Thanks, "},{"type":"answerToken","attrs":{"source":"answer","ref":"01990000-0000-7000-8000-000000000001","fallback":"there"}},{"type":"text","text":"!"}]}]},
"description": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"We will reply to your email within one working day."}]}]}
},
"fields": [
{
"id": "01990000-0000-7000-8000-000000000001",
"kind": "short_text",
"title": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"What is your name?"}]}]},
"description": {"type":"doc","content":[{"type":"paragraph","content":[]}]},
"required": true,
"options": [],
"rows": [],
"settings": {"nextButton":"Next","placeholder":"Jane Smith"},
"media": {"inherit":true,"assetId":null,"layout":"above"}
},
{
"id": "01990000-0000-7000-8000-000000000002",
"kind": "email",
"title": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"What is your email address?"}]}]},
"description": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"We only use it to reply to this message."}]}]},
"required": true,
"options": [],
"rows": [],
"settings": {"nextButton":"Next"},
"media": {"inherit":true,"assetId":null,"layout":"above"}
},
{
"id": "01990000-0000-7000-8000-000000000003",
"kind": "long_text",
"title": {"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"How can we help?"}]}]},
"description": {"type":"doc","content":[{"type":"paragraph","content":[]}]},
"required": true,
"options": [],
"rows": [],
"settings": {"nextButton":"Send message","maxLength":2000},
"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.
Save the draft
Call save_form_draft with:
| Parameter | Value |
|---|---|
form_id |
The form's ID. |
base_revision |
The draft_revision you last read, or the revision your last save returned. |
mutation_id |
A new UUIDv7 for every save. |
name |
The full form name. An empty string saves it as "Untitled form". |
definition |
The complete definition, including the parts you did not change. A partial object is rejected. |
The reply holds the new revision, issues, saved_at and the published_version_id. Use revision as the base_revision of your next save.
If the network fails and you do not know whether a save went through, send the identical request again with the same mutation_id. trulyform returns the original result instead of saving twice. Reusing a mutation_id with different content is rejected.
When a save is rejected
validation_failedwitherrors: the definition has the wrong shape, such as an unknown key, an invalid ID or a plain-string title. Nothing was saved. Fix the paths named inerrorsand save again with the samebase_revisionand a newmutation_id. Limits and errors explains the messages.draft_conflict: a newer draft exists, perhaps because the person is editing the form in trulyform. Callget_form, apply your change to the definition it returns, and save with itsdraft_revision. Never overwrite the newer draft with an old copy.
Fix the issues
A save succeeds even when the form is not ready to publish. The reply's issues list what still blocks publication:
One issue
{
"nodeId": "01990000-0000-7000-8000-000000000001",
"path": "title",
"message": "Write your question.",
"severity": "error"
}
Select the text to copy it.
nodeId is the ID of the question, rule or calculation with the problem, or form, theme, welcome or ending. Each issue also has a docs field naming the section that explains it; pass it to read_docs. Fix every issue and save again. Publish issues lists the common ones.
Review with the person
Before publishing, summarize the form in plain words: the questions in order, which ones are required, and what the logic does ("If they choose In person, we ask about dietary needs"). Give the person the form's editor_url from create_form, get_form or list_forms: it opens the form in the trulyform editor, where they can preview it exactly as respondents will see it and make changes themselves.
Publish
Call publish_form with the form_id and the revision the person reviewed, and only after they ask you to publish. The reply contains the public_url to share.
- If the draft changed since that revision, publishing fails with
draft_conflict. Show the person the newer draft first. - If the draft still has issues, publishing fails with
validation_failedand the list ofissues. - Publishing identical content again does not create a new version.
- Responses already collected keep the version they were answered on.
- A closed form stays closed. The person reopens it in trulyform.
Design a good form
- Keep it short. Most forms need fewer than ten questions. Each extra question costs answers.
- One idea per question, in plain words. Put help text in the description, not the title.
- Match the type to the answer. Use
emailfor email addresses,dropdownfor long lists such as countries,single_choicefor a handful of options,opinion_scaleornpsfor ratings, andconsentfor agreements. Anumberanswer may have decimals, so ask for whole counts, such as guests, with a choice question. - Use logic instead of "If yes…" questions, so people only see what applies to them.
- Use the welcome screen to say what the form is for and how long it takes. Turn it off for very short forms.
- Use the ending to thank people and say what happens next. Piping in their name makes it personal.