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 та обов’язковий чекбокс.

Приклад форми зворотного зв&#39;язку з п&#39;ятьма полями введення

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-надсилання.