Loading...
Loading...
Guide for using Netlify Forms for HTML form handling. Use when adding contact forms, feedback forms, file upload forms, or any form that should be collected by Netlify. Covers the data-netlify attribute, spam filtering, AJAX submissions, file uploads, notifications, and the submissions API.
npx skill4agent add netlify/context-and-tools netlify-formsdata-netlify="true"name<form name="contact" method="POST" data-netlify="true">
<label>Name: <input type="text" name="name" /></label>
<label>Email: <input type="email" name="email" /></label>
<label>Message: <textarea name="message"></textarea></label>
<button type="submit">Send</button>
</form>form-nameaction="/thank-you".htmlthank-you.html/thank-you.htmlpublic/public/__forms.html<!DOCTYPE html>
<html>
<body>
<form name="contact" data-netlify="true" netlify-honeypot="bot-field" hidden>
<input type="hidden" name="form-name" value="contact" />
<input type="text" name="name" />
<input type="email" name="email" />
<textarea name="message"></textarea>
<input name="bot-field" />
</form>
</body>
</html>nameform-nameform-name<form name="contact" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="contact" />
{/* ... fields ... */}
</form>const form = document.querySelector("form");
form.addEventListener("submit", async (e) => {
e.preventDefault();
const formData = new FormData(form);
await fetch("/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(formData).toString(),
});
});SSR frameworks (TanStack Start, Next.js, SvelteKit, Remix, Nuxt): TheURL must target the static skeleton file path (e.g.fetch), not"/__forms.html". In SSR apps,"/"is intercepted by the SSR catch-all function and never reaches Netlify's form processing middleware. See the React example and troubleshooting section below.fetch("/")
function ContactForm() {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
// For SSR apps, use the skeleton file path instead of "/" (e.g. "/__forms.html")
const response = await fetch("/__forms.html", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams(formData as any).toString(),
});
if (response.ok) {
// Show success feedback
}
};
return (
<form name="contact" method="POST" data-netlify="true" onSubmit={handleSubmit}>
<input type="hidden" name="form-name" value="contact" />
<input type="text" name="name" placeholder="Name" />
<input type="email" name="email" placeholder="Email" />
<textarea name="message" placeholder="Message" />
<button type="submit">Send</button>
</form>
);
}SSR troubleshooting: If form submissions appear to succeed (200 response) but nothing shows in the Netlify Forms UI, the POST is likely being intercepted by the SSR function. Ensuretargets the skeleton file path (e.g.fetch), not"/__forms.html". The skeleton file path routes through the CDN origin where Netlify's form handler runs."/"
<form name="contact" method="POST" netlify-honeypot="bot-field" data-netlify="true">
<p style="display:none">
<label>Don't fill this out: <input name="bot-field" /></label>
</p>
<!-- visible fields -->
</form>data-netlify-recaptcha="true"<div data-netlify-recaptcha="true"></div><form name="upload" enctype="multipart/form-data" data-netlify="true">
<input type="text" name="name" />
<input type="file" name="attachment" />
<button type="submit">Upload</button>
</form>FormDataContent-Typeawait fetch("/", { method: "POST", body: new FormData(form) });<input type="hidden" name="subject" value="Contact form" />GET /api/v1/forms/{form_id}/submissions
Authorization: Bearer <PERSONAL_ACCESS_TOKEN>| Action | Method | Path |
|---|---|---|
| List forms | GET | |
| Get submissions | GET | |
| Get spam | GET | |
| Delete submission | DELETE | |