Skip to main content
View as Markdown

Form Submissions

Every form submission produces a durable record. By default it lands in the built-in form_submissions ledger — the platform's canonical "this form received an answer" log — and, when submitTo.table is set, in your own table too. The two writes are transactional, so you never end up with a ledger entry promising a record that was never written. After the writes commit, onSuccess decides what the submitter sees and onError covers failures.

forms:
  - id: 1
    name: contact
    submitTo:
      table: leads
      # storeSubmission defaults to true — table AND ledger
    fields:
      - { kind: table-field, column: email, required: true }
    onSuccess:
      type: toast
      variant: success
      message: Thanks! We'll be in touch.

The Submission Ledger

The form_submissions ledger lives in an internal Sovrium-managed schema (mirroring the isolation of auth.*) — you never create or manage it yourself. Every submission writes one ledger row by default; set submitTo.storeSubmission: false to opt out (in which case table or automation becomes mandatory).

Column Type Description
id UUID Stable submission id; surfaced to onSuccess templates via $submission.id.
form_name text The forms[].name at submission time.
form_id integer The forms[].id at submission time (denormalized for joins).
submitted_at timestamptz Server timestamp when the row was created.
submitter_user_id UUID, nullable The authenticated user, when the form requires authentication.
submitter_ip inet, nullable Submitter IP (stored hashed by default) — used by anti-spam and audit.
submitter_user_agent text, nullable Submitter user-agent string.
data jsonb The full validated answer payload (mirrors what was written to submitTo.table).
linked_record_table text, nullable The bound table name, when submitTo.table is set.
linked_record_id text, nullable The inserted record's id (text-typed to handle integer / UUID / string ids).
status enum Lifecycle state (see below).
status_reason text, nullable Short reason when status is failed or spam.

Lifecycle Status

State Meaning Transitions to
received Accepted; written to the ledger and bound table (if any). processing (if automation), done (otherwise).
processing The bound automation is running. done on success, failed on terminal failure.
done Terminal — all writes and the bound automation completed.
spam Terminal — the anti-spam pipeline classified it as spam; preserved for moderation.
failed Terminal — a downstream step failed and was not retried; status_reason is set.

The Dual-Write Contract

When submitTo.table is set, the submission writes one row to that table and one ledger row inside a single transaction. If the table write fails — a constraint violation, type error, or foreign-key miss — the ledger row rolls back too, and the submitter receives an HTTP 4xx/5xx error with fieldErrors:[{ name, message }] naming the offending column. No "ghost" ledger entry is ever left behind.

When submitTo.automation is set, the automation is invoked after both writes commit. An automation failure does not roll back the writes — the submission is recorded, status moves to failed, and the automation's own retry/failure machinery applies.

forms:
  - id: 2
    name: stream-event
    submitTo:
      automation: post-event-to-stream
      storeSubmission: false # opt out — ledger is skipped; automation handles persistence
    fields:
      - { kind: standalone, name: event_payload, inputType: long-text }

On Success

The onSuccess object decides what the submitter sees after a successful submission. It is a discriminated union on type.

type Behavior
successPage Render a custom success page (title, message, optional buttonLabel + buttonHref, optional actions[], showSummary).
redirect Navigate to url after an optional delaySeconds (default 2; 0 is immediate). Supports $submission.id / $record.id template variables.
reset Clear the form for another submission, with an optional message and preserveFields[] to retain.
toast Show a transient toast (message, optional variant: success / info).
message Replace the form with an inline message in place.
onSuccess:
  type: redirect
  url: /thank-you?ref=$submission.id
  delaySeconds: 0

A successPage may render actions[] buttons — each is either action: reset (submit another) or action: navigate (link to url) — and showSummary: true lists the submitted values, honoring hidden fields and per-field read permissions.

On Error

The onError object controls the message shown when a submission fails server-side.

Property Description
type toast, message (inline), or errorPage (full page).
message Body message. Supports $t: keys.
title Optional title (used by errorPage).
variant Toast variant (error / warning) — only meaningful when type: toast.
onError:
  type: message
  message: Something went wrong saving your request. Please try again.
  • Forms OverviewsubmitTo, storeSubmission, and the onSuccess / onError schema.
  • Form Fields — per-field permissions that redact ledger exports.
  • File Uploads — file metadata stored in the ledger data payload.
  • Tables Overview — the bound table the dual-write targets.