---
name: netlify-forms
description: Serverless form handling on Netlify-hosted sites — detects HTML forms at deploy time, stores submissions, filters spam, and sends notifications. Use when adding a contact form, lead-capture form, file-upload form, or newsletter signup to a Netlify site; wiring AJAX form submission; setting up a custom thank-you page; adding a honeypot or reCAPTCHA to a form; getting forms working in Next.js, Nuxt, SvelteKit, Astro, or Gatsby; reading form submissions via the Netlify API; or debugging missing submissions and forms that silently fail to register.
---
# Netlify Forms
Mark a form for detection with `data-netlify="true"` (or the bare `netlify` attribute — equivalent) on the `
```
- `name` sets the form name in the UI and **must be unique per site**.
- At deploy, Netlify strips the `data-netlify`/`netlify` attribute and injects ``.
- Add an `` so the notification email's `Reply-to` is set to the submitter.
## JS-rendered / SSR / framework forms (Next.js, Nuxt, SvelteKit, Astro, Gatsby)
Two required pieces:
**1. Static skeleton file `public/__forms.html`** — a hidden copy of each form with `data-netlify="true"`, a hidden `form-name` input, and every field the component submits, with names matching **exactly** (Netlify validates field names against the registered form). Without this file, submissions silently fail.
```html
```
**2. The rendered form** carries a matching hidden `form-name` input:
```jsx
```
**⚠️ SSR POST target:** In SSR apps, `fetch("/")` is intercepted by the SSR catch-all function and never reaches form processing. POST to the static skeleton file itself — `/__forms.html` — not `/` or an arbitrary path.
**⚠️ Astro on-demand routes:** Routes with `export const prerender = false` or `output: "server"` are never scanned at build time, so their forms are never registered. Put the form on a prerendered page, or rely on the static skeleton file.
**Next.js Runtime v5 (Next.js 13.5+):** extract form definitions to the static skeleton file and submit via AJAX rather than full-page navigation. See https://docs.netlify.com/build/frameworks/framework-setup-guides/nextjs/overview#v5-breaking-changes
## AJAX submission
```js
const handleSubmit = event => {
event.preventDefault();
const formData = new FormData(event.target);
fetch("/__forms.html", { // static sites may POST to "/"; SSR must target the skeleton file
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(formData).toString()
})
.then(() => alert("Thank you for your submission")) // or navigate("/thank-you")
.catch(error => alert(error));
};
document.querySelector("form").addEventListener("submit", handleSubmit);
```
- **Body MUST be URL-encoded. JSON is NOT supported.**
- If the rendered form has no hidden `form-name` input, you MUST include a `form-name` field in the POST body.
- The honeypot field name and `g-recaptcha-response` (if used) must be in the body — automatic with `FormData()`.
## File uploads
Add `type="file"`; optionally `enctype="multipart/form-data"` on the `
```
Custom success *alert* is only possible via AJAX (substitute the redirect with your own logic).
## Spam prevention
All submissions are filtered by Akismet. Passed → **Verified submissions**; flagged → **Spam submissions**. Honeypot/reCAPTCHA failures are rejected and appear in neither list.
**Honeypot:** add `netlify-honeypot="bot-field"` to the `
```
**Netlify reCAPTCHA 2:** add `data-netlify-recaptcha="true"` to the `