Wenn Sie eine WordPress-Website erstellen, benötigen Sie einen guten Grund, sich *nicht* für ein WordPress-Formular-Plugin zu entscheiden. Sie sind praktisch und bieten viele Anpassungsmöglichkeiten, deren Erstellung von Grund auf viel Aufwand bedeuten würde. Sie rendern das HTML, validieren die Daten, speichern die Einreichungen und bieten Integrationen mit Drittanbieterdiensten.
Aber nehmen wir an, wir planen, WordPress als headless CMS zu verwenden. In diesem Fall werden wir hauptsächlich mit der REST API (oder GraphQL) interagieren. Der Front-End-Teil wird vollständig unsere Verantwortung, und wir können uns nicht mehr auf Formular-Plugins verlassen, um die Hauptarbeit in diesem Bereich zu erledigen. Jetzt sitzen wir am Steuer, wenn es um das Front-End geht.
Formulare waren ein gelöstes Problem, aber jetzt müssen wir entscheiden, was wir damit machen. Wir haben ein paar Optionen:
- Nutzen wir unsere eigene benutzerdefinierte API, falls wir so etwas haben? Wenn nicht, und wir wollen keine erstellen, können wir auf einen Dienst zurückgreifen. Es gibt viele gute statische Formularanbieter, und ständig tauchen neue auf.
- Können wir das bereits verwendete WordPress-Plugin weiterhin nutzen und dessen Validierung, Speicherung und Integration verwenden?
Das beliebteste kostenlose Formular-Plugin, Contact Form 7, verfügt über einen Submission REST API-Endpunkt, ebenso wie das bekannte kostenpflichtige Plugin, Gravity Forms, unter anderem.
Aus technischer Sicht gibt es keinen wirklichen Unterschied zwischen der Übermittlung der Formulardaten an einen Endpunkt, der von einem Dienst oder einem WordPress-Plugin bereitgestellt wird. Wir müssen also anhand anderer Kriterien entscheiden. Der Preis ist ein offensichtlicher; danach kommen die Verfügbarkeit der WordPress-Installation und ihre REST API. Die Übermittlung an einen Endpunkt setzt voraus, dass dieser immer öffentlich verfügbar ist. Das ist bei Diensten bereits klar, da wir für ihre Verfügbarkeit bezahlen. Einige Setups können den Zugriff auf WordPress auf reine Bearbeitungs- und Erstellungsprozesse beschränken. Eine weitere Überlegung ist, wo Sie die Daten speichern möchten, insbesondere unter Beachtung der DSGVO-Vorschriften.
Wenn es um Funktionen über die Einreichung hinaus geht, sind WordPress-Formular-Plugins schwer zu übertreffen. Sie haben ihr eigenes Ökosystem, Add-ons zur Erstellung von Berichten, PDFs, sofort verfügbare Integrationen mit Newslettern und Zahlungsdiensten. Nur wenige Dienste bieten so viel in einem einzigen Paket.
Selbst wenn wir WordPress auf die „traditionelle“ Weise nutzen, mit dem Front-End, das auf einem WordPress-Theme basiert, kann die Verwendung der REST API eines Formular-Plugins in vielen Fällen sinnvoll sein. Zum Beispiel, wenn wir ein Theme mit einem Utility-First-CSS-Framework entwickeln, dann hinterlässt das Styling des gerenderten Formulars mit festem Markup, das mit einer BEM-ähnlichen Klassenkonvention strukturiert ist, bei jedem Entwickler einen bitteren Nachgeschmack.
Der Zweck dieses Artikels ist es, die Einreichungs-Endpunkte zweier WordPress-Formular-Plugins vorzustellen und einen Weg zu zeigen, die typischen formularbezogenen Verhaltensweisen nachzubilden, die wir gewohnt sind, out-of-the-box zu erhalten. Bei der Einreichung eines Formulars im Allgemeinen haben wir es mit zwei Hauptproblemen zu tun. Eines ist die Übermittlung der Daten selbst, und das andere ist die Bereitstellung aussagekräftiger Rückmeldungen an den Benutzer.
Also, fangen wir dort an.
Die Endpunkte
Die Übermittlung von Daten ist der einfachere Teil. Beide Endpunkte erwarten eine POST-Anfrage, und der dynamische Teil der URL ist die Formular-ID.
Die REST API von Contact Form 7 ist sofort verfügbar, wenn das Plugin aktiviert ist, und sieht so aus:
https://your-site.tld/wp-json/contact-form-7/v1/contact-forms/<FORM_ID>/feedback
Wenn wir mit Gravity Forms arbeiten, hat der Endpunkt diese Form:
https://your-site.tld/wp-json/gf/v2/forms/<FORM_ID>/submissions
Die REST API von Gravity Forms ist standardmäßig deaktiviert. Um sie zu aktivieren, müssen wir zu den Plugin-Einstellungen gehen, dann zur Seite REST API und die Option „Zugriff auf die API aktivieren“ auswählen. Es ist nicht notwendig, einen API-Schlüssel zu erstellen, da der Endpunkt für die Formularübermittlung diesen nicht benötigt.
Der Body der Anfrage
Unser Beispielformular hat fünf Felder mit folgenden Regeln:
- ein Pflichtfeld für Text
- ein Pflichtfeld für E-Mail
- ein Pflichtfeld für Datum, das Daten vor dem 4. Oktober 1957 akzeptiert
- ein optionales Textbereich-Feld
- eine Pflicht-Checkbox

Für den body der Anfrage von Contact Form 7 müssen wir die Schlüssel mit der Formular-Tag-Syntax definieren.
{
"somebodys-name": "Marian Kenney",
"any-email": "[email protected]",
"before-space-age": "1922-03-11",
"optional-message": "",
"fake-terms": "1"
}
Gravity Forms erwartet die Schlüssel in einem anderen Format. Wir müssen eine automatisch generierte, inkrementelle Feld-ID mit dem Präfix input_ verwenden. Die ID ist sichtbar, wenn Sie das Feld bearbeiten.

{
"input_1": "Marian Kenney",
"input_2": "[email protected]",
"input_3": "1922-03-11",
"input_4": "",
"input_5_1": "1"
}
Übermittlung der Daten
Wir können uns viel Arbeit ersparen, wenn wir die erwarteten Schlüssel für die name-Attribute der Eingabefelder verwenden. Andernfalls müssen wir die Eingabenamen den Schlüsseln zuordnen.
Wenn wir alles zusammenfügen, erhalten wir für Contact Form 7 eine HTML-Struktur wie diese:
<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>
Im Falle von Gravity Forms müssen wir nur die Attribute action und name wechseln.
<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>
Da alle erforderlichen Informationen im HTML verfügbar sind, sind wir bereit, die Anfrage zu senden. Eine Möglichkeit dazu ist die Verwendung von FormData in Kombination mit 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);
Wir können die Übermittlung mit geringem Aufwand durchführen, aber die Benutzererfahrung ist gelinde gesagt unterdurchschnittlich. Wir schulden den Benutzern so viel Anleitung wie möglich, um das Formular erfolgreich einzureichen. Zumindest bedeutet das, dass wir:
- eine globale Fehler- oder Erfolgsmeldung anzeigen müssen,
- inline Validierungsfehlermeldungen und mögliche Hinweise hinzufügen müssen, und
- Aufmerksamkeit auf Teile lenken müssen, die besondere Aufmerksamkeit erfordern, mit speziellen Klassen.
Feldvalidierung
Neben der Verwendung der integrierten HTML-Formularvalidierung können wir JavaScript für zusätzliche clientseitige Validierung verwenden und/oder die serverseitige Validierung nutzen.
Wenn es um serverseitige Validierung geht, bieten sowohl Contact Form 7 als auch Gravity Forms dies standardmäßig an und geben die Validierungsfehlermeldungen als Teil der Antwort zurück. Das ist praktisch, da wir die Validierungsregeln über das WordPress-Admin-Interface steuern können.
Für komplexere Validierungsregeln, wie z. B. bedingte Feldvalidierung, kann es sinnvoll sein, sich nur auf die Serverseite zu verlassen, da die Synchronisation der clientseitigen JavaScript-Validierung mit den Plugin-Einstellungen zu einem Wartungsproblem werden kann.
Wenn wir uns ausschließlich für die serverseitige Validierung entscheiden, besteht die Aufgabe darin, die Antwort zu parsen, die relevanten Daten zu extrahieren und DOM-Manipulationen wie das Einfügen von Elementen und das Umschalten von Klassennamen durchzuführen.
Antwortnachrichten
Die Antwort bei einem Validierungsfehler für Contact Form 7 sieht so aus:
{
"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"
}
]
}
Bei erfolgreicher Einreichung sieht die Antwort so aus:
{
"into": "#",
"status": "mail_sent",
"message": "Thank you for your message. It has been sent.",
"posted_data_hash": "d52f9f9de995287195409fe6dcde0c50"
}
Im Vergleich dazu ist die Validierungsfehlermeldung von Gravity Forms kompakter:
{
"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
}
Aber die Antwort bei erfolgreicher Einreichung ist größer:
{
"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"
}
Obwohl beide die Informationen enthalten, die wir benötigen, folgen sie keiner gemeinsamen Konvention und haben beide ihre Eigenheiten. Zum Beispiel enthält die Bestätigungsnachricht in Gravity Forms HTML, und die Schlüssel für die Validierungsnachrichten haben nicht das Präfix input_ – das Präfix, das für die Anfrage benötigt wird. Auf der anderen Seite enthalten Validierungsfehler in Contact Form 7 Informationen, die nur für deren Front-End-Implementierung relevant sind. Die Feldschlüssel sind nicht sofort verwendbar; sie müssen extrahiert werden.
In einer Situation wie dieser ist es besser, eine gewünschte, ideale Formatierung zu entwickeln, anstatt mit der erhaltenen Antwort zu arbeiten. Sobald wir diese haben, können wir Wege finden, die ursprüngliche Antwort in das zu transformieren, was wir für passend halten. Wenn wir das Beste aus beiden Szenarien kombinieren und die irrelevanten Teile für unseren Anwendungsfall entfernen, erhalten wir etwas wie dieses:
{
"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."
}
}
Und bei erfolgreicher Einreichung würden wir isSuccess auf true setzen und ein leeres Validierungsfehlerobjekt zurückgeben.
{
"isSuccess": true,
"message": "Thanks for contacting us! We will get in touch with you shortly.",
"validationError": {}
}
Nun geht es darum, das Erhaltene in das umzuwandeln, was wir brauchen. Der Code zur Normalisierung der Contact Forms 7-Antwort lautet:
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,
};
};
Der Code zur Normalisierung der Gravity Forms-Antwort ist schließlich:
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,
};
};
Uns fehlt noch eine Möglichkeit, die Validierungsfehler, Erfolgsmeldungen und das Umschalten von Klassen anzuzeigen. Wir haben jedoch eine saubere Möglichkeit, auf die benötigten Daten zuzugreifen, und wir haben alle Inkonsistenzen in den Antworten durch eine leichte Abstraktion entfernt. Zusammengefügt ist es bereit, in eine bestehende Codebasis eingefügt zu werden, oder wir können darauf aufbauen.
Es gibt viele Möglichkeiten, den verbleibenden Teil zu bewältigen. Was sinnvoll ist, hängt vom Projekt ab. Für Situationen, in denen wir hauptsächlich auf Zustandsänderungen reagieren müssen, kann eine deklarative und reaktive Bibliothek sehr hilfreich sein. Alpine.js wurde hier auf CSS-Tricks behandelt und ist perfekt für sowohl Demonstrationen als auch den Einsatz auf Produktionsseiten geeignet. Fast ohne Modifikationen können wir den Code aus dem vorherigen Beispiel wiederverwenden. Wir müssen nur die richtigen Direktiven und an den richtigen Stellen hinzufügen.
Zusammenfassung
Das Front-End-Erlebnis, das WordPress-Formular-Plugins bieten, kann für einfache, unkomplizierte Formulare relativ einfach nachgebildet werden – und zwar auf eine Weise, die projektübergreifend wiederverwendbar ist. Wir können es sogar so gestalten, dass wir das Plugin wechseln können, ohne das Front-End zu beeinträchtigen.
Sicher, es kostet Zeit und Mühe, ein mehrseitiges Formular, Vorschauen von hochgeladenen Bildern oder andere fortgeschrittene Funktionen zu erstellen, die wir normalerweise direkt in einem Plugin finden würden, aber je einzigartiger die Anforderungen sind, die wir erfüllen müssen, desto sinnvoller ist es, den Einreichungs-Endpunkt zu verwenden, da wir nicht gegen die gegebene Front-End-Implementierung ankämpfen müssen, die versucht, viele Probleme zu lösen, aber nie das spezielle Problem, das wir wollen.
Die Nutzung von WordPress als headless CMS, um auf die REST API eines Formular-Plugins zuzugreifen und die Einreichungs-Endpunkte anzusteuern, wird sicherlich zu einer weiter verbreiteten Praxis werden. Es ist etwas, das es wert ist, erforscht und im Auge behalten zu werden. In Zukunft würde es mich nicht überraschen, wenn WordPress-Formular-Plugins primär für die Arbeit in einem solchen Headless-Kontext konzipiert werden. Ich kann mir ein Plugin vorstellen, bei dem das Front-End-Rendering eine Add-on-Funktion ist, die kein integraler Bestandteil seines Kerns ist. Welche Konsequenzen das hätte und ob es kommerziellen Erfolg haben könnte, bleibt zu untersuchen, ist aber ein faszinierender Bereich, dessen Entwicklung man beobachten kann.
Hallo, dein Artikel ist super hilfreich, aber es fehlt nur eine Sache: Wie nutzt man das Captcha, das im WordPress-Backend eingestellt ist?
Vielen Dank im Voraus!
Wie kann ich zu einer Dankesseite (URL) weiterleiten statt zu einer Nachricht?
Das funktioniert für CF7 nicht mehr, da sie jetzt gehashte IDs für die Formulare verwenden.