How Forms Work

How form definitions, form sections, the form function, the inbox, subscribers, notifications and confirmation pages fit together on a Kugel Platform site.

8 min read By Platform Documentation Guide #Architecture #Publishing

What a form is on this platform

A form on a Kugel Platform site is a governed object, and its submissions are stored in the site's own store, where they appear in the admin inbox. No step in the hosting provider's dashboard is needed for a form to start collecting.

This article explains the parts that make that work, follows one submission from the page to the inbox, and states what the form system does not do. It was checked on 15 October 2026 against the live form contract on this site and the engine code. Anything not checked is labelled.

The five parts

Each part has one job. Read them as a chain: the definition describes the form, a section places it, one function receives every post, the store keeps the result, and a confirmation page answers the visitor.

  • form_definition is the object that describes one form: its fields, spam gate, destinations and confirmation route. Its id is frm_NAME, where NAME is the form name with any hyphen turned into an underscore. Many definitions can exist on one site.

  • A form section is a contact_form or newsletter_signup section on a page, template or section template. It names its definition in form_id. The older formName field stays as a fallback name; when form_id is set, the definition's name wins.

  • The composed form leaf is the same idea inside a composed section. Its form_id names a definition, submit_label sets the button text, and layout is stacked (default) or inline. The registry allows one placement per form_id per section.

  • form-submit is the platform function every governed form posts to, at /.netlify/functions/form-submit. It looks the form up by name and serves published definitions only.

  • Submissions are one record per valid post, stored in the tenant's own store and read over MCP with the submission tools described below.

The confirmation page is an ordinary page. A definition redirects to its confirmation.page_route (default /thank-you), which must be a site-relative path, so a form cannot redirect a visitor off the site.

The shape of a definition

A definition has a name, a field list, optional destinations, a spam setting and a confirmation setting. The live contract enforces these rules at write time, and a violation blocks the write:

  • The name is lowercase letters and digits, optionally joined by a hyphen or underscore. It must fold to the object id, so a body whose name no longer matches its id is refused.

  • Each field has a name, a label and a type of text, email, textarea, checkbox, select or hidden. The list cannot be empty.

  • Field names must be unique, must start with a letter, and cannot use the wire names form-name, form, bot-field, _ts or _started_at.

  • A select field needs options. Only a checkbox can be a consent field. An autoresponder needs an email field to reply to.

  • Destinations are optional: notify_emails, an autoresponder with subject and body, a lead_magnet_product_id, and a webhook_url.

  • The spam setting always has the honeypot on, plus min_fill_ms (default 1500). The confirmation setting has page_route (default /thank-you) and an optional message.

To change a form, check the definition out and apply the set_form_fields patch operation. The field list is replaced wholesale, so send the whole list.

Life of one submission

Every governed form follows the same path, with or without JavaScript.

1. The page renders the inputs from the published definition, plus a hidden form name, a hidden honeypot field that people never see, and a start time.

2. With JavaScript, a small script posts the form to the platform's form function and follows the redirect it returns. Without JavaScript, the browser posts the form directly.

3. The function looks up the published definition. An unknown name, or a definition that is only a draft, gets a 404.

4. A filled honeypot, or a form completed faster than the minimum fill time, gets an answer that looks like success. The submission is filed in the inbox as spam, and no notification, autoresponder or subscriber record is created. A post with no start time skips the timing check and is tagged for triage instead.

5. A missing required field, a malformed e-mail or a choice outside the allowed options gets a 400 that names the field. Only fields that were actually shown are stored, so a consent box that was never rendered is not recorded as a refusal.

6. A valid submission is stored, counted as a lead conversion, and appears in the inbox as new. Newsletter and lead-magnet sign-ups also create or update a subscriber record with an unsubscribe link.

7. Destinations run in order: notify e-mail, autoresponder, lead-magnet delivery, then webhook. Each is best effort. A failing destination is logged and never fails the visitor's submit; the webhook times out after 5 seconds.

8. The visitor lands on the confirmation page.

The inbox: triage, subscribers and notifications

Every submission is an inbox item with a triage status: new, open, done, spam or archived. Items can carry an assignee, tags and notes, and each is classed as contact, subscribe, lead or other from its form. The admin Inbox page and the submission tools work on the same items: list and filter them, read one, update triage and notes, export one form's submissions as CSV or JSON, and manage subscribers.

Visitor e-mail addresses are masked for anyone below the admin role, agents included. Permanently deleting a submission, for an erasure request, is limited to a signed-in admin or owner.

Inbox settings decide who hears about new items. Notifications are off by default. They can be set to one e-mail per submission or a daily digest, but only once the site can send mail. A form can also name its own notify addresses, which send in addition.

What every site gets

Every site is meant to hold three published definitions, contact, newsletter and lead magnet, plus a thank-you page built from the shared recipe. A new site gets them from genesis. An existing site gets any that are missing from the hourly recipe sweep, which never overwrites a site's edited copy and never brings back an archived one. A page the site already has at /thank-you is left alone.

The same sweep upgrades older form sections that predate form definitions. It matches each one to a published definition by name, or creates one from the old fixed field set, and links the section to it. It publishes the definitions but not the pages it touched, so those pages pick up the change at their next publish and release.

Mail

Every e-mail a site sends, including inbox notifications, the daily digest, autoresponders, lead-magnet links and invitations, uses the same sender, chosen in this order: a mail key an owner saved for this site under Admin, Integrations, E-mail; otherwise mail settings in the site's environment; otherwise no sender. With no sender the submission is still stored, the skipped e-mail is noted on the submission, and the visitor sees a normal success.

The lead-magnet destination delivers nothing until the definition names the product to send. Until then the form records the address only.

Adding a form: the safe sequence

  1. Read the contract: object_contract with type form_definition.

  2. Dry-run the candidate body with object_validate and no object id. Nothing is written.

  3. Create it with object_create. The id is minted as frm_NAME.

  4. Check it out, publish it, and check it back in. A draft accepts no submissions, and publish keeps the lock until check-in.

  5. Place a section on a page with form_id set to the new id, then publish the page.

  6. Run release_to_production. Publishing exports dark, with a skip marker, and does not deploy.

If the form answers 404, the definition is not published. If the visitor lands on a missing page, the confirmation page is not published or the site has not been released.

Boundaries

The spam gate is the honeypot and the timing check. The code includes no challenge test such as Turnstile or reCAPTCHA, no CRM sync, no double opt-in and no rate limiting. A retention period can be set in the inbox settings, but no clean-up runs yet, so submissions are kept until someone deletes them.

Not verified for this article: no test submission was posted, so the answers above come from the live contract and the engine code rather than a recorded request.

Back to Library