Guide

How to add a contact form to a Cloudflare Pages site (without Formspree)

12 November 2026 · About an 8-minute read

Cloudflare Pages will serve your HTML for almost nothing, and it will not, by itself, keep a secret inbox. A <form> needs somewhere to POST. That is why so many Pages and Astro sites end up on Formspree, Basin, or “just leave it on Netlify for the forms.” You can also do it on a Worker you own. This article is how that path works, including the parts a two-line tutorial usually skips.

The shape of the system

Keep the marketing site static. Give the form an action that points at a Worker URL, such as https://forms.example.com/f/contact or a .workers.dev subdomain. The Worker should:

  1. Reject requests from origins you did not allow.
  2. Verify a Cloudflare Turnstile token with the Siteverify API.
  3. Validate fields on the server (length, email format, required fields). Browser required attributes are a courtesy.
  4. Store the message (D1 is a natural fit) and optionally send you email.
  5. Redirect the browser to a thanks page, or return JSON for a fetch client.

That split — Pages for the site, Worker for the POST — means you can change the frontend without redeploying the inbox, and you can reuse one Worker for several sites if you want to.

Turnstile is not optional, and the widget is not enough

A visible or managed Turnstile widget produces a token in a field named cf-turnstile-response. Cloudflare is explicit: you must redeem that token on the server at POST https://challenges.cloudflare.com/turnstile/v0/siteverify, with your secret key and the token. Tokens expire after five minutes and are meant for one use. If you only hide the widget and trust whatever string the client sent, an attacker can POST junk without ever solving a challenge.

Send the visitor IP (CF-Connecting-IP) as remoteip when you can. Use dummy keys from Cloudflare’s testing docs on localhost; never in production.

Three more cheap checks

Honeypot. Add a field that real people never see, hidden with CSS, tabindex="-1", and autocomplete="off". If it has a value, drop the submission. Pretending to succeed is kinder than teaching bots that the field is special.

Timing trap. When the page loads, set a hidden timestamp. Reject submits that arrive in under a second or two, and reject timestamps older than a few hours so a copied form cannot be replayed all week.

Rate limit. Count submits per IP per form in KV (or a similar store). A tight cap such as eight per ten minutes stops the obvious floods without bothering a human who typo’d their email.

Also allowlist the origins that may POST, and only redirect to URLs on those origins. A form endpoint is a surprisingly common open-redirect if you blindly honor a next= parameter.

Email, or not

Store first, notify second. If Resend (or another provider) is down, you should still have the row in D1. Send notifications after you have written the database, and consider waitUntil so the visitor is not waiting on the mail API. Send from a domain you verified. Do not try to send from @workers.dev and expect inbox placement.

Mailchannels’ old “free outbound from Workers” path is not something to build a new form on in 2026. Use a proper transactional API.

What you should tell visitors

A short note next to the form — what you will do with the message, and how to reach you if they want it deleted — is basic courtesy and, depending on who you are, may also be a legal requirement. Do not collect payment card numbers on a homemade contact form. This is practical hygiene, not legal advice.

Wiring it by hand vs using a kit

None of the above is exotic. It is also easy to ship a form that “works on my machine” and then discover, a week later, that Turnstile was never verified server-side, or that anyone can POST from another website. If you want the pieces already assembled — dashboard, CSV export, waitlist codes, file uploads into R2 — that is what Formharbor is for. If you enjoy writing it yourself, the checklist in this article is enough to start.

More guides

Want this already built?

Formharbor is a $19 one-time kit: contact forms, referral waitlists, and file uploads on your Cloudflare free tier.

Get Formharbor