Public forms (PublicFormDecl)
Generated from the doc schema (CatalogDocSchema, @canonify/kernel-catalog/doc): each field’s type, default and description are the schema’s own, and each example is a bundle file that readBundle decodes. A field’s Example is its value in one of those examples.
PublicFormDecl
A public form (runtime, C34).
The forms slot of the doc holds these, keyed by id. In a bundle each is one file, forms/<name>.json, named by its slug, which the file then leaves out.
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
id |
FormId | yes (propose assigns it; a new declaration leaves it out) | — | — | A public form id (fm_…); renames keep it. |
action |
ActionId | yes | — | "ac_1b3d5f7h9k" |
The action a submission calls; it has no subject. |
slug |
Ident | yes | — | "contact" |
The form’s name in its public address. |
title |
Text | yes | — | "Contact us" |
The title shown on the form. |
inputs |
Ident[] | yes | — | ["name","email","message"] |
The action inputs the form shows, in field order. |
fixed |
Record<Ident, Expr> | no | — | {"source":"'web'"} |
Inputs the form sets itself: actor, clock and literal CX values only. |
identity |
"anonymous" | "email_pin" |
yes | — | "email_pin" |
Who submits: anyone, or a person who proves an email address with a PIN (authz). |
onSubmit |
PublicFormDecl.onSubmit | no | — | (see the example) | What a submission does besides the call. |
challenge |
"turnstile" |
no | — | "turnstile" |
A human check the visitor must pass before the submission is looked at; without a passed check it refuses with form.challenge_failed. |
silentThankYou |
true |
no | — | true |
The form accepts that when a rule over other rows refuses a submission (a unique key, a no_overlap, an invariant that reads past its own row), the visitor is thanked as if all were well and nothing is written or sent, so no stranger learns that a row exists. Any refusal after the action starts writing gets the same thanks, a fault in the form too, and only the audit row says why; a fail trigger on the action can tell the author. A form its action can meet such a rule on does not compile without it (form.silent_thank_you_undeclared); a form that cannot is refused for declaring it (form.silent_thank_you_unneeded). To say "taken" instead, add a precondition over a type its visitors read. |
open |
boolean | yes | — | true |
Whether it accepts submissions; a closed form refuses with form.closed. |
forms/contact.json
{
"action": "ac_1b3d5f7h9k",
"title": "Contact us",
"inputs": ["name", "email", "message"],
"fixed": { "source": "'web'" },
"identity": "email_pin",
"onSubmit": {
"emit": {
"to": "email.send_template",
"input": { "recipient": "me.email", "template_id": "'received'" },
"maps": { "params": { "name": "input.name" } }
},
"message": { "text": "Thank you. We will answer within a day." }
},
"challenge": "turnstile",
"silentThankYou": true,
"open": true
}
PublicFormDecl.onSubmit
What a submission does besides the call.
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
emit |
PublicFormDecl.onSubmit.emit | no | — | (see the example) | A delivery sent with the submission, in the same transaction. |
message |
MsgRef | yes | — | {"text":"Thank you. We will answer within a day."} |
What the person sees after submitting. |
PublicFormDecl.onSubmit.emit
A delivery sent with the submission, in the same transaction.
| Field | Type | Required | Default | Example | Description |
|---|---|---|---|---|---|
to |
string (matching ^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$) |
yes | — | "email.send_template" |
A delivery operation: the only I/O, sent after commit (e.g. webhook.post). |
input |
Record<string, Expr> | yes | — | {"recipient":"me.email","template_id":"'received'"} |
The CX value of each input of the delivery operation. |
maps |
Record<string, Record<string (matching ^[A-Za-z][A-Za-z0-9_]*$), Expr>> |
no | — | {"params":{"name":"input.name"}} |
The inputs that hold an object (e.g. params, or a webhook body): each field name with its CX value. |