Netlify Forms is not an endpoint you post to - it is a build step. At deploy time, Netlify scans your generated HTML for forms carrying a data-netlify attribute and wires up handling for what it finds. That design is why setup feels magical on a static site and why everything downstream of it - JavaScript-rendered forms, changing a form, moving hosts - has a quirk attached. Migrating means deleting the magic, not replacing it: a WebForms form is a plain POST to a URL, and works identically on Netlify, Vercel, GitHub Pages or a VPS. Netlify behavior below is per their live docs as of September 3, 2026.
The attribute-to-action swap
<form name="contact" method="POST" data-netlify="true" netlify-honeypot="bot-field"> <p hidden><input name="bot-field"></p> <input type="email" name="email" required> <textarea name="message" required></textarea> <button type="submit">Send</button> </form>
<form action="https://send.webforms.to/wft_your_key" method="POST"> <p hidden><input name="_gotcha"></p> <input type="email" name="email" required> <textarea name="message" required></textarea> <button type="submit">Send</button> </form>
- data-netlify="true" -> delete it; the action attribute does the work now.
- name="contact" on the form tag -> no longer structural. Netlify used it to identify the form at build time; here the endpoint key identifies the form, and the form's display name lives in the dashboard.
- netlify-honeypot="bot-field" + the bot-field input -> a single hidden input named _gotcha. No form-tag attribute needed; the field name is the configuration.
- The hidden form-name input that AJAX submissions to Netlify require -> delete it too.
The framework problem goes away
Because Netlify's scanner reads HTML at deploy time, a form rendered by client-side JavaScript is invisible to it. The documented workaround is keeping a hidden, static copy of the form in your deployed HTML so the scanner finds it, then making sure your real form posts with a matching form-name field. Every React and Vue migration guide for Netlify Forms is mostly about this. A POST endpoint has no scanner, so the workaround simply stops existing: render your form however your framework renders it, point the submit at the endpoint, done. Nothing about your build output matters.
Spam: from Akismet's verdict to your controls
Netlify filters submissions through Akismet, which works well and is configurable not at all. WebForms exposes the levers per form: honeypot field name, time trap, disposable-email blocking, keyword and domain blocklists, an optional AI review pass, and a quarantine threshold you can tune. Quarantined submissions sit in the dashboard where marking one as not-spam teaches the filter - with Akismet, a false positive is a submission you hope you notice in the spam list before it ages out.
Notifications and what a submission can trigger
Netlify sends form notifications to email, Slack or an outgoing webhook, configured per site. The equivalent here is destinations on the form: email, Slack, Discord, Telegram and signed webhooks, all receiving the same submission at once, each with a per-attempt delivery log showing status and response. The delivery log is the piece Netlify does not have an answer to - when a notification fails there, there is no per-attempt record to consult.
And your hosting is now unentangled
The quiet cost of build-time form handling is coupling: your form backend is your host, so moving hosts means migrating forms too. After this migration your forms are one attribute away from any host. Netlify remains an excellent place to deploy the site itself - this just removes forms from the list of reasons you cannot leave.
The checklist
- Create a form in the dashboard, add destinations, copy the endpoint.
- Add the action attribute; delete data-netlify, the form-name input, and netlify-honeypot.
- Rename your honeypot input to _gotcha (or configure a custom name in spam settings).
- Delete any hidden static form copies that existed only for the build scanner.
- Recreate Slack/webhook notifications as destinations.
- Export anything you need from Netlify's submissions UI - past submissions do not migrate.
- Submit once for real and check the dashboard and delivery log.