」工欲善其事,必先利其器。「—孔子《論語.錄靈公》
首頁 > 程式設計 > WordPress REST API無頭表單提交指南

WordPress REST API無頭表單提交指南

發佈於2025-04-15
瀏覽:528

If you’re building a WordPress site, you need a good reason not to choose a WordPress form plugin. They are convenient and offer plenty of customizations that would take a ton of effort to build from scratch. They render the HTML, validate the data, store the submissions, and provide integration with third-party services.

But suppose we plan to use WordPress as a headless CMS. In this case, we will be mainly interacting with the REST API (or GraphQL). The front-end part becomes our responsibility entirely, and we can’t rely anymore on form plugins to do the heavy lifting in that area. Now we’re in the driver’s seat when it comes to the front end.

Forms were a solved problem, but now we have to decide what to do about them. We have a couple of options:

  • Do we use our own custom API if we have such a thing? If not, and we don’t want to create one, we can go with a service. There are many good static form providers, and new ones are popping up constantly.
  • Can we keep using the WordPress plugin we already use and leverage its validation, storage, and integration?

The most popular free form plugin, Contact Form 7, has a submission REST API endpoint, and so does the well-known paid plugin, Gravity Forms, among others.

From a technical standpoint, there’s no real difference between submitting the form‘s data to an endpoint provided by a service or a WordPress plugin. So, we have to decide based on different criteria. Price is an obvious one; after that is the availability of the WordPress installation and its REST API. Submitting to an endpoint presupposes that it is always available publicly. That’s already clear when it comes to services because we pay for them to be available. Some setups might limit WordPress access to only editing and build processes. Another thing to consider is where you want to store the data, particularly in a way that adheres to GPDR regulations.

When it comes to features beyond the submission, WordPress form plugins are hard to match. They have their ecosystem, add-ons capable of generating reports, PDFs, readily available integration with newsletters, and payment services. Few services offer this much in a single package.

Even if we use WordPress in the “traditional” way with the front end based on a WordPress theme, using a form plugin’s REST API might make sense in many cases. For example, if we are developing a theme using a utility-first CSS framework, styling the rendered form with fixed markup structured with a BEM-like class convention leaves a sour taste in any developer’s mouth.

The purpose of this article is to present the two WordPress form plugins submission endpoints and show a way to recreate the typical form-related behaviors we got used to getting out of the box. When submitting a form, in general, we have to deal with two main problems. One is the submission of the data itself, and the other is providing meaningful feedback to the user.

So, let’s start there.

The endpoints

Submitting data is the more straightforward part. Both endpoints expect a POST request, and the dynamic part of the URL is the form ID.

Contact Form 7 REST API is available immediately when the plugin is activated, and it looks like this:

https://your-site.tld/wp-json/contact-form-7/v1/contact-forms//feedback

If we’re working with Gravity Forms, the endpoint takes this shape:

https://your-site.tld/wp-json/gf/v2/forms//submissions

The Gravity Forms REST API is disabled by default. To enable it, we have to go to the plugin’s settings, then to the REST API page, and check the “Enable access to the API” option. There is no need to create an API key, as the form submission endpoint does not require it.

Update (Sep. 10, 2024): The IDs are hashed in Contact Form 7 version 5.8, but can still to be found in the URL of the form’s editing page.

The body of the request

Our example form has five fields with the following rules:

  • a required text field
  • a required email field
  • a required date field that accepts dates before October 4, 1957
  • an optional textarea
  • a required checkbox

For Contact Form 7’s request’s body keys, we have to define them with the form-tags syntax:

{
  "somebodys-name": "Marian Kenney",
  "any-email": "[email protected]",
  "before-space-age": "1922-03-11",
  "optional-message": "",
  "fake-terms": "1"
}

Gravity Forms expects the keys in a different format. We have to use an auto-generated, incremental field ID with the input_ prefix. The ID is visible when you are editing the field.

{
  "input_1": "Marian Kenney",
  "input_2": "[email protected]",
  "input_3": "1922-03-11",
  "input_4": "",
  "input_5_1": "1"
}

Submitting the data

We can save ourselves a lot of work if we use the expected keys for the inputs’ name attributes. Otherwise, we have to map the input names to the keys.

Putting everything together, we get an HTML structure like this for Contact Form 7:

In the case of Gravity Forms, we only need to switch the action and the name attributes:

Since all the required information is available in the HTML, we are ready to send the request. One way to do this is to use the FormData in combination with the 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);

We can send the submission with little effort, but the user experience is subpar, to say the least. We owe to users as much guidance as possible to submit the form successfully. At the very least, that means we need to:

  • show a global error or success message,
  • add inline field validation error messages and possible directions, and
  • draw attention to parts that require attention with special classes.

Field validation

On top of using built-in HTML form validation, we can use JavaScript for additional client-side validation and/or take advantage of server-side validation.

When it comes to server-side validation, both Contact Form 7 and Gravity Forms offer that out of the box and return the validation error messages as part of the response. This is convenient as we can control the validation rules from the WordPress admin.

For more complex validation rules, like conditional field validation, it might make sense to rely only on the server-side because keeping the front-end JavaScript validation in sync with the plugins setting can become a maintenance issue.

If we solely go with the server-side validation, the task becomes about parsing the response, extracting the relevant data, and DOM manipulation like inserting elements and toggle class-names.

Response messages

The response when there is a validation error for Contact Form 7 look like this:

{
  "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"
    }
  ]
}

On successful submission, the response looks like this:

{
  "into": "#",
  "status": "mail_sent",
  "message": "Thank you for your message. It has been sent.",
  "posted_data_hash": "d52f9f9de995287195409fe6dcde0c50"
}

Compared to this, Gravity Forms’ validation error response is more compact:

{
  "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
}

But the response on a successful submission is bigger:

{
  "is_valid": true,
  "page_number": 0,
  "source_page_number": 1,
  "confirmation_message": "
Thanks for contacting us! We will get in touch with you shortly.
", "confirmation_type": "message" }

While both contain the information we need, they don‘t follow a common convention, and both have their quirks. For example, the confirmation message in Gravity Forms contains HTML, and the validation message keys don’t have the input_ prefix — the prefix that’s required when we send the request. On the other side, validation errors in Contact Form 7 contain information that is relevant only to their front-end implementation. The field keys are not immediately usable; they have to be extracted.

In a situation like this, instead of working with the response we get, it’s better to come up with a desired, ideal format. Once we have that, we can find ways to transform the original response to what we see fit. If we combine the best of the two scenarios and remove the irrelevant parts for our use case, then we end up with something like this:

{
  "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."
  }
}

And on successful submission, we would set isSuccess to true and return an empty validation error object:

{
  "isSuccess": true,
  "message": "Thanks for contacting us! We will get in touch with you shortly.",
  "validationError": {}
}

Now it’s a matter of transforming what we got to what we need. The code to normalize the Contact Forms 7 response is this:

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,
  };
};

The code to normalize the Gravity Forms response winds up being this:

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,
  };
};

We are still missing a way to display the validation errors, success messages, and toggling classes. However, we have a neat way of accessing the data we need, and we removed all of the inconsistencies in the responses with a light abstraction. When put together, it’s ready to be dropped into an existing codebase, or we can continue building on top of it.

There are many ways to tackle the remaining part. What makes sense will depend on the project. For situations where we mainly have to react to state changes, a declarative and reactive library can help a lot. Alpine.js was covered here on CSS-Tricks, and it’s a perfect fit for both demonstrations and using it in production sites. Almost without any modification, we can reuse the code from the previous example. We only need to add the proper directives and in the right places.

Wrapping up

Matching the front-end experience that WordPress form plugins provide can be done with relative ease for straightforward, no-fuss forms — and in a way that is reusable from project to project. We can even accomplish it in a way that allows us to switch the plugin without affecting the front end.

Sure, it takes time and effort to make a multi-page form, previews of the uploaded images, or other advanced features that we’d normally get baked right into a plugin, but the more unique the requirements we have to meet, the more it makes sense to use the submission endpoint as we don’t have to work against the given front-end implementation that tries to solve many problems, but never the particular one we want.

Using WordPress as a headless CMS to access the REST API of a form plugin to hit the submissions endpoints will surely become a more widely used practice. It’s something worth exploring and to keep in mind. In the future, I would not be surprised to see WordPress form plugins designed primarily to work in a headless context like this. I can imagine a plugin where front-end rendering is an add-on feature that’s not an integral part of its core. What consequences that would have, and if it could have commercial success, remains to be explored but is a fascinating space to watch evolve.

最新教學 更多>
  • Go web應用何時關閉數據庫連接?
    Go web應用何時關閉數據庫連接?
    在GO Web Applications中管理數據庫連接很少,考慮以下簡化的web應用程序代碼:出現的問題:何時應在DB連接上調用Close()方法? ,該特定方案將自動關閉程序時,該程序將在EXITS EXITS EXITS出現時自動關閉。但是,其他考慮因素可能保證手動處理。 選項1:隱式關閉終...
    程式設計 發佈於2025-04-18
  • 為你的所見即所得編輯器添加圖像編輯功能
    為你的所見即所得編輯器添加圖像編輯功能
    图像编辑是每个Wysiwyg编辑器的强大功能。它极大地增强了应用程序的功能,并使想要使用上传图像做更多更多事情的用户感到高兴。 如今,用户期望应用程序具有图像编辑功能,但是实现此类功能并不像听起来那么琐碎。 上传图像和其他媒体可能是编辑器具有的正常功能,但是图像编辑通常不是。这就是为什么在本教程中,...
    程式設計 發佈於2025-04-18
  • 為什麼HTML無法打印頁碼及解決方案
    為什麼HTML無法打印頁碼及解決方案
    無法在html頁面上打印頁碼? @page規則在@Media內部和外部都無濟於事。 HTML:Customization:@page { margin: 10%; @top-center { font-family: sans-serif; font-weight: ...
    程式設計 發佈於2025-04-18
  • 如何避免Go語言切片時的內存洩漏?
    如何避免Go語言切片時的內存洩漏?
    ,a [j:] ...雖然通常有效,但如果使用指針,可能會導致內存洩漏。這是因為原始的備份陣列保持完整,這意味著新切片外部指針引用的任何對象仍然可能佔據內存。 copy(a [i:] 對於k,n:= len(a)-j i,len(a); k
    程式設計 發佈於2025-04-18
  • 如何正確使用與PDO參數的查詢一樣?
    如何正確使用與PDO參數的查詢一樣?
    在pdo 中使用類似QUERIES在PDO中的Queries時,您可能會遇到類似疑問中描述的問題:此查詢也可能不會返回結果,即使$ var1和$ var2包含有效的搜索詞。錯誤在於不正確包含%符號。 通過將變量包含在$ params數組中的%符號中,您確保將%字符正確替換到查詢中。沒有此修改,PD...
    程式設計 發佈於2025-04-18
  • NLTK如何高效地將文本分割成句子?
    NLTK如何高效地將文本分割成句子?
    如何有效地將文本分配到句子將文本分配到句子中可能是一個棘手的任務。縮寫和句子內的使用時的微妙之處可能會構成挑戰。 While many approaches exist, one effective method involves leveraging the Natural Language To...
    程式設計 發佈於2025-04-18
  • eval()vs. ast.literal_eval():對於用戶輸入,哪個Python函數更安全?
    eval()vs. ast.literal_eval():對於用戶輸入,哪個Python函數更安全?
    稱量()和ast.literal_eval()中的Python Security 在使用用戶輸入時,必須優先確保安全性。強大的python功能eval()通常是作為潛在解決方案而出現的,但擔心其潛在風險。 This article delves into the differences betwee...
    程式設計 發佈於2025-04-18
  • 如何更改選擇框選項的背景顏色?
    如何更改選擇框選項的背景顏色?
    如何在打開時如何更改選擇框選項的背景顏色在嘗試自定義選擇框時,您會遇到難度在激活時更改選項的難度。要解決這個問題,至關重要的是針對正確的html元素。 高級自定義: { 背景:RGBA(150、150、150、0.3); } [2 選項1 選項2 < 背景:藍色; }
    程式設計 發佈於2025-04-18
  • 表單刷新後如何防止重複提交?
    表單刷新後如何防止重複提交?
    在Web開發中預防重複提交 在表格提交後刷新頁面時,遇到重複提交的問題是常見的。要解決這個問題,請考慮以下方法: 想像一下具有這樣的代碼段,看起來像這樣的代碼段:)){ //數據庫操作... 迴聲“操作完成”; 死(); } ? > ...
    程式設計 發佈於2025-04-18
  • 如何在Java中正確顯示“ DD/MM/YYYY HH:MM:SS.SS”格式的當前日期和時間?
    如何在Java中正確顯示“ DD/MM/YYYY HH:MM:SS.SS”格式的當前日期和時間?
    如何在“ dd/mm/yyyy hh:mm:mm:ss.ss”格式“ gormat 解決方案:的,請訪問量很大,並應為procectiquiestate的,並在整個代碼上正確格式不多: java.text.simpledateformat; 導入java.util.calendar; 導入java...
    程式設計 發佈於2025-04-18
  • Android如何向PHP服務器發送POST數據?
    Android如何向PHP服務器發送POST數據?
    在android apache httpclient(已棄用) httpclient httpclient = new defaulthttpclient(); httppost httppost = new httppost(“ http://www.yoursite.com/script.p...
    程式設計 發佈於2025-04-18
  • 如何將PANDAS DataFrame列轉換為DateTime格式並按日期過濾?
    如何將PANDAS DataFrame列轉換為DateTime格式並按日期過濾?
    Transform Pandas DataFrame Column to DateTime FormatScenario:Data within a Pandas DataFrame often exists in various formats, including strings.使用時間數據時...
    程式設計 發佈於2025-04-18
  • HTML格式標籤
    HTML格式標籤
    HTML 格式化元素 **HTML Formatting is a process of formatting text for better look and feel. HTML provides us ability to format text without us...
    程式設計 發佈於2025-04-18
  • 您可以使用CSS在Chrome和Firefox中染色控制台輸出嗎?
    您可以使用CSS在Chrome和Firefox中染色控制台輸出嗎?
    在javascript console 中顯示顏色是可以使用chrome的控制台顯示彩色文本,例如紅色的redors,for for for for錯誤消息? 回答是的,可以使用CSS將顏色添加到Chrome和Firefox中的控制台顯示的消息(版本31或更高版本)中。要實現這一目標,請使用以下...
    程式設計 發佈於2025-04-18
  • 快速解決MySQL "root@localhost訪問被拒"錯誤
    快速解決MySQL "root@localhost訪問被拒"錯誤
    [2 求解'root'user 的mysql“訪問拒絕”錯誤 常見的mysql錯誤“訪問用戶'root'@'localhost'(使用密碼:yes)”通常會導致用戶沿著複雜解決方案的兔子孔掉落。 幸運的是,一個簡單的修復程序通常可行。 而不是複雜的...
    程式設計 發佈於2025-04-18

免責聲明: 提供的所有資源部分來自互聯網,如果有侵犯您的版權或其他權益,請說明詳細緣由並提供版權或權益證明然後發到郵箱:[email protected] 我們會在第一時間內為您處理。

Copyright© 2022 湘ICP备2022001581号-3