Cloudflare Pages form submissions without Pages Functions
Collect Cloudflare Pages form submissions with a hosted endpoint — no Pages Functions required, exact domain allow-listing, optional path-style URL.
If your Cloudflare Pages site is static, you can collect form submissions without Pages Functions by posting the form to a hosted endpoint. The page stays fast and static; the endpoint stores the address, rejects junk, and lets you export later.
Cloudflare Pages is excellent at shipping marketing sites, docs, and launch pages. What it does not give you out of the box is a durable place for email signups. The minimum setup is a public campaign id, exact domain allow-list entries for your Pages hosts, and the usual honeypot and spam guardrails.
Pages is static; forms need an endpoint
A Pages deployment is files at the edge. Relative form actions that expect PHP, a home-grown API route, or a same-origin backend fail unless you add that backend yourself. You still want something that accepts POST, checks the caller, and keeps the list somewhere you control.
Common options on the Cloudflare stack
- Pages Functions + storage you operate. Full control, and you also own rate limits, bot checks, domain policy, exports, and double opt-in if you need them.
- A general form SaaS embed. Fast until branding, data ownership, or anti-abuse settings fight your stack.
- Simple Signups. A campaign-scoped subscribe API that already lives on Cloudflare Workers. Your Pages site stays static; the form posts to the Simple Signups origin. That is intake, not a substitute for arbitrary Pages Functions backends.
When Pages Functions still make sense
Use Pages Functions when the submit must run app-specific server logic on your own origin: private API calls, authenticated user flows, or writes into your own schema. If the job is just signup or contact intake, adding a Function only to forward the email is usually extra surface area, not extra value.
Allow-list every hostname you embed on
Each campaign stores an exact list of allowed hostnames. On subscribe, the Worker checks browser Origin (or Referer) against that list. Matching is exact after normalization — no *.pages.dev wildcards, and apex does not automatically include www.
Typical Cloudflare Pages entries:
- Production custom domain — e.g.
www.example.comand, if you serve it bare,example.com - Project Pages hostname — e.g.
my-site.pages.devif people still hit the default URL - Preview deployments — each preview uses its own host (often a unique subdomain under
pages.dev). Add the hosts you actually test from; there is no blanket preview wildcard
The operational gotcha is previews: they are great for testing the form from a real browser origin, but each hostname must be allow-listed explicitly if you expect the submit to succeed there.
Empty allow-lists deny third-party embeds. First-party traffic on the Simple Signups product host is handled separately so on-product forms work without listing the product domain on every campaign. Cross-site embeds from your Pages project always need an explicit entry.
Drop-in HTML (direct POST to Simple Signups)
The default path is simple: host the form on Pages, set action to the Simple Signups subscribe URL, include your public campaign id. No Pages Function required for the happy path.
<form action="https://simple-signups.com/api/subscribe" method="POST"> <input type="hidden" name="campaignId" value="pub_your_campaign_id" /> <input type="email" name="email" required placeholder="you@example.com" /> <input type="text" name="hp" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px" aria-hidden="true" /> <button type="submit">Subscribe</button></form>Prefer the campaign id in the path? Use the path-style endpoint so the body only needs email (and the honeypot):
<form action="https://simple-signups.com/c/pub_your_campaign_id" method="POST"> <input type="email" name="email" required placeholder="you@example.com" /> <input type="text" name="hp" tabindex="-1" autocomplete="off" style="position:absolute;left:-9999px" aria-hidden="true" /> <button type="submit">Subscribe</button></form>What this does not replace
Simple Signups is the signup intake layer — domain gate, spam controls, storage, export. It is not a general replacement for Pages Functions, Workers for Platforms, or custom APIs you already run on Cloudflare. Keep those for app logic; point the marketing form at a dedicated campaign.
Related reading
- Newsletter signups use case — product-shaped guide for list growth.
- Waitlist on a static website — same static-host idea framed for launch lists.
- Docs quickstart, path-style endpoint, and anti-abuse & domains.