Forms step by step
Instances, site binding, submission and error handling.
A working form in a Loom theme
A form consists of a GrowSite instance, field configuration and a page element referencing its instance_id. The theme supplies presentation; the instance belongs to one website. Never ship a customer's form ID inside a reusable theme package.
The example package includes a contact page and sections/contact.html with a heading, description and gs-form. Enter the instance ID in form_id, or add a separate canvas form. The current importer does not create form instances from Loom schema.
1. Prepare the site and instance
- Enable
contact-formfor the site/workspace. - Open the contact page and add a form element to the section canvas.
- Choose an existing instance or create one in the element settings.
- Name it, configure fields, required answers and a success message.
- Save the form. The builder assigns its returned ID to the element.
- Save the page and test submission on the public view of a test site.
Submission is disabled in the editor. A submit button doing nothing in edit mode does not indicate a broken endpoint. The instance must be active and belong to the same site.
2. Instance configuration
This JSON configures an instance; it is not templates/contact.json or Loom schema. Reproduce it in the form editor. Application integrators may send it to authenticated POST /api/v1/sites/{site}/plugin-instances as a user authorized to manage that site.
{
"key":"contact-form",
"name":"Contact form",
"status":"active",
"page_id":null,
"configuration":{
"fields":[
{"key":"name","label":"Name","type":"text","required":true},
{"key":"email","label":"Email","type":"email","required":true},
{"key":"topic","label":"Topic","type":"select","required":true,"options":["Quote","Support"]},
{"key":"message","label":"Message","type":"textarea","required":true}
],
"success_message":"Thank you. Your message has been received."
}
}page_id may identify one of this site's pages or be null. Instance names allow 120 characters and success messages 300. See inputs for field limits. Update an instance through PATCH /api/v1/sites/{site}/plugin-instances/{instance}.
3. Connect the canvas element
This is the shape of a node in content.canvas.nodes. Replace the example ID with the saved instance ID; preferably let the builder select and persist it.
{
"id":"contact-node",
"type":"form",
"name":"Contact",
"instance_id":"01ARZ3NDEKTSV4RRFFQ69G5FAV",
"style":{"padding":24,"radius":16}
}content.canvas also contains version:1, slots, fields and optional elements. These are page data, not Loom block settings. The renderer turns a form node into a PluginRenderer instance of contact-form.
For a native Loom section, shared SectionCanvas renders canvas nodes after section content. Do not add another gs-canvas just to display the form: that can render nodes twice. Use gs-form from step 5 to embed the form inside a section.
4. Submission and errors
The shared component posts to /api/v1/render/sites/{slug}/contact. PluginProvider supplies the API host and endpoint; do not hardcode the production host in the theme. Request shape:
{
"instance_id":"01ARZ3NDEKTSV4RRFFQ69G5FAV",
"values":{"name":"Anna","email":"anna@example.com","topic":"Quote","message":"Please contact me."},
"website":""
}website is a hidden protection field and must stay empty. Only keys declared by the instance belong in values. The API checks instance ownership/status, site availability, plugin access and answer types. The endpoint is rate limited.
On success, the component displays the configured or localized success message and resets fields. On failure, it retains values and displays an error. The button is disabled while busy and status uses aria-live="polite". Do not attach a second submission handler.
Submission requires a published site. Local draft rendering depends on sites.render_drafts; this is not production behavior. Recording a submission does not itself integrate with an external newsletter service.
5. Embed directly in Loom
<gs-form instance-id="[[ section.settings.form_id ]]" submit-label="Send"></gs-form>Add a text schema setting with ID form_id, label “Form ID” and an empty default. After creating the form in the panel, enter its real ID into this section setting. Use the same setting name in blocks so preview can load the instance. Assign the form to this page or the whole site. Do not simultaneously add the same form as a canvas node.
No TSX adapter is required. The element uses the shared component: a disabled plugin, empty ID or unavailable instance produces no output. Optional submit-label changes the button label; fields and success messages come from the instance. Never distribute a package containing a customer's form ID.
6. Test the complete flow
Check missing instances, disabled plugins, an instance owned by another site, an empty required email, an invalid option, successful submission, a network error and retry. Responses should appear in the correct site's submissions. Confirm a double click does not send concurrent requests and that reloading the editor preserves the selected instance.
Contact page files
sections/contact.html
<section class="launch-contact">
<h1 data-text-field="[[ section.settings_path ]].title" gs-text="section.settings.title"></h1>
<p data-text-field="[[ section.settings_path ]].text" gs-text="section.settings.text"></p>
<gs-form instance-id="[[ section.settings.form_id ]]"></gs-form>
</section>templates/contact.json
{"title":"Contact","sections":{"contact":{"type":"contact"}},"order":["contact"]}