Form Prefill
A form that already knows the answer should not ask for it. The campaign that sent the visitor is in the URL; the signed-in user's email is in the session. Asking anyway costs a field, and the answer you get back is worse than the one you already had.
prefill maps field names to where their initial value comes from:
forms:
- id: 1
name: lead-capture
title: Request a demo
submitTo: { table: leads }
prefill:
utm_source: $query.utm_source
utm_campaign: $query.utm_campaign
plan: Starter
fields:
- { kind: table-field, column: email, required: true }
- { kind: standalone, name: plan, inputType: short-text }
- { kind: standalone, name: utm_source, inputType: short-text, hidden: true }
- { kind: standalone, name: utm_campaign, inputType: short-text, hidden: true }
Prefill Sources
Every value in the map is either a reference or a literal. References resolve server-side while the form is being rendered.
| Source | Resolves to | When it cannot resolve |
|---|---|---|
$query.<name> |
The named URL query-string parameter of the request that rendered the form. | Entry dropped; the field renders empty. |
$user.<prop> |
A property of the signed-in user — $user.email, $user.id. |
Entry dropped; the field renders empty. |
| String, number, boolean | Itself, verbatim — a plain default the submitter can overwrite. | — |
An unresolvable reference is removed from the map, never rendered. The submitter sees an empty input, and the literal $query.utm_campaign never leaks into the HTML.
Prefilled Hidden Fields
A prefill on a hidden: true field is the point of the feature for attribution work. The field renders as an <input type="hidden"> carrying the resolved value, so the campaign the visitor arrived on rides into the record without ever appearing on screen — and without a hand-written tracking script.
prefill:
campaign: $query.ref
fields:
- { kind: table-field, column: email, required: true }
- { kind: table-field, column: campaign, hidden: true }
/forms/lead-capture?ref=partner-x now stores partner-x in the record's campaign column.
$user Needs a Session
$user.<prop> resolves only when the request carries an authenticated session, which in practice means the form requires one. On a public form the reference simply drops and the field renders empty — no error, no leaked session state.
That tolerance is specific to the prefill map. A per-field defaultValue: $user.email on a public form is a configuration mistake rather than a runtime shrug, and it is rejected when the config is decoded, naming the form and the offending field. Either require authentication, or move the reference into prefill.
$parent and $record do not resolve here. The schema accepts them, but the top-level prefill map is resolved against the request — query string and session — and knows nothing about a host record. Any token it does not recognise passes through as a literal string, so $parent.id in forms[].prefill renders the characters $parent.id into the field. Parent-record prefill belongs on the page's form control, as inlinePrefill, where a host record actually exists.
Prefill Versus defaultValue
Both seed an initial value, and both accept $query / $user references. The choice is about where the wiring lives.
| Use | When |
|---|---|
prefill |
Attribution and session seeding — the wiring is a concern of the form, and reads better in one block. |
defaultValue |
A value that is part of the field's own definition, such as a fixed starting quantity. |
Related Pages
- Form Fields —
defaultValue,hidden, and the fields a prefill key names. - Form Controls —
inlinePrefill, where$parentand$recordresolve. - Access Control — the
requirelevel that makes$userresolvable. - Forms Overview — where
prefillsits in the full form schema.
Last updated July 27, 2026
This documentation was written with AI, so errors or outdated content are possible. Sovrium is in beta — contributions and corrections are welcome.