Form2Workflow

Building forms

Field reference

Every field type form2wf renders, with a sample and the data it submits.

Common properties#

PropertyDescription
idUnique id within the form. Required for buttons and row actions to work — always set one (the dashboard generates field_… ids).
typeOne of the types below.
nameKey in the submitted data.
labelLabel shown above the input.
placeholderPlaceholder text. For subtitle it is the heading text; for checkbox, the text beside the box.
requiredBlocks submission until filled (browser-side check).
colSpanHow many grid columns the field takes, from 1 (default) up to the form's columns, or "full" for the whole row. See multi-column layouts.
newRowtrue starts the field on a new row.
colStartColumn the field starts in (1 = new row). If earlier fields on the row already use that column, the field moves to the next row.
defaultValueInitial value. Supports templates such as {{params.plan}}.

Text inputs: text, email, number, tel, date, url, password#

Rendered as <input type="…">. Any HTML input type works — the type is passed through — and browsers apply their native validation for email, url and number. All values are submitted as strings.

{ "id": "phone", "type": "tel",  "name": "phone", "label": "Phone", "placeholder": "+1 555 0100" }
{ "id": "start", "type": "date", "name": "start_date", "label": "Start date", "required": true }
{ "id": "qty",   "type": "number", "name": "quantity", "label": "Quantity", "defaultValue": "1" }
submits
{ "phone": "+1 555 0100", "start_date": "2026-10-01", "quantity": "1" }

textarea#

{ "id": "msg", "type": "textarea", "name": "message", "label": "Message", "required": true, "placeholder": "How can we help?" }

select#

A dropdown whose first entry is an empty “Select an option”. Each option's value equals its label. defaultValue selects an exact match.

{ "id": "plan", "type": "select", "name": "plan", "label": "Plan",
  "options": ["Starter", "Pro", "Enterprise"], "defaultValue": "{{params.plan}}" }

radio#

{ "id": "contact", "type": "radio", "name": "contact_by", "label": "Contact me by",
  "options": ["Email", "Phone"], "required": true }

rating#

Star rating. Visitors click or drag across the stars, or use the arrow keys (Home or Delete clears it). The value is submitted as a number, such as 3.75; an unrated optional field submits an empty string.

{ "id": "score", "type": "rating", "name": "score", "label": "How was your visit?", "required": true,
  "ratingConfig": { "max": 5, "step": 0.25 } }
ratingConfig keyValuesDefault
maxNumber of stars, 1–105
step0.25 (quarter stars), 0.5 or 10.25

defaultValue sets the starting rating and supports templates, e.g. {{data.score}} to show a stored rating on a data page.

checkbox#

A single checkbox. Checked submits "true"; unchecked is omitted entirely, so treat a missing key as false in n8n. defaultValue of "true" starts it checked.

{ "id": "tos", "type": "checkbox", "name": "accept_terms", "label": "Terms",
  "placeholder": "I agree to the terms of service", "required": true }
submits
{ "accept_terms": "true" }

file#

A drag-and-drop zone for one file. When a file is attached the submission is sent as multipart/form-data instead of JSON — see multipart payloads. Limit: 10 MB per file.

{ "id": "cv", "type": "file", "name": "resume", "label": "Résumé (PDF)", "required": true }

Multipart submissions only carry top-level text values. Don't combine file with object or array fields in the same form — nested values are dropped.

hidden#

Sends a value the visitor doesn't see — ideal for tracking the source page or a record id passed in the URL.

{ "id": "src", "type": "hidden", "name": "source", "defaultValue": "{{params.utm_source}}" }

display#

Read-only text, usually filled from the onload response. Rendered as plain text (HTML is not interpreted).

{ "id": "greet", "type": "display", "label": "Account", "displayValue": "Signed in as {{data.user.name}} ({{data.user.plan}})" }

Layout: subtitle, divider, linebreak#

{ "id": "h1", "type": "subtitle", "label": "Shipping address" }
{ "id": "hr", "type": "divider" }
{ "id": "br", "type": "linebreak" }

subtitle renders a section heading, divider a horizontal rule, and linebreak ends the current row in a multi-column layout.

object#

Groups child fields into a nested object. Optionally collapsible, with a preview of chosen child values while collapsed.

{
  "id": "addr", "type": "object", "name": "address", "label": "Address",
  "objectConfig": { "collapsible": true, "defaultCollapsed": false, "collapsedPreviewFields": ["city"] },
  "children": [
    { "id": "street", "type": "text", "name": "street", "label": "Street" },
    { "id": "city",   "type": "text", "name": "city",   "label": "City", "required": true },
    { "id": "zip",    "type": "text", "name": "zip",    "label": "Postal code" }
  ]
}
submits
{ "address": { "street": "1 Main St", "city": "Oslo", "zip": "0150" } }

array#

A repeatable group: visitors add, remove and drag-reorder items. Always shows at least one item.

arrayConfigDefaultDescription
minItems0Items can't be removed below this.
maxItemsunlimitedThe add button stops at this count.
itemLabel"Item {{index}}"Heading for each item; {{index}} is 1-based.
addButtonLabel"Add Item"Text of the add button.
allowReordertrueDrag handles to reorder items.
collapsedPreviewFields[]Child names shown when an item is collapsed.
{
  "id": "people", "type": "array", "name": "attendees", "label": "Attendees",
  "arrayConfig": { "minItems": 1, "maxItems": 10, "itemLabel": "Attendee {{index}}", "addButtonLabel": "Add attendee" },
  "children": [
    { "id": "pname",  "type": "text",  "name": "name",  "label": "Name",  "required": true },
    { "id": "pemail", "type": "email", "name": "email", "label": "Email" }
  ]
}
submits
{ "attendees": [ { "name": "Ada", "email": "ada@example.com" }, { "name": "Grace", "email": "" } ] }

table#

Shows rows from the onload response, with optional row actions, a search box (tableSearch) and pages (pageSize). See Data pages.

{ "id": "orders", "type": "table", "label": "Recent orders", "dataSource": "orders",
  "tableSearch": true, "pageSize": 20,
  "columns": [ { "field": "number", "label": "Order" }, { "field": "total", "label": "Total" } ] }

markdown#

Formatted text across the full width of the form: headings, paragraphs, bold and italic, lists and task lists, tables, quotes, code blocks, links and images. Use it for instructions, terms, a welcome message, or an answer your workflow writes (an AI summary, an article, a status report). It submits nothing.

{ "id": "intro", "type": "markdown",
  "markdown": "## Welcome, {{data.user.name}}\n\nUpload your **2026 receipts** below. We'll email you when we're done." }

Or take the text from the onload response with dataSource, a plain dot path to a markdown string:

{ "id": "answer", "type": "markdown", "label": "Summary", "dataSource": "summary", "maxHeight": 400 }
onload returns
{ "data": { "summary": "### Contract review\n\n- **Term:** 24 months\n- **Risk:** auto-renewal clause (section 9)" } }
PropertyMeaning
markdownThe text. Supports templates ({{data.x}}, {{params.x}}).
dataSourcePath to a markdown string in the onload data. When it has text, it's shown instead of markdown.
maxHeightPixels (80+). Taller content scrolls inside the field.
labelOptional heading above the text.

HTML inside the markdown is shown as text and never runs. Links may only be https, http, mailto, tel or #anchors, and open in a new tab; images must be https or http. That makes it safe to show text that came from visitors or from an AI model.

A full-width menu that turns a set of forms into an app: each item opens one of your forms in place (or an external page in a new tab), and the item for the form being shown is highlighted. Put the same menu at the top of each form in the app.

{ "id": "menu", "type": "nav", "navItems": [
  { "label": "Dashboard",   "form": "portal-home" },
  { "label": "My requests", "form": "portal-requests", "params": { "status": "open" } },
  { "label": "Help",        "url": "https://example.com/help" }
] }

form is a form slug in your organization; params are passed like a navigate link's ({{params.x}} in the target form, and in its onload webhook). In the builder, add a Menu Bar element and write one item per line: Label | form-slug or Label | https://….

chart#

Draws data from the onload response as a bar, line, area, pie or donut chart, or as a single headline number (indicator). Charts take the width their column span gives them and redraw when it changes, so they fit a quarter-width cell as well as a full row. Visitors can hover or use the arrow keys for exact values, and a "Show table" link lists every value. Charts submit nothing.

{ "id": "sales", "type": "chart", "label": "Sales by month", "colSpan": 2,
  "chartConfig": { "kind": "bar", "dataSource": "sales", "labelKey": "month",
    "series": [ { "key": "online", "label": "Online" }, { "key": "store", "label": "In store" } ],
    "stacked": true, "format": "currency", "currency": "USD" } }
onload returns
{ "data": { "sales": [ { "month": "Jan", "online": 12400, "store": 8300 },
                       { "month": "Feb", "online": 13900, "store": 7900 } ] } }
chartConfig keyApplies toValues
kindallbar (default), line, area, pie, donut, indicator
dataSourceallDot path to an array in the onload data, like sales (not {{data.sales}}). Each row is one bar, point or slice.
labelKeybar, line, area, pie, donutRow key for the category or slice name. Default: the first text key.
seriesallNumber keys to plot: ["online"] or [{ "key": "online", "label": "Online" }]. Several series make a grouped or stacked bar chart or a multi-line chart. Default: every number key. Pie and donut use the first; indicator uses it for its trend line.
dataallStatic rows instead of dataSource, for a chart that never changes.
horizontal, stackedbartrue for horizontal bars or stacked series.
valueLabelsbarValues at the bar ends (one series, or stacked totals). Default true.
smooth, markersline, areasmooth: true curves the line. markers: true or false; by default dots show for up to 12 points.
sliceLabelspie, donutpercent (default), value or none. Labels only appear where they fit.
centerLabeldonutText under the total in the hole. Default Total.
valueindicatorA template such as {{data.visitors}}. Empty = the last row of dataSource.
compareTo, compareLabel, goodWhenindicatorShows the change as a percentage: compareTo is a template or previous (the row before the last). compareLabel e.g. vs last month. goodWhen: up (default, increases are green), down or none.
captionindicatorA short line under the number.
formatallauto (default: 1610.01 → 1.6K, 99.6912 → 99.7), compact (1.6K), number (1,610.01), percent (fractions: 0.997 → 99.7%), currency ($1,610.01; $48.2K from 10,000). Axes, labels and tooltips use it too.
decimals, currency, prefix, suffixallFixed decimal places (default automatic); ISO currency code (default USD); text before or after every number, e.g. "suffix": " kg". For values already in 0–100, use "suffix": "%" instead of percent.
palette, colorsalltheme (default: your theme's primary colour first), default, vivid, or custom with colors: ["#2a78d6", "#eb6834"]. Default and vivid are tested to stay distinguishable with colour blindness. Extra series beyond the palette are not drawn; extra pie slices are combined into "Other".
heightbar, line, area, pie, donutPixels, 120–800. Defaults: 260, pie and donut 240, horizontal bars grow with the rows.
tablebar, line, area, pie, donutfalse hides the "Show table" link.
headline number
{ "id": "visitors", "type": "chart", "label": "Visitors",
  "chartConfig": { "kind": "indicator", "value": "{{data.visitors}}", "compareTo": "{{data.visitorsLastMonth}}",
    "compareLabel": "vs last month" } }

button#

Triggers a webhook, opens another form, opens a web page, or goes back. Consecutive buttons and links render in one row. See Buttons.

{ "id": "approve", "type": "button", "label": "Approve", "buttonAction": "webhook", "buttonStyle": "primary",
  "buttonWebhookUrl": "https://n8n.example.com/webhook/approve?id={{params.id}}", "buttonWebhookMethod": "POST" }
{ "id": "open", "type": "button", "label": "Open", "buttonAction": "url",
  "buttonUrl": "https://files.example.com/{{data.file.path}}" }

A url button opens buttonUrl in a new tab, like a link with linkAction: "url". Templates work in it; only https, http, mailto and tel URLs open.

{ "id": "help", "type": "link", "label": "Pricing details", "linkAction": "url", "linkUrl": "https://example.com/pricing?plan={{params.plan}}" }
{ "id": "next", "type": "link", "label": "Continue to step 2", "linkAction": "navigate",
  "linkTargetForm": "signup-step-2", "linkTargetParams": { "email": "{{params.email}}" } }

linkAction: navigate (default — load another form of yours by slug), back, or url (opens in a new tab).

Not supported#

Conditional visibility, min/max/pattern validation rules, multi-select dropdowns, and options with separate values and labels. Validate important rules in your n8n workflow.