integrations · hosting platform

A contact form on Cloudflare Pages, without writing a Worker.

Cloudflare Pages will serve your site from 300-odd cities and never once accept a form submission for you. There is no equivalent of Netlify Forms here. Cloudflare's own tutorial on the subject sends you to a third-party form service, and the closest first-party thing, the Static Forms plugin for Pages Functions, is not a form service at all: it intercepts the POST and then hands it to a function you have to write, wired to a KV namespace you have to provision.

Pointing your form's action at a mailcontact endpoint removes that whole layer. No Functions directory, no KV binding, no wrangler config, no Worker to keep alive. Submissions land in your project's inbox and you get pinged in Discord, Slack, email, or a webhook.

recommended setup

Use the MailContact script with your own design.

Add this HTML to your site, or use the form builder. Replace the form link with yours. The hosted script injects the hidden check, handles sending and automatic verification, and preserves the draft on errors. You control the HTML and CSS. Free forms display a visible “Processed by mailcontact.app” link; Pro and Plus do not. Feedback follows the browser’s language; set data-mailcontact-language="fr" on the form to force French.

contact.html
<form action="https://mailcontact.app/api/forms/your-form-id" method="POST" data-mailcontact-form>
  <fieldset>
    <legend>Contact us</legend>
    <label>
      Name (optional)
      <input name="name" type="text" autocomplete="name" maxlength="200" />
    </label>
    <label>
      Email
      <input name="email" type="email" autocomplete="email" maxlength="320" required />
    </label>
    <label>
      Message
      <textarea name="message" rows="5" maxlength="5000" required></textarea>
    </label>
    <noscript>
    <div aria-hidden="true" style="position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden;">
      <label>Website
        <input name="company_website" type="text" tabindex="-1" autocomplete="off" />
      </label>
    </div>
    </noscript>
    <button type="submit">Send message</button>
  </fieldset>
  <p data-contact-status role="status" aria-live="polite" aria-atomic="true"></p>
</form>
<script defer src="https://mailcontact.app/forms/v1.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

For a component with its own submit handler, load the same script and use MailContact.prepare(form) and MailContact.send() instead of the automatic marker. See the custom interface reference for loading, feedback and Free attribution.

the snippet

Alternative: a direct integration for Cloudflare Pages.

This example posts directly to the API. Keep the required company_website field present and empty. Native posts open JSON on success or a verification page when needed. Custom scripts without verification support send suspicious messages to Spam for review; strong spam is also held there without notifications or quota usage. A success response confirms reception, including review, or a silent discard of a filled check. See spam protection for the full behavior.

public/contact.html
<!-- No functions/ directory. No KV namespace. No wrangler.toml. -->

<form action="https://mailcontact.app/api/forms/your-form-id" method="POST">
  <input type="text" name="name" placeholder="Your name" required />
  <input type="email" name="email" placeholder="[email protected]" required />
  <textarea name="message" placeholder="What's on your mind?" required></textarea>

  <!-- Bots fill this in. Humans never see it. -->
  <input type="text" name="company_website" type="text" tabindex="-1" autocomplete="off"
      style="position:absolute;left:-10000px" aria-hidden="true" />

  <button type="submit">Send</button>
</form>

how it works

Three steps, start to finish.

01

Create an inbox

One inbox per project. Name it after the Pages project and you get a form endpoint and a project email address straight away.

02

Paste the form into your output

Whatever you deploy to Pages, whether that is plain HTML, an Astro build, or a React bundle, the form is just markup in the page. Nothing changes in your build command.

03

Deploy as usual

git push, or wrangler pages deploy. The form works on the first preview deployment, on every branch alias, and on your custom domain, with no per-environment setup.

why mailcontact

Built to pair well with Cloudflare Pages.

The Static Forms plugin is not a form backend

It parses the submission and calls a handler you supply. You still write the persistence, the notification, the spam handling and the error paths. That is the actual work, and the plugin does none of it.

Nothing to keep inside the free tier

Workers bill on requests and CPU time, and a form handler that emails you drags in a mail API and its own key. An endpoint you post to has no invocation budget to blow and no cold path to debug at 2am.

It survives the Pages to Workers drift

Cloudflare now steers new full-stack projects toward Workers. A form that is markup pointing at an external URL does not care which product your site ends up living on, because it has no platform binding to migrate.

faq

Cloudflare Pages + mailcontact, answered

Does Cloudflare Pages have built-in form handling like Netlify Forms?

No. Netlify detects a form attribute at deploy time and stores submissions for you; Cloudflare has no such feature. Cloudflare's documented answers are either the Static Forms plugin, which requires you to write the handler and wire storage yourself, or an external form service. There is no zero-code option in the platform.

What about the Static Forms plugin for Pages Functions?

It is plumbing, not a product. The plugin catches the POST from a form marked with data-static-form-name and passes it to a respondWith handler that you implement. Persisting the message, notifying you, and filtering spam are all still yours to build. If you want to write that, the plugin helps; if you wanted to skip it, an endpoint is the shorter path.

Can I still use Pages Functions for other things?

Yes. Nothing here conflicts. The form is an HTML action attribute pointing at another origin, so your functions/ directory, middleware, and bindings are untouched. This only replaces the part of Functions you would have written to handle a contact form.

Will the form work on preview deployments and branch aliases?

Yes. The endpoint accepts cross-origin posts from any domain, so your *.pages.dev previews, branch aliases, and custom domain all post to the same inbox without per-environment configuration or an allowlist to maintain.

related

Also works with

One inbox for every Cloudflare Pages project you ship.

Forms and a real email address, together, with notifications where you live. Start with a free account.

Create your free form

No credit card needed. Free includes one project and 50 form messages per month.