Forms
Forms is Typeroll’s built-in core module for collecting structured information from a published site. It covers simple contact forms, booking requests, applications, lead funnels, and other multi-step flows without requiring a third-party form provider.
Availability
Section titled “Availability”Forms is included in every Typeroll Cloud site and every self-hosted Typeroll installation. It requires no premium Typeroll App, Extension, or separate subscription. Typeroll Cloud sends submissions to the hosted Forms service; a self-hosted installation sends them to its own Forms endpoint and datastore. The published customer site remains static in both cases.
Cloud plans may set submission-volume, retention, or delivery limits. Those limits do not turn Forms into an optional module. Self-hosted operators control their own capacity, storage, retention, and delivery costs.
Capabilities
Section titled “Capabilities”- single-step and multi-step forms;
- reusable field blocks with client-side and server-side validation;
- partial saves as a visitor advances through a funnel;
- a submissions inbox and agent-readable submissions;
- email notifications through a configured email provider;
- post-submit actions, success messages, and redirects;
- module-contributed forms, actions, and prefill sources.
Forms are stored as steps. Each step contains the same block tree used by the
rest of Typeroll, so a simple form is one step and a funnel is several steps.
The core/form block places a form on a page and wires its signed submission
token and runtime automatically.
Runtime and protection
Section titled “Runtime and protection”Published forms submit to Typeroll’s forms service. Each submission is checked with a signed token, a honeypot, server-side validation, and rate limiting. Multi-step forms can save partial answers between steps. Submission data is not embedded in the static site build.
Limits that apply to every submission:
- the request body may be at most 64 KiB;
- each answer must be text, a number, a boolean or a list of those, and text is
limited to 10,000 characters unless the field sets a higher
max; - a solved proof of work counts for one accepted post;
- an email action that sends to an address taken from the answers (for example
an autoresponder to
{{email}}) reaches the same address at most three times per Site per hour; - forms that belong to an app installation cannot be submitted directly.
Self-hosted installs behind a reverse proxy must set AUTH_TRUST_PROXY_HOPS
(Compose sets it to 1 behind Caddy); otherwise every visitor shares one rate
limit.
In previews (the editor, Preview site, preview links and page previews) forms validate and step through as published but send nothing; a “Preview – nothing is sent” notice marks them. See Forms in previews.
Do not collect sensitive or regulated data unless the site’s legal basis, retention, access controls, and downstream delivery have been reviewed for that specific use case.
Delivery
Section titled “Delivery”Accepted submissions are stored for the site’s submissions inbox. A form can also run configured actions, such as sending an email notification. Email credentials are configured separately under the site’s email settings and stored encrypted.
Privacy and retention
Section titled “Privacy and retention”A submission stores the answers, its status and step, and timestamps. The visitor’s IP address and browser are used in memory for rate limiting only and are not stored. Submissions saved before this change may still hold them: API and MCP responses never return the IP address, and only site administrators see the earlier browser string, shortened to 256 characters.
To remove those earlier values from the datastore, a self-hosted operator on
Firestore can run scripts/scrub-submission-network-data.mjs from the Core
repository with credentials for the project. It deletes only the fields
submitted_from_ip and user_agent from documents in the submissions
collection group, in batches, and prints counts. It is a dry run unless you
pass --apply --confirm-project with the same project ID:
node scripts/scrub-submission-network-data.mjs --project PROJECT_IDnode scripts/scrub-submission-network-data.mjs --project PROJECT_ID \ --apply --confirm-project PROJECT_IDCompleted submissions are kept until someone deletes them, unless the site opts
into a retention period. A site administrator sets it under Forms →
Submission retention, or with form_submission_retention_days (30–3650 days)
in the site settings. With a
period set:
- each completed submission expires that many days after it was completed, on every form of the site;
- submissions that are already older are deleted when you save the period, and
the rest get an expiry date (
expires_at); - an expired submission is never listed or returned, and reading the inbox deletes the expired ones it finds;
- clearing the period (empty, or
null) keeps all remaining submissions again.
Partial submissions keep the form’s partial_ttl_days. Questionnaire
responses that belong to an app installation are managed by that app.
Each expiring submission also carries a native timestamp, delete_at, so the
datastore can delete it without anyone opening the inbox. The operator of the
Forms datastore enables this; on a self-hosted installation on Firestore,
enable a TTL policy for the collection group submissions on the field
delete_at, for example:
gcloud firestore fields ttls update delete_at \ --collection-group=submissions --enable-ttlFirestore deletes expired documents asynchronously, usually within a day. With a file-backed store, or before the policy is enabled, expired submissions are hidden and deleted when the inbox is read. Webhook delivery records hold only delivery metadata, not answers.
Agent and API surface
Section titled “Agent and API surface”Agents can create, read, update, list, and delete forms. They can also list and
delete submissions. With an admin API key they read and set a form’s email
notifications and webhooks (actions), exactly as the portal’s Forms editor
does; webhook secrets are masked in every response. The chat assistant inside
the portal does not manage notifications or read submissions. A caller may provide a flat field list for a simple form;
Typeroll converts it to a one-step definition. Multi-step flows use explicit
steps.
See Forms tools for the API-oriented reference and Build forms for the task workflow.
Scope boundary
Section titled “Scope boundary”The Forms module documents the general form engine. A narrower flow such as a quote request, event registration, lead qualification funnel, or directory edit form should be documented as a recipe or as part of the Extension that ships that form. Customer-specific questions, recipients, legal copy, and routing stay in the site’s configuration rather than this module reference.
Typed answer controls
Section titled “Typed answer controls”form/boolean (or simple field type boolean) renders Yes/No with a clear action.
It preserves false, null and omitted answers separately. App-bound forms can
declare target.context_params: ["page_id"] to forward explicit URL context
keys while hydrating; security context cannot be overridden. Hydration supplies
hidden identities and a base revision. Unchanged prefilled answers are omitted
from revision-bound submissions. A hidden honeypot stays outside keyboard and
assistive-technology interaction. See Owner answers and review
for proposal isolation and the separate publication step.
Live choices and actions supplied by installed apps
Section titled “Live choices and actions supplied by installed apps”Starting with Core 0.2.123 (Extension runtime 0.45.0), an enabled app can contribute Live field options and completion actions. This is available to Cloud and self-hosted installations running the matching Core version.
A Site administrator configures these in the Form editor. Agents first call
get_form_capabilities for the selected Site, then read the enabled app’s private
instructions through read_app_documentation. Use the returned action/source IDs
rather than guessing them. Pass option_sources through create_form or
update_form; each source targets an existing select or radio field. App actions
receive only answers named in their field_map.
Live choices refresh when the form loads, so adding a choice at the provider does not require another site publication. The server checks choices again before accepting answers. If a source supplies a notice, bind its version to a declared hidden field; the runtime displays the notice next to its choices.
A required app action must succeed before the visitor sees success. If it fails,
the form retains the answers and offers a retry of that saved submission.
The Forms inbox/API exposes action_status separately from stored-submission
status. Ordinary optional notification failures do not undo a successful action.
For the provider protocol, safe retries and the typeroll:form-success browser
event, see Forms integration.
Never infer success by observing a message’s CSS class. The success event does not
itself authorize analytics or advertising tracking; check the visitor’s consent.