Recently, I wanted to try to build a custom form because the design was quite complicated and using Contact Form 7 would force me to add a bunch of HTML in the Contact Form 7 options. Ofcourse, when clients are modifying forms, they can make mistakes, breaking the designs. This is what we want to prevent.


  1. Custom Post Type UI plugin to create a custom post type for the submitted form request
  2. Advanced Custom fields plugin to create the fields for the submitted form request
  3. A functions.php or a plugin to load in some functions

How to

Step by step instruction summary

  1. Create Custom Post type with Custom Post type UI

    This is very simple, in the plugin interface you can name your custom post type slug and other information.

  2. Create the fields in ACF

    We need the fields in ACF so that they are displayed in the Admin interface. They need to match with the fields you have in the form. Standard is ofcourse, Email, Name, Message, Phone Number

  3. Create the HTML for the Form

    Create the HTML based on the form fields that you require

  4. Create the JS for submitting the form

    We need some JS to verify the content, nonce and do the AJAX post request to the REST API

  5. Handle the incoming request

    In the REST callback we can make sure to store the incoming data and do some additional validation if needed.

1. Create custom post type

In CPT UI create the CPT with contact_request or another name that you prefer.


2. Create the ACF fields for that Custom Post Type

In order for the fields to show on the Custom Post type you need to create a new field group, and assign it to your newly created Custom Post Type.

Make sure to note down your fields.

3. Create the HTML form

You can create the HTML form with simple html first to validate if everything is working. Use the same names for your input names as your ACF fields. That will give things simple. See below an example. Note that there are several additional informations coming from the page ACF fields here such as \”the_field( \’privacy_policy_text\’ )\”. You can exchange them with your own text or ACF field.

                <form class=\"contact-form\" method=\"post\">

                    <div class=\"mobile-title\">
                    <?php the_title() ?>

                    <?php $nonce = wp_create_nonce( \'contact-form\' ); ?>
                    <input type=\"hidden\" name=\"nonce\" value=\"<?= $nonce ?>\">

                    <label for=\"honeyput\" aria-hidden=\"true\" class=\"visually-hidden d-none\" style=\"display:none\">
                        <input type=\"text\" name=\"honeyput\" id=\"honeyput\" style=\"display:none\"></label>

                    <div class=\"inner-form\">

                        <div class=\"f-col\">
                            <div class=\"inner\">

                                <div class=\"top-section\">
                            <div class=\"field text-field\">
                                <input type=\"text\" name=\"first_name\" class=\"form-control\" placeholder=\"<?php _e(\'First Name\', \'understrap\') ?>\" required />

                            <div class=\"field text-field\">
                                <input type=\"text\" name=\"last_name\" class=\"form-control\" placeholder=\"<?php _e(\'Last Name\', \'understrap\') ?>\" required />

                            <div class=\"fullwidth-field email-field\">

                                <input type=\"email\" name=\"email\" class=\"form-control\" placeholder=\"<?php _e(\'Email Address\', \'understrap\') ?>\" required />


                            <div class=\"fullwidth-field\">
                                <input type=\"text\" name=\"subject\" class=\"form-control\" placeholder=\"<?php _e(\'Subject\', \'understrap\') ?>\" required />
                                    <div class=\"checkbox-field\">
                                        <label for=\"checkbox\">
                                            <input type=\"checkbox\" id=\"checkbox\" class=\"\" name=\"privacy_policy\" required />

                                            <span><?php the_field( \'privacy_policy_text\' ); ?></span>

                                <div class=\"fullwidth-field message-field\">
                                    <label for=\"message\"><?php _e(\'Message\', \'understrap\') ?></label>
                                    <textarea name=\"message\" id=\"message\" class=\"form-control\" cols=\"30\" rows=\"15\" placeholder=\"<?php _e(\'What would you like to tell us?\', \'understrap\') ?>\"></textarea>

                                <div class=\"bottom-section\">

                                    <div class=\"button-field\">
                                        <button type=\"submit\" class=\"button dark\" title=\"<?php the_field( \'send_button\' ); ?>\">
                                            <?php the_field( \'send_button\' ); ?>


                    <div class=\"result\">


4. Create the JS for sending the form

We have the form now, but we need to submit it with AJAX post in jQuery. We need a nonce verification as well, so we need to send that as well along with the data.

This is the JS file that I used for the contact form. Note that it is an example, you can exchange the field names for what fields you use. Mine is based on the above HTML form. Note also this only works with one form on one page. It\’s specifically for a contact page.

;( function( $, window, undefined ) {

    \'use strict\'
var current_domain = window.location.hostname;

var contact_url = \'https://\'+window.location.hostname+\'/wp-json/xyz/v1/contact_form/\';

var contact_form = $(\'.contact-form\');

// newsletter
// TODO decide on which pages this should work?



    if($(\'input[name=\"honeyput\"]\').val() !== \"\"){
        $( \".result\" ).html(\"Oops\");

        var data = new FormData();

        data.append(\'first_name\', $(\'input[name=\"first_name\"]\').val());
        data.append(\'last_name\', $(\'input[name=\"last_name\"]\').val());
        data.append(\'subject\', $(\'input[name=\"subject\"]\').val());
        data.append(\'email\', $(\'input[name=\"email\"]\').val());
        data.append(\'topic\', $(\'select[name=\"topic\"] option:selected\').val());
        data.append(\'message\', $(\'textarea[name=\"message\"]\').val());
        data.append(\'privacy_policy\', $(\'input[name=\"privacy_policy\"]\').val());

        // TODO make sure checkbox is valid
    // TODO make sure email is valid.. So it doesnt even go to post

        url: contact_url,
        method: \"post\",
        processData: false,
        contentType: false,
        data: data,
        beforeSend: function (xhr) {
            xhr.setRequestHeader(\'X-WP-Nounce\', $(\'input[name=\"nonce\"]\'));
            $(\'.button-field button\').addClass(\'loading\')
        success: function (data) {
            $(\'.button-field button\').removeClass(\'loading\');
            $( \".contact-form .result\" ).html( data.message );
        error: function (e) {
            $( \".contact-form .result\" ).html( e.message );
            $(\'.button-field button\').removeClass(\'loading\');


} ( jQuery, window ) );

5. REST API: Handle the incoming request

Now we have everything in place to actually handle the incoming post request. We need to initialise the rest api route and the callback so that we can insert the custom post with all the ACF field data.

Init the rest route & Callback

add_action(\'rest_api_init\', function () {
    register_rest_route( \'xyz/v1\', \'contact_form\',array(
        \'methods\'  => \'POST\',
        \'callback\' => \'contact_form\'


Callback function

This contains the actual handling. If you want to send an email as well when the user submits the form, then follow the following article for instructions.

function contact_form($request){


    $args = array(
        \'post_title\' =>  sanitize_text_field($request[\'first_name\']).\' \'.sanitize_text_field($request[\'last_name\']), // set the title however you want
        \'post_excerpt\' => \'\',
        \'post_type\' => \'contact_application\', // this the slug for my CPT
        \'post_status\' => \'publish\',

    $post_id = wp_insert_post($args);

    $first_name = sanitize_text_field($request[\'first_name\']);
    $last_name = sanitize_text_field($request[\'last_name\']);
    $email = sanitize_email($request[\'email\']);
    $topic = sanitize_text_field($request[\'topic\']);
    $subject = sanitize_text_field($request[\'subject\']);
    $message = sanitize_textarea_field($request[\'message\']);

    update_field(\'first_name\', $first_name, $post_id);
    update_field(\'last_name\', $last_name, $post_id);
    update_field(\'email\', $email, $post_id);
    update_field(\'topic\', $topic, $post_id);
    update_field(\'subject\', $subject, $post_id);
    update_field(\'message\', $message, $post_id);

    $data = [
        \'title\' => \'Contact Form Request\',
        \'first_name\' => $first_name,
        \'last_name\' => $last_name,
        \'subject\' => $subject,
        \'message\' => $message,
        \'topic\' => $topic,
        \'email\' => $email,
        \'site_url\' => site_url(),
        \'edit_link\' => get_edit_post_link( $post_id, \'\' )

    $response_info = array(\'message\' => __(\'Thank you for your contact request. We will be in touch with you soon\', \'understrap\'), \'success\' => true);

    $response = new WP_REST_Response($response_info);

    return $response;



This is a simple foolproof way to use the REST API above the wp_admin handlers. I prefer it, I always found the WP_ADMIN solution a little limited. Ofcourse, it works as well with Nonce. This is also quite interesting, since in React applications, you\’ll need to access the nonce from somewhere. I\’ve made an article about that as well as well as a longer tutorial with example code.

1 thought on “Custom Contact form with REST API”

  1. Pingback: Send Customised E-Mail Templates with Wordpress (easily) and without a plugin. - Hendrik Vlaanderen

Leave a Comment

Your email address will not be published. Required fields are marked *