关于 netlify-forms
netlify-forms 是帮助开发者在 Netlify 上配置和排查表单的技能。它说明构建时表单检测、静态骨架文件、AJAX 提交、反垃圾机制及提交数据读取方式。
主要功能
- 01通过 HTML 属性启用表单检测
- 02为动态表单准备静态骨架
- 03使用 URL 编码提交表单
- 04配置上传与成功页面
- 05使用蜜罐和 reCAPTCHA 防垃圾
- 06通过 API 分页读取提交记录
适用场景
- 01在静态站点收集表单提交
- 02让 SSR 或 JavaScript 表单完成注册与提交
- 03排查未显示的表单提交
- 04读取并检查垃圾提交记录
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ netlify/ context-and-tools --skill netlify-forms安装后在 Claude Code 中输入/netlify-forms确认已加载。
详细介绍
Netlify 表单
在 <form> 标签上添加 data-netlify="true"(或等效的裸 netlify 属性)以标记表单供检测。Netlify 会在部署时通过解析最终构建的 HTML检测表单——不会调用运行时 API,也不需要后端代码。客户端渲染、JS 渲染或 SSR 表单不会出现在构建后的 HTML 中,因此无法单独检测到;它们需要一个静态骨架文件(见下文)。
前置条件:必须先在 Netlify UI 中启用一次表单检测(Forms > Enable form detection)。设置会在下一次部署时生效。
静态 HTML 表单
<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>
name用于设置 UI 中的表单名称,且每个站点都必须唯一。- 部署时,Netlify 会移除
data-netlify/netlify属性,并注入<input type="hidden" name="form-name" value="contact" />。 - 添加
<input name="email">,以便将通知邮件的Reply-to设置为提交者的地址。
JS 渲染 / SSR / 框架表单(Next.js、Nuxt、SvelteKit、Astro、Gatsby)
需要满足以下两项:
1. 静态骨架文件 public/__forms.html ——为每个表单添加一份隐藏副本,包含 data-netlify="true"、一个隐藏的 form-name 输入框,以及组件提交的所有字段,字段名称必须完全一致(Netlify 会根据已注册的表单验证字段名称)。缺少此文件时,提交会静默失败。
<form name="pizzaOrder" data-netlify="true" hidden>
<input type="hidden" name="form-name" value="pizzaOrder" />
<input name="order" type="text" />
</form>
2. 渲染后的表单中必须包含匹配的隐藏 form-name 输入框:
<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>
**⚠️ SSR POST 目标:**在 SSR 应用中,fetch("/") 会被 SSR 全匹配函数拦截,无法到达表单处理流程。请将 POST 发送到静态骨架文件本身 /__forms.html,而不是 / 或其他任意路径。
**⚠️ Astro 按需路由:**带有 export const prerender = false 或 output: "server" 的路由不会在构建时扫描,因此其表单永远不会注册。请将表单放在预渲染页面上,或使用静态骨架文件。
**Next.js Runtime v5(Next.js 13.5+):**将表单定义提取到静态骨架文件,并通过 AJAX 提交,而不是进行整页导航。请参阅 https://docs.netlify.com/build/frameworks/framework-setup-guides/nextjs/overview#v5-breaking-changes
AJAX 提交
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);
- 请求正文必须使用 URL 编码。不支持 JSON。
- 如果渲染后的表单没有隐藏的
form-name输入框,则必须在 POST 请求正文中包含form-name字段。 - honeypot 字段名称和
g-recaptcha-response(如果使用)必须包含在请求正文中——使用FormData()时会自动包含。
文件上传
添加 type="file";也可以选择在 <form> 上添加 enctype="multipart/form-data"。对于 AJAX 文件上传,不要设置 Content-Type 标头——让浏览器自行设置(包含 multipart 边界)。
document.forms.fileForm.addEventListener("submit", event => {
event.preventDefault();
fetch("/", { body: new FormData(event.target), method: "POST" }) // no headers
.then(() => { /* success */ });
});
限制:每个字段只能上传一个文件(多个文件请使用多个字段)· 请求大小上限为 8 MB · 上传超时为 30 秒 · 删除表单后,已上传文件仍可通过其直接 URL 访问 24 小时。PII 上传需要额外的安全措施(Very Good Security 集成)。
自定义成功页面
添加一个相对于站点根目录、以 / 开头的 action 路径。请使用不带扩展名的路径——Netlify 会在 /thank-you 提供 thank-you.html;带 .html 的路径会返回 404。
<form name="contact" action="/thank-you" method="POST" data-netlify="true"></form>
自定义成功提示只能通过 AJAX 实现(用你自己的逻辑替换重定向)。
垃圾信息防护
所有提交内容都会经过 Akismet 过滤。通过 → 已验证的提交;被标记 → 垃圾信息提交。蜜罐/reCAPTCHA 验证失败的提交会被拒绝,且不会出现在任何列表中。
**蜜罐:**在 <form> 中添加 netlify-honeypot="bot-field",并包含一个该名称的 CSS 隐藏字段。任何输入值都会导致提交被静默拒绝。
<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>
</form>
**Netlify reCAPTCHA 2:**在 <form> 中添加 data-netlify-recaptcha="true",并在其呈现位置添加一个空的 <div data-netlify-recaptcha="true"></div>。每个页面只能使用一个由 Netlify 提供的验证挑战;如需多个,请使用自定义 reCAPTCHA。对于由 JS 渲染的表单,还需将该 div 添加到静态骨架文件中。
**自定义 reCAPTCHA 2:**使用你自己的 reCAPTCHA 代码片段,并在 <form> 中添加 data-netlify-recaptcha="true",此外还需设置环境变量:
SITE_RECAPTCHA_KEY— 站点密钥(作用范围:Builds + Runtime)SITE_RECAPTCHA_SECRET— 密钥(作用范围:Runtime)
电子邮件通知和主题行
默认发件人:[email protected]。通过隐藏的 subject 输入字段或 Netlify UI(Forms > Submission notifications)设置主题 — 不能同时使用两种方式;HTML 中的值始终会覆盖 UI 设置。
<input type="hidden" name="subject" value="New lead from %{formName} (%{submissionId})" />
变量:%{formName}、%{siteName}、%{submissionId}。在 2023 年 5 月 5 日之前创建的表单,其主题带有 [Netlify] 前缀;若要移除该前缀,请为 subject 输入字段添加 data-remove-prefix 属性。
在 UI 中设置通知(电子邮件/webhook/Slack):Forms > Submission notifications > Add notification。
ChatGPT 自动化:订阅来自 ChatGPT 的表单提交事件,并据此运行自动化(例如,审核新提交并标记需要跟进的提交)。设置方法:https://docs.netlify.com/build/build-with-ai/agent-setup-guides/use-netlify-with-chatgpt/#subscribe-to-netlify-events
通过 API 读取提交内容
仅使用有文档说明的接口。请勿编造 api.netlify.com 端点,也不要从本地 CLI 配置文件中读取令牌。参考:https://open-api.netlify.com/#tag/submission/operation/listFormSubmissions
- 使用
Link标头翻阅结果 — 只读取第一个响应的代码会静默丢弃其余结果。 listFormSubmissions会返回旧字段或已移除字段的数据,这些字段已不再显示在 UI 中。- 使用
?state=spam查询垃圾信息。
提交摘要(字段顺序很重要)
UI 摘要根据字段类型生成,而非字段名称:
- 标题:第一个非隐藏的文本
<input>,且其内容不像电子邮件(type="email",或名称匹配email/mail/from/twitter/sender);若没有,则回退到名为title或subject的字段。 - 正文:第一个
<textarea>。
HTML 中的字段顺序会影响摘要中显示的内容。
调试缺失的提交
- 首先怀疑 Akismet 误判。合法提交缺失通常是因为被标记为垃圾信息 — 请检查垃圾信息列表(或 API
?state=spam),并将其标记为已验证。不要一开始就构建自定义恢复函数或禁用垃圾信息过滤。 - 测试提交被标记为垃圾信息:请使用真实电子邮件地址(不要使用
[email protected]),填写完整句子,并避免从同一个 IP 频繁提交。 - 完全没有提交:确认已启用表单检测(Forms > Form detection),然后重新部署。
- SSR/JS 表单静默失败:确认静态骨架文件存在,且字段名称完全一致;同时确认 AJAX 指向骨架文件,而不是
/。 - 缺少旧字段数据:UI 仅显示最近部署的表单版本中的字段。请将旧字段标记为
hidden,而不是移除它们,以便继续显示;旧数据仍可通过listFormSubmissions获取。 - 仍无法解决:参阅 Netlify Support Guide,了解如何调试表单 — https://answers.netlify.com/t/common-issue-how-to-debug-your-form/92
限制
- 删除表单是永久操作:后续提交会返回
404,过去的提交将无法访问。请先导出 CSV。 - 提交的代码会经过清理(
<script>→ 转义实体)。 - 对于 PII,请定期导出并删除数据。
- 数据存储在 Netlify 的数据库中,只能通过 UI/API/CSV 访问。
Netlify 表单约定
这些是组织惯例和实践中总结的防护措施,并非文档事实——ctx-gen 会将其合并到渲染后的技能中,且不会生成这些内容。 摘自之前手写的 netlify-forms 技能;由技能维护者负责维护。
- 在 SSR 应用(Next.js、Nuxt、SvelteKit 等)中,
fetch("/")会被 SSR 全匹配函数拦截,永远不会到达 Netlify 的表单处理流程。 将 AJAX 提交 POST 到静态骨架文件本身(例如/__forms.html),不要提交到任意路径。 - 只使用有文档说明的接口:不要使用虚构的端点格式调用
https://api.netlify.com/...,也不要从本地 CLI 配置文件(~/Library/Preferences/netlify/config.json)中读取令牌。 - 通过 API 读取提交内容时,要翻页获取结果(
Link标头);只读取第一个响应的代码会悄无声息地丢弃其余内容。 - 对于由 JS 渲染的表单和 SSR 表单,始终创建静态骨架文件
public/__forms.html:其中包含每个表单的隐藏副本,带有data-netlify="true"、隐藏的form-name输入项,以及组件提交的每个字段——字段名称必须完全匹配(Netlify 会根据已注册的表单验证字段名称)。没有此文件,提交会悄无声息地失败。 - Astro 按需渲染的路由(
export const prerender = false或output: "server"路由)永远不会在构建时扫描,因此其中的表单永远不会注册。请将表单放在预渲染页面上,或依赖静态骨架文件。 - “缺失”的有效提交通常是 Akismet 误判:请检查垃圾邮件列表(或通过 API 使用
?state=spam),并将其标记为已验证。不要把自定义恢复函数或关闭垃圾邮件过滤作为首选方案。 - 对于自定义成功页面,请使用不带扩展名的
action路径(/thank-you,而不是/thank-you.html)——Netlify 会在/thank-you提供thank-you.html,而.html路径会返回 404。