# formbase Docs — Building Forms

# Understanding the editor

A quick tour of the form builder and where to find every feature.

## Understanding the editor

A tour of the form builder — where everything lives and how to get around fast.

<h2 id="layout">Layout at a glance</h2>

  <p>The editor is a single, document-style canvas — type, insert blocks, and arrange them like a doc. Three areas do the work:</p>
  <ul>
    <li>
      <strong>Top toolbar</strong> — <strong>Edit</strong>, <strong>Translate</strong>, and <strong>Preview</strong>, then Settings, Theme,
      the issue indicator, and <strong>Publish</strong>. Publish shows whenever there is something to publish. After your first publish,{' '}
      <strong>Submissions</strong>, <strong>Share</strong>, and <strong>Publish history</strong> join them, and{' '}
      <strong>Revert unpublished changes</strong> appears whenever a draft differs from what is live
    </li>
    <li>
      <strong>Canvas (center)</strong> — your form as respondents will see it, with an optional cover image and logo at the top
    </li>
    <li>
      <strong>AI chat</strong> — floating button in the bottom-right; opens a side panel where you can ask AI to build or edit your form
    </li>
  </ul>
  <p>The workspace sidebar on the left holds navigation across all your forms.</p>

<p>
  Beside Publish sits the <strong>issue indicator</strong>, a traffic light for the readiness check: green when the form is ready, amber for
  warnings, red for errors. Click it to list what it found — each entry links to the place you fix it. Errors block publishing. Warnings do
  not, but formbase asks first and offers to fix them for you. <a href="/building-forms/publish-checks">Publish checks</a> lists every
  finding and how to clear it.
</p>

<h2 id="cover-logo">Cover and logo</h2>
<p>
  Above the form title, use <strong>Add Cover</strong> and <strong>Add Logo</strong>. The picker has four tabs — <strong>Upload</strong>{' '}
  (PNG, JPG, or WebP up to 5&nbsp;MB), <strong>Colors</strong> for a solid color, <strong>Unsplash</strong>, and <strong>Icons</strong>.
  Drag a cover up or down to reposition it. Only uploads count toward your storage.
</p>

<h2 id="adding-blocks">Add a question or content</h2>

  <p>
    Click an empty line on the canvas and type <kbd>/</kbd> — or press <kbd>Cmd/Ctrl + /</kbd> — to open the block menu. Keep typing to
    filter. Four categories, in this order:
  </p>
  <ul>
    <li>
      <strong>Basic Blocks</strong> — Heading 1&ndash;3, Bullet List, Numbered List
    </li>
    <li>
      <strong>Form Fields</strong> — every question type, from Text Input to Payment and Schedule appointment
    </li>
    <li>
      <strong>Layout</strong> — Table, 2 Columns, Repeating Group, Logic, Hidden Field, Calculated Field, Page Break, Thank You Page
    </li>
    <li>
      <strong>Media</strong> — Image, YouTube Embed, Google Maps Embed, Embed anything
    </li>
  </ul>
  <p>
    The menu only offers what fits where the cursor is: no questions or logic on a thank-you page, no root-only blocks inside a column. See{' '}
    <a href="/building-forms/field-types">Question types</a> for the full catalogue and{' '}
    <a href="/building-forms/form-structure#block-menu">Form structure → Block menu</a> for what the drag handle offers.
  </p>

> 💡 **Just describe what you need**
> <p>
>     Don&apos;t feel like browsing the menu? Open AI chat and say <em>&ldquo;add a phone number and a date picker&rdquo;</em>. It inserts the
>     fields for you.
>   </p>

<h2 id="block-actions">Select and change blocks</h2>

<p>
  Click a block's drag handle to open its menu. What it offers depends on the block: <strong>Turn into</strong>, <strong>Options</strong>,{' '}
  <strong>Validation</strong>, align, create repeating group, insert, duplicate, move, hide, and delete.
</p>

<p>
  Select several blocks by dragging a rectangle around them, by shift-clicking a second block to take the range, or by pressing{' '}
  <kbd>Cmd/Ctrl+A</kbd> once for the current block's text and again for every block. The handle menu then acts on the whole selection: turn
  compatible blocks into another type, mark questions required or optional, hide or show titles, create a repeating group, duplicate, move,
  hide, or delete.
</p>

<h2 id="shortcuts">Keyboard shortcuts</h2>

<p>On a Mac use Cmd; on Windows and Linux use Ctrl.</p>

<h2 id="format-text">Format text</h2>

  <p>
    Select any text to see a floating toolbar: bold, italic, underline, strikethrough, inline code, equation, link, font, font size, and
    color. See <a href="/building-forms/content-blocks#text-formatting">Content blocks → Text formatting</a> for the full list.
  </p>

<h2 id="pages-columns">Pages &amp; columns</h2>

  <p>
    Forms can be a single page or split into many. Insert a <strong>Page Break</strong> to create a multi-step flow with a progress bar, and{' '}
    <strong>2 Columns</strong> to place blocks side by side. Both behave like any other block — drag to reorder, delete from the block menu.
    See <a href="/building-forms/form-structure">Form structure</a> for the full guide.
  </p>

<h2 id="translate">Translate</h2>

  <p>
    The <strong>Translate</strong> tab opens a side-by-side editor for every visible string in your form. Add languages, translate each
    question, and publish. See <a href="/building-forms/translating-form-content">Translating form content</a> for the full guide.
  </p>

<h2 id="ai-chat">AI chat</h2>

  <p>
    The floating button in the bottom-right opens <strong>AI chat</strong> — an assistant that can write, audit, and edit your form. Pick a
    starter prompt or just describe what you want. Usage draws from your AI credit balance, shown above the input.
  </p>

<h2 id="theme">Theme</h2>

  <p>
    The <strong>Theme</strong> button (palette icon) opens a docked panel with a light/dark mode toggle, preset themes, and per-section
    controls for fonts, colors, inputs, choices, buttons, and more. Presets work on every plan; custom colors and fonts need Pro. See{' '}
    <a href="/branding-domains/appearance-theming">Appearance & theming</a> for details.
  </p>

<h2 id="share">Share</h2>

  <p>
    The <strong>Share</strong> button opens a sheet with two channels. <strong>Public link</strong> holds your share links — create several
    to tell campaigns apart or to set different languages and limits — with <strong>Embed</strong>, <strong>Popup</strong>, and{' '}
    <strong>QR Code</strong> beside the URL. <strong>Requests</strong> sends the form to one named person at a time. See{' '}
    <a href="/sharing-publishing/sharing-embedding">Share links</a>,{' '}
    <a href="/sharing-publishing/embedding-popups">Embedding &amp; popups</a>, and{' '}
    <a href="/requests/creating-requests">Creating requests</a>.
  </p>

<h2 id="settings">Settings</h2>

  <p>
    The <strong>Settings</strong> button (gear icon) opens a dialog with six tabs. For a deep dive, see{' '}
    <a href="/building-forms/form-settings">Form settings</a>.
  </p>
  <ul>
    <li>
      <strong>General</strong> — form name, default language, branding toggle
    </li>
    <li>
      <strong>Access</strong> — authentication, password, bot protection (public link only)
    </li>
    <li>
      <strong>Submissions</strong> — close submissions, redirect URL, retention; edit after submit and allow another response for the public
      link
    </li>
    <li>
      <strong>E-mail Notifications</strong> — sending domain, notify on new submission, respondent confirmation, reminders, and the request
      invitation
    </li>
    <li>
      <strong>Integrations</strong> — GA4/Axiom analytics streaming; Google Sheets, Airtable, Notion, Slack, Discord, Linear, GitHub Issues,
      and Webhook submission delivery. Zapier and Make are marked coming soon
    </li>
    <li>
      <strong>Danger Zone</strong> — delete all submissions, delete analytics data, or delete the form
    </li>
  </ul>

<h2 id="preview-publish">Preview &amp; publish</h2>

  <p>
    Switch to <strong>Preview</strong> to fill the form exactly as a respondent would: logic fires, variables resolve, and you can add
    repeating-group entries. Nothing is saved — answers live in memory and reset on reload, and uploads and payments are stubbed. Click{' '}
    <strong>Publish</strong> when you are happy; formbase keeps your draft separate, so changes only go live when you publish. To undo edits
    before publishing, hit <strong>Revert unpublished changes</strong> to go back to the last published version.
  </p>

<h2 id="version-history">Publish history</h2>

  <p>
    The <strong>Publish history</strong> button (clock icon) lists every published version with a timestamp, and shows each one's{' '}
    <strong>Form</strong>, <strong>Emails</strong>, and <strong>Translations</strong> as they were frozen at publish. Browsing works on
    every plan. Restoring needs <em>Business</em>: pick a version, click <strong>Revert to this version</strong>, then choose{' '}
    <strong>Open as draft</strong> (load it into the editor without publishing) or <strong>Publish now</strong> (go live right away).
  </p>

> ℹ️ **Quick search**
> <p>
>     Press <kbd>Cmd K</kbd> (or <kbd>Ctrl K</kbd>) outside the canvas to jump to any form, folder, workspace, or setting. Inside the canvas
>     the same keys add a link instead.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Form structure & pages](/building-forms/form-structure) — Pages, columns, drag and drop
  - [Question types](/building-forms/field-types) — Every question type in the block menu
  - [Appearance & theming](/branding-domains/appearance-theming) — Theme, cover, logo, dark mode
  - [Share links](/sharing-publishing/sharing-embedding) — Create and manage share link URLs
</div>


# Form structure

Pages, columns, drag-and-drop, and the progress bar.

## Form structure

Pages, columns, drag-and-drop, the progress bar, and the thank-you page — everything that controls your form's layout.

<h2 id="pages">Pages</h2>

<p>
  Every form starts as a single page. For short forms (under ~7 fields), that is usually enough. When a form gets longer or branches into
  different flows, split it into pages so respondents see one focused set of questions at a time.
</p>

<h3 id="when-to-use-multiple-pages">When to use multiple pages</h3>

- **7+ fields** — a long scroll feels heavy; pages break the work into manageable steps.
- **Mixed-purpose forms** — intake, payment, and confirmation each belong on their own page.
- **Conditional branching** — logic rules can jump to or skip whole pages based on answers.

<h3 id="adding-a-page">Adding a page</h3>

<p>
  Respondents navigate between pages with <strong>Next</strong> and <strong>Back</strong> buttons that formbase adds automatically. The
  final page shows a <strong>Submit</strong> button instead of Next. <strong>Back</strong> retraces the pages the respondent actually
  visited, so a page jump is undone step by step rather than dropping them on the page before.
</p>

<p>The thank-you page is not one of these pages — it is the screen after submitting, and it never appears in the page count.</p>

<h2 id="progress-bar">Multi-page progress bar</h2>

<p>A thin progress bar at the top of the form shows respondents how far along they are.</p>

<h3 id="how-it-works">How it works</h3>

> ℹ️ **Page jumps with logic**
> <p>
>     Conditional logic can jump to a specific page — for example, "If the respondent picks 'Self-employed', skip to page 4". The jump happens
>     when the respondent presses Next on the page with that question. On later pages, Next goes on in page order. A rule that reads only
>     hidden or calculated fields can jump from any page, but only forward. A page the jumps skip counts as hidden: its questions are not
>     required, and its answers are dropped. See <a href="/building-forms/conditional-logic">Conditional logic</a>.
>   </p>

<h2 id="columns">Columns</h2>

<p>
  Columns let you place blocks side-by-side. Use them for compact layouts — a name and email on one row, an image next to a description, or
  grouped choice fields.
</p>

<h3 id="adding-columns">Adding columns</h3>

> ℹ️ **What cannot go in a column**
> <p>
>     Logic, Hidden Field, Calculated Field, Page Break, Thank You Page, and a repeating group only work at the top level of the form. The
>     block menu hides them while the cursor is inside a column. A repeating group forbids a little more: Payment, Schedule appointment, and
>     Documents are also out, because a submission pays, books, and is handed documents once — not once per entry.
>   </p>

> ℹ️ **Responsive behavior**
> <p>Columns stack vertically on small screens. Respondents on mobile see one column at a time, so the form stays readable.</p>

<h2 id="drag-and-drop">Drag and drop</h2>

<p>Every block has a drag handle on its left edge. Grab it to reorder blocks within a page or move them between columns.</p>

<h3 id="reordering-blocks">Reordering blocks</h3>

- **Within a page** — drag a block up or down to change its position.
- **Between columns** — drag a block from one column into another.

<h3 id="block-menu">Block menu</h3>

<p>
  Click the drag handle (or tap the block indicator on touch devices) to open a menu. What it offers depends on the block; these are the
  entries you will meet most.
</p>

<p>
  Select several blocks at once — drag a rectangle around them, shift-click a second block to take the range, or press <kbd>Cmd/Ctrl+A</kbd>{' '}
  twice — and the same menu acts on all of them: turn into, required, title visibility, wrap into a group, duplicate, move, hide, delete.
</p>

<h2 id="repeating-groups">Repeating groups</h2>

<p>
  A repeating group is a labeled container that holds a set of member fields — like a "Add additional guests" group with Name and Age.
  Respondents add as many entries as they need, so you don't have to guess how many you'll get up front. Insert a fresh{' '}
  <strong>Repeating Group</strong> from the block menu, or select a run of existing fields and pick <strong>Create repeating group</strong>.
</p>

<p>
  For the full guide — building groups, filling them out, and how entries show up in submissions — see{' '}
  <a href="/building-forms/repeating-groups">Repeating groups</a>.
</p>

<h2 id="thank-you-page">Thank-you page</h2>

<p>
  Every form has a <strong>thank-you page</strong> — always the last page. It is what respondents see after they submit. Customize it with
  headings, images, links, and piped answers — or redirect to an external URL instead.
</p>

<p>
  For the full guide, see <a href="/building-forms/custom-thank-you">Custom thank-you page</a>.
</p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Question types](/building-forms/field-types) — Every question type and when to use each one
  - [Conditional logic](/building-forms/conditional-logic) — Show, hide, and branch based on answers
  - [Repeating groups](/building-forms/repeating-groups) — Let respondents add as many entries as they need
  - [Content blocks](/building-forms/content-blocks) — Images, videos, embeds, and rich text
</div>


# Question types

Every question type available in formbase — from text to payments.

## Question types

Pick the right question for each field. Type / on the canvas to open the block menu and browse every type. Each type brings its own validation, layout, and summary chart.

---

<p>
  The block menu groups blocks into four categories: <strong>Basic Blocks</strong>, <strong>Form Fields</strong>, <strong>Layout</strong>,
  and <strong>Media</strong>. Every question lives under <strong>Form Fields</strong>. The names below are the exact menu labels — type a
  few letters to filter.
</p>

<h2 id="text">Text and input</h2>

<p>These fields collect typed answers. Email, Website URL, and Phone Number also check the format as the respondent types.</p>

<p>
  Only these six inputs carry a <a href="/building-forms/field-configuration#common-settings">placeholder and a default value</a>. Turn into
  switches freely between them.
</p>

<h2 id="choice">Choice</h2>

<p>Choice fields let respondents pick from a list you write.</p>

<p>
  <strong>Decision</strong> is the one choice question whose values are fixed — <code>approve</code>, <code>decline</code>,{' '}
  <code>changes</code> — so a workflow can branch on the answer whatever you call the labels, in any language.{' '}
  <a href="/requests/decisions-and-approvals">Decisions &amp; approvals →</a>
</p>

<p>
  Picture choice is one block with two modes. Switch between them with <strong>Allow multiple selection</strong> /{' '}
  <strong>Allow single selection</strong> in the block menu.
</p>

<h2 id="rating">Rating and scale</h2>

<p>These fields measure sentiment, preference, or priority. The Summary tab charts their distribution.</p>

> ℹ️ **Linear scale is not in the block menu**
> <p>
>     A linear scale (a numbered scale from 1 up to 10 — good for NPS or Likert) has no entry of its own. Insert a{' '}
>     <strong>Star Rating</strong> and use <strong>Turn into → Linear scale</strong> in the block menu, or ask AI chat for one. Adjust the
>     maximum with <strong>Increase / Decrease scale max</strong>.
>   </p>

<h2 id="date">Date and time</h2>

<h2 id="files">Files and documents</h2>

<p>Uploads on both sides count toward your workspace storage.</p>

<h2 id="advanced">Payments and scheduling</h2>

> ℹ️ **Looking for CAPTCHA?**
> <p>
>     Bot protection is a form-level setting, not a question type. It is always on for free forms; from Pro you can toggle it per form under{' '}
>     <a href="/building-forms/form-settings#access">Form settings → Access</a>. See{' '}
>     <a href="/building-forms/captcha-bot-protection">CAPTCHA and bot protection</a>.
>   </p>

<h2 id="layout">Blocks that are not questions</h2>

<p>The rest of the block menu structures and enriches the form. None of them collects an answer.</p>

<p>
  <strong>Basic Blocks</strong> — Heading 1, Heading 2, Heading 3, Bullet List, Numbered List. Paragraph has no menu entry: it is the
  default block, so just type on the canvas.
</p>

<p>
  <strong>Layout</strong> — Table, 2 Columns, Repeating Group, Logic, Hidden Field, Calculated Field, Page Break, Thank You Page.
</p>

<p>
  <strong>Media</strong> — Image, YouTube Embed, Google Maps Embed, Embed anything.
</p>

<ul>
  <li>
    <strong>Hidden Field</strong> and <strong>Calculated Field</strong> hold values rather than layout — see{' '}
    <a href="/building-forms/hidden-fields">Hidden fields</a> and <a href="/building-forms/calculated-fields">Calculated fields</a>.
  </li>
  <li>
    <strong>Logic</strong> shows, hides, requires, jumps, calculates, or blocks submission — see{' '}
    <a href="/building-forms/conditional-logic">Conditional logic</a>.
  </li>
  <li>
    <strong>Repeating Group</strong> lets respondents add the same set of fields more than once — see{' '}
    <a href="/building-forms/repeating-groups">Repeating groups</a>.
  </li>
  <li>
    <strong>Page Break</strong>, <strong>2 Columns</strong>, and <strong>Thank You Page</strong> are covered in{' '}
    <a href="/building-forms/form-structure">Form structure</a>.
  </li>
</ul>

> ℹ️ **On a thank-you page**
> <p>
>     Questions, logic, and page breaks are hidden from the block menu while the cursor sits on the thank-you page — it is the last screen, so
>     there is nothing left to ask or branch on.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Field settings](/building-forms/field-configuration) — Labels, placeholders, defaults
  - [Conditional logic](/building-forms/conditional-logic) — Show/hide based on answers
  - [Hidden fields](/building-forms/hidden-fields) — Capture URL parameters invisibly
  - [Payment collection](/building-forms/payment-collection) — Accept payments via Stripe
  - [Documents block](/building-forms/documents-block) — Hand files to the respondent
  - [Cal.com scheduling](/integrations/cal-com) — Book appointments inside forms
</div>


# Field settings

Labels, placeholders, defaults, validation rules, and per-field options.

## Field settings

Every question has shared settings — title, required, placeholder — plus type-specific options like character limits, file restrictions, and validation rules. This page covers them all.

<h2 id="accessing-settings">Accessing field settings</h2>
<p>
  Type a question's title and options straight on the canvas. Everything else lives in the block menu: click the drag handle on the block's
  left edge (or tap the block indicator on touch) and pick from <strong>Turn into</strong>, <strong>Options</strong>,{' '}
  <strong>Validation</strong>, and the usual duplicate / move / hide / delete actions.
</p>

<h2 id="common-settings">Common settings</h2>
<p>These options appear on every question, whatever its type.</p>

<p>
  A field's key — the name automations prefill and read it by — is set in the <strong>Keys</strong> table, which the toolbar's key icon
  opens for the whole form: one key per field, option, row and column. Leave a key empty to use the one derived from the title or label.
  Editing a key on a published form warns you that automations using the old key will break. Each option's hint row also carries its key as
  a chip you can edit in place. See <a href="/requests/field-keys">Field keys</a> for how keys are derived, frozen at publish, and used by
  callers.
</p>

> 💡 **Adding help text**
> <p>
>     formbase is a document-style editor — to add help text or instructions for a question, insert a paragraph or heading block above or
>     below the field on the canvas. Content blocks between questions are visible to respondents and work just like any other block.
>   </p>

> ℹ️ **Title visibility and data**
> <p>
>     Hiding a title does not remove the field from submissions. It still shows as a column header in exports, so give hidden-title fields a
>     clear internal name.
>   </p>

<h2 id="text-options">Text options</h2>
<p>
  <strong>Min length</strong> and <strong>Max length</strong> live under <strong>Validation</strong> in the block menu and apply to{' '}
  <strong>Text Input</strong>, <strong>Text Area</strong>, and <strong>Website URL</strong>. Text Area is the multi-line variant of Text
  Input — switch between them with <strong>Turn into</strong>.
</p>

<h2 id="number-options">Number options</h2>
<p>
  <strong>Minimum</strong> and <strong>Maximum</strong> live under <strong>Validation</strong> and apply to <strong>Number</strong>,{' '}
  <strong>Star Rating</strong>, and a linear scale. <strong>Number</strong> also has <strong>Step</strong>: the answer must be a multiple of
  it, so a step of 1 accepts only whole numbers and 0.5 accepts halves. Leave it empty to accept any number.
</p>

<h2 id="choice-options">Choice options</h2>
<p>
  These settings apply to choice fields: <strong>Radio Buttons</strong>, <strong>Checkboxes</strong>, <strong>Dropdown</strong>,{' '}
  <strong>Ranking</strong>, <strong>Single Picture Choice</strong>, and <strong>Multi Picture Choice</strong>.
</p>

<p>
  Single options can also be hidden or preselected from the canvas: with the cursor in an option, press ⌘ + H (Ctrl + H) to hide it, or ⌘ +
  D (Ctrl + D) to select it by default.
</p>

<h2 id="rating-scale-options">Rating and scale options</h2>

<h3 id="rating">Rating</h3>
<p>
  The <strong>Star Rating</strong> field shows 5 stars by default. Use <strong>Increase max stars</strong> and{' '}
  <strong>Decrease max stars</strong> in the block menu to set anything from 1 to 10. Turn it into a linear scale from the same menu; the
  maximum carries over and the menu items become <strong>Increase / Decrease scale max</strong>.
</p>

<h3 id="matrix">Matrix</h3>
<p>
  The <strong>Matrix / Grid</strong> field creates a grid of choices. Define <strong>Rows</strong> (the sub-questions) and{' '}
  <strong>Columns</strong> (the answers). The respondent picks one column per row. Under <strong>Validation</strong> you can{' '}
  <strong>Require all rows</strong> or set <strong>Min answered rows</strong>.
</p>

<h2 id="file-payment-options">File upload and payment options</h2>

<h3 id="file-upload">File upload</h3>
<p>
  <strong>File Upload</strong> takes <strong>Min files</strong>, <strong>Max files</strong>, <strong>Max size (MB)</strong>, and{' '}
  <strong>Accepted types</strong> under <strong>Validation</strong>. See{' '}
  <a href="/building-forms/file-uploads-signatures">File uploads &amp; signatures</a> for the details.
</p>

<h3 id="payment">Payment</h3>
<p>
  The <strong>Payment</strong> field collects a one-time charge. Set the <strong>Amount</strong> and <strong>Currency</strong> on the block
  — USD, EUR, GBP, JPY, CAD, AUD, CHF, SEK, NOK, DKK, NZD, SGD, HKD, AED, or TRY. Each currency has a minimum charge (0.50 for USD and EUR),
  and publishing is blocked until Stripe is connected and the amount is valid. See{' '}
  <a href="/building-forms/payment-collection">Payment collection</a>.
</p>

<h2 id="default-values">Default values from other fields</h2>
<p>
  A field's default value can come from another field in the same form instead of fixed text — a hidden field (for example a URL parameter)
  or a calculated field. It works on the six inputs that have a default-value slot: <strong>Text Input</strong>, <strong>Text Area</strong>,{' '}
  <strong>Email</strong>, <strong>Website URL</strong>, <strong>Phone Number</strong>, and <strong>Number</strong>.
</p>
<p>To set it up:</p>
<ol>
  <li>Click into the field's input.</li>
  <li>
    Type <kbd>@</kbd> as the first character. A menu lists your hidden and calculated fields; keep typing to filter.
  </li>
  <li>
    Pick a field. The input switches to <strong>default value</strong> mode on its own and shows the field as a chip, for example{' '}
    <strong>@recipient</strong>.
  </li>
</ol>
<p>
  To remove the reference, click the <strong>×</strong> on the chip, press <kbd>Backspace</kbd>, or press <kbd>⌘</kbd> + <kbd>D</kbd> again.
  If you want a literal <kbd>@</kbd> instead — a placeholder like <code>@yourhandle</code> — press <kbd>Esc</kbd> to close the menu and keep
  typing; the input stays in the mode it was in. A chip turns red when its field has been deleted; re-adding a field with the same name
  reconnects it.
</p>
<p>
  The chip replaces the field's placeholder text. The <kbd>@</kbd> only opens the menu as the first character, so a default like{' '}
  <code>jeff@example.com</code> stays plain text.
</p>
<p>
  When someone opens the form, the field is pre-filled with the referenced value — for example, a link ending in{' '}
  <code>?recipient=Jeff</code> fills a Text Input that references the <code>recipient</code> hidden field with "Jeff". How it behaves:
</p>
<ul>
  <li>Respondents can change or clear the value. Their own answer is never overwritten.</li>
  <li>
    A calculated field fills the input with the first value it has — usually its initial value. Later changes from logic rules don't replace
    what is already in the input.
  </li>
  <li>
    If the referenced field has no value, the input starts empty. A Number field also stays empty when the value isn't a number (for example{' '}
    <code>?age=abc</code>).
  </li>
  <li>The submission stores both values: the hidden or calculated field, and the question's final answer.</li>
</ul>
<p>
  See <a href="/building-forms/hidden-fields">Hidden fields</a> and <a href="/building-forms/calculated-fields">Calculated fields</a>.
</p>

<h2 id="examples">Examples</h2>

<ul>
  <li>
    <strong>Contact form</strong> — Email field marked required, Text Area for message with a placeholder "Tell us how we can help…", Phone
    Number field marked optional with a paragraph below it: "We'll only call if we need to clarify your request"
  </li>
  <li>
    <strong>NPS survey</strong> — Star Rating set to 10 stars, followed by a Text Area (hidden by default, shown via logic when the score is
    6 or below) with placeholder "What could we improve?"
  </li>
  <li>
    <strong>Product order</strong> — Number field for quantity with min 1 / max 100, Checkboxes for add-ons, and a payment field whose
    amount is driven by a calculated field
  </li>
  <li>
    <strong>Event RSVP</strong> — Radio Buttons for attendance, Checkboxes for dietary requirements with randomized order to avoid bias,
    Date Picker with an earliest date set
  </li>
</ul>

<h2 id="validation-rules">Validation rules</h2>

<p>
  Validation runs as the respondent fills out your form. formbase highlights bad input inline and refuses to submit until everything passes.
</p>

<h3 id="built-in-validation">Built-in by field type</h3>

<p>
  Each question type checks its own shape, with nothing to configure. <strong>Email</strong> checks the address format.{' '}
  <strong>Website URL</strong> accepts http and https links only and rejects a host that is not a real domain. <strong>Phone Number</strong>{' '}
  checks the number against the selected country. <strong>Number</strong>, <strong>Date Picker</strong>, and <strong>Time Picker</strong>{' '}
  reject anything that is not a number, a date, or a time.
</p>

<p>
  On Business, an email question can additionally require verification — the respondent confirms that address with a one-time code before
  the form accepts the submission. The toggle sits under <strong>Validation → Verification</strong>. See{' '}
  <a href="/building-forms/email-verification">respondent email verification</a>.
</p>

<h3 id="optional-rules">Optional rules per field</h3>

<p>
  Open the block menu and pick <strong>Validation</strong>. The rules on offer depend on the question type; these are the exact menu labels.
</p>

<p>
  Radio Buttons, Dropdown, Toggle Switch, Signature, Ranking, Payment, and Schedule appointment take no validation rules — required or not
  is the only choice. Inside a repeating group, email verification is not offered.
</p>

<h3 id="custom-patterns">Custom pattern examples</h3>

<ul>
  <li>
    <strong>US ZIP code</strong> — <code>{`^\\d{5}(-\\d{4})?$`}</code>
  </li>
  <li>
    <strong>Hex color code</strong> — <code>{`^#[0-9a-fA-F]{6}$`}</code>
  </li>
  <li>
    <strong>URL-friendly slug</strong> — <code>{`^[a-z0-9-]+$`}</code>
  </li>
</ul>

<p>
  A value that doesn't match shows a generic "Invalid value" error, so add a paragraph near the field describing the format you expect. An
  invalid regex is ignored and never blocks submission. Length rules count characters after trimming leading and trailing spaces.
</p>

<h3 id="how-validation-runs">How validation runs</h3>

<p>
  formbase checks validation live in the browser as the respondent types. Format and rule errors appear immediately with an inline message;
  a "This field is required" error appears once the field has been touched, or when the respondent tries to submit.
</p>

> ⚠️ **Don't validate sensitive data with regex alone**
> <p>For things like credit cards or government IDs, use the appropriate field type or integration. Regex catches shape, not validity.</p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Answer piping](/building-forms/answer-piping) — Use @ mentions to reference answers anywhere in your form.
  - [Conditional logic](/building-forms/conditional-logic) — Show or hide fields based on answers
  - [Hidden fields](/building-forms/hidden-fields) — Capture URL parameters invisibly
  - [Calculated fields](/building-forms/calculated-fields) — Compute scores, totals, and dynamic values
</div>


# File uploads & signatures

Collect files, documents, and digital signatures from respondents.

## File uploads & signatures

Collect documents, images, and drawn signatures — formbase stores them alongside each submission.

<h2 id="file-upload">File upload</h2>

<p>
  The File Upload field lets respondents attach files to their submission. Type <kbd>/</kbd> on the canvas and pick{' '}
  <strong>File Upload</strong>.
</p>

<p>
  Click the drag handle next to the field and open <strong>Validation</strong> to set its rules:
</p>

<p>
  The field shows respondents what it expects — "Accepted files: .pdf, .png", "Max 5 MB", "2-4 files" — so the rules double as the hint.
</p>

<h2 id="file-validation">File validation rules</h2>

<p>
  Size, count, and type are checked in the browser as the respondent picks a file; a violation shows an inline error and blocks submission.
  Two more limits apply on the server and cannot be raised per field:
</p>

<ul>
  <li>
    <strong>1 GiB per file</strong>, whatever your Max size says.
  </li>
  <li>
    <strong>Your workspace storage quota</strong> — 100 MB on Free, 500 GB on Pro and Business. When the owner is out of room the form
    refuses uploads with "This form is currently unable to accept file uploads," so respondents cannot finish. See{' '}
    <a href="/subscription-billing/limits-quotas">limits &amp; quotas</a>.
  </li>
</ul>

<p>
  Uploaded files are stored with the submission. Open a submission in the <a href="/submissions-analytics/submission-inbox">inbox</a> to
  view or download them.
</p>

<h3 id="files-in-logic">File uploads in conditional logic</h3>

<p>
  A File Upload field offers two logic conditions: <strong>has file</strong> and <strong>has no file</strong>. Use them to show a follow-up
  question only when nothing was attached, or to skip a page once the document is in.
</p>

<h2 id="signatures">Signatures</h2>

<p>
  The Signature field captures a drawn signature. Type <kbd>/</kbd> and pick <strong>Signature</strong> from the slash menu. Respondents
  draw with a mouse, trackpad, or finger. formbase stores the result as an image, viewable and downloadable from the submission inbox.
</p>

> ⚠️ **A signature makes the submission final**
> <p>
>     Any form version that contains a Signature, Payment, or Schedule appointment field turns off{' '}
>     <a href="/submissions-analytics/edit-after-submit">edit after submit</a> for submissions made against it. Respondents cannot reopen and
>     change what they signed.
>   </p>

<h3 id="signatures-in-logic">Signatures in conditional logic</h3>

<p>
  Signature fields support two logic conditions: <strong>is signed</strong> and <strong>is not signed</strong>. Use them to show a
  confirmation page only after a waiver is signed, or to require extra info when a signature is missing.
</p>

<h3 id="signature-privacy">Signatures and partial responses</h3>

<p>
  Signature data is not shown for partial (in-progress) responses: the inbox hides the thumbnail and exports leave the column empty until
  the respondent submits.
</p>

<h3 id="signature-use-cases">When to use signatures</h3>

<p>Signatures work well for any form that needs documented consent or acknowledgment:</p>

<ul>
  <li>Contracts and agreements</li>
  <li>Consent forms (medical, research)</li>
  <li>Liability waivers</li>
  <li>Approval workflows</li>
</ul>

<h2 id="common-patterns">Common patterns</h2>

<ul>
  <li>
    <strong>Job application</strong> — a File Upload for the resume (accept .pdf and .doc, .docx) plus a second upload for the cover letter
  </li>
  <li>
    <strong>Insurance claim</strong> — a File Upload set to <code>image/*</code> for photo evidence, with a high Max files so claimants can
    document the damage properly
  </li>
  <li>
    <strong>Contract signing</strong> — a <a href="/building-forms/documents-block">Documents block</a> holding the contract, then a
    Signature field under it
  </li>
  <li>
    <strong>Event registration</strong> — an ID photo upload (images only, Max files 1) alongside the contact fields
  </li>
</ul>

> ℹ️ **Uploads count toward your storage quota**
> <p>
>     Every file, including signatures and generated PDFs, counts against the workspace owner's storage. Set conservative size limits if you
>     collect large files regularly.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Documents block](/building-forms/documents-block) — Hand files to the respondent — the other direction
  - [Payment collection](/building-forms/payment-collection) — Accept Stripe payments in the same form
  - [Submission inbox](/submissions-analytics/submission-inbox) — Download and review uploaded files from responses
  - [Limits & quotas](/subscription-billing/limits-quotas) — Storage and submission allowances per plan
</div>


# Documents block

Hand files to the respondent — a contract draft, a price list, a how-to — as a download list inside the form.

## Documents block

Files usually travel one way — from the respondent to you. The Documents block sends them the other way: put the contract draft, the price list or the how-to right in the form, where the respondent reads it before answering.

<h2 id="what-it-is">What it is</h2>

<p>
  A Documents block is a titled download list. You upload one or more files once, give each a display name, and every respondent sees the
  same list — on a public link and on a <a href="/requests/overview">request</a> alike. Each row opens the file in a new tab and has a
  download button. The block asks nothing of the respondent: it collects no answer of its own.
</p>

<h2 id="add">Add one</h2>

Add document</strong> or drop files onto the block. PDF and images, up to 25 MB each, up to 20 documents and 100 MB in one block. They count against your workspace storage like any upload.',
    },
    {
      title: 'Name each row',
      description:
        'Click a display name to rename it — "Lease contract draft" reads better than "lease_v3_final.pdf". The file itself is untouched.',
    },
    {
      title: 'Publish',
      description:
        'Documents are part of the published form. A request created against that version keeps showing exactly those files, even if you replace them later.',
    },
  ]}
/>

<h2 id="acknowledgement">Ask for confirmation</h2>

<p>
  The block itself has no checkbox — you decide what "read" should mean. Add a required <strong>Switch</strong> question right below it,
  titled "I have read the lease contract", and the form cannot be submitted until it is on. Prefer several statements? Use a Checkbox with
  one option per document. Need a signature? Add a Signature question. All of them work with{' '}
  <a href="/building-forms/conditional-logic">conditional logic</a>, translations and exports as usual.
</p>

> 💡 **Why a separate question?**
> <p>
>     A Switch, a Checkbox and a Signature already do everything a built-in checkbox could, and they can be required, translated, referenced
>     in logic and read back through the API. The Documents block stays a list; the proof of reading is whatever question you put under it.
>   </p>

<h2 id="in-submissions">In submissions and exports</h2>

<p>
  Every completed submission records, under the block's <a href="/requests/field-keys">field key</a>, the documents that respondent was
  shown — its name and whether it came from the form or from a request. It appears in the submission view, in exports, in the PDF and in{' '}
  <a href="/requests/callbacks">callbacks</a>:
</p>

```
"documents": [
  { "name": "Price list 2026",     "source": "form" },
  { "name": "Your lease contract", "source": "request" }
],
"contract_read": true
```

<h2 id="per-request">Different files for each recipient</h2>

<p>
  On a <a href="/requests/overview">request</a>, an automation can add documents for that one recipient into the same block. They appear
  below your authored files, marked "For you"; the authored files stay exactly as published for everyone. See{' '}
  <a href="/requests/creating-requests#documents">Creating requests → Documents</a>.
</p>

<h2 id="limits">Limits</h2>

> ℹ️ **Office documents**
> <p>Word, Excel and PowerPoint files are not accepted yet — export them as PDF first.</p>

---

<div class="not-prose grid gap-4 sm:grid-cols-2">
  - [File uploads & signatures](/building-forms/file-uploads-signatures) — Collect files from respondents — the other direction
  - [Creating requests](/requests/creating-requests) — Add documents to a request from your automation
</div>


# Respondent email verification

Require a one-time code so email answers are real, reachable addresses.

## Respondent email verification

Ask respondents to confirm their email answer with a one-time code before the form accepts the submission. Every verified answer in your inbox is a real, reachable mailbox.

<h2 id="what-it-does">What it does</h2>
<p>
  Respondent email verification is a per-question rule on <strong>Email</strong> fields. When it is on, the respondent confirms the address
  they typed with a 6-digit code that formbase emails to them. The form does not accept the submission until the code checks out, so fake or
  mistyped addresses never reach your inbox. Turning the rule on requires a Business plan.
</p>

<h2 id="turning-it-on">Turning it on</h2>

Validation</strong>.',
    },
    {
      title: 'Turn on Require email verification',
      description:
        'The switch sits under <strong>Verification</strong> and carries a Business badge. Turning it on needs Business; turning it off works on any plan.',
    },
    { title: 'Publish as usual', description: 'The published form now asks respondents to verify before they can submit.' },
  ]}
/>

<p>
  Verification is not offered for email questions inside <a href="/building-forms/repeating-groups">repeating groups</a> — the server skips
  grouped questions too.
</p>

<h2 id="respondent-experience">What respondents see</h2>
<ol>
  <li>
    After typing a correctly formatted address, a <strong>Verify email</strong> action appears under the input.
  </li>
  <li>formbase emails a 6-digit code to that address. The code expires after 10 minutes.</li>
  <li>
    The respondent enters the code and the field shows a green <strong>Verified</strong> pill.
  </li>
</ol>
<p>
  Code didn't arrive? <strong>Resend</strong> unlocks after 30 seconds. Five wrong codes retire the code and the respondent asks for a new
  one. Disposable-mailbox domains are refused outright, with "Use a non-temporary email address."
</p>
<p>
  Respondents cannot leave the page or submit while a verification-required email question holds an unverified answer. The server checks the
  same thing on submit, so the rule cannot be bypassed by a hand-made request.
</p>
<p>
  If your form sends from a <a href="/branding-domains/custom-email-domains">custom email domain</a>, verification codes use it too.
</p>

<h2 id="same-address">Same address, multiple questions</h2>
<p>
  A verification belongs to the address and the respondent's session, not to the question. If two email questions hold the same address, one
  verification covers both. Changing the value re-checks it: a new address must be verified again.
</p>

<h2 id="good-to-know">Good to know</h2>
<ul>
  <li>
    <strong>Drafts</strong> — a respondent who leaves and resumes a saved draft from the same link keeps their verified addresses. Opening
    the form fresh in another browser starts a new session, so they verify again.
  </li>
  <li>
    <strong>Edit after submit</strong> — verified email answers are locked. Respondents cannot change them when editing a completed
    submission. See <a href="/submissions-analytics/edit-after-submit">edit after submit</a>.
  </li>
  <li>
    <strong>Downgrades</strong> — if your plan drops below Business, published forms keep accepting submissions and stop asking anyone to
    verify. Republishing is blocked while the rule is still on, so turn the switch off (possible on any plan) before you publish again.
  </li>
</ul>

> ℹ️ **Verifying answers vs. gating access**
> <p>
>     This rule verifies an email <em>answer</em>. To require respondents to sign in before they can open the form at all, use the{' '}
>     <a href="/sharing-publishing/authentication-gate">authentication gate</a> instead.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Field settings](/building-forms/field-configuration) — Validation rules and per-field options
  - [Edit after submit](/submissions-analytics/edit-after-submit) — What respondents can change later
  - [Authentication gate](/sharing-publishing/authentication-gate) — Require sign-in or a password to submit
  - [Plans & pricing](/subscription-billing/plans-pricing) — What each plan is for
</div>


# Repeating groups

Collect as many entries as a respondent needs from one repeating block.

## Repeating groups

A repeating group bundles a few related questions into one block that respondents can add over and over. One block collects a whole list — guests, line items, contacts — without you guessing how many rows to build.

<h2 id="what-it-is">What a repeating group is</h2>

<p>
  A repeating group is a labeled container that holds one or more questions — its <strong>member fields</strong>. Respondents add as many{' '}
  <strong>entries</strong> of the whole group as they need. Each entry collects a fresh set of answers for every member field inside.
</p>

<p>Reach for one whenever a single respondent might supply the same shape of data more than once:</p>

<ul>
  <li>
    <strong>Event guests</strong> — a Name and Age for each person they bring.
  </li>
  <li>
    <strong>Order line items</strong> — a product, quantity, and price per row.
  </li>
  <li>
    <strong>Contacts</strong> — name, email, and role for everyone on a team.
  </li>
  <li>
    <strong>Work history</strong> — employer, title, and dates for each past job.
  </li>
</ul>

<h2 id="add-a-repeating-group">Add a repeating group</h2>

<p>
  A repeating group is a structural block: a group title, the member fields inside it, and an add button. Groups live at the top level of
  your form — the editor pins that shape automatically, and you cannot nest one in another.
</p>

Repeating Group</strong>. The cursor lands in the title.',
    },
    {
      title: 'Name the group',
      description:
        'Give it a title, like "Additional guests". This is the heading respondents see above the entries. Hide it later from the block menu if you do not want it.',
    },
    {
      title: 'Add member fields inside',
      description:
        'Drop ordinary questions into the group — Text Input, Number, Date Picker, Dropdown, and more. Each keeps its own label, placeholder, and required toggle.',
    },
    {
      title: 'Edit the add-button label',
      description: 'The button reads "Add one more" until you change it. Make it match the content — "Add another guest".',
    },
  ]}
/>

<p>
  Already built the fields? Select them, open the block menu from the drag handle, and choose <strong>Create repeating group</strong> — it
  works on one block or on a run of them. The same menu on a group offers <strong>Hide title</strong> and <strong>Remove grouping</strong>,
  which unwraps the questions again.
</p>

<h2 id="what-respondents-see">What respondents see</h2>

<p>
  The first entry shows inline — the group title, its member fields, and the add button, just like any other section of the form. As soon as
  a respondent adds a second entry, each one becomes a numbered card (01, 02, 03 …) with a remove control in its corner.
</p>

<ul>
  <li>
    <strong>Add button</strong> — appends a new blank entry each time it is clicked.
  </li>
  <li>
    <strong>Remove control</strong> — the X on a card deletes that entry. It appears once there are two or more entries.
  </li>
  <li>
    <strong>At the ceiling</strong> — a group holds up to 100 entries, and the add button also goes dead when the answers reach the 256 KB
    per-submission payload limit. Both are guards against runaway submissions, not something respondents normally meet.
  </li>
</ul>

<p>
  On a <a href="/requests/overview">request</a> that locks one of the group's member fields, the shape is fixed for that recipient: they can
  neither add nor remove entries.
</p>

> 💡 **Keep member fields focused**
> <p>
>     A repeating group works best with a handful of tight questions — the set a respondent fills in once per entry. The required toggle
>     applies per entry, so every entry must clear the same validation.
>   </p>

<h2 id="logic-and-groups">Groups and conditional logic</h2>

<p>
  A whole group can be shown or hidden by a <a href="/building-forms/conditional-logic">logic</a> rule, exactly like any other block. Member
  fields cannot: they are not offered as conditions, as show/hide targets, or as required/optional targets, because a rule would silently
  act on the first entry only. Branch on a question outside the group instead.
</p>

<h2 id="where-answers-show-up">Where the answers show up</h2>

<p>Once responses arrive, the group stays grouped across every surface. Each page below carries the full detail — this is just the map:</p>

<ul>
  <li>
    <strong>
      <a href="/submissions-analytics/submission-inbox">Submission inbox</a>
    </strong>{' '}
    — one submission shows numbered cards per entry; the table view gets a single count chip that opens the detail.
  </li>
  <li>
    <strong>
      <a href="/submissions-analytics/question-summary">Question summary</a>
    </strong>{' '}
    — the group appears as one card with entry and submission counts plus a mini block per member field.
  </li>
  <li>
    <strong>
      <a href="/submissions-analytics/exports">Exports</a>
    </strong>{' '}
    — each entry exports under its own keys, and the whole group can also collapse into a single labeled text column.
  </li>
  <li>
    <strong>
      <a href="/requests/callbacks">Callbacks and the API</a>
    </strong>{' '}
    — the group answers under its own <a href="/requests/field-keys">field key</a> as a list, one object per entry keyed by member field, in
    the order the respondent left them.
  </li>
</ul>

<h2 id="referencing-group-answers">Referencing group answers</h2>

<p>
  Pipe a repeating group into other questions, notifications, or integrations with an <strong>@</strong> mention — mention a single member
  field to join that field's value across every entry, or mention the whole group to pull in all entries at once. See{' '}
  <a href="/building-forms/answer-piping#referencing-repeating-groups">Answer piping → Referencing repeating groups</a> for exactly how each
  reference resolves, with examples.
</p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Answer piping](/building-forms/answer-piping) — Reference group answers across your form
  - [Conditional logic](/building-forms/conditional-logic) — Show, hide, and branch on answers
  - [Submission inbox](/submissions-analytics/submission-inbox) — Read entries as numbered cards
  - [Exports](/submissions-analytics/exports) — Download group entries to CSV or Excel
</div>


# Content blocks & formatting

Add rich text, images, videos, embeds, and tables to your form.

## Content blocks & formatting

Forms aren't just questions. Use content blocks to add context, branding, and rich media between fields.

<h2 id="available-blocks">Available blocks</h2>
<p>
  Type <kbd>/</kbd> on the canvas (or press <kbd>Cmd/Ctrl + /</kbd>) to open the block menu, then pick a block. Content blocks sit alongside
  questions and reorder with drag and drop. Paragraph is the default block — just type on the canvas; it has no menu entry.
</p>

<p>
  An image carries an optional caption (<strong>Add caption</strong> in the block menu) and an <strong>Align</strong> submenu with Left,
  Center, and Right. <strong>Replace image</strong> swaps the file without losing the caption.
</p>

<h2 id="embeds">Embeds</h2>
<p>
  Three embed blocks live under <strong>Media</strong>. Each one starts empty; open its block menu to fill it in.
</p>

<h2 id="text-formatting">Text formatting</h2>
<p>Select text in a paragraph, heading, list item, or table cell to reveal the floating toolbar:</p>
<ul>
  <li>
    <strong>Bold</strong> (<kbd>Cmd/Ctrl + B</kbd>), <em>italic</em> (<kbd>+ I</kbd>), underline (<kbd>+ U</kbd>), strikethrough (
    <kbd>+ S</kbd>)
  </li>
  <li>Inline code and equation (LaTeX)</li>
  <li>
    Link — the toolbar button or <kbd>Cmd/Ctrl + K</kbd> with text selected
  </li>
  <li>Font and font size</li>
  <li>
    Color — one menu with a <strong>Highlight</strong> row and an <strong>Underline</strong> row, each offering Default, Blue, Green,
    Yellow, and Red
  </li>
</ul>
<p>Headings and lists are inserted from the block menu, not the floating toolbar.</p>

<h2 id="tables">Tables</h2>
<p>
  Insert a <strong>Table</strong> from the block menu. Tables are static — respondents read them but cannot edit cells. Good for pricing
  grids or reference data.
</p>
<p>Click any cell to edit it. Hover a row or column to reveal its grip, then click the grip for its actions:</p>
<ul>
  <li>
    <strong>Add row above</strong> / <strong>Add row below</strong>, <strong>Add column left</strong> / <strong>Add column right</strong>
  </li>
  <li>
    <strong>Duplicate row</strong> / <strong>Duplicate column</strong>
  </li>
  <li>
    <strong>Delete row</strong> / <strong>Delete column</strong>
  </li>
</ul>
<p>
  Drag the same grip to reorder a row or column. Drag the border between two cells to resize; the first drag freezes the current widths, so
  the table keeps the proportions you saw. Cells take the same inline formatting as paragraphs — bold, italic, links, and answer piping with{' '}
  <kbd>@</kbd> mentions.
</p>

<h2 id="piping">Answer piping in content</h2>
<p>
  Content blocks support answer piping. Type <strong>@</strong> in any paragraph, heading, or list item to open the mention menu and insert
  a field reference. For example, a heading that reads "Thanks, <strong>@First name</strong>" shows the respondent's actual name when they
  fill out the form. See <a href="/building-forms/answer-piping">Answer piping</a> for the full guide.
</p>

<h2 id="examples">Common examples</h2>
<ul>
  <li>
    <strong>Application form</strong> — a Heading introduces each section ("Personal info", "Work experience"), Paragraphs explain what to
    fill in, and an Image block shows a sample resume layout
  </li>
  <li>
    <strong>Event registration</strong> — a YouTube Embed shows the event trailer, a Google Maps Embed shows the venue, and a Table lists
    the schedule
  </li>
  <li>
    <strong>Product order</strong> — Single Picture Choice for product selection, a 2-Column layout puts quantity and size side by side, and
    a Paragraph below shows terms and conditions
  </li>
  <li>
    <strong>Personalized confirmation</strong> — on the thank-you page, a Heading reads "Thanks, <strong>@Name</strong>!" and a Paragraph
    pipes <strong>@Order total</strong> from a calculated field
  </li>
</ul>

> 💡 **Don't overdo it**
> <p>
>     Heavy media slows down form load. Use embeds where they directly help the respondent — a short explainer video, a map showing your
>     location.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Validation rules](/building-forms/field-configuration#validation-rules) — Required, min/max, patterns, and constraints
  - [Conditional logic](/building-forms/conditional-logic) — Show or hide blocks based on answers
  - [Answer piping](/building-forms/answer-piping) — Reference answers with @ mentions
  - [Appearance & theming](/branding-domains/appearance-theming) — Theme, cover, logo, dark mode
</div>


# Conditional logic

Show, hide, require, and branch based on answers.

## Conditional logic

Logic blocks react to what a respondent enters — show or hide any block, jump to pages, block submission, or calculate values. Layer simple rules into sophisticated branching without code.

<h2 id="what-logic-can-do">What logic can do</h2>

<p>
  Every logic block has one or more <strong>rules</strong>. Each rule pairs a <em>condition group</em> (when should this fire?) with one or
  more <em>actions</em> (what should happen?). Nine action types are available:
</p>

<h2 id="adding-a-logic-block">Adding a logic block</h2>

<p>
  A logic block is a document element — it sits on the canvas between questions, just like a heading or an image. You can place it anywhere:
  between two fields on the same page, at the top of a page, or right before a page break.
</p>

<h2 id="conditions">Conditions</h2>

<p>
  Conditions are the <strong>When</strong> side of a rule. Each condition picks a <strong>field</strong>, applies an{' '}
  <strong>operator</strong>, and often compares against a <strong>value</strong>. The field picker groups what you can watch:{' '}
  <strong>Questions</strong>, <strong>Hidden Fields</strong>, <strong>Calculated Fields</strong>, and <strong>Metadata</strong> (today, the
  respondent's locale).
</p>

<h3 id="all-or-any">All or Any</h3>

<p>Once a rule has two or more conditions, a toggle appears at the top of the When section:</p>

<ul>
  <li>
    <strong>All</strong> — every condition must be true for the rule to fire. The rows read "and".
  </li>
  <li>
    <strong>Any</strong> — one true condition is enough. The rows read "or".
  </li>
</ul>

<h3 id="sets-of-conditions">Sets of conditions</h3>

<p>
  <strong>Add a set of conditions</strong> nests a second group inside the first, one level deep, with its own All/Any toggle. That covers
  compound logic like "plan <strong>is</strong> Pro <em>and</em> (country <strong>is</strong> Norway <em>or</em> country <strong>is</strong>{' '}
  Sweden)". A set holds conditions only — it cannot hold another set.
</p>

> ⚠️ **A hidden question has no value**
> <p>
>     If logic hides a question, the answer it may already hold counts for nothing — not in conditions, not in calculations, not in the
>     payment amount, and not in the stored submission. The answer comes back only if the question becomes visible again. The same goes for
>     every question inside a hidden row, column or repeating group, and for a picked option that logic hides. A page that the Set next page
>     rules skip counts as hidden too: its questions are not required, and whatever was answered there is dropped. Hidden <em>fields</em> are
>     the opposite: they always carry their value.
>   </p>

> 💡 **Keep it readable**
> <p>
>     If a single logic block feels too complex, consider splitting the logic across multiple logic blocks or restructuring your form pages.
>   </p>

<h2 id="operators-reference">Operators reference</h2>

<p>
  The operator list is decided by the field you picked — you only ever see the ones that field supports. Below, each heading names the exact
  set.
</p>

<h3 id="text-text-input-text-area-and-text-calculated-fields">Text (Text Input, Text Area, and text calculated fields)</h3>

<p>
  Every text comparison ignores letter case and spaces at either end, so <code>x@Competitor.com</code> ends with{' '}
  <code>@competitor.com</code>.
</p>

<p>The other text-like inputs carry a narrower set, so pick the type that matches how you need to compare:</p>

<h3 id="numeric-number-star-rating-linear-scale-numeric-hidden-and-calculated-fields">
  Numeric (Number, Star Rating, linear scale, numeric hidden and calculated fields)
</h3>

<h3 id="checkboxes-and-picture-choice">Checkboxes and picture choice</h3>

<h3 id="dropdown-and-radio-buttons-single-select">Dropdown and Radio Buttons (single-select)</h3>

<h3 id="toggle-switch-and-boolean-calculated-fields">Toggle Switch (and boolean calculated fields)</h3>

<h3 id="signature">Signature</h3>

<h3 id="file-upload">File Upload</h3>

<h3 id="date-picker-and-time-picker">Date Picker and Time Picker</h3>

<h3 id="ranking">Ranking</h3>

<h3 id="matrix--grid">Matrix / Grid</h3>

<h3 id="payment">Payment</h3>

<h3 id="fields-you-cannot-watch">Fields you cannot watch</h3>

<p>
  <strong>Schedule appointment</strong> and <strong>Documents</strong> can be visibility targets but never condition sources. A booking is
  attached to the submission rather than stored as an ordinary answer, and a Documents block collects no answer at all. Use earlier
  qualification answers to show or hide either one. Every visible Schedule appointment question must be booked before submission; a hidden
  one does not block it. See <a href="/integrations/cal-com#logic">Cal.com scheduling with logic</a>.
</p>

<h2 id="targeting">Targeting</h2>

<p>
  Each action needs a <strong>target</strong> — the thing it acts on. The target depends on the action type:
</p>

<p>
  Show and Hide reach beyond questions. Target any block on the canvas — a paragraph, heading, image, or layout container — to reveal a note
  that was hidden by default, or hide content once it is no longer relevant. They can also target individual options inside a choice field,
  not just the whole question. Hide a block from the block menu first, then write a rule to reveal it.
</p>

<h2 id="how-rules-combine">How multiple rules combine</h2>

<p>
  Every rule is independent, and they all evaluate together. Rules are read top to bottom, across logic blocks, in document order. How
  several rules on one target combine depends on the action.
</p>

<p>
  So you can point several rules at one block without them fighting. A feedback form has a Radio Buttons question — "What can we help you
  with?" — with four options, and a "Can we contact you?" email field hidden by default. Four rules each <strong>Show</strong> that same
  email field (plus their own follow-up question) for their option. The email field appears no matter which option is picked, and stays
  hidden only if none is.
</p>

> 💡 **Often one rule is enough**
> <p>
>     To reveal a block for several answers, one rule with <strong>Any</strong> and a condition per answer — or a single{' '}
>     <strong>is not empty</strong> condition to cover them all — is easier to read than four rules. Reach for several rules when each option
>     also needs its own distinct follow-up.
>   </p>

<h3 id="conflicting-rules">Conflicting rules</h3>

<p>
  When two rules disagree on the same target — one <strong>Show</strong>s a block while another <strong>Hide</strong>s it, or one makes a
  field required while another makes it optional — and both conditions match at once, the <strong>first rule wins</strong>. The later,
  losing rule is flagged in the editor: "This action never applies — an earlier rule already controls its target. Rule order decides: the
  first rule wins."
</p>

<p>
  One ordering exception: a rule whose condition reads a calculated field runs after the rule that writes it, whatever the document order,
  so a score is always computed before anything branches on it. Rules that write each other's calculated fields in a loop raise a publish
  warning that names the chain — break the loop so values resolve predictably.
</p>

<h3 id="hidden-and-required">Hidden beats required</h3>

<p>
  A question that ends up hidden is never required, whether it was hidden by a rule or hidden from the block menu with no rule to reveal it.
  The asterisk disappears and submission is not blocked — otherwise a respondent could be locked out of a form by a field they cannot see.
</p>

<h2 id="common-patterns">Common patterns</h2>

<h3 id="show-a-follow-up-question-based-on-a-previous-answer">Show a follow-up question based on a previous answer</h3>

<p>
  A satisfaction survey asks "How would you rate your experience?" with a 1-5 Star Rating. A logic block below it uses the condition "rating
  is less than or equal to 3" with the action <strong>Show</strong> targeting a Text Area field that asks "What could we improve?"
  Respondents who rate 4 or 5 never see the follow-up.
</p>

<h3 id="reveal-a-message-when-an-option-is-picked">Reveal a message when an option is picked</h3>

<p>
  A signup form has a Radio Buttons question: "Which plan fits you best?" Below it sits a paragraph — "Good choice! Our Pro plan includes
  priority support." — hidden by default with the block menu's <strong>Hide</strong>. A logic block reveals it: if plan <strong>is</strong>{' '}
  "Pro", <strong>Show</strong> the paragraph. The encouraging note appears only for Pro pickers, and stays hidden for everyone else.
</p>

<h3 id="branch-to-different-pages-based-on-a-selection">Branch to different pages based on a selection</h3>

<p>An event registration form has a Radio Buttons question: "Are you attending in person or remotely?" Two logic blocks follow:</p>

<ul>
  <li>
    <strong>Rule 1</strong> — condition: attendance <strong>is</strong> "In person", action: <strong>Set next page</strong> "Venue details".
  </li>
  <li>
    <strong>Rule 2</strong> — condition: attendance <strong>is</strong> "Remote", action: <strong>Set next page</strong> "Streaming setup".
  </li>
  <li>
    <strong>Rule 3</strong> — condition: arrival time <strong>is not empty</strong>, action: <strong>Set next page</strong> "Confirmation".
    Arrival time is a required question on "Venue details".
  </li>
</ul>

<p>
  Rules 1 and 2 read the attendance question, so they act when the respondent leaves its page. After that, Next goes on in page order. Rule
  3 reads a question on "Venue details", so it acts when the respondent leaves that page. In-person attendees then skip "Streaming setup".
  Each path collects only relevant information, then both converge on a shared "Confirmation" page.
</p>

<h3 id="calculate-a-score-and-branch-on-it">Calculate a score and branch on it</h3>

<p>
  A lead-qualification form has three Star Rating questions: budget, timeline, and authority. A calculated field named "Lead score" uses a
  logic block with the <strong>Calculate value</strong> action to add the value of each rating. A second logic block reads the calculated
  field: if Lead score <strong>is greater than 12</strong>, <strong>Set next page</strong> "Priority follow-up"; otherwise jump to the
  standard confirmation page.
</p>

<h3 id="react-to-a-hidden-field-value">React to a hidden field value</h3>

<p>
  The URL populates a hidden field named <strong>plan</strong> (e.g. <code>?plan=enterprise</code>). A logic block checks: if plan{' '}
  <strong>is</strong> "enterprise", <strong>Show</strong> the "Dedicated account manager" question and <strong>Make required</strong>
  the "Company size" field. Free-tier respondents never see these questions.
</p>

<h3 id="gate-submission-on-a-calculated-total">Gate submission on a calculated total</h3>

<p>
  An order form calculates a running total. A logic block uses the condition: if Order total <strong>is less than 10</strong>,
  <strong>Prevent submit form</strong>. The respondent cannot submit until the minimum order threshold is met.
</p>

<p>
  While a prevent rule is active, the button reads as unavailable and the respondent is told why. Type the reason into{' '}
  <strong>Message shown to respondent</strong> on the action row — "Your order must total at least 10" — so they know which answer to
  change. Leave it empty and the form falls back to "This form cannot be submitted with the current answers." (or "You cannot continue to
  the next page with the current answers." for a navigation block). When several prevent rules fire at once, the first one that carries a
  message supplies the wording.
</p>

> 💡 **Test logic with Preview**
> <p>
>     After setting up your rules, switch to <strong>Preview</strong> in the top toolbar and fill out the form as a respondent would. Logic
>     fires in real time in Preview, so you can verify every branch, hidden field, and validation gate before publishing.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Hidden fields](/building-forms/hidden-fields) — Capture URL parameters and use them in logic
  - [Calculated fields](/building-forms/calculated-fields) — Compute scores and totals with logic operations
  - [Answer piping](/building-forms/answer-piping) — Reference any answer elsewhere in the form
</div>


# Hidden fields

Pass invisible data into forms via URL parameters.

## Hidden fields

Attach invisible metadata to every submission — traffic sources, CRM IDs, referral codes, or any context you already know about the respondent. Hidden fields capture data through URL parameters without showing anything on the form.

<h2 id="why">Why use hidden fields?</h2>

<p>
  Forms often need context that the respondent can't (or shouldn't) provide themselves. Hidden fields solve this by letting you attach
  metadata to each submission through the URL. This is useful when you already know something about the respondent before they open the form
  — where they came from, who referred them, or which record they belong to in your system.
</p>

<ul>
  <li>
    <strong>Track traffic sources</strong> — know whether a submission came from an email campaign, a social media post, or your website
  </li>
  <li>
    <strong>Connect to your CRM</strong> — pass a contact ID, deal ID, or account ID so the submission links back to the right record
  </li>
  <li>
    <strong>Personalize follow-ups</strong> — capture a name or email you already have, then use it in conditional logic or email
    notifications
  </li>
  <li>
    <strong>Multi-form workflows</strong> — pass data from one form into the next so respondents don't repeat themselves
  </li>
  <li>
    <strong>A/B testing</strong> — tag each submission with the variant or experiment the respondent saw
  </li>
</ul>

<h2 id="adding">Adding a hidden field</h2>

<p>
  Type <code>/</code> on an empty line and pick <strong>Hidden Field</strong> from the Layout category. It only works at the top level of
  the form — not inside a column or a repeating group. Each hidden field has two settings:
</p>

<ul>
  <li>
    <strong>Field type</strong> — store a <strong>number</strong> or <strong>text</strong> value (text by default)
  </li>
  <li>
    <strong>Parameter name</strong> — the URL parameter that fills this field (e.g. <code>utm_source</code>, <code>contact_id</code>). It is
    also this field's key, so it is what a caller sends and what the answer comes back under. See{' '}
    <a href="/requests/field-keys">Field keys</a>
  </li>
</ul>

<h2 id="url-parameters">URL parameter pre-fill</h2>

<p>
  A parameter name must be URL-safe as written: letters, digits, and <code>- _ . ~ ! * ' ( )</code> are safe. Write spaces and other special
  characters percent-encoded, for example <code>recipient%20name</code>. An empty, invalid, or duplicate parameter name is a publish error —
  the issue indicator in the toolbar flags it and publishing stops until you fix it.
</p>

<p>
  When a respondent opens your form with a matching parameter in the URL, the hidden field captures that value automatically. For example,
  if your hidden field's parameter name is <strong>utm_source</strong>, the URL below sets the field to <strong>newsletter</strong>.
</p>

```
https://form.formbase.so/abc123?utm_source=newsletter
```

<p>You can pass multiple hidden fields at once:</p>

```
https://form.formbase.so/abc123?utm_source=newsletter&utm_medium=email&contact_id=12345
```

<ul>
  <li>Values are read once, when the form opens.</li>
  <li>
    An empty parameter (<code>?utm_source=</code>) leaves the field empty.
  </li>
  <li>
    A number hidden field ignores values that aren't numbers, so <code>?score=abc</code> leaves it empty.
  </li>
  <li>
    <a href="/requests/overview">Request</a> links ignore URL parameters. Hidden fields there get their values from the request's{' '}
    <a href="/requests/creating-requests#context">context</a> instead.
  </li>
</ul>

<p>
  On a share link, to show a URL value in a visible field the respondent can edit — say, pre-filling their name — make the hidden field the
  field's default value by typing <kbd>@</kbd>. See{' '}
  <a href="/building-forms/field-configuration#default-values">Default values from other fields</a>.
</p>

<h2 id="requests-prefill-directly">Requests don't need a hidden field to pre-fill</h2>

<p>
  The hidden-field-plus-<kbd>@</kbd> technique above exists because a share link has only one way in: the URL. Anyone can edit a URL, so a
  share link can carry a value only through a parameter, and a hidden field is what receives it.
</p>

<p>
  A <a href="/requests/overview">request</a> has a second, private way in. The caller sets the answers when they create the request, server
  side, so a request can pre-fill a <strong>visible question directly</strong> — no hidden field and no <kbd>@</kbd> default involved. The
  recipient opens the form with the answer already in the box.
</p>

<ul>
  <li>
    <strong>
      <code>prefill</code>
    </strong>{' '}
    — starting answers for <strong>visible questions</strong>, addressed by field key. The recipient sees them and can change them.
  </li>
  <li>
    <strong>
      <code>readonly</code>
    </strong>{' '}
    — the subset of those pre-filled keys the recipient may read but not edit.
  </li>
  <li>
    <strong>
      <code>context</code>
    </strong>{' '}
    — values for <strong>hidden fields</strong>, which the recipient never sees.
  </li>
</ul>

<p>
  The split is enforced. Sending a visible question's key in <code>context</code> is rejected, with an error telling you to send it in{' '}
  <code>prefill</code> instead, and the reverse is rejected too. So add a hidden field when you want to{' '}
  <strong>carry data you don't want shown</strong>, not merely to pre-fill something.
</p>

> ⚠️ **A request link ignores URL parameters completely**
> <p>
>     On a request, <code>?param=</code> values are not read at all — they do not fill a hidden field, and they do not override anything the
>     caller set. This is deliberate: the recipient holds the link, so honouring URL parameters would let them rewrite the caller's own
>     context. Request context always wins, because on a request it is the only source.
>   </p>

<h2 id="common-setups">Common setups</h2>

<ul>
  <li>
    <strong>UTM tracking</strong> — create hidden fields named <strong>utm_source</strong>, <strong>utm_medium</strong>, and{' '}
    <strong>utm_campaign</strong>. The form catches them from the URL automatically, and each value saves as a submission column. The
    Analytics Traffic Sources chart reads utm_source and utm_medium from the URL on its own, with or without hidden fields. Set per-link
    defaults in the <a href="/sharing-publishing/sharing-embedding#query-parameters">Share dialog</a>.
  </li>
  <li>
    <strong>CRM integration</strong> — pass a contact or deal ID from tools like HubSpot, ActiveCampaign, or Mailchimp. When submissions
    land in your integrations, the ID ties everything together.
  </li>
  <li>
    <strong>Referral attribution</strong> — each partner gets a unique link with their code in the URL. You see exactly who drove each
    submission.
  </li>
  <li>
    <strong>Pre-filled context</strong> — pass a customer name or plan tier into a support form. Use it in conditional logic to show
    different questions, or pipe it into the thank-you page.
  </li>
  <li>
    <strong>Pre-filled answers</strong> — send <code>?email=jane@example.com</code> from your email tool and make that hidden field the
    default of the form's Email question. The respondent sees their address already filled in and only fixes it if it's wrong.
  </li>
</ul>

> 💡 **Works with any link tool**
> <p>
>     Email platforms (Mailchimp, ActiveCampaign, HubSpot), ad platforms (Google Ads, Meta), and link shorteners all let you append URL
>     parameters. Hidden fields catch them with zero extra setup on the form side.
>   </p>

<h2 id="viewing-data">Viewing hidden field data</h2>

<p>
  In the Submissions table, hidden fields show as their own columns — formbase marks them with an eye-slash icon so you can tell them apart
  from visible questions. They also appear in exports, integrations (Google Sheets, webhooks, etc.), and the submission detail panel.
</p>

> ℹ️ **UTM in analytics vs. submissions**
> <p>
>     formbase records utm_source, utm_medium, and utm_campaign with each visit's analytics, and the Traffic Sources chart groups submissions
>     by source and medium, but it doesn't save them on each response. To see a parameter's value per submission, add a hidden field with that
>     name (for example utm_source). It then shows in both the submissions table and analytics.
>   </p>

<h2 id="with-logic">Using hidden fields with logic</h2>

<p>
  Hidden fields work as <strong>condition sources</strong> in <a href="/building-forms/conditional-logic">conditional logic</a>. A logic
  rule can read a hidden field's value and branch — showing different questions, jumping to a page, or updating a{' '}
  <a href="/building-forms/calculated-fields">calculated field</a>.
</p>

<p>
  For example: pass <code>?plan=enterprise</code> via the URL. A logic rule checks if the hidden "plan" field equals "enterprise" and jumps
  to a dedicated page with enterprise-specific questions.
</p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Calculated fields](/building-forms/calculated-fields) — Compute scores, totals, and dynamic text from answers
  - [Answer piping](/building-forms/answer-piping) — Reference any answer elsewhere in the form with @ mentions
  - [Custom thank-you page](/building-forms/custom-thank-you) — Show calculated values and personalized content after submission
  - [Submission inbox](/submissions-analytics/submission-inbox) — See hidden field values alongside regular answers
</div>


# Calculated fields

Compute scores, totals, and dynamic text from form answers.

## Calculated fields

Invisible fields that compute values from answers — quiz scores, order totals, lead ratings, or concatenated text. They update in real time as the respondent fills out the form.

<h2 id="how-it-works">How it works</h2>

<p>
  Calculated fields are invisible to respondents but compute values based on logic rules you define. They store either a{' '}
  <strong>number</strong> or <strong>text</strong> result, and update live as the respondent answers questions.
</p>

<p>
  The flow: you create a calculated field, set an initial value, then add <a href="/building-forms/conditional-logic">logic rules</a> with
  the <strong>Calculate value</strong> action. Each rule runs an operation (add, multiply, set, etc.) when its condition is met.
</p>

<h2 id="adding">Adding a calculated field</h2>

<p>
  Type <code>/</code> on an empty line and pick <strong>Calculated Field</strong> from the Layout category. It only works at the top level
  of the form — not inside a column or a repeating group. Set:
</p>

<ul>
  <li>
    <strong>Field type</strong> — number or text
  </li>
  <li>
    <strong>Name</strong> — how you refer to it in logic rules and @ mentions. It is also this field's key, so the value goes out to
    integrations and callbacks under it
  </li>
  <li>
    <strong>Initial value</strong> — the starting value before any rules fire. Either type a literal (e.g. <code>0</code> for a score) or
    pick a <a href="/building-forms/hidden-fields">hidden field</a> of the same type to seed it from a URL parameter. If the parameter is
    missing or invalid, the field starts at <code>0</code> (number) or empty (text)
  </li>
</ul>

<h2 id="numeric-operations">Numeric operations</h2>

<p>These are the exact entries in the operation picker on a Calculate value action.</p>

<h2 id="text-operations">Text operations</h2>

<h2 id="operation-values">Operation values</h2>

<p>Each operation takes a value that is either:</p>

<ul>
  <li>
    <strong>A fixed value</strong> — a specific number or string you type in (e.g. always add <strong>10</strong>)
  </li>
  <li>
    <strong>The value of another field</strong> — pick a question, hidden field, or calculated field so the value is dynamic (e.g. add
    whatever the respondent entered in Q2)
  </li>
</ul>

<p>
  A numeric operation is skipped when its value is empty or isn't a number, and dividing by 0 is skipped too, so the current total stays as
  it is. Answers to questions hidden by logic don't count — a hidden question has no value at all, so its answer never reaches a
  calculation.
</p>

<p>
  Every matching Calculate value rule runs, in document order, on the running value — they stack rather than compete. One exception to
  document order: a rule that <em>reads</em> a calculated field always runs after the rule that <em>writes</em> it, so a score is finished
  before anything branches on it. Rules that write each other's fields in a loop are flagged as a publish warning that names the chain, so
  break the loop rather than publish through it.
</p>

<h2 id="examples">Examples</h2>

<p>
  <strong>Quiz scoring</strong> — Create a calculated field "Total score" with initial value <strong>0</strong>. Add logic rules: if Q1
  equals "Yes", <strong>Add 10</strong>; if Q2 is greater than 5, <strong>Add</strong> the value of Q2. The score updates live as the
  respondent answers.
</p>

<p>
  <strong>Dynamic pricing</strong> — A product order form has a Number field for quantity and a Dropdown for size (Small, Medium, Large).
  Create a "Price" calculated field. Logic rules: if size is "Small", <strong>Set to 10</strong>; if "Medium", <strong>Set to 15</strong>;
  if "Large", <strong>Set to 20</strong>. Below those, add a rule with no conditions that <strong>Multiplies</strong> Price by the quantity
  field. Rules run top to bottom, and a rule with no conditions always runs. Pipe <strong>@Price</strong> into the thank-you page. To charge
  that calculated price directly, see <a href="/building-forms/payment-collection#dynamic-pricing">Dynamic pricing</a> in Payment
  collection.
</p>

<p>
  <strong>Lead qualification</strong> — Three Star Rating questions rate budget, timeline, and authority. A "Lead score" field uses{' '}
  <strong>Add</strong> for each rating. A logic rule checks: if Lead score is greater than 12, <strong>Set next page</strong> "Priority
  follow-up"; otherwise jump to the standard thank-you.
</p>

<p>
  <strong>Concatenating text</strong> — A text calculated field "Full name" starts empty. Logic appends <strong>@First name</strong>, then
  appends a space, then appends <strong>@Last name</strong>. Use <strong>@Full name</strong> in the thank-you page or email notifications.
</p>

> 💡 **Chain with logic**
> <p>
>     Calculated fields also work as condition sources. One rule computes a value, another reacts to it — e.g. "if Total > 50, jump to the Premium page." This lets you build multi-step scoring without any code.
>   </p>

<h2 id="pre-populating">Pre-populating other fields</h2>

<p>
  A visible field can start with a calculated field's value, so the respondent reviews a computed value instead of typing it. In a Text
  Input, Text Area, Email, Phone Number, Website URL, or Number field, type <kbd>@</kbd> at the start of the input and pick the calculated
  field. The respondent can still change the value.
</p>

<p>
  The input takes the first value the calculated field has — usually its initial value, or the hidden field that seeds it. Later changes
  from logic rules don't replace what is already in the input. To show a value that keeps updating, pipe it into text with an{' '}
  <a href="/building-forms/answer-piping">@ mention</a> instead. See{' '}
  <a href="/building-forms/field-configuration#default-values">Default values from other fields</a> for details.
</p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Answer piping](/building-forms/answer-piping) — Show calculated values on the form or in emails
  - [Thank-you page](/building-forms/custom-thank-you) — Display calculated results after submission
  - [Form settings](/building-forms/form-settings) — Notifications, access control, and more
  - [Appearance & theming](/branding-domains/appearance-theming) — Theme, cover, logo, dark mode
</div>


# Answer piping

Use @ mentions to reference answers anywhere in your form.

## Answer piping

Type @ to reference any field's answer — in question titles, content blocks, thank-you pages, email notifications, and integration messages. Values update dynamically so each respondent sees personalized content.

<h2 id="how-it-works">How it works</h2>

<p>
  Type <strong>@</strong> in any text that supports it (see below). A mention menu appears listing your form's fields grouped by type:{' '}
  <strong>Hidden Fields</strong>, <strong>Answers</strong>, <strong>Repeating groups</strong>, <strong>Calculated Fields</strong>, and{' '}
  <strong>Metadata</strong>. Pick a field and formbase inserts a variable chip at your cursor.
</p>

<p>
  In the editor, piped values show as a chip (e.g. <strong>@First name</strong>). When a respondent fills out the form, formbase replaces
  each chip with their actual answer.
</p>

<p>
  To type a plain <strong>@</strong> instead — an email address or a handle — press <kbd>Esc</kbd> to close the menu and keep typing. The
  menu also closes on its own when nothing matches what you typed after the <strong>@</strong>.
</p>

<h2 id="where-it-works">Where piping works</h2>

<ul>
  <li>Question titles — a question never offers its own answer, which would be circular</li>
  <li>Content blocks: paragraphs, headings, list items, table cells, links, and image captions</li>
  <li>Thank-you page content</li>
  <li>Translations of any of the above, where the mention carries into each language</li>
  <li>
    Email notification subjects and bodies: self-notification, respondent confirmation, and abandoned-form reminder emails, plus the request
    invitation
  </li>
  <li>
    Integration templates: <a href="/integrations/slack">Slack</a> and <a href="/integrations/discord">Discord</a> messages,{' '}
    <a href="/integrations/github">GitHub</a> and <a href="/integrations/linear">Linear</a> issue titles and descriptions, and{' '}
    <a href="/integrations/notion">Notion</a> and <a href="/integrations/airtable">Airtable</a> field mappings
  </li>
</ul>

<p>
  Choice option labels and matrix row and column labels take mentions too, but a narrower menu: only Hidden Fields, Calculated Fields, and
  Metadata — the values that exist before anyone answers. An option label is how the option reads in the summary, in exports, and in the
  logic picker, so it cannot depend on another answer.
</p>

<p>
  Button labels and repeating group titles take no mentions. Two related features work differently:{' '}
  <a href="/building-forms/redirect-after-submit#query-parameters">redirect query parameters</a> pick a field from a dropdown for each
  parameter, and <a href="#input-defaults">input defaults</a> put a value inside a field instead of in text.
</p>

<h2 id="input-defaults">Pre-filling an editable field</h2>

<p>
  A mention shows a value as text the respondent can't change. To put a value <em>inside</em> a field so the respondent can confirm or edit
  it, use a field reference as the field's default instead: type <kbd>@</kbd> at the start of a Text Input, Text Area, Email, Phone Number,
  Website URL, or Number field. That menu lists only <strong>Hidden Fields</strong> and <strong>Calculated Fields</strong>, because those
  have a value before the respondent starts answering. See{' '}
  <a href="/building-forms/field-configuration#default-values">Default values from other fields</a>.
</p>

<p>
  That is the technique for a <strong>share link</strong>, where a value can only arrive through a URL parameter and so needs a hidden field
  to land in. On a <a href="/requests/overview">request</a> you don't need any of it: the caller pre-fills a visible question directly when
  they create the request, using <code>prefill</code>, and can mark a key <code>readonly</code> so the recipient sees it but cannot change
  it. Build a hidden field when you want to carry data the respondent should never see, not just to pre-fill an answer. See{' '}
  <a href="/building-forms/hidden-fields#requests-prefill-directly">Requests don't need a hidden field to pre-fill</a>.
</p>

<h2 id="field-types">What you can pipe</h2>

<p>In body text the @ menu lists five groups, in this order:</p>

<p>
  A field that a chip points at but that no longer exists shows the tooltip "Referenced field was deleted" and renders as empty at fill
  time. If you delete a hidden or calculated field and add another with the same name, existing chips reconnect to it on their own. Question
  references never reconnect that way — titles change and collide, so they stay broken until you re-insert them.
</p>

<h2 id="metadata-variables">Metadata variables</h2>

<p>
  Beyond field answers, the <strong>Metadata</strong> group carries runtime values. Some are available everywhere; others only in email
  templates. The name below is what the chip reads.
</p>

<h3 id="metadata-form-and-email">Available in both form content and email templates</h3>

<h3 id="metadata-form-only">Form content only</h3>

<h3 id="metadata-email-only">Email templates only</h3>

> 💡 **PINs as reference numbers**
> <p>
>     Drop a <strong>6 Digit PIN</strong> into the respondent confirmation email as a reference number. Respondents can quote it in follow-up
>     conversations, and you can search for it in the submissions table.
>   </p>

<h2 id="examples">Examples</h2>

<ul>
  <li>
    <strong>Personalized question</strong> — "Thanks @First name! How would you rate your experience with @Company?"
  </li>
  <li>
    <strong>Dynamic thank-you</strong> — "Your total is @Price. We'll send a confirmation to @Email within 24 hours."
  </li>
  <li>
    <strong>Conditional email</strong> — "Hi @First name, your lead score is @Lead score. Our team will be in touch."
  </li>
  <li>
    <strong>Redirect with context</strong> — in redirect settings, add a <code>name</code> parameter mapped to the Name question and a{' '}
    <code>plan</code> parameter mapped to Plan. The preview shows the URL respondents are sent to:
  </li>
</ul>

```
https://yoursite.com/confirm?name={Name}&plan={Plan}
```

<h2 id="referencing-repeating-groups">Referencing repeating groups</h2>

<p>
  <a href="/building-forms/repeating-groups">Repeating groups</a> let respondents add as many entries as they need, so a single reference
  can pull in more than one value. How the @ mention resolves depends on what you point it at.
</p>

<ul>
  <li>
    <strong>A single member field</strong> — formbase joins that field's values across every entry with <strong> · </strong>. Mentioning the
    Name field of three guests shows "Jane Appleseed · Marcus Lee · Priya Patel".
  </li>
  <li>
    <strong>The whole group</strong> — every entry appears inline. Member values within an entry join with <strong> · </strong>, and entries
    are separated by commas. A group with Name and Age shows "Jane Appleseed · 34, Marcus Lee · 29".
  </li>
</ul>

<p>
  In an integration column the whole group appears as numbered, labeled rows instead, so each entry stays readable: "1. Full name: Jane
  Appleseed, Age: 34, 2. Full name: Marcus Lee, Age: 29".
</p>

<h2 id="deleting-piped-fields">Deleting a piped field</h2>

<p>
  A mention left pointing at a deleted field is a publish <em>error</em>, not a warning: the issue indicator in the toolbar flags every
  stale reference — in form content, email templates,{' '}
  <a href="/building-forms/translating-form-content#field-reference-checks">translated emails</a>, and integration message templates — and
  publishing stops until each one is fixed. Open the flagged location, remove the broken chip, and re-insert the correct field.
</p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Custom thank-you page](/building-forms/custom-thank-you) — Pipe answers into the post-submit screen
  - [Form settings](/building-forms/form-settings) — Set up notification templates with piped variables
  - [Self notifications](/submissions-analytics/self-notifications) — Use piping in team alert emails
  - [Respondent notifications](/submissions-analytics/respondent-notifications) — Personalize confirmation emails with piping
  - [Repeating groups](/building-forms/repeating-groups) — Let respondents add multiple entries to a group
</div>


# Thank-you page & redirect

Customize post-submission screens or redirect to your own URL.

## Thank-you page & redirect

Replace the default 'thanks for your response' screen with whatever you want — branded copy, next-step links, even answers piped from the response.

<h2 id="where-to-find-it">Add a thank-you page</h2>

Without one, respondents see the built-in screen: **Thanks for your response!** and "Your submission has been received." To replace it, click an empty line on the canvas, type <kbd>/</kbd> and pick **Thank You Page**. The editor adds a green **Thank You Page** divider — badged **Success only** — at the bottom of the form and puts the cursor beneath it. Everything after that divider is the thank-you screen. To go back to the default, click the trash icon on the divider.

A form has at most one thank-you page, and the editor keeps it last: you cannot drag it, and adding a second one demotes the first to an ordinary page.

<h2 id="what-you-can-put-on-it">What you can put on it</h2>

- Headings, paragraphs, lists, and tables
- Images, YouTube videos, Google Maps, and iframe embeds
- Links, including link buttons to your site, store, or calendar
- Columns, to lay any of the above out side by side
- Answers from earlier questions — type **@** to mention a field ("Thanks @name, we'll review your application…")

Questions, logic blocks, and page breaks are not allowed after the divider; the editor removes them if you move them there. Logic cannot jump to the thank-you page either — a **Set next page** action pointed at it is cleared.

<h2 id="examples">Examples</h2>

<ul>
  <li>
    <strong>Job application</strong> — "Thanks, <strong>@Name</strong>! We'll review your application and get back to you within 5 business
    days." with a link button to "View open roles"
  </li>
  <li>
    <strong>Event registration</strong> — show the event date, a Google Maps embed for the venue, and an "Add to calendar" link
  </li>
  <li>
    <strong>Order confirmation</strong> — pipe <strong>@Order total</strong> from a calculated field and add a "Track your order" button
  </li>
  <li>
    <strong>Quiz result</strong> — "You scored <strong>@Score</strong> out of 10!" piped from a calculated field
  </li>
</ul>

<h2 id="redirect-instead">Redirect instead</h2>

To send respondents to a page you own, set a redirect URL in{' '}

<a href="/building-forms/form-settings#after-submit">Form settings → Submissions</a>. See
<a href="/building-forms/redirect-after-submit">Redirect after submit</a> for query parameters and the rules that come with it.

<h2 id="thank-you-plus-redirect">What happens when you have both</h2>

A thank-you page and a redirect URL work together: formbase renders your thank-you content, then redirects after a three-second countdown. During the countdown the **Submit again** and **Edit your response** buttons are replaced by "Redirecting in 3 seconds…", so nobody interrupts the jump.

> 💡 **Don't waste the moment**
> <p>
>     The thank-you screen is the highest-attention moment after a respondent commits. Use it for the next ask: book a call, share the form,
>     view a confirmation.
>   </p>

<h2 id="translations">Translations</h2>

<p>
  Thank-you page text translates alongside the rest of your form. Open the <strong>Translate</strong> tab and the thank-you blocks show up
  under each language — headings, paragraphs, and link labels can all be localized. Piped answers (<kbd>@</kbd> mentions) carry through
  automatically. See <a href="/building-forms/translating-form-content">Translating form content</a> for the full workflow.
</p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Redirect after submit](/building-forms/redirect-after-submit) — Send respondents to your own URL
  - [Appearance & theming](/branding-domains/appearance-theming) — Theme, colors, and logo for your form
  - [Translating form content](/building-forms/translating-form-content) — Translate thank-you page content into any language
  - [Answer piping](/building-forms/answer-piping) — Use field values in text with @ mentions
</div>


# Redirect after submit

Send respondents to your URL after they submit, with optional query params.

## Redirect after submit

After a respondent submits, formbase shows the success screen for three seconds, then sends them to your URL — your site, a page you own, or a downstream tool.

<h2 id="setup">Setting up a redirect</h2>

In **Form settings → Submissions**:

- **Redirect URL** — where to send the respondent after submission
- **Redirect URL query parameters** — optionally append field values to that URL

Only `http` and `https` URLs are used. A bare host like `yoursite.com/welcome` is treated as `https://yoursite.com/welcome`; anything that is not a valid URL is ignored and the respondent simply stays on the success screen.

<h2 id="what-respondents-see">What respondents see</h2>

The success screen appears with a three-second countdown — "Redirecting in 3 seconds…" — in place of the **Submit again** and **Edit your response** buttons, then the browser navigates. If you also built a [thank-you page](/building-forms/custom-thank-you), your content shows during that countdown.

<h2 id="query-parameters">Query parameters</h2>

Add a row per parameter: a name on the left, a field on the right. The picker groups what you can send as **Form fields**, **Hidden fields**, **Calculated fields**, and **Metadata** (submission ID, submitted at, form name, generated PINs and codes). The settings page previews the finished URL as you type.

Values are URL-encoded. Example:

```
https://yoursite.com/welcome?email=alice%40example.com&plan=pro
```

Use this to:

- **Personalize the destination page** with the respondent's name or email
- **Pre-fill another form** on your site
- **Trigger analytics events** with submission metadata

A member field of a [repeating group](/building-forms/repeating-groups) sends one parameter per entry — `?guest=Alice&guest=Bob` — rather than one joined value, so a comma inside an answer cannot corrupt the list. A whole group cannot be mapped to a parameter. Empty answers are left out of the URL entirely.

> ℹ️ **Redirect and 'Allow another response' are mutually exclusive**
> <p>
>     Setting a redirect URL turns off <strong>Allow another response</strong>, and turning that on clears the redirect. If respondents need
>     to submit several times, use a <a href="/building-forms/custom-thank-you">thank-you page</a> instead.
>   </p>

> ℹ️ **Embeds with autoClose**
> <p>
>     An <a href="/sharing-publishing/embedding-popups">embedded form</a> opened with the <code>autoClose</code> parameter does not redirect —
>     the embed closes itself instead.
>   </p>

<h2 id="next-steps">Next steps</h2>

<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Custom thank-you page](/building-forms/custom-thank-you) — In-form completion screen without redirect
  - [Form settings](/building-forms/form-settings#submissions) — All submission behavior options
  - [Answer piping](/building-forms/answer-piping) — Use field values in text with @ mentions
</div>


# Form settings

Access control, submission behavior, and notification options.

## Form settings

The settings panel (gear icon) has six tabs: General, Public link access, Submissions, Emails, Integrations, and Danger zone. This page covers the first four.

<h2 id="general">General</h2>

<p>Three options that apply to the form as a whole. Changes are saved with the dialog's Save changes button.</p>

<p>
  For source language, browser matching, and localized links, see <a href="/building-forms/language-setting">Language setting</a>. For
  translating questions, options, and descriptions into other languages, see{' '}
  <a href="/building-forms/translating-form-content">Translating form content</a>.
</p>

<h2 id="access">Public link access</h2>

<p>
  Public link access decides who can open and submit the form through its public link. A one-line summary at the top of the tab says who can
  open it as the switches stand, and whether spam checks run. A request is addressed to one person and opens from a signed link, so it skips
  these gates.
</p>

<h3 id="require-authentication">Require sign-in</h3>

<p>
  People sign in before they submit, so each person submits once across devices. Someone who has already submitted is blocked on every
  device, so this is the strongest way to keep one response per person.
</p>

<h3 id="password-protect">Password</h3>

<p>
  Turn on <strong>Password</strong> and type one in the field that appears under the switch. People enter it before the form opens. If you
  leave the switch on without typing a password, publishing warns you and turns the gate off rather than locking everyone out.
</p>

<h3 id="bot-protection">Spam protection</h3>

<p>
  A Cloudflare Turnstile check runs before each submission: invisible bot detection that blocks automated submissions, with a quick
  interactive check in rare cases. It's always on for free forms and can't be turned off; on Pro the switch is yours per form. On a custom
  domain it uses your workspace Turnstile keys, and the row says so. See{' '}
  <a href="/building-forms/captcha-bot-protection">Bot protection (Turnstile)</a> for details.
</p>

<h2 id="submissions">Submissions</h2>

<p>
  The Submissions tab covers when the form accepts answers, what happens after someone submits, and how long you keep responses. Rows that
  only apply to people who arrive through a share link say <strong>Public link only</strong>; a request already knows its recipient and is
  completed once.
</p>

<h3 id="close-submissions">Accepting submissions</h3>

<p>
  Three states: <strong>Open</strong>, <strong>Until a date</strong>, or <strong>Closed</strong>. Until a date shows a date picker; the form
  stops taking answers at the start of that day and shows a "Form closed" message after it. Closed stops new responses right away, and
  pending requests expire with the form. The row's description always tells you which state the form is in.
</p>

<h3 id="retention">Delete submissions (Business)</h3>

<p>
  <strong>Never</strong>, <strong>On a date</strong>, or <strong>After</strong> a number of days. After deletes each response once it is
  that old; On a date deletes everything daily once that date passes, including responses that arrive afterwards. Picking one clears the
  other, and the earliest date you can pick is tomorrow.
</p>

<p>
  Setting or changing a retention rule requires Business. A rule already stored keeps running after a plan change — retention is a promise
  to your respondents, not a perk — and you can clear it on any plan. See{' '}
  <a href="/submissions-analytics/submission-retention">Submission retention</a>.
</p>

<h3 id="after-submit">After someone submits</h3>

<p>One choice of three; picking one clears the others.</p>

<ul>
  <li>
    <strong>Show thank-you page</strong> — the ending you built in the editor. The default.
  </li>
  <li>
    <strong>Offer another response</strong> — public link only. Shows a "Submit again" button on the thank-you page, which loops the
    respondent back to a fresh form. <strong>Maximum responses per respondent</strong> appears once it is chosen and accepts 2 to 1000;
    leave it empty for unlimited. For signed-in respondents it is enforced on the server; for anonymous respondents it is a soft limit held
    in the browser.
  </li>
  <li>
    <strong>Go to a web page</strong> — send respondents to a URL instead of the thank-you page. Type the address in the{' '}
    <strong>Web page</strong> field, then add <strong>Redirect URL query parameters</strong> that pass answers along. A URL that is not a
    valid web address is cleared at publish with a warning. See <a href="/building-forms/redirect-after-submit">Redirect after submit</a>{' '}
    for the full guide.
  </li>
</ul>

<h3 id="edit-after-submission">Let people change their answers</h3>

<p>
  Public link only. Shows an "Edit your response" link on the thank-you page. The form reopens pre-filled with their answers, and formbase
  updates the submission when they resubmit. Pick <strong>1, 2 or 3 edits</strong> next to the switch — there is no unlimited.
</p>

<p>
  A form with a Payment, Signature, or Schedule appointment question cannot support editing after submission; publishing warns you and turns
  the setting off.
</p>

> ℹ️ **Pair with the submission confirmation**
> <p>
>     Include the edit link in the submission confirmation email so they can find it later. Set this up in the{' '}
>     <a href="#respondent-confirmation">Submission confirmation</a> section below.
>   </p>

<h3 id="auto-save-drafts">Auto-save and drafts</h3>

<p>
  There is nothing to turn on. formbase creates a draft when the respondent answers their first question, saves every change in the
  background, and restores the progress if they come back. On submit, the draft becomes the completed response.
</p>

<p>Where the draft lives depends on your plan — not on who the respondent is:</p>

<ul>
  <li>
    <strong>Free</strong> — share-link drafts stay in the respondent's browser. Clearing site data or switching devices loses them, and no
    reminder can bring them back.
  </li>
  <li>
    <strong>Pro and Business</strong> — share-link drafts are saved on the server, so the <a href="#respondent-reminder">reminder email</a>{' '}
    can carry a resume link that restores progress even on another browser.
  </li>
  <li>
    <strong>Requests</strong> — a request's draft is always saved on the server, on every plan. The request link is the resume link.
  </li>
</ul>

<h2 id="email-notifications">Emails</h2>

<p>
  One list of every email the form sends: <strong>New submission alert</strong>, <strong>Request invitation</strong>,{' '}
  <strong>Submission confirmation</strong>, and <strong>Reminders</strong>. Each row shows who receives it and when, with a switch to turn
  it on or off — the invitation is always on. Click a row to unfold its editor in place: a <strong>Write</strong> view with the To, Subject
  and Message fields, and a <strong>Preview</strong> view that renders the email with sample answers. All of them take a custom subject and
  body with <a href="/building-forms/answer-piping">@ variables</a>.
</p>

<h3 id="custom-email-domain">Sender (Pro)</h3>

<p>
  The line under the tab title names the address every email on this form is sent from — <code>noreply@formbase.so</code> until you pick a
  verified domain of your own. One setting covers every email in the list. Domains are managed for the whole workspace — follow{' '}
  <strong>Use your domain</strong> to add and verify one, then any form in the workspace can pick it.
</p>

<h3 id="self-notification">New submission alert</h3>

<p>
  Sends an email to you or your team whenever a response comes in. Leave the subject and body empty and formbase sends its own template.
</p>

<p>
  The default alert to the workspace owner's address works on Free. Adding other recipients, attaching the PDF, or customizing the subject,
  the body, or the "View Submission" button, requires Pro or Business.
</p>

<ul>
  <li>
    <strong>To</strong> — type an address and press Enter, pick a workspace member, or insert a form value so the alert follows the answer
  </li>
  <li>
    <strong>Attach submission PDF</strong> — include a PDF copy of the response
  </li>
  <li>
    <strong>Show 'View Submission' button</strong> — links straight to the response in formbase
  </li>
</ul>

<h3 id="respondent-confirmation">Submission confirmation (Pro)</h3>

<p>
  Sends a confirmation to the respondent right after they submit. Pick the email question that holds their address in the{' '}
  <strong>To</strong> row; leaving it empty is a publish warning and the confirmation is switched off. If the form asks for no address at
  all, the confirmation still goes to a request's recipient — public-link responses get none.
</p>

<ul>
  <li>
    <strong>To</strong> — the email question to send to
  </li>
  <li>
    <strong>Attach submission PDF</strong> — send respondents a PDF copy of their answers
  </li>
</ul>

<h3 id="respondent-reminder">Reminders (Pro)</h3>

<p>
  One switch, one schedule, two kinds of target: a share-link respondent sitting on an unfinished draft, and a request recipient who has not
  completed. They get the same email, and the row shows the schedule at a glance.
</p>

<ul>
  <li>
    <strong>Reminder schedule</strong> — add steps from 12 hours, 1 day, 3 days, 1 week, and 2 weeks. Each step is an idle offset counted
    from the person's last activity, and new activity restarts the schedule. Reminders stop when they finish, or after 8 emails
  </li>
  <li>
    <strong>Public-link drafts → To</strong> — a draft has no address until the respondent types one, so name the email question that holds
    it. Leaving it empty is a publish warning and reminders are switched off. Requests already know their recipient
  </li>
  <li>
    <strong>Wait for</strong> — a draft is only chased once the To field and these fields are filled. Leave it empty to chase any draft that
    has an address
  </li>
</ul>

<h3 id="requests-invitation">Request invitation</h3>

<p>
  Sent once, when a request is created with email delivery. Its recipient is set on each request, so you only author the copy, and there is
  no switch: the row reads <strong>Always on</strong>. Nothing here affects the public link. See{' '}
  <a href="/requests/invitations-and-reminders#invitation">The request invitation</a>.
</p>

<h3 id="template-variables">Template variables</h3>

<p>
  Every email above takes the same @ variables. Type <strong>@</strong> in the subject or body to open the mention menu. See{' '}
  <a href="/building-forms/answer-piping#metadata-variables">Answer piping → Metadata variables</a> for the full reference.
</p>

<p>
  Emails go out in the language the form is shown in. Respondents reading the form in another language get formbase's localized default
  unless you translate your copy — the note under the list links straight to{' '}
  <a href="/building-forms/translating-form-content">Translate emails</a>.
</p>

> ℹ️ **Field reference validation**
> <p>
>     If you delete a form field that an email template still references, formbase warns you in the publish indicator before you publish. The
>     same check covers <a href="/building-forms/translating-form-content#field-reference-checks">translated email templates</a> and
>     integration message templates.
>   </p>

<h2 id="integrations-danger-zone">Integrations and Danger Zone</h2>

<p>The remaining two tabs in the settings panel link out to dedicated features:</p>

<ul>
  <li>
    <strong>Integrations</strong> — grouped by category. Spreadsheets &amp; databases: Google Sheets, Airtable, Notion. Chat &amp;
    notifications: Slack, Discord. Project management: Linear, GitHub Issues. Developer: Webhook. Zapier and Make are listed under
    Automation and marked <em>Coming soon</em>. The tab also holds <strong>Analytics streaming</strong> — whether this form forwards its
    view, engaged, and submit events to a workspace GA4 or Axiom destination, and (Axiom only) whether it streams answers. Cal.com is
    connected on the Schedule appointment question itself. See <a href="/integrations/overview">Integrations overview</a>.
  </li>
  <li>
    <strong>Danger Zone</strong> — three destructive actions: <strong>Delete all submissions</strong>,{' '}
    <strong>Delete analytics data</strong>, and <strong>Delete form</strong>. The first two are permanent and only unlock once the form has
    been published. A deleted form goes to <a href="/workspaces-teams/trash">Trash</a> and can be restored within 30 days.
  </li>
</ul>

<h2 id="next-steps">Next steps</h2>

<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Appearance and theming](/branding-domains/appearance-theming) — Brand your form with colors, fonts, and presets
  - [Language setting](/building-forms/language-setting) — Serve your form in multiple languages
  - [Bot protection (Turnstile)](/building-forms/captcha-bot-protection) — Set up Cloudflare Turnstile for spam prevention
  - [Share links](/sharing-publishing/sharing-embedding) — Create and manage share link URLs
</div>


# Version history

View every published version of your form and revert to any previous snapshot.

## Version history

Every time you publish, formbase saves a full snapshot of your form — content, theme, logo, and cover. Open the publish history to browse past versions and roll back to one.

<h2 id="how-it-works">How it works</h2>

<p>
  Each publish creates a snapshot. On Business, formbase keeps the 50 most recent per form and drops the oldest when a new publish passes
  the cap — but never a snapshot a submission or a live respondent link still points at, so a busy form can hold more than 50.
</p>
<p>
  On Free and Pro, formbase keeps only the newest published version, plus any older version a submission, an open request, or a live
  respondent link still points at. Older versions are removed the next time the form is published.
</p>
<p>
  Anyone who can edit the form can open the history and browse the versions it holds. Reverting requires the <strong>Business</strong>
  plan, and it is the workspace owner's plan that counts: a member on Business cannot revert a form owned by a Free or Pro workspace.
</p>

<h2 id="open-history">Opening publish history</h2>

<p>
  Click the <strong>Publish history</strong> button (clock icon) in the top toolbar — it appears once the form has been published at least
  once. The dialog lists your 20 most recent versions, newest first, each with its date, time, and how long ago it was; the live one carries
  a <strong>Live</strong> badge. Click <strong>Load older versions</strong> at the bottom to fetch the rest.
</p>
<p>
  Selecting a version previews it without changing anything. The preview has three tabs: <strong>Form</strong>, <strong>Emails</strong> (the
  owner notification, respondent confirmation, and reminder copy saved in that version), and <strong>Translations</strong> (the translation
  bundles it published with).
</p>

<h2 id="reverting">Reverting to a previous version</h2>

<p>
  Pick any version other than the live one and click <strong>Revert to this version</strong>. A confirmation dialog offers two ways out:
</p>

<p>A trashed form cannot be reverted — restore it first.</p>

<h2 id="what-is-restored">What gets restored</h2>

<p>A revert restores everything the snapshot froze:</p>

<ul>
  <li>Form content (questions, pages, content blocks, logic rules)</li>
  <li>Cover image and logo</li>
  <li>Light and dark mode themes</li>
  <li>Translation drafts and notification email subject/body drafts</li>
</ul>

<p>Access controls, notification delivery settings and recipients, integrations, and retention rules stay as they are.</p>

> ⚠️ **Reverting replaces your current draft**
> <p>
>     If you have unpublished changes in the editor, reverting overwrites them. Only published versions become snapshots, so publish your
>     current draft first if you might want it back.
>   </p>

<h2 id="use-cases">When to use version history</h2>

<ul>
  <li>
    <strong>Undo a bad publish</strong> — you shipped a version with broken logic or a missing page. Revert and publish the previous one.
    Most of these are caught before they ship: see <a href="/building-forms/publish-checks">Publish checks</a> for the errors and warnings
    formbase raises beside the Publish button.
  </li>
  <li>
    <strong>Seasonal forms</strong> — you run the same event form every quarter. Revert to last quarter's version as a draft and update the
    dates instead of starting from scratch.
  </li>
  <li>
    <strong>A/B testing</strong> — publish version A and collect responses through one share link. When ready, revert to version B, publish
    it, and create a new link. Each link tracks its responses separately in{' '}
    <a href="/submissions-analytics/share-link-analytics">share link analytics</a>. Share links always serve the live form, never a specific
    snapshot.
  </li>
  <li>
    <strong>Audit trail</strong> — check what the form looked like when a batch of responses came in.
  </li>
</ul>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Sharing & Embedding](/sharing-publishing/sharing-embedding) — Understand how share links always serve the live version
  - [Share link analytics](/submissions-analytics/share-link-analytics) — Compare performance across versions and channels
</div>


# Publish checks

What formbase checks before your form goes live, which findings block publishing, and how to clear each one.

## Publish checks

Every time you edit, formbase re-reads your form and lists what would not work once it is live. Errors stop the publish; warnings let it through after you confirm.

<h2 id="where">Where the check lives</h2>

<p>
  The <strong>issue indicator</strong> sits beside the Publish button in the top toolbar. It is a traffic light for the whole form: green
  with a check mark when nothing is wrong, amber with a triangle when there are warnings, red with a cross when there are errors. Click it
  to open the list, grouped under <strong>Errors</strong> and <strong>Warnings</strong>.
</p>
<p>
  Each entry shows the finding and, underneath it, the action that clears it. Clicking the entry takes you there — into the editor and
  scrolled to the block, into the right tab of form settings, or into the Translate tab for that language. The list updates as you type, so
  you never have to press Publish to find out.
</p>

<h2 id="errors-vs-warnings">Errors and warnings</h2>

Review needed</strong> dialog lists every warning with the note “These may not work as expected once your form is live.” Cancel and fix them, or press <strong>Publish</strong> to go ahead.',
      ],
    },
  ]}
/>

<p>
  For the settings warnings, pressing <strong>Publish</strong> in that dialog is the one-click fix: formbase applies exactly what each
  warning says it will do — switching off the password gate, the self-notification, the respondent confirmation, the reminder, or
  edit-after-submit, or clearing the redirect — and then publishes. Content warnings are never auto-fixed; the form publishes as it is.
</p>

<h2 id="content">Content and references</h2>

decision</code> is no longer a choice question carrying the approve, decline, and changes values, so requests finish with no outcome recorded.',
        'Insert a Decision question from the / menu.',
      ],
    },
  ]}
/>

<h2 id="logic">Logic and page jumps</h2>

<h2 id="field-keys">Field keys and hidden fields</h2>

<p>
  Field keys are the names your integrations and requests address — and so are the option, row and column keys under them. See{' '}
  <a href="/requests/field-keys">Field keys</a> for how they are derived and frozen; publish is where the collisions are caught. The publish
  dialog gathers every key this publish removes, renames or duplicates, at either level, under one heading:{' '}
  <strong>Keys changing in this publish</strong>.
</p>

requests.create</code> starts rejecting the key.',
        'Update the automations that use this key.',
      ],
    },
    {
      label: {
        title: '…“Company” now publishes as “company”. Set its field key back to “company_name” to keep them working.',
        description: 'Warning',
      },
      cells: [
        'Same warning, with the successor named: the field whose key you retyped, or the single new field standing where the old one was.',
        'Set the old field key on this field — the warning clears and the key carries on.',
      ],
    },
    {
      label: {
        title:
          'Option key “pro” of “Plan” will no longer exist after this publish. Integrations and requests that use it will stop matching that answer.',
        description: 'Warning',
      },
      cells: [
        'An option, row or column key the live version publishes is dropped by this one — you deleted the option or retyped its key. A workflow branching on <code>answers.plan == "pro"</code> stops matching, and a request prefilling <code>pro</code> is refused. When the option still exists the warning names the key it publishes as now.',
        'Set the old key back on the option in the Keys table, or update the automations that use it.',
      ],
    },
    {
      label: { title: 'Two options of “Plan” use the option key “pro”.', description: 'Error' },
      cells: [
        'Two options of one question — or two rows or two columns of one matrix — were typed onto the same key. Derived keys break their own ties with <code>_2</code>; only keys you set by hand can collide.',
        'Give each option its own key.',
      ],
    },
  ]}
/>

> ℹ️ **Removed keys are your call**
> <p>
>     Dropping a key is a warning, never an error. If you removed the field on purpose, publish anyway and update the automations that read
>     it.
>   </p>

<h2 id="payments-scheduling">Payments and scheduling</h2>

<h2 id="access-email">Access, email, and redirect</h2>

<p>These are the settings warnings, and every one of them is cleared by the dialog's Publish button.</p>

<h2 id="integrations-translations">Integrations and translations</h2>

<p>Form copy and email copy are counted separately, so the same language can appear once for each.</p>

<h2 id="server-refusals">Other reasons a publish is refused</h2>

<p>These are not issue-indicator entries — they appear as a red notification when you press Publish:</p>

<ul>
  <li>The form is in the trash. Restore it first.</li>
  <li>
    An email question has <a href="/building-forms/email-verification">respondent email verification</a> switched on and the workspace
    owner's plan is not <strong>Business</strong>. Upgrade, or switch verification off.
  </li>
</ul>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Field keys](/requests/field-keys) — How keys are derived, frozen, and safely renamed
  - [Version history](/building-forms/version-history) — Roll back a publish you regret
  - [Form settings](/building-forms/form-settings) — Access, notifications, and submission rules
  - [Translating forms](/building-forms/translating-form-content) — Clear the translation warnings
</div>


# Language setting

Set source language, control respondent language selection, and understand locale fallback behavior.

## Language setting

Choose the source language for a form, decide how respondents land in a language, and understand how locale fallback works.

<h2 id="source-language">Source language</h2>
<p>
  Set the source language under <strong>Form language</strong> in <strong>Form settings → General</strong>. This is the language you author
  the form in and the fallback respondents see when no published translation matches.
</p>
<p>
  Source language controls formbase's built-in respondent UI: buttons, validation messages, progress text, default success messages, default
  respondent emails, date/time formatting, and number formatting.
</p>

> ℹ️ **Source language is not translation**
> <p>
>     Changing source language localizes built-in UI. It does not rewrite your question labels, help text, options, content blocks, custom
>     thank-you pages, or custom emails. Use <a href="/building-forms/translating-form-content">Translating form content</a> for custom
>     content.
>   </p>

<h2 id="supported-languages">Supported languages</h2>
<p>
  formbase offers 65 languages, and the built-in respondent UI is translated into every one of them. The list runs from Afrikaans, Amharic,
  Arabic, Bengali, Bulgarian, Burmese, Catalan, Croatian, Czech and Danish through to Ukrainian, Urdu, Uzbek, Vietnamese, Chinese
  (Simplified) and Chinese (Traditional), and it includes the regional variants <code>en-GB</code>, <code>es-MX</code>, <code>fr-CA</code>{' '}
  and <code>pt-BR</code>. The picker in <strong>Form settings → General</strong> is searchable — type a name or a code to find one.
</p>

<h2 id="respondent-language">Respondent language</h2>
<p>
  Respondents see the source language unless the form has published translations. Once at least one translation is published, formbase
  resolves the content language in this order, highest priority first:
</p>

> ℹ️ **The language screen**
> <p>
>     A respondent who arrives with none of the first three — no stored pick, no <code>?lang=</code>, no share-link default — sees{' '}
>     <strong>"Pick your language to start"</strong> before the questions, whatever their browser prefers. That screen is skipped as soon as
>     one of the three is present, which is why a share-link default or a <code>?lang=</code> link is the shortest path for a campaign.
>   </p>

<h2 id="language-links">Localized links</h2>
<p>
  Use <code>?lang=</code> when you want to send people directly to one language. For example, send French respondents a link ending in{' '}
  <code>?lang=fr</code>. If the requested language is not published, formbase falls back to the normal resolution order.
</p>
<p>
  Share-link defaults are useful when one campaign, QR code, embed, or partner link should open in a specific language without forcing that
  language forever. Respondents can still switch if more published languages are available.
</p>

<h2 id="rtl">Right-to-left layout</h2>
<p>
  Respondent forms and generated PDFs lay out right-to-left when the active language is written that way — Arabic, Hebrew, Persian, Urdu,
  and the other RTL scripts in the list. Direction follows the active respondent language, so one form can serve both directions.
</p>

<h2 id="what-language-affects">What language affects</h2>

<h2 id="tips">Language setup tips</h2>
<ul>
  <li>Pick the source language before writing lots of copy; source text becomes the baseline for translation status.</li>
  <li>
    Pick a regional variant when wording or tone differs by market. Four are available: <code>en-GB</code>, <code>es-MX</code>,{' '}
    <code>fr-CA</code>, and <code>pt-BR</code>.
  </li>
  <li>
    Preview localized links before sending campaigns: open the public form with <code>?lang=xx</code>.
  </li>
  <li>Submit one test response per important language when emails or attached PDFs are enabled.</li>
</ul>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Translating form content](/building-forms/translating-form-content) — Translate questions, content blocks, emails, and PDFs
  - [Form settings](/building-forms/form-settings) — Set source language, emails, PDFs, and reminders
  - [Share links](/sharing-publishing/sharing-embedding) — Create links, embeds, and QR codes for localized campaigns
  - [Respondent emails](/submissions-analytics/respondent-notifications) — Send localized confirmations
</div>


# Translating form content

Translate form content, respondent emails, and generated PDFs while keeping published versions in sync.

## Translating form content

Translate respondent-facing content, review source changes, and publish each language without losing sync with the form version respondents see.

<h2 id="how-translations-work">How translations work</h2>
<p>
  formbase separates the <strong>source language</strong> from each <strong>published translation</strong>. Your form content stays authored
  in the source language. Each translated language stores only the translated fragments for that form: question titles, options, content
  blocks, placeholders, email copy, and other respondent-facing strings.
</p>
<p>
  Translation rows are tied to stable block ids and source-text hashes. When you edit the source text later, formbase can tell which
  translations are still current, which are missing, and which need review.
</p>

> ℹ️ **Source language does not auto-translate your custom content**
> <p>
>     Setting the source language localizes formbase's built-in UI. It does not rewrite labels, help text, options, custom thank-you content,
>     or custom emails that you authored. Use the translation editor for those. See{' '}
>     <a href="/building-forms/language-setting">Language setting</a> for source-language and respondent-language behavior.
>   </p>

<h2 id="translation-editor">Translation editor</h2>
<p>
  Open a form and click <strong>Translate</strong> in the toolbar. Pick a language, then translate each row manually or ask AI to draft a
  suggestion. Each row shows the <strong>Original</strong>, the <strong>Translation</strong>, and a status. Two tabs split the work:{' '}
  <strong>Form content</strong> and <strong>Emails</strong>. Changes save as you type.
</p>

<h3 id="add-language">Add a language</h3>
<p>
  Click <strong>Add language</strong> and pick one from the list. Any of the 65 languages formbase supports can be added except the form's
  own source language, and each language can be added once. The four regional variants — <code>en-GB</code>, <code>es-MX</code>,{' '}
  <code>fr-CA</code>, <code>pt-BR</code> — are separate entries, so pick one when wording, formatting, or legal copy differs by market. See{' '}
  <a href="/building-forms/language-setting#supported-languages">supported languages</a>.
</p>

> ℹ️ **Right-to-left languages**
> <p>
>     Arabic, Hebrew, Persian, Urdu and the other right-to-left languages can be added like any other. The respondent form and the generated
>     PDF lay themselves out right-to-left for them.
>   </p>

<h3 id="what-you-can-translate">What you can translate</h3>
<ul>
  <li>Headers, paragraphs, lists, tables, and other text blocks</li>
  <li>Question titles, descriptions, placeholders, scale labels, matrix labels, and choice options</li>
  <li>Image captions and custom thank-you content</li>
  <li>Custom respondent confirmation email subject/body</li>
  <li>Custom abandoned-response reminder email subject/body</li>
</ul>

<h3 id="what-is-not-translated">What is not translated</h3>
<ul>
  <li>Hidden fields and calculated fields as standalone fields</li>
  <li>Uploaded files, signatures, and respondent answers</li>
  <li>Integration payload destinations such as table names, database names, or webhook URLs</li>
  <li>
    The event title and location on a <a href="/integrations/cal-com">Cal.com scheduling</a> question, such as "Cal Video". They come from
    Cal.com, so rename them in the Cal.com event type
  </li>
  <li>Custom source-language email text when no translation exists for that respondent's language</li>
</ul>

<p>
  Field references stay intact across translations. If an email says <code>Hello @Name</code>, the translation should keep the field mention
  and translate only the surrounding text.
</p>

<h2 id="ai-translation">AI translation</h2>
<p>
  Use AI for first drafts, not blind publishing. The translation page offers quick-action chips that change with the state of the language
  you have open, and each row has its own <strong>Suggest</strong> / <strong>Re-suggest</strong> button.
</p>
<ul>
  <li>
    <strong>Translate … missing</strong> — after adding new questions. The chip names the count.
  </li>
  <li>
    <strong>Refresh … outdated</strong> — after rewriting source copy.
  </li>
  <li>
    <strong>Translate everything</strong> — bootstrapping a language from scratch.
  </li>
  <li>
    <strong>Polish translations</strong> — when translations are literal but need native phrasing.
  </li>
  <li>
    <strong>Spot-check for issues</strong> — a review pass over what is already there.
  </li>
  <li>
    <strong>Translate to several languages</strong> — run one language's work across the rest.
  </li>
</ul>
<p>
  AI writes into the row as a suggestion, not a saved translation. Accept or decline per row, or use <strong>Accept all</strong> /{' '}
  <strong>Decline all</strong> in the toast. Keep product names, legal terms, and glossary terms consistent across rows.
</p>

> 💡 **Translate in review-sized batches**
> <p>
>     Translate one language, review the most important pages and email copy, then publish. For high-stakes forms, have a native speaker
>     review legal text, consent language, and payment-related copy before publishing.
>   </p>

<h2 id="statuses">Translation statuses</h2>

<p>
  Above the rows, each language carries a completion badge counting how many rows are <strong>complete</strong>, <strong>missing</strong>,
  and <strong>outdated</strong>.
</p>

<h2 id="keeping-in-sync">How formbase keeps translations in sync</h2>
<p>
  The translation editor builds a live source manifest from your current form and email settings. Each manifest row has a stable translation
  key and a hash of the source fragment. This lets formbase update translation status without guessing from plain text.
</p>

<h3 id="field-reference-checks">Field reference checks</h3>
<p>
  Email translations can include <code>@</code> mentions that pipe respondent answers into the message. If you delete or rename a field that
  a translated email still references, formbase flags the stale reference before you publish.
</p>
<ul>
  <li>An inline warning appears below the affected email translation row.</li>
  <li>The publish indicator flags the stale reference so you can fix it before shipping.</li>
</ul>
<p>
  The same reference checks apply to source email templates and integration message templates in{' '}
  <a href="/building-forms/form-settings#email-notifications">notification settings</a>.
</p>

<h2 id="publishing">Drafts and publishing</h2>
<p>
  Translations are draft-first. Respondents only see a language after you publish it. Publishing an empty language removes it from the
  respondent language picker.
</p>
<p>
  Publish after source copy is stable when possible. If you rewrite source copy after translating, use the outdated status to review only
  rows that changed instead of re-checking every row.
</p>

<h3 id="fallbacks">Missing or blank translations</h3>
<p>
  Missing strings fall back to the source language for that specific row. Respondents never see blank labels because one translation entry
  is empty. This makes it safe to publish incrementally, but mixed-language forms can feel unfinished, so review the completion count before
  shipping.
</p>

<h2 id="emails-and-pdfs">Emails and PDFs</h2>
<p>Translations carry beyond the on-page form.</p>
<ul>
  <li>
    <strong>Respondent confirmation emails</strong> use the translated custom subject/body when the respondent submits in a translated
    language. If no translated custom copy exists, formbase uses the default localized template instead of sending the source-language
    custom copy to a non-source respondent.
  </li>
  <li>
    <strong>Abandoned-response reminder emails</strong> use the respondent's saved language and the translated reminder subject/body when
    available.
  </li>
  <li>
    <strong>Attached submission PDFs</strong> are generated from the same translated form content the respondent saw, including labels,
    options, page content, and RTL layout where applicable.
  </li>
  <li>
    <strong>Owner PDF downloads</strong> are in the respondent's language and use the translation bundle frozen with the submitted version,
    so old downloads do not silently change after you edit and republish translations.
  </li>
</ul>

> ℹ️ **Submission language is preserved**
> <p>
>     formbase stores the respondent language with the submission. Later emails, PDF generation, and re-renders can use that language even if
>     you publish new translations later.
>   </p>

<h2 id="tips">Practical translation tips</h2>
<ul>
  <li>Keep source copy short before translating; concise labels reduce localization drift.</li>
  <li>Translate button-like choices as actions, not word-for-word nouns, when the target language needs it.</li>
  <li>
    Check variables and <code>@</code> mentions after AI translation. They should stay present and in the right sentence position.
  </li>
  <li>
    Preview the form with <code>?lang=xx</code> before sending localized links.
  </li>
  <li>Submit one test response per important language when emails or PDFs are enabled.</li>
  <li>Republish after accepting AI suggestions; accepted suggestions are still drafts until publish.</li>
</ul>

> ℹ️ **Free on every plan**
> <p>
>     Every plan includes the translation editor. AI-assisted suggestions use AI credits — see{' '}
>     <a href="/ai/ai-credits-usage">AI credits & usage</a>.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Language setting](/building-forms/language-setting) — Source language, browser matching, and ?lang= links
  - [Share links](/sharing-publishing/sharing-embedding) — Pin localized links with ?lang= or share-link defaults
  - [Respondent emails](/submissions-analytics/respondent-notifications) — Send localized confirmation emails
  - [Form settings](/building-forms/form-settings) — Set source language, emails, PDFs, and reminders
  - [AI credits & usage](/ai/ai-credits-usage) — What AI-assisted translation costs
</div>


# Payment collection

Accept one-time payments from respondents via Stripe.

## Payment collection

Collect one-time payments inside your form. Connect Stripe, insert a Payment field, set a price, and respondents pay through Stripe Checkout.

<h2 id="connect-stripe">Connect Stripe</h2>

<p>
  A Stripe connection belongs to one form: connect it from the form you want to collect money on, and repeat for the next form. Card details
  never pass through formbase.
</p>

<p>The badge on the Payment block tells you where the connection stands:</p>

> ⚠️ **Publishing is blocked until payments are complete**
> <p>
>     A form with a Payment field will not publish until every payment block has a supported currency and an amount at or above that
>     currency's minimum, and the form has a connected Stripe account with charges enabled.
>   </p>

<h2 id="how-it-works">How the Stripe connection works</h2>

<p>
  formbase uses{' '}
  <a href="https://docs.stripe.com/connect" target="_blank" rel="noopener">
    Stripe Connect
  </a>{' '}
  to link your Stripe account to your form. The charge is created on your account, not ours. In practice:
</p>

<p>
  formbase can see your Stripe account status (whether charges are enabled and onboarding is done) and basic transaction info (payment
  status, amount, receipt link). It cannot read your Stripe balance, your customer list, or anything outside payments made through your
  forms.
</p>

<h2 id="set-the-price">Set the price</h2>

<p>
  Pick a currency and type an amount on the Payment block. The amount must be at least Stripe's minimum charge for that currency — $0.50 for
  USD and EUR, £0.30 for GBP, ¥50 for JPY, 3 kr for SEK and NOK. Stripe caps a single charge just under 1,000,000, so keep amounts below
  that.
</p>

<p>Supported currencies: USD, EUR, GBP, JPY, CAD, AUD, CHF, SEK, NOK, DKK, NZD, SGD, HKD, AED, TRY.</p>

<p>The Payment field takes one-time charges only. Recurring subscriptions are not part of it.</p>

<h2 id="dynamic-pricing">Dynamic pricing</h2>

<p>
  You can vary the amount from the respondent's answers. Add a <strong>Set payment amount</strong> action to a logic rule above your Payment
  field and point it at the block.
</p>

<p>
  Each action picks how it changes the price — <strong>set to</strong>, <strong>increase by</strong>, or <strong>decrease by</strong>:
</p>
<ul>
  <li>
    <strong>Set to</strong> — replace the running price with an absolute amount.
  </li>
  <li>
    <strong>Increase by</strong> / <strong>decrease by</strong> — adjust the running price. Stack several for add-ons, surcharges, or
    discounts; each builds on the amount so far, and the result never drops below zero.
  </li>
</ul>
<p>The value itself can be:</p>
<ul>
  <li>
    <strong>A fixed number</strong> — a literal amount.
  </li>
  <li>
    <strong>A field reference</strong> — pick a <a href="/building-forms/calculated-fields">calculated field</a> so the amount follows the
    answers.
  </li>
</ul>

<p>
  Rules run in the order they appear in the form, starting from the price on the block. A later <strong>set to</strong> overwrites whatever
  earlier rules produced, so put your most specific rule last.
</p>

<p>
  Example: a ticket form has a Dropdown ("Standard" or "VIP") and a Number field for quantity. A calculated field multiplies the base price
  by quantity. A logic rule targets the Payment field: if the Dropdown is "VIP", <strong>set</strong> the payment amount to the calculated
  field.
</p>

<p>
  Add-on example: a $50 price on the block, a "Rush delivery" switch that <strong>increases</strong> the amount by 15, and a "Member" switch
  that <strong>decreases</strong> it by 5. With both on, the respondent pays $60.
</p>

> 💡 **The block price is the fallback**
> <p>
>     The price you set on the Payment block is what respondents see before any rule fires. Make it your most common price so the form never
>     shows a placeholder amount.
>   </p>

<h2 id="respondent-experience">Respondent experience</h2>

<p>
  The respondent clicks <strong>Complete Payment</strong> on the card and Stripe Checkout opens, hosted by Stripe. After paying they return
  to the form to finish and submit. The amount charged is recomputed on the server from your own rules — a tampered client cannot change the
  price.
</p>

<p>If the payment fails (expired card, insufficient funds), the card shows the error and a Retry payment button that reopens Checkout.</p>

<h2 id="after-payment">After payment</h2>

<p>
  Once a respondent has paid, formbase locks the questions that fed the amount — the conditions and values of any rule that set it, the
  fields behind a referenced calculated field, and anything that controls whether the Payment block is visible. Locked questions show their
  answers read-only with "Locked because this answer was used for a completed payment." The rest of the form stays editable.
</p>

> ⚠️ **A payment makes the submission final**
> <p>
>     Any form version containing a Payment, Signature, or Schedule appointment field turns off{' '}
>     <a href="/submissions-analytics/edit-after-submit">edit after submit</a> for submissions made against it.
>   </p>

<h2 id="viewing-payments">Viewing payments</h2>

<p>Payments show up with the response in the Submissions tab:</p>

<p>
  The payment also goes out with the submission: notification emails, the PDF, exports, webhooks, Zapier, n8n, Make, request callbacks and
  the API all carry its amount, currency, status and receipt link under the Payment question. See{' '}
  <a href="/developers/webhooks-reference#bookings-and-payments">Bookings and payments</a> for the shape.
</p>

<h2 id="refunds-and-disputes">Refunds and disputes</h2>

<p>
  Payments land in your Stripe account, so you handle refunds and disputes yourself. formbase reserves the right to issue refunds on your
  behalf in cases of abuse or policy violations. When Stripe reports a refund or a dispute, the payment on the submission changes to{' '}
  <strong>Refunded</strong>, <strong>Partially refunded</strong> or <strong>Disputed</strong>; integrations are not sent again.
</p>

<p>Stripe's own documentation covers both in depth:</p>
<ul>
  <li>
    <a href="https://docs.stripe.com/refunds" target="_blank" rel="noopener">
      Refunds
    </a>
  </li>
  <li>
    <a href="https://docs.stripe.com/disputes" target="_blank" rel="noopener">
      Disputes
    </a>
  </li>
</ul>

<h2 id="logic-conditions">Payment logic conditions</h2>

<p>
  A Payment field can also be read by logic: <strong>amount equals</strong>, <strong>amount is greater than</strong>,{' '}
  <strong>amount is less than</strong>, <strong>is completed</strong>, and <strong>is not completed</strong>. See{' '}
  <a href="/building-forms/conditional-logic">Conditional logic</a> for the full operator reference.
</p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Bot protection](/building-forms/captcha-bot-protection) — Block fraudulent submissions on payment forms
  - [Submission inbox](/submissions-analytics/submission-inbox) — View payment details alongside responses
  - [Share links](/sharing-publishing/sharing-embedding) — Create and manage share link URLs
  - [Calculated fields](/building-forms/calculated-fields) — Compute dynamic prices from respondent answers
</div>


# Bot protection (Turnstile)

Defend forms against bot submissions with Cloudflare Turnstile.

## Bot protection (Turnstile)

Cloudflare Turnstile checks share-link submissions for you. It is always on for free forms and yours to control on Pro and Business. Most respondents never see it.

<h2 id="how-it-works">How it works</h2>
<p>
  When a respondent presses submit, formbase asks Turnstile for a token and verifies it on the server before the submission is written. The
  check normally runs silently; if it cannot resolve on its own, a small dialog appears and asks the respondent to confirm. There are no
  image puzzles. The dialog follows your form's theme and language.
</p>
<p>
  The check runs on the submit, not on page load, so it never delays the form opening. A submission that fails verification is rejected and
  the respondent is asked to try again.
</p>

> ℹ️ **Requests are not gated**
> <p>
>     Bot protection applies to <a href="/sharing-publishing/sharing-embedding">share links</a> only. A{' '}
>     <a href="/requests/overview">request</a> link already names one recipient and authorises one completion, so it skips Turnstile — as it
>     skips the sign-in requirement and response limits.
>   </p>

<h2 id="formbase-hosted">formbase-hosted forms</h2>
<p>
  For forms shared on <code>form.formbase.so</code> links, bot protection is built in — no setup, no keys, nothing to configure. formbase
  manages Turnstile for you.
</p>
<p>
  On the <strong>Free</strong> plan (and for guest forms), bot protection is <strong>always on and can't be turned off</strong>. The{' '}
  <strong>Bot protection (Turnstile)</strong> switch under <strong>Form settings → Access</strong> stays locked on.
</p>
<p>
  On a <strong>Pro or Business plan</strong>, the switch is yours. Turn bot protection on or off per form from{' '}
  <strong>Form settings → Access → Bot protection (Turnstile)</strong>.
</p>
<p>
  If your paid plan lapses, your own setting keeps applying for the first 30 days. From day 30 bot protection locks back on — the Free-plan
  default. See <a href="/subscription-billing/upgrading-downgrading#grace-period">upgrading &amp; downgrading</a>.
</p>

<h2 id="custom-domains">Custom domain forms (BYOK)</h2>
<p>
  Custom domains require a <strong>Pro or Business plan</strong>. If you share forms on a custom domain (for example{' '}
  <code>fill.yourdomain.com</code>), you bring your own Turnstile keys (BYOK): Cloudflare ties a Turnstile widget to specific hostnames, and
  formbase's own key only covers formbase.so.
</p>

dash.cloudflare.com</a> if you don\'t have one.',
    },
    {
      title: 'Create a Turnstile widget',
      description:
        'In <a href="https://dash.cloudflare.com/?to=/:account/turnstile" target="_blank" rel="noopener noreferrer">Turnstile</a>, add your custom domain hostnames to the allowed list. One widget with hostname validation disabled covers every domain in the workspace.',
    },
    {
      title: 'Copy your keys',
      description: 'Copy the <strong>Site key</strong> and <strong>Secret key</strong>.',
    },
    {
      title: 'Paste the keys in formbase',
      description:
        'Open <strong>Domains</strong> from the sidebar, find the <strong>Bot Protection</strong> card, paste both keys, and save. The keys are per workspace, not per form.',
    },
    {
      title: 'Turn it on per form',
      description: 'Switch bot protection on for individual forms in <strong>Form settings → Access</strong>.',
    },
  ]}
/>

<p>
  Until the keys are saved, the switch on a custom-domain form is disabled and shows as off, because no challenge can run there. What
  happens to submissions on that branded URL depends on the plan:
</p>

> ℹ️ **No custom domains? Skip this.**
> <p>If all your forms use the default formbase share links, you do not need Turnstile keys. Bot protection works automatically.</p>

<h2 id="submission-limits">Submission limits</h2>
<p>
  Turnstile does not change your monthly allowance — 1,000 submissions a month on Free, 50,000 on Pro and Business. See{' '}
  <a href="/subscription-billing/limits-quotas">limits &amp; quotas</a>.
</p>

<h2 id="layered-defense">Layered defense strategies</h2>
<p>
  Turnstile is one layer. Combine it with the access controls in <a href="/building-forms/form-settings#access">Form settings → Access</a>:
</p>

> ⚠️ **No protection is perfect**
> <p>
>     Sophisticated bots can bypass any CAPTCHA. For high-value forms, layer defenses: Turnstile, an authentication gate,{' '}
>     <a href="/building-forms/email-verification">email verification</a>, and a look at the submissions before you act on them.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Submission inbox](/submissions-analytics/submission-inbox) — Review responses after bot protection is in place
  - [Share links](/sharing-publishing/sharing-embedding) — Create and manage share link URLs
  - [Custom domains](/branding-domains/custom-domains) — Set up branded form URLs
  - [Plans & pricing](/subscription-billing/plans-pricing) — Compare Free, Pro, and Business
</div>

