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
enctypefor multipart
The tempting Function
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.
Related pages
The rest of the library
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