netlify-forms
netlify/context-and-tools
Gestión de formularios sin servidor en sitios alojados en Netlify: detecta formularios HTML en el momento de la implementación, almacena los envíos, filtra el spam y envía notificaciones. Úsalo cuando añadas un formulario de contacto, un formulario de captura de clientes potenciales, un formulario de subida de archivos o una suscripción al boletín informativo a un sitio de Netlify; configurar el envío de formularios AJAX; configurar una página de agradecimiento personalizada; añadir un honeypot o reCAPTCHA a un formulario; hacer que los formularios funcionen en Next.js, Nuxt, SvelteKit, Astro o Gatsby; leer los envíos de formularios a través de la API de Netlify; o depurar envíos que faltan.
...Expandir todoAcerca de «netlify-forms»
Esta skill es una guía sobre Netlify Forms, el mecanismo sin servidor de Netlify para recopilar envíos de formularios HTML sin necesidad de escribir código del lado del servidor. Resuelve el problema habitual de conseguir que los formularios se registren correctamente y se recopilen los datos en Netlify: la detección se produce en el momento de la implementación mediante el análisis del HTML prerenderizado, por lo que los formularios de aplicaciones renderizadas en JavaScript o por el servidor fallan de forma silenciosa a menos que se configuren correctamente. La skill recorre todo el ciclo de vida y explica los numerosos escollos.
Entre las capacidades que se tratan se incluyen la configuración básica con el atributo «data-netlify» y un nombre de formulario único, páginas de agradecimiento personalizadas mediante rutas de acción sin extensión, y el patrón fundamental para los marcos de trabajo de JavaScript (React, Vue, Astro, Next.js, SvelteKit, Remix, Nuxt, TanStack Start): la creación de un archivo HTML estático de estructura básica (por ejemplo, public/__forms.html) que contenga una copia oculta de cada formulario con los nombres de campo coincidentes, de modo que la detección en el momento de la compilación se realice con éxito. Se explica que los envíos AJAX deben realizarse en formato x-www-form-urlencoded o multipart/form-data (nunca en JSON), la trampa del SSR al apuntar a la ruta del esqueleto en lugar de a «/», el filtrado de spam mediante Akismet automático, además de campos «honeypot» y reCAPTCHA, y el uso seguro de tus propias claves de reCAPTCHA a través de las variables de entorno de Netlify (SITE_RECAPTCHA_KEY y SITE_RECAPTCHA_SECRET), que se almacenan en el servidor en lugar de estar codificadas de forma fija. También hace referencia a la subida de archivos, las notificaciones y la API de envíos.
Los usuarios a los que va dirigido son desarrolladores front-end y full-stack que implementan sitios web estáticos o basados en frameworks en Netlify y que necesitan formularios de contacto, de comentarios, de captación de clientes potenciales, de boletines informativos o de subida de archivos. Entre los casos de uso habituales se incluyen la integración de un formulario de contacto AJAX, la incorporación de protección contra el spam, la detección de formularios en un marco de trabajo de SSR y la depuración de envíos que parecen completarse con éxito pero que nunca aparecen en la interfaz de usuario de Forms.
Preguntas frecuentes
¿Por qué no se detectan mis formularios?
Netlify analiza el HTML prerenderizado en el momento de la implementación. Los formularios renderizados únicamente mediante JavaScript o SSR son invisibles para el analizador; debes añadir un archivo HTML estático de estructura básica (por ejemplo, public/__forms.html) con una copia oculta de cada formulario y los nombres de campo correspondientes, y luego volver a implementar.
¿Por qué mis envíos AJAX se realizan con éxito pero nunca aparecen?
Netlify Forms no acepta JSON. Envía «application/x-www-form-urlencoded» (a través de URLSearchParams) o «multipart/form-data». En las aplicaciones SSR, la llamada fetch debe apuntar a la ruta del archivo esqueleto (p. ej., /__forms.html), no a «/», que es interceptada por el filtro genérico de SSR.
¿Cómo funciona el filtrado de spam?
Akismet se ejecuta automáticamente; puedes añadir un campo «honeypot» (netlify-honeypot) y reCAPTCHA. Los envíos marcados se trasladan de forma silenciosa a una lista de spam independiente en la interfaz de usuario de Forms.
¿Cómo se gestionan de forma segura las credenciales de reCAPTCHA?
Por defecto, Netlify te proporciona reCAPTCHA. Para utilizar tus propias claves, configura SITE_RECAPTCHA_KEY y SITE_RECAPTCHA_SECRET como variables de entorno de Netlify, de modo que el secreto permanezca en el lado del servidor y nunca se codifique de forma estática en el código del cliente.
¿Afecta la activación de la detección de formularios a las implementaciones existentes?
No. La detección solo afecta a las futuras implementaciones, por lo que, tras habilitarla, debes iniciar una nueva compilación antes de que un formulario ya publicado comience a recopilar envíos.
Mark a form for detection with data-netlify="true" (or the bare netlify attribute — equivalent) on the <form> tag. Forms are detected by parsing the final built HTML at deploy time — there is no runtime API call or backend code. Client-side/JS-rendered/SSR forms are NOT in the built HTML and are never detected on their own; they require a static skeleton file (see below).
Prerequisite: form detection must be enabled once in the Netlify UI (Forms > Enable form detection). Takes effect on the next deploy.
Static HTML form
<form name="contact" method="POST" data-netlify="true"> <p><label>Your Name: <input type="text" name="name" /></label></p> <p><label>Your Email: <input type="email" name="email" /></label></p> <p><label>Message: <textarea name="message"></textarea></label></p> <p><button type="submit">Send</button></p></form>
namesets the form name in the UI and must be unique per site.- At deploy, Netlify strips the
data-netlify/netlifyattribute and injects<input type="hidden" name="form-name" value="contact" />. - Add an
<input name="email">so the notification email'sReply-tois 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.
<!-- public/__forms.html --><form name="pizzaOrder" data-netlify="true" hidden> <input type="hidden" name="form-name" value="pizzaOrder" /> <input name="order" type="text" /></form>
2. The rendered form carries a matching hidden form-name input:
<form name="pizzaOrder" method="post" data-netlify="true" onSubmit={handleSubmit}> <input type="hidden" name="form-name" value="pizzaOrder" /> <input name="order" type="text" onChange={handleChange} /> <input type="submit" /></form>
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.
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
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-nameinput, you MUST include aform-namefield in the POST body. - The honeypot field name and
g-recaptcha-response(if used) must be in the body — automatic withFormData().
File uploads
Add type="file"; optionally enctype="multipart/form-data" on the <form>. For AJAX file uploads, do NOT set a Content-Type header — let the browser set it (with the multipart boundary).
document.forms.fileForm.addEventListener("submit", event => { event.preventDefault(); fetch("/", { body: new FormData(event.target), method: "POST" }) // no headers .then(() => { /* success */ });});
Limits: one file per field (use multiple fields for multiple files) · 8 MB max request size · 30 s upload timeout · after form deletion, uploaded files stay at their direct URL for 24 h. PII uploads need extra security (Very Good Security integration).
Custom success page
Add an action path relative to site root, starting with /. Use extensionless paths — Netlify serves thank-you.html at /thank-you; the .html path returns 404.
<form name="contact" action="/thank-you" method="POST" data-netlify="true"></form>
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 <form> and include a CSS-hidden field of that name. Any value entered → submission quietly rejected.
<form name="contact" method="POST" netlify-honeypot="bot-field" data-netlify="true"> <p class="hidden"><label>Don’t fill this out: <input name="bot-field" /></label></p> <!-- real fields --></form>
Netlify reCAPTCHA 2: add data-netlify-recaptcha="true" to the <form> AND an empty <div data-netlify-recaptcha="true"></div> where it renders. Only ONE Netlify-provided challenge per page — for multiple, use custom reCAPTCHA. For JS-rendered forms, also add the div to the static skeleton file.
Custom reCAPTCHA 2: your own reCAPTCHA snippet + data-netlify-recaptcha="true" on the <form>, plus env vars:
SITE_RECAPTCHA_KEY— site key (scopes: Builds + Runtime)SITE_RECAPTCHA_SECRET— secret (scope: Runtime)
Email notifications & subject line
Default sender: [email protected]. Set subject via a hidden subject input or the Netlify UI (Configuration > Notifications) — not both; the HTML value always overrides the UI.
<input type="hidden" name="subject" value="New lead from %{formName} (%{submissionId})" />
Variables: %{formName}, %{siteName}, %{submissionId}. Forms created before May 5, 2023 carry a [Netlify] subject prefix — remove it by adding the data-remove-prefix attribute to the subject input.
Set up notifications (email/webhook/Slack) in the UI: Configuration > Notifications > Form submission notifications > Add notification.
Reading submissions via the API
Use only documented surfaces. Do NOT invent api.netlify.com endpoints or read tokens from local CLI config files. Reference: https://open-api.netlify.com/#tag/submission/operation/listFormSubmissions
- Page through results using the
Linkheader — code that reads only the first response silently drops the rest. listFormSubmissionsreturns data from old/removed fields no longer shown in the UI.- Query spam with
?state=spam.
Submission summary (field order matters)
The UI summary is derived from field type, not name:
- Title: first non-hidden text
<input>that isn't email-like (type="email", or name matchingemail/mail/from/twitter/sender); falls back to a field namedtitleorsubject. - Body: first
<textarea>.
Field order in the HTML affects what appears in the summary.
Debugging missing submissions
- First suspect: Akismet false positive. A missing legitimate submission is usually spam-flagged — check the Spam list (or API
?state=spam) and mark it verified. Do NOT build a custom recovery function or disable spam filtering as a first resort. - Test submissions get flagged as spam: use a real email (not
[email protected]), write full sentences, don't hammer from one IP. - No submissions at all: confirm form detection is enabled and redeploy.
- SSR/JS forms silently failing: verify the static skeleton file exists with exactly-matching field names and that AJAX targets the skeleton file, not
/. - Missing old-field data: the UI shows only fields from the last deployed form version. Mark old fields
hiddeninstead of removing them to keep them visible; old data remains available vialistFormSubmissions.
Constraints
- Deleting a form is permanent: future submissions return
404, past submissions become unavailable. Export CSV first. - Submitted code is sanitized (
<script>→ escaped entities). - For PII, export and delete data regularly.
- Data is stored in Netlify's database, not accessible except via UI/API/CSV.
Netlify house rules (forms)
These are org conventions and field-learned guardrails, not docs facts — theyare merged into the rendered skill by ctx-gen and are never generated.Extracted from the previous hand-written netlify-forms skill; owned by theskills maintainer.
- In SSR apps (Next.js, Nuxt, SvelteKit, etc.),
fetch("/")is interceptedby the SSR catch-all function and never reaches Netlify's form processing.POST the AJAX submission to the static skeleton file itself (e.g./__forms.html), not to an arbitrary path. - Use only documented surfaces: do not curl
https://api.netlify.com/...with an invented endpoint shape, and do not read tokens out of local CLIconfig files (~/Library/Preferences/netlify/config.json). - When reading submissions via the API, page through results (
Linkheader); code that reads only the first response silently drops the rest. - For JS-rendered and SSR forms, always create the static skeleton file
public/__forms.html: a hidden copy of each form withdata-netlify="true", a hiddenform-nameinput, and every field thecomponent submits — names matching exactly (Netlify validates field namesagainst the registered form). Without this file, submissions silently fail. - Astro routes rendered on demand (
export const prerender = false, oroutput: "server"routes) are never scanned at build time, so their formsare never registered. Put the form on a prerendered page or rely on thestatic skeleton file. - A "missing" legitimate submission is usually an Akismet false positive:check the Spam list (or the API with
?state=spam) and mark it verified.Do not build a custom recovery function or disable spam filtering as afirst resort. - For custom success pages, use extensionless
actionpaths (/thank-you,not/thank-you.html) — Netlify servesthank-you.htmlat/thank-youand the.htmlpath returns 404.
Todos los archivos
0 archivosInstalar netlify-forms
Descarga y descomprime los archivos de habilidades en tu directorio .claude/skills/.
Descargar ZIPClona el repositorio y copia los archivos de la habilidad a tu proyecto.
git clone https://github.com/netlify/context-and-tools/blob/main/skills/netlify-forms/SKILL.md # Copy SKILL.md to your .claude/skills/ directory
Copiar





Hogar
