# formbase Docs — Public Link

# Choosing a channel

A published form reaches people two ways — a public link for anyone, or a request for one named person. How to pick.

## Choosing a channel

One form, two ways out: a public link anyone can open, or a request addressed to one person. You can use both, and the form itself never changes.

<h2 id="two-channels">The two channels</h2>

<p>
  A <strong>channel</strong> is one of the two ways a published form reaches people. You choose it in the <strong>Share</strong> sheet:
</p>

<ul>
  <li>
    <strong>Public link</strong> — anyone with the URL can answer, through a share link, an embed, or a QR code. You do not know in advance
    who will respond, and one link collects many submissions.
  </li>
  <li>
    <strong>Requests</strong> — one named person, one <a href="/requests/overview">request</a> each, created by a caller. You know who you
    are asking before you ask, and you can track whether they answered.
  </li>
</ul>

<p>
  Nothing about the form changes between the two. The same questions, logic, theme and settings serve both. A form can use both at once, and
  a form's channel is never a setting you declare — it is simply observed from the share links and requests that exist.
</p>

<h2 id="which-one">Which one to use</h2>

<h2 id="what-requests-add">What requests add</h2>

<p>A request is addressed, so it can carry things a public link cannot:</p>

<ul>
  <li>
    <strong>Answers you already know</strong> — the caller pre-fills visible questions server side, and can lock any of them so the
    recipient reads but cannot change them. See{' '}
    <a href="/building-forms/hidden-fields#requests-prefill-directly">Requests don't need a hidden field to pre-fill</a>.
  </li>
  <li>
    <strong>A status you can track</strong> — pending, completed, expired or canceled, per person, on the Requests page.
  </li>
  <li>
    <strong>Delivery and chasing</strong> — an optional invitation email and reminders, or your own delivery if you'd rather send the link
    yourself.
  </li>
  <li>
    <strong>A hand-back</strong> — a callback fires when the request reaches a terminal state, so the workflow that asked can resume.
  </li>
</ul>

<h2 id="what-public-links-add">What public links add</h2>

<ul>
  <li>
    <strong>Reach without a list</strong> — one URL serves everyone, so you do not need an address for each respondent.
  </li>
  <li>
    <strong>Several links per form</strong> — each with its own default language, expiry, response limit and tracking, so you can tell
    campaigns apart. See <a href="/submissions-analytics/share-link-analytics">share link analytics</a>.
  </li>
  <li>
    <strong>URL parameters</strong> — <code>?utm_source=…</code> style values seed the form's
    <a href="/building-forms/hidden-fields">hidden fields</a> and nothing else. This is the only way to pass data into a public link, and it
    is why hidden fields exist.
  </li>
  <li>
    <strong>Embeds and QR codes</strong> — see <a href="/sharing-publishing/embedding-popups">embedding &amp; popups</a>.
  </li>
</ul>

> ⚠️ **A request link ignores URL parameters**
> <p>
>     Request links do not read <code>?param=</code> values at all. The recipient holds that link, so honouring parameters would let them
>     rewrite what the caller set. On a request, the caller's context is the only source.
>   </p>

<h2 id="allowance">Both channels spend the same allowance</h2>

<p>
  Your workspace has one monthly allowance shared across both channels. A submission collected through a share link spends one unit, and{' '}
  <strong>creating a request spends one unit</strong> — whether or not the recipient ever answers. The submission a request collects is
  already paid for by the request and is not counted again. See{' '}
  <a href="/subscription-billing/limits-quotas#monthly-allowance">limits &amp; quotas</a>.
</p>

<h2 id="telling-them-apart">Telling them apart afterwards</h2>

<p>
  The submissions table has a <strong>Source</strong> column showing <strong>Public link</strong> or <strong>Request</strong> for each row,
  and you can filter by it. Analytics covers both channels together with a channel filter rather than separate pages, and adds a request
  funnel when requests exist. See <a href="/submissions-analytics/analytics-insights">analytics &amp; insights</a>.
</p>

> ℹ️ **Step by step in Zapier and n8n**
> <p>
>     <a href="/guides/zapier/public-link-submissions">Send public link submissions to another app</a> sends each submission to email, Sheets,
>     Slack or a CRM through Zapier. <a href="/guides/zapier/send-a-request">Send a request from a Zap</a> does the same for requests. The
>     <a href="/guides/overview#n8n">n8n guides</a> do both in a self-hosted n8n.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Sharing & embedding](/sharing-publishing/sharing-embedding) — Set up the public link channel
  - [Requests overview](/requests/overview) — Ask one named person
  - [Hidden fields](/building-forms/hidden-fields) — Carry data the respondent never sees
  - [Limits & quotas](/subscription-billing/limits-quotas) — What each channel spends
</div>


# Share links

Create, manage, and track share links for your form.

## Share links

Every published form gets a unique link you can share directly. Create multiple links per form — each with its own name, expiration, response limit, and analytics — so you can track which channel drives the most completions.

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

<p>
  A share link is how respondents get to your form. Each link has its own 8-character code and its own settings, so you can create separate
  links for different campaigns, channels, or audiences.
</p>

<h3 id="create-link">Creating a share link</h3>

> ℹ️ **Two channels, one sheet**
> <p>
>     The Share panel has two tabs. <strong>Public link</strong> — this page — is one link anyone can open. <strong>Requests</strong> sends
>     the form to one named recipient who submits once. See <a href="/requests/creating-requests">creating requests</a>.
>   </p>

<h3 id="link-settings">Link settings</h3>

<p>
  <strong>Link name</strong> and <strong>Custom URL</strong> sit in the always-open <strong>Link</strong> section. The rest live in three
  collapsible sections below it: <strong>Appearance</strong>, <strong>Limits</strong>, and <strong>Query parameters</strong>.
</p>

<h3 id="query-parameters">Query parameter defaults</h3>

<p>
  The <strong>Query parameters</strong> section bakes default values into the link URL. formbase always shows the three UTM fields it reads
  for analytics (utm_source, utm_medium, utm_campaign), plus a row for every <a href="/building-forms/hidden-fields">hidden field</a> on the
  form. Fill in a value, leave the rest empty, and formbase appends it to the copied link and the QR code automatically. Embed and popup
  snippets don't include these defaults, and a parameter named <code>lang</code> is ignored because the default language uses it.
</p>

<p>This saves you from editing the URL by hand. Set utm_medium to "email" on your newsletter link once, and every copy carries it.</p>

<p>
  A hidden-field value set here also fills any visible field that uses that hidden field as its{' '}
  <a href="/building-forms/field-configuration#default-values">default value</a>, so one link can pre-fill answers for a whole audience.
</p>

> ℹ️ **What gets saved where**
> <p>
>     Default parameters are added to the link URL, not to each response. The three UTM fields feed the{' '}
>     <a href="/submissions-analytics/analytics-insights#traffic-source-classification">Traffic Sources</a> chart in Analytics. To save a
>     value on every submission, add a <a href="/building-forms/hidden-fields">hidden field</a> with the same name (for example utm_source) —
>     it then shows in both the submissions table and analytics.
>   </p>

<h3 id="revoke-link">Revoking a link</h3>

<p>
  Click <strong>Revoke</strong> in the Share panel footer. Anyone who opens the link then sees "Link revoked", and the link stops taking new
  responses. Its settings can't be changed while it's revoked. Changed your mind? Click <strong>Re-activate</strong> in the same footer.
  Revoking keeps the responses the link already collected, and it doesn't affect the other links for the same form.
</p>

<p>
  Moving a form to <a href="/workspaces-teams/trash">Trash</a> revokes all of its links. Restoring the form leaves them revoked until you
  click <strong>Re-activate</strong> on each one.
</p>

<h3 id="delete-link">Deleting a link</h3>

<p>
  Delete a link from the <strong>⋯</strong> menu in the Share panel footer. If the link has no submitted responses yet (unfinished drafts
  don't count), it's removed for good.
</p>

<p>
  If the link already has responses, formbase revokes it instead of deleting it. A prompt explains this before anything happens. Revoking
  keeps the existing data — your analytics and per-channel breakdown stay intact — but stops the link from taking new responses. This way
  you never lose responses or the channel they came from just by clearing out an old link.
</p>

<h3 id="multiple-links">Why multiple links?</h3>

<p>
  Creating a separate link per channel lets you compare performance in the Analytics tab. Filter by share link to see views, submissions,
  and bounce rate for each channel independently. Common patterns:
</p>

<ul>
  <li>One link for email, another for social media, a third for your website embed</li>
  <li>A time-limited link for a promotion with a max response cap</li>
  <li>A link defaulted to French for your Quebec audience, another in English for the rest</li>
</ul>

<h2 id="utm-parameters">UTM parameters for campaign tracking</h2>

<p>
  Append UTM parameters to any share link URL to tag traffic for the Analytics tab's Traffic Sources chart. formbase reads three standard
  parameters:
</p>

```
https://forms.yoursite.com/feedback?utm_source=linkedin&utm_medium=social&utm_campaign=product-launch
```

<p>
  Source and medium are matched case-insensitively; campaign names keep their case. Without UTM parameters, formbase falls back to the
  browser referrer to classify traffic — but some platforms strip referrers, so UTM parameters give you reliable attribution. See{' '}
  <a href="/submissions-analytics/analytics-insights#traffic-source-classification">How traffic sources are classified</a> for the full
  rules.
</p>

<p>
  To skip editing the URL by hand, set these as defaults in the link's <a href="#query-parameters">Query parameters</a> section.
</p>

<h2 id="qr-codes">QR codes</h2>

<p>
  Every share link can generate a QR code. It encodes exactly the URL the <strong>Copy link</strong> button gives you, including any query
  parameter defaults and the language pin, so expiration, response limit, and analytics all apply the same way.
</p>

<p>
  Print it on a sign, a slide, packaging, or a receipt. Create one share link per placement (for example <code>qr-event-london</code>,{' '}
  <code>qr-product-package</code>) so the Analytics tab shows responses per placement.
</p>

> 💡 **Test before printing**
> <p>
>     Always scan the printed QR with the camera you expect respondents to use. Some printers crush dark areas — test at the intended print
>     size before distributing.
>   </p>

<h2 id="social-preview">Social preview metadata</h2>

<p>
  When a share link is posted on social media or messaging apps, formbase generates an Open Graph preview with the form title and
  description. You can customize the preview title, description, image, and favicon per form under{' '}
  <strong>Appearance → Share link preview</strong> in the Share panel.
</p>
<p>
  If you use a <a href="/branding-domains/custom-domains">custom domain</a>, you can also set domain-level defaults that apply to all forms
  on that domain. A "Hide from search engines" toggle adds a noindex tag, at the domain level or per form (the form setting wins). On the
  default formbase.so domain, search engine indexing is always disabled, so the toggle only takes effect on custom domains.
</p>

> 💡 **Test your link before distributing**
> <p>
>     Open the share link in an incognito window to experience exactly what respondents will see — including authentication gates, password
>     prompts, and the default language setting.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Embedding & popups](/sharing-publishing/embedding-popups) — Embed or show your form as a popup
  - [Custom domains](/branding-domains/custom-domains) — Serve forms from your own domain
  - [Share link analytics](/submissions-analytics/share-link-analytics) — Compare performance per link
  - [Response limits](/sharing-publishing/response-limits) — Cap how many responses a link accepts
</div>


# Embedding & popups

Embed your form on a website or show it as a popup overlay.

## Embedding & popups

Put your form inside any webpage as an inline embed or a popup overlay. You configure it in the Share panel and paste one snippet into your site.

<p>
  Both snippets are built from the share link you have selected, so they carry that link's expiration, response limit, custom domain, and
  analytics. Create the link first — see <a href="/sharing-publishing/sharing-embedding">share links</a>. Query parameter defaults set on
  the link are the one exception: they are not added to embed or popup snippets.
</p>

<h2 id="embed">Inline embed</h2>

<p>
  An inline embed places your form directly inside a page using an iframe. Open the <strong>Share</strong> panel, click the{' '}
  <strong>Embed</strong> card, and copy the generated snippet into your site's HTML.
</p>

<h3 id="embed-layout">Layout</h3>

<h3 id="embed-display">Display</h3>

<p>
  Click <strong>View code</strong> to see the generated HTML. Use <strong>Copy embed code</strong> to copy it, then paste it into your site.{' '}
  <strong>Reset to defaults</strong> restores every setting.
</p>

<h3 id="embed-snippet">What the snippet contains</h3>

<p>
  With auto-resize off, the snippet is a single iframe pointing at your share link with <code>?embed=1</code> appended. The display options
  add their own parameters: <code>hideCover=1</code>, <code>hideLogo=1</code>, and <code>theme=dark</code> or <code>theme=light</code>.
</p>

```html
<iframe src="https://form.formbase.so/Abc12XYz?embed=1" width="100%" height="600" style="border: none;"></iframe>
```

<p>
  With auto-resize on, the iframe is wrapped in a <code>div</code> and followed by a script. The embedded form posts a{' '}
  <code>formbase:resize</code> message on every height change; the script checks the message origin and the sending frame, then sets the
  iframe height. Nothing else on your page is touched, and no external script is loaded.
</p>

<h2 id="popup">Popup</h2>

<p>
  A popup shows your form as an overlay on top of your page. Open the <strong>Share</strong> panel, click the <strong>Popup</strong> card,
  set up your options, and paste the generated code snippet into your site.
</p>

<h3 id="popup-trigger">Trigger</h3>

<p>Choose how the popup opens:</p>

<h3 id="popup-layout">Layout</h3>

<h3 id="popup-display">Display</h3>

<h3 id="popup-styling">Popup styling</h3>

<h3 id="popup-behavior">Behavior</h3>

<p>
  Click <strong>View code</strong> to see the generated snippet. Use <strong>Copy popup code</strong> to copy it, then paste it into your
  site. <strong>Reset to defaults</strong> restores every setting.
</p>

<h3 id="popup-snippet">What the snippet contains</h3>

<p>
  The popup snippet is one self-contained <code>{'<script>'}</code> block. It builds the overlay, the close button, and the iframe in plain
  JavaScript when the popup opens — nothing is added to your page before that, no stylesheet is injected, and no external script is loaded.
  The comment under the script tells you the function name to call:
</p>

```html
<!-- Call window.openFormbasePopup_Abc12XYz() to open the form popup -->
```

<p>
  A hyphen in a custom slug becomes an underscore in the function name, because a hyphen isn't valid in a JavaScript identifier. Copy the
  name from the comment rather than deriving it yourself.
</p>

<h2 id="color-scheme">Color scheme</h2>

<p>Embeds and popups share the same three options, and both override the form's own theme inside the frame:</p>

<ul>
  <li>
    <strong>Let user specify</strong> — the default. The form follows the visitor's operating system or browser dark/light preference.
  </li>
  <li>
    <strong>Dark mode</strong> — forces dark mode regardless of the visitor's preference.
  </li>
  <li>
    <strong>Light mode</strong> — forces light mode regardless of the visitor's preference.
  </li>
</ul>

> 💡 **Match your site's theme**
> <p>
>     If your website is always dark, set the embed or popup to Dark mode so the form blends in. If your site respects the visitor's
>     preference, choose Let user specify. To theme the form itself, see{' '}
>     <a href="/branding-domains/appearance-theming">appearance &amp; theming</a>.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Share links](/sharing-publishing/sharing-embedding) — Create and manage share link URLs
  - [QR codes](/sharing-publishing/sharing-embedding#qr-codes) — Generate and place scannable codes
  - [Custom domains](/branding-domains/custom-domains) — Serve forms from your own domain
  - [Appearance & theming](/branding-domains/appearance-theming) — Theme, cover, logo, dark mode
</div>


# Authentication gate

Require respondents to sign in or enter a password before submitting.

## Authentication gate

Control who can access your form. Require sign-in to tie responses to verified identities, or add a password to limit access to people who have the code.

> ℹ️ **Public links only**
> <p>
>     Both gates apply to the public share link. A <a href="/requests/creating-requests">request</a> is addressed to one recipient and opens
>     from a signed link, so it skips them.
>   </p>

<h2 id="require-authentication">Require authentication</h2>

In **Form settings → Access**, toggle **Require authentication**. Respondents must sign in before they can view or fill out the form.

<h3 id="whats-enforced">What's enforced</h3>

- The respondent must sign in — the only two methods are Google and an email magic link (they enter their email and receive a sign-in link)
- Their identity is recorded with the submission
- Drafts are tied to the account, not a browser cookie — so they can resume from any device
- Without authentication, duplicate-submission blocking is per-browser only. With authentication, it's per-account across all devices

<h3 id="internal-forms">Internal forms</h3>

With authentication enabled, anyone who opens the form must sign in first. Their email address is recorded with each submission, so you can verify respondents belong to your company domain — no single sign-on setup needed. Share the link with your team and you're done.

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

- **Internal forms** — share the link internally and require sign-in so every response has a verified company email attached
- **Member-only flows** — only existing customers should respond
- **Anti-fraud** — financial or high-value forms where anonymous submissions are too risky

<h3 id="when-not-to-use-it">When NOT to use it</h3>

Public-facing forms like newsletter signups, contact forms, and lead-gen pages tend to lose conversions when sign-in is required. For those, [CAPTCHA](/building-forms/captcha-bot-protection) is usually a better fit.

> ℹ️ **One response per user**
> <p>
>     When authentication is enabled and <strong>Allow another response</strong> (in Form settings → Submissions) is off — the default — each
>     signed-in user can only submit once. Further attempts from the same account are blocked on every device. To cap the total number of
>     respondents as well, add a <a href="/sharing-publishing/response-limits">response limit</a> to the share link.
>   </p>

<p>
  Looking to verify an email <em>answer</em> instead of who opens the form? The gate verifies the person signing in.{' '}
  <a href="/building-forms/email-verification">Respondent email verification</a> confirms the address typed into an Email question with a
  one-time code — no sign-in needed.
</p>

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

Turn on **Password protect** in **Form settings → Access** and type a password. The form stays publicly reachable by URL, but respondents see "Password required" until they enter it.

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

- The password is checked on the server against a salted hash. The form's questions are never sent to the browser before it matches
- A wrong entry shows "Incorrect password" and nothing else is revealed
- Share the password through whichever channel you trust: email, internal docs, a payment receipt
- Anyone with the password can submit

> ⚠️ **Enable the toggle and set a password together**
> <p>
>     If you publish with the toggle on but no password saved, formbase warns you and turns the gate off rather than locking everyone out.
>     Check the publish dialog if you expected a gate and respondents get straight in.
>   </p>

<h3 id="rotating-the-password">Rotating the password</h3>

You can change the password at any time. Respondents who are already filling the form keep their session, because their access key was
minted before the change. New visitors need the updated password.

> ℹ️ **Pair with response limits**
> <p>
>     For paid content, combine the password with a response limit on the share link so a single password isn't shared and reused infinitely.
>     You can also set the limit to 1 to make a link single-use.
>   </p>

<h2 id="next-steps">Next steps</h2>
<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [CAPTCHA / bot protection](/building-forms/captcha-bot-protection) — Block automated submissions on public forms
  - [Scheduled close](/sharing-publishing/scheduled-close) — Auto-close forms immediately or on a set date
  - [Response limits](/sharing-publishing/response-limits) — Cap submissions per share link
</div>


# Scheduled close

Auto-close a form immediately or on a scheduled date.

## Scheduled close

Close your form immediately or schedule it to stop accepting submissions on a specific date.

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

In **Form settings → Submissions**, toggle **Close submissions now or at a scheduled date**.

- **Close immediately** — turn the switch on and leave the date empty. The form stops accepting submissions right away and stays closed until you re-open it.
- **Schedule a close** — pick a date in the **Close at a specific date** picker that appears below the toggle. The form stays open until then, and closes on its own.

Clearing the date while the toggle is on closes the form immediately. While a close is scheduled, the form's status badge in the toolbar reads "Unpublishes" followed by that date.

<h2 id="what-closing-does">What closing does</h2>

Closing is a submission gate, not an unpublish. The form stays published and its share links stay valid.

- New visitors see **"This form is closed"** — "This form is not accepting new responses."
- Respondents who already started are blocked at submit too. A close is a deadline, so a tab left open past it can't slip a response through.
- Abandoned-response reminder emails stop going out for that form.

<h2 id="re-opening">Re-opening</h2>

Turn the close switch off in **Form settings → Submissions**. The scheduled date is cleared with it — set a new one if you want the form to close again automatically.

<h2 id="use-cases">Use cases</h2>

- **Application windows** — close when the deadline passes
- **Limited-time campaigns** — auto-close at the end of a promotion
- **RSVPs** — close 24h before the event

> 💡 **Combine with share link limits**
> <p>
>     Want "first 100 respondents, and only until Friday"? Turn on <strong>Limit response slots</strong> on the share link as well — see{' '}
>     <a href="/sharing-publishing/response-limits">response limits</a>. Whichever hits first closes the form to new respondents.
>   </p>

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

<div class="not-prose grid gap-3 sm:grid-cols-2">
  - [Response limits](/sharing-publishing/response-limits) — Cap submissions per share link
  - [Redirect after submit](/building-forms/redirect-after-submit) — Send respondents to a custom URL on completion
</div>


# Response limits

Cap submissions per share link to control how many people can respond.

## Response limits

Each share link can have its own response cap. Once the limit is reached, new visitors see a closed message — other links stay open.

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

Response limits are set **per share link**, not per form. This gives you fine-grained control over each distribution channel.

Open **Share**, pick a link, expand **Limits**, and toggle **Limit response slots**. Enter the maximum number of slots — at least 1. Once every slot is claimed, the link stops accepting new responses.

<h3 id="slots">What a slot is</h3>

A slot is claimed when a respondent **starts** the form, not when they submit. That is what stops 200 people opening a 30-seat registration at once and 170 of them losing their answers at the end.

An unfinished slot is not lost forever. A claim lasts **one hour**, and a cleanup job running every 15 minutes hands abandoned slots back to the pool, so a visitor who opens the form and walks away frees their seat within roughly an hour and a quarter. Submitted responses hold their slot permanently.

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

- **Limited-spot events** — cap a workshop registration link at 30 seats
- **A/B testing** — give each link variant 100 responses, then compare
- **Staggered rollout** — set a low cap on the first link, increase as you scale

<h2 id="multiple-links">Multiple links, different caps</h2>

Since limits are per-link, you can create multiple links for the same form with different caps. For example, you could give your newsletter link a cap of 50 and your social media link a cap of 200.

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

When a link hits its cap, new visitors see a "Response limit reached" message. Existing respondents who already started filling out the form can still complete and submit.

> ℹ️ **No form-level cap**
> <p>
>     Response limits live on share links. If you want the entire form to stop accepting responses at a certain count, set the same limit on
>     every active link — or use <a href="/sharing-publishing/scheduled-close">scheduled close</a> combined with a link cap.
>   </p>

<h2 id="per-respondent-limits">Limits per respondent</h2>

Share-link caps limit the total. To limit how many times <em>one person</em> can respond, use the per-respondent settings under

<strong>Form settings → Submissions</strong>:

- **Allow another response** — off by default, so each respondent submits once. Turn it on to show a "Submit again" button after
  submitting. It can't be combined with a redirect URL.
- **Maximum responses per respondent** — with "Allow another response" on, cap how many times the same person can submit. Leave it empty
  for unlimited.

The cap is strictly enforced for signed-in respondents. For anonymous respondents it's best-effort — formbase remembers them in their
browser, so someone determined enough can get around it by switching devices. If the count really matters, require sign-in with the{' '}

<a href="/sharing-publishing/authentication-gate">authentication gate</a>.

There's also a separate cap on how many times a respondent may <em>edit</em> a submission they already sent — **Maximum edits**, 1 to 3. See{' '}

<a href="/submissions-analytics/edit-after-submit">edit after submit</a>.

<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 a custom URL on completion
  - [Version history](/building-forms/version-history) — View and restore previous published versions
</div>

