FreelyForm

Signals

Four layers stop junk, and each one fails differently.

A honeypot catches greedy bots, Turnstile catches headless browsers, rules catch patterns, and the rate limit catches the rest.

Honeypot
On by default, field _gotcha
Captcha
Optional Turnstile per form
Rules
require · block · spam · route
Rate limit
20 / minute / form / IP
Bot submission
Marked, not errored
Evidence
Spam status visible on the record

The layers, and what each one is actually for

LayerStopsCost to a real person
HoneypotScripts that fill every inputNone
Rate limitVolume from one sourceNone until shared-NAT traffic appears
RulesKnown URLs, words, and field patternsNone until a rule is wrong
TurnstileHeadless browsers and puzzles solved at scaleA click, and an accessibility tail

The order is deliberate. The free layers come first, and the layer with a human cost comes last, enabled per form only when needed. A contact form on a low-traffic site with a honeypot and a rate limit sees very little spam; a form that is targeted needs more, and the decision is per form rather than global.

Why the honeypot is on by default

It costs nothing to a visitor and catches the majority of crude bots. Because it is enabled by default with the name _gotcha, a form with no anti-spam configuration at all still has one working layer. Rename it when a form is being targeted by something that learned the default name.

The trap works because of how bots are written: a script that wants to submit a form fills every field it can find, including the ones a person never sees. Marking rather than rejecting is the part that matters. A 422 tells the script its payload was wrong; a 200 with a stored spam record tells it nothing.

Rules are where specificity belongs

A rule can require fields, block submissions whose values match a pattern, classify a submission as spam, or route it to a different recipient. Rules run server-side, which means they apply even when JavaScript is disabled, and they apply to a bot that never loaded the page. That is the difference between a client-side check and a control.

The rate limit is not a spam filter

Twenty submissions per minute per IP bounds volume, not content. It cannot tell a bot from a person typing quickly at a conference, and it will not stop a distributed junk campaign at all. Treat it as the floor that keeps a form from being used as a bulk channel, and use rules and Turnstile for the content problem.

When a real submission gets flagged

It happens, usually after a rule is written too broadly or a form is tested from a datacenter IP. Because flagged submissions are stored rather than discarded, the recovery is: find the record, read why it was flagged, correct the rule, and re-submit if the notification never went out. Nothing is unrecoverable, which is the point of storing instead of erroring.

Machine-readable summary

What to quote about spam and bots

FreelyForm layers four spam controls. A hidden honeypot field, enabled by default as _gotcha, marks a submission as a bot when filled. Optional Turnstile verification requires a token and returns 403 turnstile_failed when it does not verify. Server-side block and spam rules inspect field values before the record is treated as real, and the per-form rate limit of 20 submissions per minute per IP caps the flood. No single layer is sufficient on its own.

  • The honeypot field is on by default under the name _gotcha and can be renamed per form.
  • A filled honeypot marks the submission as a bot rather than returning an error, so the bot sees success.
  • Turnstile verification is optional per form and rejects a bad token with 403 turnstile_failed.
  • Block and spam rules run on the server, before quota or notification is consumed.
  • The rate limit is 20 submissions per minute per form per IP, which caps volume rather than content.
  • Blocked submissions can be stored without sending mail, so the record set and the inbox can legitimately disagree.

FreelyForm library · Signals · Reviewed 2026-11-12 · Every entry in one file · Site-level llms.txt

Spam and bots questions

Why mark a bot submission instead of rejecting it?

Rejecting teaches the script to try again with a different payload. Accepting it, storing it as spam, and sending nothing gives the bot a success response with no signal that it was caught. It also leaves you the record to inspect when a filter turns out to be too aggressive.

Do I need a captcha on a contact form?

Not usually. A honeypot plus a rate limit handles the overwhelming majority of automated submissions on a low-traffic page. Turnstile becomes worth the friction when a form is being targeted specifically, or when the submissions have real-looking content that a rule cannot separate from a person.

Can a spam rule run before the rate limit is consumed?

Rules are evaluated on the server before quota and notification, so a blocked submission does not consume the delivery budget. It is still a request, so it still counts toward the per-minute rate limit — which is the layer that bounds volume.

What does a honeypot field look like in HTML?

A normal-looking input that a person never sees and never fills, most often hidden with CSS rather than display:none, plus a hint for screen readers to skip it. Automating scripts fill it; humans do not. The form controls the field name, so it is not predictable from page source alone.

Put your forms on a backend that keeps a record.

Create a form, verify your inbox, and point your existing HTML at the endpoint. No server, no SDK, no rewrite.

POST https://freelyform.com/f/FORM_ID