Forms

A form is a schema — it defines what information gets collected. Each submission creates a Record that stores the field values, timestamps, and any attached files.

Forms vs. Records

Think of a form as a database table definition: it describes the shape of your data. Records are the rows — individual filled-in submissions of that form.

One form can have thousands of Records. Each Record stores its field values, a creation timestamp, an optional submitter email, and a full audit trail of changes.

Creating a form

In the sidebar, click Forms then New Form. You'll provide:

The form's Kanban tab can also set a Default Board (and optionally a Default Column) — new records then land on that board automatically as cards. If no column is chosen, the board's first column is used.

Field types

Add fields via the Add Field button on the form editor. Each field has a type, label, placeholder, and optional flags: required, show on board card (the value appears on the record's Kanban card), and internal (hidden from the public form — visible to your team only).

TypeWhat it rendersStored as
textSingle-line text inputString
textareaMulti-line text areaString
emailEmail input with format validationString
numberNumeric inputNumber
telPhone number inputString
selectDropdown with your defined optionsString (chosen option)
radioRadio button group with your defined optionsString (chosen option)
checkboxSingle true/false toggleBoolean
dateDate pickerISO date string
sequenceAuto-assigned document number (e.g. PO-0042) drawn from an org-wide sequence. Read-only unless "allow manual editing" is enabled — see Auto-numberingString (assigned server-side on create)
fileFile upload buttonFile reference (stored in Snapi)
hiddenHidden input — not shown to the submitterString
objectGroup of nested sub-fields (text, number, dropdown, radio, date, checkbox, reference …)Object
arrayRepeating rows of sub-fields with add/remove and optional drag-reorder — rows can include reference pickers (e.g. PO lines, each choosing a Part)Array
tableRead-only data grid fed by the onload webhook, with optional row actions: navigate to a form carrying {{row.x}} params (list → detail) or call a webhook with the rowNot stored (display)
referenceSearchable dropdown linking one record from another form (with inline "create new"). Works at the top level, inside object fields, and in array rows. Always internal — never shown on the public formRecord ID + title label
displayRead-only text shown on the formNot stored
titleLarge page heading (layout only) — the natural top element of an app hub pageNot stored
subtitleSection heading (layout only)Not stored
dividerHorizontal divider (layout only)Not stored
linebreakLine break (layout only)Not stored
buttonAction button: call a webhook URL (server-side proxied), navigate to another form (with params), or go back — see NavigationNot stored
linkText link: navigate to another form (with params), open an external URL (interpolated), or go backNot stored

Tip: Field order matters — drag fields to reorder them (sub-fields of object/array fields reorder the same way inside the field editor). The order in the editor is the order in the public form.

Embedded forms can link to each other, turning a set of forms into a lightweight app on any website — all inside one embed container, SPA-style, with browser back/forward support and no page reloads.

MechanismWhat it does
Navigate button / linkA button or link field with the Navigate to another form action loads the target form in place. Optional params carry data across: values support {{data.fieldName}} (current inputs) and {{params.x}}
Table row actionsPer-row buttons on a data table: navigate to a form with {{row.x}} params from the clicked row (list → detail), or call a webhook with the row payload
Back buttonA button field with the Go back action returns to the previously shown form (browser history)
Post-submit navigationPer form (Settings → "After submit, navigate to"): after a successful submit the embed loads the target form. Params support {{data.fieldName}} and {{record.id}} (the new record)
Receiving paramsThe target form reads params as {{params.x}} — in display fields, or as a field's default value to prefill it. URL query params (?x=y) work the same way
SDKSnapi.navigateToForm(slug, params) is also callable directly from your page's JavaScript

Passing data around: {{data.x}}, {{params.x}}, {{row.x}}, {{record.id}}

Several places in Snapi accept small template placeholders that are replaced with live values. There are four sources, and where you are determines which ones exist:

PlaceholderWhere the value comes fromAvailable in
{{data.fieldName}}The current form: what the user has typed so far, merged over any onload-webhook data. Use dot-paths for nested values ({{data.customer.email}})Display fields, navigation params, external link URLs, button payloads
{{params.x}}How the visitor arrived: URL query parameters (?x=y) merged with params passed by whichever navigation brought them here. Navigation params winDisplay fields, field default values (prefill!), hidden fields, nav params
{{row.x}}The clicked table row — only meaningful inside a table's row-action paramsRow-action params
{{record.id}}The id of the record that was just createdPost-submit navigation params only

The pattern that ties an app together: form A sends a value as a nav param → form B receives it as {{params.x}} → B surfaces it via a display field, prefills an input with it (set the field's default value to {{params.x}}), or carries it invisibly in a hidden field — which submits with the record, so your webhooks see where the user came from.

Worked example — order follow-up: the Orders form's post-submit navigation targets "Order Feedback" with params {"order": "{{record.id}}", "email": "{{data.email}}"}. Order Feedback has a hidden field order_ref with default {{params.order}} and an email field with default {{params.email}} (prefilled, editable). When feedback is submitted, the record — and your n8n webhook — carries order_ref linking it back to the order.

Unknown placeholders resolve to an empty string — they never error. Values are substituted at render/click time with whatever is currently in the form.

Apps (starter forms)

An App is a form used as a starting point — a hub page with typically no inputs, just display text, links, and buttons that tie your other forms and boards into one experience: a CRM hub, an order portal, a helpdesk entry page. Apps live under the Apps menu (opened as full pages in Snapi) and embed on any website like any public form — the Submit button is omitted automatically when a form has no input fields. Full guide: Apps →

Portal users (sign-in-required forms)

Snapi distinguishes two kinds of people. Members are your team on the inside — they sign in to the Snapi workspace to build forms, manage boards, and work the data, each with a role (viewer / editor / admin / owner). Portal users are your application's end users — the clients, vendors, and field staff who use what you build: they sign in on your embedded forms, portals, and apps, and never see the Snapi workspace at all.

Turn on Require sign-in for a public form and only authenticated users can view or submit it: members always can; portal users can when assigned (per form, or all gated forms). Portal users are managed in Settings → Portal Users (a Team plan feature), can be suspended instantly, and share the plan's seat pool with members. The embedded form shows a sign-in screen automatically; sessions last 7 days.

Embed themes

Manage reusable color presets in Settings → Themes (primary, text, background, border, danger, success). Set one as the org default and/or pick a theme per form in the form builder — ideal for matching each client's website when you embed forms on their domains.

Webhook runs & proxying

All webhook calls — pre/post submit, onload data fetches, and button/row actions — are made server-side: no CORS requirements on your webhook endpoints, and headers/secrets never reach the browser. Every call is logged with status, duration, and error detail; see the Recent Webhook Runs panel on the form builder's Webhooks tab (last 100 calls per form).

Auto-numbering with sequences

Sequences give records automatic document numbers — PO numbers, invoice numbers, ticket IDs. A sequence is org-wide and named, so several forms can share one counter. Manage them in Settings → Sequences (or create one inline from the field editor), then add a Sequence Number field to a form and pick the sequence.

SettingWhat it does
PrefixText before the number, e.g. PO-. Supports date tokens {YYYY} {YY} {MM} {DD} rendered at assignment time — e.g. INV-{YYYY}-INV-2026-0042
Zero-paddingDigit width of the number: padding 4 → 0042
Counter resetOptionally restart at 1 every year or month. Pair with a matching date token in the prefix, or numbers will repeat across periods
Allow manual editingPer field: users may type their own value; leaving it empty still auto-assigns. Manual values never move the counter

Numbers are assigned server-side, atomically with the record insert — concurrent submissions (UI, public forms, REST API, and MCP agents alike) can never receive the same number. Values are stored as plain text and frozen at creation: renaming the sequence or changing its prefix later never rewrites existing records. When the sequence field is a form's first text-like field, its value becomes the record's title automatically.

Public form URL

Every form has a shareable public URL in the format:

https://app.snapi.ca/embed/{org-slug}/{form-slug}

Anyone with this URL can submit the form — no account needed. Use this link in emails, on websites, or in chatbots. To embed the form in an iframe or use the JavaScript SDK, see Embedding Forms.

Viewing and managing records

The Records view on any form shows all submissions in reverse chronological order. Each row shows the submission time, the value of the first text-like field as a title, and any status or board assignment.

Clicking a record opens the detail panel, where you can:

Webhooks on submit

Each form can have webhooks that fire around the form lifecycle. There are three types:

TypeWhen it firesCan abort the submission?
onload webhookWhen the form loadsNo — its response can prefill form data before anything is submitted
pre-webhookBefore the record is savedYes — respond with {"abort": true} to reject the submission and surface the error to the submitter; the response can also modify the submitted data before it's saved
post-webhookAfter the record is savedNo — the record already exists; use for notifications and downstream automation

The pre-webhook payload is a JSON object containing the form's ID, name, and slug plus all submitted field values; post-webhook payloads also include the saved record. See Webhooks for the full payload schema and authentication headers.

Note: forms are called entities in the API for backwards compatibility — API paths and webhook payload keys use entity.

Record data via API

You can read and create records programmatically using your API key. This is useful for building integrations, bulk imports, or giving an AI agent read access to submitted data. See API Keys for the endpoints and authentication details.

Note on file uploads: File fields store a reference to the uploaded file, not the raw bytes. Use the GET /api/v1/files/:filename endpoint to download the actual file. Files count against your plan's storage limit.