Skip to main content
View as Markdown

Forms Overview

Forms capture input from submitters and route it somewhere useful — into a table, through an automation, into the built-in submission ledger, or any combination of the three. They are declared in the top-level forms array, each one reachable at its own URL, embeddable inside a page, and addressable from an automation trigger.

A form is the public-facing counterpart to a table: where a table defines what data looks like, a form defines how people contribute it. The same form can write directly to a tables[] entry, fire a notify-sales automation, or simply land in the platform's form_submissions ledger for later review.

forms:
  - id: 1
    name: contact
    title: Contact Sales
    path: /contact
    submitTo:
      table: leads
    fields:
      - { kind: table-field, column: email, required: true }
      - { kind: table-field, column: message }

Form Properties

Each entry in the forms array accepts the following properties.

Property Description
id Unique positive integer identifying the form. Must be unique across forms[].
name Kebab-case unique identifier (^[a-z][a-z0-9-]*, 1–64 chars). Referenced by page components (formRef) and the form automation trigger. Must be unique across forms[].
title Human-friendly title shown to submitters. Supports $t: i18n keys.
description Optional intro paragraph rendered above the first field.
path Optional friendly public URL (e.g. /contact). When set, the form is served there and at the canonical /forms/{name}. See Public Routes.
submitTo Where the submission is persisted and/or routed: a table, an automation, the ledger, or a combination. Required. See Submit Targets.
fields Ordered list of field definitions rendered in the form. At least one required. See Form Fields.
layout Rendering mode: single-page (default), multi-step, or one-question. See Multi-Step Forms.
steps Step definitions for multi-step / one-question layouts. See Multi-Step Forms.
fieldGroups Labeled section dividers grouping fields within a single-page layout.
display Cosmetic options (columns, progress bar, submit label, per-form theme). See Multi-Step Forms.
access Access control: all, authenticated, or a role list, with an optional redirectTo. See Access Control.
availability Submission window (opensAt, closesAt) and submission cap (maxSubmissions), with an optional custom closed-form page.
antiSpam Honeypot, sliding-window rate limits, and a CAPTCHA connection stub.
analytics Per-form opt-out (enabled: false) excluding the form from aggregate analytics. Defaults to enabled.
prefill Map of form field → $query.{name} / $user.{prop} / $parent.{path} reference or literal default. See Form Fields.
submitter Save-and-resume, edit-after-submit, and unique-submission rules.
onSuccess Post-submit behavior (success page, redirect, reset, toast, inline message). See Submissions.
onError What to show when a submission fails server-side (toast, inline message, error page). See Submissions.

Submit Targets

The submitTo object decouples a form from any single persistence model. At least one of table, automation, or storeSubmission: true (the default) must be set — otherwise the submission would be discarded silently and the schema rejects the config.

Property Description
table Persist the submission as a record in this table. References a tables[].name; validated against the app's tables[] array.
automation Invoke this automation after the writes commit. References an automations[].name; validated against the app's automations[] array.
mapping Map form field names to destination column names. Defaults to identity (form field name = table column name). A mapping target that does not exist on the table fails validation.
storeSubmission Persist the submission in the built-in form_submissions ledger. Defaults to true — set false to opt out (and then table or automation is mandatory).
submitTo:
  table: support_tickets
  automation: page-on-call
  mapping:
    userEmail: email # form field 'userEmail' -> table column 'email'

Public Routes

Every form is reachable at the canonical route /forms/{name} regardless of configuration — a form without a path is not private, it is simply only reachable there. When path is set, the form is served at that custom path and at /forms/{name} simultaneously (no redirect; both URLs render the same form).

Rule Description
Canonical route /forms/{name} always serves the form (HTTP 200). An unknown name returns 404.
Custom path Optional. Must start with /, 2–256 URL-safe characters; static (no :id dynamic segments).
Reserved prefixes /api/, /admin/, /forms/, and /auth/ are rejected so a form cannot shadow a built-in route.
Collision rule A forms[].path MUST NOT collide with any pages[].path. Collisions fail validation with an error naming both.
Embed route /forms/{name}/embed serves a minimal HTML shell for iframe embedding on third-party sites.

Access Control

The access object reuses the shared permission model used by tables, pages, buckets, automations, and agents.

Property Description
require 'all' (everyone, including anonymous), 'authenticated' (any logged-in user), or a role list (['admin', 'editor']).
redirectTo Optional path (e.g. /login) to redirect denied submitters to, instead of returning a 401/403. Must start with /.
forms:
  - id: 2
    name: member-survey
    title: Member Survey
    access:
      require: authenticated
      redirectTo: /login
    submitTo: { table: survey_responses }
    fields:
      - { kind: standalone, name: rating, inputType: rating, required: true }

Embedding a Form in a Page

A top-level form can be rendered inline inside a page via the form control's formRef. The form is defined once and reused anywhere — fields, validation, conditional logic, multi-step layout, file uploads, and onSuccess/onError all flow from app.forms[]. The host page's access control is intersected with the form's own rules at render time.

pages:
  - id: 1
    name: landing
    path: /
    components:
      - type: form
        formRef: contact # renders the top-level "contact" form inline
        props:
          label: Get in touch # display-only override of the submit label

See Form Controls for the page-component side of formRef.

  • Form Fields — standalone vs table-bound fields, prefill, inline relationship create.
  • Conditional Logic — visible / required / disabled rules.
  • Multi-Step Forms — steps, branching, one-question layouts, display overrides.
  • Submissions — the dual-write ledger, success and error handling.
  • File Uploads — attachment fields backed by buckets.
  • Tables Overview — the data models forms write to.
  • Form Controls — embedding a form inside a page.
  • Auth — roles and authentication behind access.