如果您正在构建一个 WordPress 网站,那么您需要一个充分的理由不选择 WordPress 表单插件。它们非常方便,并提供大量自定义选项,这些选项如果从头开始构建将需要大量工作。它们渲染 HTML、验证数据、存储提交内容,并提供与第三方服务的集成。
但是,假设我们计划将 WordPress 用作 无头 CMS。在这种情况下,我们将主要与 REST API(或 GraphQL)进行交互。前端部分完全成为我们的责任,我们不能再依赖表单插件来完成该领域的繁重工作。现在,在前端方面,我们掌握了主动权。
表单曾经是一个已解决的问题,但现在我们必须决定如何处理它们。我们有几个选择
- 如果我们有自己的自定义 API,是否使用它?如果没有,并且我们不想创建自定义 API,我们可以使用服务。有很多 优秀的静态表单提供商,并且新的提供商不断涌现。
- 我们能否继续使用我们已经使用的 WordPress 插件并利用其验证、存储和集成功能?
最流行的免费表单插件 Contact Form 7 具有提交 REST API 端点,同样知名的付费插件 Gravity Forms 也具有该端点,以及其他一些插件。
从技术角度来看,将表单数据提交到服务提供的端点或 WordPress 插件提供的端点之间没有真正的区别。因此,我们必须根据不同的标准做出决定。价格是一个显而易见的因素;其次是 WordPress 安装及其 REST API 的可用性。提交到端点的前提是它始终公开可用。对于服务来说,这一点已经很明确,因为我们支付费用以确保其可用性。某些设置可能会将 WordPress 访问权限限制为仅编辑和构建流程。另一个需要考虑的因素是您希望将数据存储在何处,尤其是在遵守 GDPR 规定方面。
在提交之外的功能方面,WordPress 表单插件很难匹敌。它们拥有自己的生态系统,附加组件能够生成报告、PDF,并轻松集成到新闻通讯和支付服务中。很少有服务能够在一个软件包中提供如此多的功能。
即使我们以“传统”方式使用 WordPress,前端基于 WordPress 主题,在许多情况下使用表单插件的 REST API 仍然有意义。例如,如果我们使用实用优先 CSS 框架开发主题,使用固定标记和类似 BEM 的类约定对渲染的表单进行样式设置会让任何开发者感到不爽。
本文的目的是介绍这两个 WordPress 表单插件的提交端点,并展示一种方法来重新创建我们习惯于开箱即用的典型表单相关行为。通常,在提交表单时,我们必须处理两个主要问题。一个是数据本身的提交,另一个是向用户提供有意义的反馈。
因此,让我们从这里开始。
端点
提交数据是比较简单的一部分。这两个端点都期望一个 POST
请求,并且 URL 的动态部分是表单 ID。
激活 Contact Form 7 插件后,其 REST API 会立即可用,如下所示
https://your-site.tld/wp-json/contact-form-7/v1/contact-forms/<FORM_ID>/feedback
如果我们使用 Gravity Forms,则端点采用以下形式
https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions
Gravity Forms REST API 默认情况下是禁用的。要启用它,我们必须转到插件的设置,然后转到 REST API 页面,并选中“启用对 API 的访问”选项。无需创建 API 密钥,因为表单提交端点不需要它。
请求正文
我们的示例表单有五个字段,具有以下规则
- 一个必填文本字段
- 一个必填电子邮件字段
- 一个必填日期字段,接受 1957 年 10 月 4 日之前的日期
- 一个可选文本区域
- 一个必填复选框

对于 Contact Form 7 请求的 body
密钥,我们必须使用 表单标签语法 定义它们
{
"somebodys-name": "Marian Kenney",
"any-email": "[email protected]",
"before-space-age": "1922-03-11",
"optional-message": "",
"fake-terms": "1"
}
Gravity Forms 期望密钥采用不同的格式。我们必须使用以 input_
为前缀的自动生成的增量字段 ID。在编辑字段时,可以查看 ID。

{
"input_1": "Marian Kenney",
"input_2": "[email protected]",
"input_3": "1922-03-11",
"input_4": "",
"input_5_1": "1"
}
提交数据
如果我们对输入的 name
属性使用预期的密钥,可以节省大量工作。否则,我们必须将输入名称映射到密钥。
将所有内容组合在一起,我们得到如下所示的 Contact Form 7 HTML 结构
<form action="https://your-site.tld/wp-json/contact-form-7/v1/contact-forms/<FORM_ID>/feedback" method="post">
<label for="somebodys-name">Somebody's name</label>
<input id="somebodys-name" type="text" name="somebodys-name">
<!-- Other input elements -->
<button type="submit">Submit</button>
</form>
对于 Gravity Forms,我们只需要切换 action
和 name
属性
<form action="https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions" method="post">
<label for="input_1">Somebody's name</label>
<input id="input_1" type="text" name="input_1">
<!-- Other input elements -->
<button type="submit">Submit</button>
</form>
由于所有必需的信息都可以在 HTML 中找到,因此我们已准备好发送请求。一种方法是将 FormData
与 fetch
结合使用
const formSubmissionHandler = (event) => {
event.preventDefault();
const formElement = event.target,
{ action, method } = formElement,
body = new FormData(formElement);
fetch(action, {
method,
body
})
.then((response) => response.json())
.then((response) => {
// Determine if the submission is not valid
if (isFormSubmissionError(response)) {
// Handle the case when there are validation errors
}
// Handle the happy path
})
.catch((error) => {
// Handle the case when there's a problem with the request
});
};
const formElement = document.querySelector("form");
formElement.addEventListener("submit", formSubmissionHandler);
我们可以轻松地发送提交内容,但用户体验至少可以说是不尽如人意。我们应该尽可能地为用户提供指导,以便他们成功提交表单。至少,这意味着我们需要
- 显示全局错误或成功消息,
- 添加内联字段验证错误消息和可能的说明,以及
- 使用特殊类吸引人们注意需要关注的部分。
字段验证
除了使用内置的 HTML 表单验证之外,我们还可以使用 JavaScript 进行额外的客户端验证和/或利用服务器端验证。
在服务器端验证方面,Contact Form 7 和 Gravity Forms 都开箱即用地提供了该功能,并将验证错误消息作为响应的一部分返回。这很方便,因为我们可以从 WordPress 管理员控制验证规则。
对于更复杂的验证规则(如条件字段验证),仅依赖服务器端可能更有意义,因为使前端 JavaScript 验证与插件设置保持同步可能会成为维护问题。
如果我们只使用服务器端验证,则任务变为解析响应、提取相关数据以及 DOM 操作(如插入元素和切换类名)。
响应消息
Contact Form 7 验证错误时的响应如下所示
{
"into": "#",
"status": "validation_failed",
"message": "One or more fields have an error. Please check and try again.",
"posted_data_hash": "",
"invalid_fields": [
{
"into": "span.wpcf7-form-control-wrap.somebodys-name",
"message": "The field is required.",
"idref": null,
"error_id": "-ve-somebodys-name"
},
{
"into": "span.wpcf7-form-control-wrap.any-email",
"message": "The field is required.",
"idref": null,
"error_id": "-ve-any-email"
},
{
"into": "span.wpcf7-form-control-wrap.before-space-age",
"message": "The field is required.",
"idref": null,
"error_id": "-ve-before-space-age"
},
{
"into": "span.wpcf7-form-control-wrap.fake-terms",
"message": "You must accept the terms and conditions before sending your message.",
"idref": null,
"error_id": "-ve-fake-terms"
}
]
}
成功提交时的响应如下所示
{
"into": "#",
"status": "mail_sent",
"message": "Thank you for your message. It has been sent.",
"posted_data_hash": "d52f9f9de995287195409fe6dcde0c50"
}
相比之下,Gravity Forms 的验证错误响应更加简洁
{
"is_valid": false,
"validation_messages": {
"1": "This field is required.",
"2": "This field is required.",
"3": "This field is required.",
"5": "This field is required."
},
"page_number": 1,
"source_page_number": 1
}
但是成功提交时的响应更大
{
"is_valid": true,
"page_number": 0,
"source_page_number": 1,
"confirmation_message": "<div id='gform_confirmation_wrapper_1' class='gform_confirmation_wrapper '><div id='gform_confirmation_message_1' class='gform_confirmation_message_1 gform_confirmation_message'>Thanks for contacting us! We will get in touch with you shortly.</div></div>",
"confirmation_type": "message"
}
虽然两者都包含我们所需的信息,但它们没有遵循通用约定,并且两者都有一些特殊之处。例如,Gravity Forms 中的确认消息包含 HTML,并且验证消息密钥没有 input_
前缀——我们在发送请求时需要的前缀。另一方面,Contact Form 7 中的验证错误包含的信息仅与其前端实现相关。字段密钥不能立即使用;它们必须被提取。
在这种情况下,与其使用我们获得的响应,不如想出一个我们想要的理想格式。一旦有了该格式,我们就可以找到方法将原始响应转换为我们认为合适的格式。如果我们结合这两个场景的优点并删除与我们的用例无关的部分,那么我们最终会得到类似以下内容
{
"isSuccess": false,
"message": "One or more fields have an error. Please check and try again.",
"validationError": {
"somebodys-name": "This field is required.",
"any-email": "This field is required.",
"input_3": "This field is required.",
"input_5": "This field is required."
}
}
成功提交时,我们将 isSuccess
设置为 true
并返回一个空的验证错误对象
{
"isSuccess": true,
"message": "Thanks for contacting us! We will get in touch with you shortly.",
"validationError": {}
}
现在,问题是如何将我们获得的内容转换为我们所需的内容。规范化 Contact Forms 7 响应的代码如下所示
const normalizeContactForm7Response = (response) => {
// The other possible statuses are different kind of errors
const isSuccess = response.status === 'mail_sent';
// A message is provided for all statuses
const message = response.message;
const validationError = isSuccess
? {}
: // We transform an array of objects into an object
Object.fromEntries(
response.invalid_fields.map((error) => {
// Extracts the part after "cf7-form-control-wrap"
const key = /cf7[-a-z]*.(.*)/.exec(error.into)[1];
return [key, error.message];
})
);
return {
isSuccess,
message,
validationError,
};
};
规范化 Gravity Forms 响应的代码如下所示
const normalizeGravityFormsResponse = (response) => {
// Provided already as a boolean in the response
const isSuccess = response.is_valid;
const message = isSuccess
? // Comes wrapped in a HTML and we likely don't need that
stripHtml(response.confirmation_message)
: // No general error message, so we set a fallback
'There was a problem with your submission.';
const validationError = isSuccess
? {}
: // We replace the keys with the prefixed version;
// this way the request and response matches
Object.fromEntries(
Object.entries(
response.validation_messages
).map(([key, value]) => [`input_${key}`, value])
);
return {
isSuccess,
message,
validationError,
};
};
我们仍然缺少一种方法来显示验证错误、成功消息和切换类。但是,我们有了一种简洁的方法来访问我们所需的数据,并且通过轻量级抽象消除了响应中的所有不一致之处。当组合在一起时,它可以轻松地放入现有的代码库中,或者我们可以继续在其基础上进行构建。
解决剩余部分的方法有很多。哪种方法最合适取决于项目。对于我们主要需要对状态变化做出反应的情况,声明式和反应式库可以提供很大帮助。Alpine.js 在 CSS-Tricks 上 有所介绍,它非常适合演示和在生产网站中使用。几乎无需任何修改,我们就可以重用上一个示例中的代码。我们只需要在正确的位置添加适当的指令。
总结
对于简单易用的表单来说,复制 WordPress 表单插件提供的前端体验相对容易,并且可以实现跨项目的重用。我们甚至可以以一种允许我们切换插件而不会影响前端的方式来实现它。
当然,制作多页表单、上传图片的预览或其他我们通常会直接嵌入到插件中的高级功能需要时间和精力,但是我们遇到的需求越独特,使用提交端点就越有意义,因为我们不必与试图解决许多问题但从未解决我们想要解决的特定问题的给定前端实现对抗。
使用 WordPress 作为无头 CMS 来访问表单插件的 REST API 以访问提交端点,这肯定将成为一种更广泛使用的实践。这值得探索并牢记。将来,我不会对看到主要针对无头上下文设计的 WordPress 表单插件感到惊讶。我可以想象一个插件,其中前端渲染是一个附加功能,而不是其核心不可分割的一部分。这将产生什么后果,以及它是否能够取得商业成功,还有待探索,但这是一个值得关注的迷人领域。
您好,您的文章非常有帮助,但是缺少了一点,如何利用从 WordPress 后台设置的 recaptcha?
提前感谢!
如何发送到感谢页面(URL)而不是消息?
现在 CF7 使用散列 ID 作为表单,因此这不再有效。