Compose a contact form in a page layout
form-basics explains the form operation itself. This recipe adds the layout and composition around that operation: a contained contact section, an explanatory heading, grouped inputs, and a clear completion control.
Prerequisite checklist
Section titled “Prerequisite checklist”- Create or look up the target model before authoring this page. Substitute its real, per-tenant UUID for every
<model-id>below; do not publish a literal placeholder or another tenant’s ID. - Use the renderer contract, not the block-pack editor schema, for the form source. The block-pack schema’s
formentry has emptycontentFields, even thoughFormBlock.sveltereads thesesourcefields. - The target model must permit the intended form write. For a public contact intake, configure public create deliberately; for an authenticated flow, verify the consumer session and policy first.
The complete tree below uses the form contract: source.target_mode: "create", source.model_id, and source.on_success_toast. source.on_success_redirect, source.on_success_set_field, and source.defaults are optional source controls; add only the ones the page needs. Each child input names its model field with binding.field and defers its value with save_mode: "submit". The submit control is a button whose display.type is exactly "submit".
{ "blocks": [ { "id": "contact-section", "type": "container", "display": {"layout": "flex", "direction": "column", "gap": 24, "full_width": true}, "style": {"class": "mx-auto max-w-2xl px-6 py-12"}, "blocks": [ {"id": "contact-heading", "type": "heading", "content": {"text": "Contact us", "level": "h1"}}, {"id": "contact-intro", "type": "text", "content": {"text": "Tell us how we can help and we will reply soon."}}, { "id": "contact-form", "type": "form", "source": { "target_mode": "create", "model_id": "<model-id>", "on_success_toast": "Thanks — we received your message.", "defaults": {"source": "website"} }, "display": {"layout": "flex", "direction": "column", "gap": 16}, "blocks": [ {"id": "contact-name", "type": "input", "content": {"label": "Name", "placeholder": "Your name", "required": true}, "binding": {"field": "name", "save_mode": "submit"}}, {"id": "contact-email", "type": "input", "content": {"label": "Email", "placeholder": "you@example.com", "required": true}, "display": {"type": "email"}, "binding": {"field": "email", "save_mode": "submit"}}, {"id": "contact-message", "type": "textarea-block", "content": {"label": "Message", "placeholder": "How can we help?", "required": true}, "binding": {"field": "message", "save_mode": "submit"}}, {"id": "contact-submit", "type": "button", "content": {"label": "Send message", "variant": "default"}, "display": {"type": "submit"}} ] } ] } ]}Keep optional success behavior at the form level. For example, on_success_redirect can send a visitor to a thank-you page, and on_success_set_field can write a named value into the enclosing form scope after success. defaults pre-populates values that should be submitted even without a visible input; its keys must be valid fields on the chosen model.
Actor rule for rules
Section titled “Actor rule for rules”form.source.form_id changes which user a rule sees as the actor. A direct authenticated consumer create stamps the application owner, while an embedded model-bound public form submit stamps the validated app-user as submitted_by. Choose the path intentionally before attaching an actor-sensitive rule.
