Skip to content
FFormhook
Back to blog

Blog

How to add a contact form to Cloudflare Pages

· 3 min read

Cloudflare Pages is a fast, generous static host - push a repo, get a global deploy, done. What it doesn't have is anything like Netlify Forms: no built-in service that scans your HTML for a <form> tag and starts collecting submissions. Deploy a plain HTML contact form to Cloudflare Pages as-is and the POST goes nowhere. Here's why, and the two ways to fix it.

Why Cloudflare Pages doesn't have a native forms product

Cloudflare Pages is a static-file host with an optional Functions layer bolted on for dynamic routes - it's not a form-handling platform. Netlify's forms feature works by parsing your build output at deploy time and provisioning a hidden backend for each form it finds; Cloudflare Pages does no such parsing. Your build artifacts are just files on a CDN. If a form needs somewhere to send data, you have to build or borrow that somewhere yourself. That's not a gap Cloudflare is likely to close either - Pages is deliberately scoped to static assets plus a thin serverless layer, not a forms-parsing pipeline.

Option 1: write your own Pages Function

Cloudflare Pages supports Functions - small Workers-style handlers that live in a /functions directory and run on Cloudflare's edge. You could add a functions/api/contact.ts that reads the POST body, validates it, filters spam, sends an email (via a provider's API, since Workers can't make raw SMTP connections), and maybe writes to KV or D1 for storage. It works, and it keeps everything inside one Cloudflare project. But you're now the one maintaining validation, spam filtering, storage, and email delivery - the exact backend a static host was supposed to save you from writing.

Option 2: point the form at a hosted backend (recommended)

The faster route is to send the POST to a service built for exactly this. Keep your markup, add one attribute:

<form action="https://formhook.app/f/YOUR_API_KEY" method="POST">
  <input type="text" name="name" required />
  <input type="email" name="email" required />
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

That's the entire integration. No Function, no Worker, no email API keys to manage. Submissions show up in a dashboard and can trigger a web push notification - including on iOS, once the site's added to the home screen. Spam protection is on by default: a honeypot field and rate limiting catch most of it before it reaches you, and you can layer on Cloudflare Turnstile - Cloudflare's own privacy-friendly CAPTCHA, a fitting pair for a form living on a Cloudflare Pages site - for anything getting targeted harder. Submissions and any uploaded files are stored on EU infrastructure. None of this depends on how the rest of the site is built: Astro, plain HTML, a Next.js static export, Hugo, whatever generates the markup, the form tag is the same and the deploy is unchanged.

Getting your endpoint

  1. Sign up and create a form in the dashboard - no card required.
  2. Copy the form's unique URL (it looks like https://formhook.app/f/fh_xxxxxxxx).
  3. Paste it into the action attribute of your existing <form>.
  4. Commit and deploy as usual.

The free tier covers 250 submissions a month, which is plenty for most contact forms. See the pricing page for what's on Pro if you need file uploads or a verified sending domain later.

Ship a working form in one line

EU-hosted, submissions kept forever, push notifications on every tier.

Start free

No credit card · read the docs

Keep reading