Form trigger
Let a person start a run by filling in a hosted form.
Lets a person start a run by filling in a hosted form — a request form, an intake questionnaire, an internal ticket — with nothing for you to build or host. Add a Form Trigger node (trigger_form), declare its fields, and publish.
https://<the origin you open Studio on>/forms/{formToken}The form page is served by the app, not by the API, so this is not an api. host — the panel builds the link from the origin you are already on.
The formToken is opaque, is never the workflow's own id, and is minted by publishing — Studio's Form Trigger panel shows the real link once a deployment exists, and says the workflow is not published yet until then. It is carried forward unchanged every time you republish, so a URL you have already handed out keeps working across deploys. A workflow whose published graph has no Form Trigger node has no token at all.
Each field carries a name and a human-readable label — deliberately two things, since ticketId is the right data key and the wrong question to put in front of a person. Eleven field types are available (text, textarea, number, email, password, date, dropdown, radio, checkboxes, file, hidden); the three choice types carry their own options, and a file field carries its own Accepted file types and Allow multiple files options — see File uploads below.
The three auth modes
Set on the node, applied by the published deployment:
| Mode | Who gets in |
|---|---|
| None | Anyone with the link. |
| Basic | A username and password you set on the node. The password is bcrypt-hashed at publish time and never stored in the graph. |
| Elizon login | A signed-in, active member of the workflow's own organization — not any Elizon user. |
Under Basic and Elizon login the field list is itself protected: an unauthenticated visitor cannot see what the form even asks, let alone submit to it. A visitor who is signed in but belongs to another organization gets a specific "not a member of this organization" state rather than a silent empty form or a second sign-in prompt.
Changing the mode or the username takes effect on the next publish — and a username change has to carry a new password with it, because a stored hash is only kept for the username it was set for; publishing a renamed Basic form without one is refused rather than left accepting the old pair. Changing nothing keeps the existing password working — an ordinary republish does not invalidate credentials you have already handed out.
File uploads
A file field does not carry bytes in the submission itself — it carries a reference, minted by its own upload endpoint:
POST /api/v1/forms/{formToken}/upload?field={fieldName}One call per file, multipart/form-data with a single file part — mirroring how the field behaves on the hosted page: select a file and it uploads immediately, before the rest of the form is necessarily filled in. A successful upload returns:
{ "fileId": "…", "filename": "resume.pdf", "sizeBytes": 48213 }Submit the returned fileId as the field's value — a plain string, or a string[] of them when the field's Allow multiple files option is on — never the file itself. The submission ingress resolves each id before the run starts, so a workflow reads the field's real value as:
{
"filename": "resume.pdf",
"contentType": "application/pdf",
"sizeBytes": 48213,
"text": "…extracted text…"
}text is extracted once, at upload time: .txt/.csv/.md/.html/.xml are read directly, .pdf/.docx/.doc/.xlsx/.xls are parsed, and anything else (images included) falls back to a short description rather than failing the upload.
Two settings live on the field itself:
| Setting | Effect |
|---|---|
| Accepted file types | A comma-separated extension list (.pdf,.docx,.csv). Empty accepts any extension. |
| Allow multiple files | Off by default. On, the field submits an array and the upload endpoint is called once per file. |
| Status | Meaning |
|---|---|
201 | Stored — body is the fileId/filename/sizeBytes shown above. |
400 | field names no file field on this form. |
422 | The file was rejected — wrong extension, over the size limit, or storage failed. |
429 | Rate limit — the same one submissions share. |
Upload size is a platform setting, not a per-form one
Every upload is capped at the same size regardless of the field's own configuration — 16 MB by
default. A rejected file's 422 response names the real, current limit, so a form need not
duplicate the number anywhere.
An upload nobody ever submits is deleted after 2 hours. Once a submission actually uses it, it is kept for 30 days from that submission — long enough to outlive a run parked on a Human Review node — after which it is deleted along with its stored bytes. Both windows, like the size limit, are platform settings rather than something a form author configures.
Submission responses
A submission is validated against the declared fields in the request, so an incomplete form reports its errors on the page rather than starting a run that fails where nobody is looking.
| Status | Meaning |
|---|---|
202 | Accepted — body is { "status": "received" }. The run proceeds in the background. |
400 | A field failed validation — issues names each one — or the graph behind the form is empty, or the body holds more than 20,000 values (array elements plus object keys across the whole body, each array or object counting 5 more) or nests deeper than 256 levels. |
401 | Credentials required, or wrong. |
403 | Signed in, but not a member of the workflow's organization. |
404 | No published form matches that token — including an unpublished or deleted workflow. |
413 | Body exceeded the 1 MB limit. |
429 | Rate limit, or the concurrency ceiling. |
A submitter never sees the run's result
The acknowledgment is immediate and static: no execution id, no polling handle, nothing that waits for the run. A workflow can park 30 days on a Human Review node, which makes "wait for the result" the wrong contract for a browser tab. If the submitter needs an answer, have the graph send them one.
Submitted values arrive under body, keyed by each field's name, so a form-driven graph reads {{ trigger.body.email }} — the same envelope the webhook path uses.
Configuration
| Field | Type | Default | Description |
|---|---|---|---|
fields | list of form fields (below) | [] | The form's questions, in order. |
authMode | none | basic | elizon_login | none | Who can open and submit the form — see The three auth modes. |
basicAuthUsername | string | — | The username for basic. The password is set in the panel, hashed at publish, and never stored in the graph. |
Each entry of fields:
| Key | Type | Description |
|---|---|---|
name | string | The data key the value arrives under ({{ trigger.body.<name> }}). |
label | string | The question shown to the person. |
type | string | text, textarea, number, email, password, date, dropdown, radio, checkboxes, file or hidden. |
required | boolean | Whether the form can be submitted without it. |
options | string[] | The choices. Required on dropdown, radio and checkboxes; refused on every other type. |
acceptedExtensions | string[] | file only: lower-case extensions without a dot (pdf, docx). Empty accepts any extension. |
allowMultiple | boolean | file only: the field submits a list of files. |
Output
| Reference | Value |
|---|---|
{{ trigger.body.<name> }} | The submitted value. A checkboxes field is a list; a file field is { filename, contentType, sizeBytes, text }, or a list of them. |
{{ run.trigger.type }} | form. |
{{ run.trigger.id }} | This node's id. |
Limits and errors
- One per workflow. The form URL is issued once per workflow, so a second Form trigger is refused at save.
- Bodies over 1 MB are refused, and so are bodies over 20,000 values (array elements plus object keys across the whole body, each array or object counting 5 more) or nested deeper than 256 levels, with
400, even under 1 MB; uploads are capped by a platform setting (16 MB by default). - See Submission responses for every status code.
Example
name: Service request
description: Takes a service request from the customer form.
graph:
nodes:
- id: request-form
type: trigger_form
position: { x: 0, y: 0 }
data:
authMode: none
fields:
- { name: email, label: Your email, type: email, required: true }
- { name: machine, label: Machine, type: dropdown, required: true, options: [X200, X300] }
- {
name: photo,
label: Photo of the fault,
type: file,
required: false,
acceptedExtensions: [jpg, png],
}
- id: triage
type: ai_agent
position: { x: 280, y: 0 }
data:
mode: existing
agent: agents.triage
agentName: Triage
inputMappings: []
edges:
- { id: form-to-triage, source: request-form, target: triage }In a Package
A Package declares no manifest trigger for a form: the Form trigger node in the packaged workflow is the entry point, and the form's link is minted when the installed workflow is first published. On the node:
- Travels:
fields,authMode - Converted: none
- Never carried:
basicAuthUsername
A form that uses Basic auth asks the installer for its username (and a password) at install; neither is ever carried. See Values the installer supplies.