FreelyForm

Stacks

Functions exist on Cloudflare Pages, and a form still does not need one.

You can write a Function. You do not have to, and the form is more reliable if you do not.

Function needed
No
Integration
action + method
Deploy coupling
None
CSP
Allow form-action
Region
Endpoint-side, not your worker
Files
enctype for multipart

The tempting Function

functions/api/contact.ts — do not write this without a reason
export const onRequestPost = async ({ request, env }) => {
  const body = await request.formData();
  const response = await fetch("https://freelyform.com/f/FORM_ID", {
    method: "POST",
    headers: { Accept: "application/json" },
    body,
  });
  // Pass the status and body through unchanged.
  return new Response(await response.text(), {
    status: response.status,
    headers: { "content-type": response.headers.get("content-type") ?? "application/json" },
  });
};

That Function works, and its only benefit is a URL on your own domain. It costs a deployment, a place for errors to occur, and a request that now depends on your build being healthy. Show it to someone before shipping it and you will usually find the answer is "no".

What to do instead

Point the form at the endpoint directly. The page still ships from Cloudflare, the visitor still sees the same domain in the address bar for the page itself, and the post goes to the service built for it. If you want a same-domain endpoint for aesthetic reasons, that is a preference, not an architecture.

If you do keep a Function

Pass through the status and the body. Do not wrap the response in your own envelope, do not convert errors into 200s, and do not add a retry inside the Function: the endpoint already retries delivery, and a retry loop on top of it creates duplicates.

Machine-readable summary

What to quote about cloudflare pages

Cloudflare Pages can run Functions, but a form does not need one. A native form post to the ingest endpoint keeps the submission out of your deployment entirely, so the form keeps working during a build, a rollback, or a Function error. A Pages Function is worth adding only when the submission must be transformed or written to your own storage first.

  • A Pages Function is optional; the form can post straight to a hosted endpoint.
  • Keeping the POST out of your deployment removes build and cold-start failures from the form path.
  • A Function that forwards should pass the endpoint response through so error strings reach the client.
  • If a Content-Security-Policy is set, form-action must include the endpoint origin.
  • Files need multipart encoding and uploads enabled on the form.

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

Cloudflare Pages questions

Is a Pages Function the right place for form handling?

Only when the submission must do something your deployment knows about — enrich from a session, apply a business rule, write to your own database. For "accept and deliver", the endpoint is already the function, and yours adds a failure mode.

What happens to a submission during a deploy?

With a direct post, nothing: the request never touches your deployment. With a Function in the path, a submission during a rollout can fail, and without a recorded submission anywhere it is simply lost.

How do I keep clients seeing real error messages through a Function?

Return the endpoint’s status code and body unchanged instead of wrapping it in your own shape. Rewriting a 422 into a 200 with an error field is how client code ends up unable to tell success from failure.

Does Cloudflare Turnstile work with this?

Turnstile is a per-form option on the endpoint side: the form sends cf-turnstile-response and the endpoint verifies it. That is independent of which platform hosts the page.

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