Skip to content

Всё для WordPress, веб-разработки — и не только

📤 Отправка форм в headless WordPress: REST API Contact Form 7 и Gravity Forms

📤 Отправка форм в headless WordPress: REST API Contact Form 7 и Gravity Forms

Вы строите сайт на WordPress, и форма обратной связи для вас, решённый вопрос. Плагины вроде Contact Form 7 дают готовый HTML, валидацию, хранение отправок и десятки интеграций. Нажать «Установить» и вставить шорткод, дело двух минут.

Но всё меняется, когда WordPress становится headless CMS. Вы отвечаете за фронтенд целиком: React, Vue, чистый HTML/JS. И плагин форм, который раньше рендерил разметку за вас, больше не контролирует клиентскую часть. Его REST API, однако, никуда не делся. Достаточно отправить POST на правильный эндпоинт, и вся мощь плагина (проверка полей, хранение, интеграции) остаётся при вас.

На практике через REST API плагинов форм можно закрыть и сугубо «традиционные» кейсы. Скажем, верстаете кастомную тему на Tailwind, и фиксированная разметка CF7 с её жёсткой структурой классов режет глаз. Отправка через API позволяет контролировать каждый пиксель формы без отказа от наработанной экосистемы плагина.

💡 Быстрый обзор:

  • Какие эндпоинты дают Contact Form 7 и Gravity Forms и как их активировать в headless-окружении.
  • В каком формате отправлять поля, чтобы плагин корректно принял данные и вернул результат.
  • Как собрать HTML-форму, привязать fetch-запрос и показать пользователю success-сообщение или ошибки валидации.
  • Как унифицировать разнобой форматов ответа от CF7 и Gravity Forms в единую удобную структуру.

Что нужно знать о конечных точках

Отправка данных через REST API, технически простая часть. Оба плагина ожидают POST на эндпоинт, где динамический сегмент URL, идентификатор конкретной формы.

Contact Form 7 предоставляет REST API сразу после активации. Эндпоинт выглядит так:

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

С версии 5.8 (август 2023) Contact Form 7 перешёл на хешированные SHA-1 идентификаторы форм. Старые числовые ID продолжают работать, но для новых форм идентификатор нужно брать из URL страницы редактирования формы в админке, это последний сегмент после post=. На апрель 2026 года плагин насчитывает более 10 миллионов активных установок и протестирован вплоть до WordPress 7.0.

Gravity Forms использует REST API v2 (доступен с версии 2.4):

1https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions

Важный нюанс: REST API Gravity Forms отключён по умолчанию. Чтобы активировать, зайдите в настройки плагина → вкладка REST API → отметьте «Enable access to the API». API-ключ для эндпоинта отправки форм не нужен, он публичный по дизайну. Идентификатор формы в Gravity Forms, числовой, виден в админке при редактировании.

Структура тела запроса

Возьмём для примера форму из пяти полей: обязательные текст, email и дата (до 4 октября 1957 года), опциональный textarea и обязательный чекбокс.

Пример формы обратной связи с пятью полями ввода

Contact Form 7 ожидает ключи в формате, заданном через синтаксис тегов формы. Ключ совпадает с name соответствующего поля в HTML:

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

Gravity Forms использует другой подход: автоматически генерируемые инкрементные идентификаторы с префиксом input_. ID поля виден прямо в админке при редактировании конкретного поля.

Редактирование поля Gravity Forms с видимым идентификатором input_3

Для той же формы тело запроса к Gravity Forms выглядит иначе:

1{
2 "input_1": "Marian Kenney",
3 "input_2": "[email protected]",
4 "input_3": "1922-03-11",
5 "input_4": "",
6 "input_5_1": "1"
7}

Ключевой вывод: если вы дадите HTML-инпутам атрибуты name, совпадающие с ожидаемыми ключами плагина, то сопоставление происходит автоматически, FormData соберёт данные в нужном формате без ручного маппинга.

Собираем HTML и отправляем запрос

Для Contact Form 7 HTML-разметка будет такой (обратите внимание на action, это эндпоинт, а name полей совпадает с ключами выше):

1<form action="https://your-site.tld/wp-json/contact-form-7/v1/contact-forms/<FORM_ID>/feedback" method="post">
2 <label for="somebodys-name">Ваше имя</label>
3 <input id="somebodys-name" type="text" name="somebodys-name" required>
4
5 <label for="any-email">Email</label>
6 <input id="any-email" type="email" name="any-email" required>
7
8 <label for="before-space-age">Дата</label>
9 <input id="before-space-age" type="date" name="before-space-age" max="1957-10-04" required>
10
11 <label for="optional-message">Сообщение</label>
12 <textarea id="optional-message" name="optional-message"></textarea>
13
14 <label>
15 <input type="checkbox" name="fake-terms" value="1" required>
16 Я принимаю условия
17 </label>
18
19 <button type="submit">Отправить</button>
20</form>

Для Gravity Forms меняются только action и атрибуты name:

1<form action="https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions" method="post">
2 <label for="input_1">Ваше имя</label>
3 <input id="input_1" type="text" name="input_1" required>
4 <!-- ... -->
5</form>

Теперь отправка через JavaScript, FormData сам собирает значения по name, ничего маппить не нужно:

1const formSubmissionHandler = (event) => {
2 event.preventDefault();
3
4 const formElement = event.target;
5 const { action, method } = formElement;
6 const body = new FormData(formElement);
7
8 fetch(action, { method, body })
9 .then((response) => response.json())
10 .then((response) => {
11 if (isFormSubmissionError(response)) {
12 // Обработка ошибок валидации
13 handleValidationErrors(response);
14 return;
15 }
16 // Успешная отправка
17 handleSuccess(response);
18 })
19 .catch((error) => {
20 // Сетевая ошибка или недоступность сервера
21 handleNetworkError(error);
22 });
23};
24
25const formElement = document.querySelector("form");
26formElement.addEventListener("submit", formSubmissionHandler);

Данные ушли. Но пользователю этого мало, ему нужна обратная связь: сообщение об успехе, подсветка полей с ошибками, глобальное уведомление. К счастью, оба плагина возвращают эту информацию в ответе.

Валидация: сервер решает, клиент показывает

Помимо встроенной HTML5-валидации (атрибуты required, type="email" и max) разумно положиться на серверную проверку правил, которую дают плагины. Почему: правила настраиваются в админке WordPress централизованно, а дублировать их на клиенте, двойная работа и источник расхождений.

И Contact Form 7, и Gravity Forms возвращают ошибки валидации прямо в теле ответа. Для сложных сценариев, условные поля, зависимая валидация, опора на серверную проверку особенно выигрышна: не нужно синхронизировать логику между фронтендом и настройками плагина.

Задача сводится к трём шагам: разобрать JSON-ответ, извлечь сообщения об ошибках и вставить их в DOM рядом с соответствующими полями.

Форматы ответов и их нормализация

Ответ Contact Form 7 при ошибке валидации:

1{
2 "into": "#",
3 "status": "validation_failed",
4 "message": "One or more fields have an error. Please check and try again.",
5 "posted_data_hash": "",
6 "invalid_fields": [
7 {
8 "into": "span.wpcf7-form-control-wrap.somebodys-name",
9 "message": "The field is required.",
10 "idref": null,
11 "error_id": "-ve-somebodys-name"
12 }
13 ]
14}

При успехе ответ компактнее:

1{
2 "into": "#",
3 "status": "mail_sent",
4 "message": "Thank you for your message. It has been sent.",
5 "posted_data_hash": "d52f9f9de995287195409fe6dcde0c50"
6}

Ответ Gravity Forms при ошибке валидации устроен иначе:

1{
2 "is_valid": false,
3 "validation_messages": {
4 "1": "This field is required.",
5 "2": "This field is required.",
6 "3": "This field is required.",
7 "5": "This field is required."
8 },
9 "page_number": 1,
10 "source_page_number": 1
11}

А успешный ответ содержит подтверждение внутри HTML:

1{
2 "is_valid": true,
3 "page_number": 0,
4 "source_page_number": 1,
5 "confirmation_message": "<div>Thanks for contacting us! We will get in touch with you shortly.</div>",
6 "confirmation_type": "message"
7}

Разница подходов очевидна: CF7 вкладывает ошибки в массив объектов с CSS-селекторами, а Gravity Forms, в плоский объект с числовыми ключами без префикса input_. Сообщение об успехе у Gravity Forms приходит в HTML-обёртке. Ключи полей в ответах CF7 «зашиты» в селекторы (например, span.wpcf7-form-control-wrap.somebodys-name) и требуют извлечения регуляркой.

Вместо того чтобы ветвить логику под каждый плагин, удобнее привести оба ответа к единому формату:

1{
2 "isSuccess": false,
3 "message": "One or more fields have an error. Please check and try again.",
4 "validationError": {
5 "somebodys-name": "The field is required.",
6 "any-email": "The field is required.",
7 "input_3": "The field is required.",
8 "input_5": "This field is required."
9 }
10}

При успехе isSuccess выставляется в true, а validationError приходит пустым объектом.

Код нормализации для Contact Form 7:

1const normalizeContactForm7Response = (response) => {
2 const isSuccess = response.status === 'mail_sent';
3 const message = isSuccess
4 ? response.message
5 : response.message || 'One or more fields have an error.';
6
7 const validationError = isSuccess
8 ? {}
9 : Object.fromEntries(
10 response.invalid_fields.map((error) => {
11 const key = /cf7[-a-z]*.(.*)/.exec(error.into)[1];
12 return [key, error.message];
13 })
14 );
15
16 return { isSuccess, message, validationError };
17};

Код нормализации для Gravity Forms (обратите внимание: ключам ошибок добавляется префикс input_, чтобы они совпадали с ключами запроса):

1const normalizeGravityFormsResponse = (response) => {
2 const isSuccess = response.is_valid;
3 const message = isSuccess
4 ? stripHtml(response.confirmation_message)
5 : 'There was a problem with your submission.';
6
7 const validationError = isSuccess
8 ? {}
9 : Object.fromEntries(
10 Object.entries(response.validation_messages).map(([key, value]) => [
11 `input_${key}`,
12 value,
13 ])
14 );
15
16 return { isSuccess, message, validationError };
17};

Теперь у вас есть единый объект ответа независимо от плагина. Остаётся дописать отображение ошибок и переключение классов на DOM-элементах, и форма готова к бою.

От нормализации к живому интерфейсу

Когда ответ приведён к единой структуре, отображение обратной связи сводится к манипуляции DOM. Добавить сообщение об ошибке рядом с полем, переключить класс у обёртки и показать глобальное уведомление: этих трёх действий достаточно для подавляющего большинства сценариев.

Для реактивного обновления интерфейса удобно использовать лёгкие декларативные библиотеки вроде Alpine.js. Минимальный синтаксис, отсутствие шага сборки и естественная интеграция с серверным ответом делают его практичным выбором для форм в headless-окружении. Подход с Alpine.js детально разбирался на CSS-Tricks, код из того материала практически без изменений ложится на нормализованный ответ, который мы получили выше.

Что в итоге

Повторить клиентскую часть, которую плагины форм дают «из коробки», для простых форм, задача на пару часов. Приятный бонус: абстрагировав ответ через функцию-нормализатор, вы получаете сменный бэкенд. Перейти с Contact Form 7 на Gravity Forms (или наоборот) можно без правок фронтенда, достаточно заменить эндпоинт и функцию нормализации.

Многостраничные формы, предпросмотр загруженных изображений, калькуляторы стоимости, да, это уже серьёзная разработка. Но чем более уникальные требования у проекта, тем сильнее аргумент в пользу собственного фронтенда поверх REST API: вы не боретесь с чужой разметкой и не обходите ограничения готового рендеринга.

Headless-подход к формам, не гипотетическое будущее. Уже сегодня плагины вроде Contact Form 7 и Gravity Forms дают полноценный REST API, а фронтенд-фреймворки позволяют собрать форму за часы, а не дни. Попробуйте на ближайшем проекте, где внешний вид формы критичен: возьмите CF7 или GF как бэкенд и соберите интерфейс с нуля. Скорее всего, вы удивитесь, насколько это прямолинейно.

⁉️🤔 Частые вопросы

Работает ли REST API Contact Form 7 на бесплатной версии?

Да, REST API доступен сразу после активации бесплатного плагина, никаких дополнительных настроек не требуется. На апрель 2026 года у Contact Form 7 более 10 миллионов активных установок, и REST API остаётся стабильной частью ядра плагина начиная с версии 4.8.

Чем отличаются хешированные ID форм в новых версиях Contact Form 7?

Начиная с версии 5.8 (август 2023) CF7 генерирует SHA-1 хеш в качестве идентификатора формы вместо числового ID. Старые числовые ID продолжают работать. Хеш можно найти в URL страницы редактирования формы в админке: /wp-admin/admin.php?page=wpcf7&post=<HASH>&action=edit. В эндпоинте он подставляется так же, как и числовой ID.

Нужен ли API-ключ для отправки форм через Gravity Forms REST API?

Нет, эндпоинт /gf/v2/forms/<ID>/submissions не требует аутентификации для отправки. Однако сам REST API Gravity Forms отключён по умолчанию, его нужно включить в настройках плагина (Forms → Settings → REST API → Enable access to the API).

Можно ли использовать один и тот же JavaScript-код для Contact Form 7 и Gravity Forms?

Да, именно для этого нужна нормализация ответа. Обе функции-нормализатора (для CF7 и GF) возвращают объект с одинаковой структурой: поля isSuccess, message и validationError. Подключите нужную функцию в зависимости от плагина, и весь остальной код (отображение ошибок, подсветка полей, глобальное уведомление) будет работать без изменений.

Что делать, если форма не отправляется и сервер возвращает 404?

Проверьте три вещи: включён ли REST API плагина (особенно актуально для Gravity Forms), правильный ли идентификатор формы в URL эндпоинта, и не заблокирован ли REST API на уровне сервера или плагина безопасности. В Contact Form 7 также убедитесь, что WordPress REST API активен глобально, без него CF7 не сможет обработать AJAX-отправку.