I think I've been looking at too much code and have confused myself while trying to get a better understanding of forms in WordPress with AJAX. I set out this week to learn how to create a form for a page and submit it through AJAX.

The page is a template and the action I've looked into two solutions of the handling. One through a redirect to a PHP, such as:

<form action="<?php echo get_template_directory_uri() . "/validation.php"; ?>" id="contactForm">

after reading several posts on the topic that a separate validation of the form should be made. Is this correct and why? Then the other issue is if I do want to process the form on the template-page the form action should look like:

<form action="#" id="contactForm">

but outside of WordPress I've seen:

<form action="" id="contactForm">

why is this? On the AJAX portion I've seen a difference of:

    type :"post",
    url: ajaxurl,

then a different URL of:

    type :"post",
    url: MBAjax.admin_url,

and lastly:

    type :"post",
    url: /wp-admin/admin-ajax.php?action=contactForm",

So what is the proper way to write the action in WordPress if:

  • If the form is processed on the same page?
  • If a separate PHP file validates the form?

Then what is the proper AJAX call in WordPress?

Reference points:


After further reading decided to step into RESTful submissions. I referenced "REST APIs for Absolute Beginners" but I'm not getting the return I'm hoping for:


<form id="contactForm" action="" method="POST">
    <div class="form-group">
        <label for="form_email"><span class="asterisk">*</span>Email address:</label>
        <input type="email" class="form-control" id="form_email">
    <div class="form-group">
        <button id="form_submit" class="supportbutton" type="submit">Submit</button>
<div id="testForm"></div>


$(document).ready(function() {
    $('#contactForm').submit(function(e) {
            url: '<?php echo home_url(); ?>/wp-json/darthvader/v1/contact',
            type: 'post',
            dataType: 'json',
            data: $('#contactForm').serialize(),
            success: function(data) {
             error: function() {
                alert("There was an issue");


add_action('rest_api_init', function() {
    register_rest_route('darthvader/v1', '/contact/', array(
        'methods'   => 'POST',
        'callback'  => 'darth_contact_form'
function darth_contact_form(\WP_REST_Request $request) {
    $email = $request['form_email'];
    return "Your contact request had the title " . $email;

Why do I only get Your contact request had the title on the return and not the email too?

<form action="<?php echo get_template_directory_uri() . "/validation.php"; ?>" id="contactForm">


So I'll outline the basic fundamentals, so you have a framework to move forwards with

Fixing Admin AJAX

So I'll cover this very briefly as it's not the crux of my answer, but would be useful:

  • Add a hidden input field named action rather than adding it to the URL
  • validation.php and any other standalone files should burn with the fire of a thousand suns, do this validation in JS, and again in the form handler. Handling the form and validating it are the same step, else someone could skip validation and go straight to handling

Finally, use a standard form handler for when JS isn't used/possible, check for the existence of something in the form that would only happen if it's been submitted, and submit the form to the same page, by having an empty action attribute e.g.:

if ( !empty( $_POST['form_page'] ) ) {
    $valid = false;

    // do validation
    if ( true === $valid ) {
        // yay do whatever submitting the form is meant to do
        // if it's a multipage form, handle that here
    } else {
        get_template_part( 'form' ); // show the form again, but with warnings for validation
} else {
    get_template_part( 'form' );

RESTful Form Submissions

Change your form submission jQuery to use a REST endpoint, lets call this darthvader/v1/contact, I used some code from a stackoverflow answer here:

jQuery('input#submitButton').click( function() {
        url: '/wp-json/darthvader/v1/contact',
        type: 'post',
        dataType: 'json',
        data: jQuery('form#contactForm').serialize(),
        success: function(data) {
            //... do something with the data...

That's pretty much every form submission via REST API you'll ever need, but you still need that endpoint to exist, '/wp-json/darthvader/v1/form' needs creating, so lets tell WP we want an endpoint, that accepts POST's:

add_action( 'rest_api_init', function () {
        register_rest_route( 'darthvader/v1', '/contact/', array(
                'methods' => 'POST',
                'callback' => 'darth_contact_form'
        ) );
} );

Then define what will happen when it's called:

function darth_contact_form( \WP_REST_Request $request ) {

That's where we'll handle our form, e.g.:

function darth_contact_form( \WP_REST_Request $request ) {
    $title = $request['title'];
    $message = $request['message'];
    // send an email or create a post, or whatever
    // the contact form does when its submitted
    return "Your contact request had the title ".$title;

The return value gets turned into JSON and sent back to the browser, so that you can handle it in the success function in javascript that you saw earlier in this answer. You can also return other responses, e.g.:

return \WP_REST_Response( "Bad request, X Y and Z are empty", 400);

and you can return a WP_Error object to indicate something going wrong or incorrect:

return new WP_Error( 'awesome_no_author', 'Invalid author', array( 'status' => 404 ) );

You can do your validation inside the endpoint, but you can also make the endpoint do the validation for you by specifying what fields to expect, and a function to validate them, but I leave that as an exercise ( or new question ) for you

  • so are you saying not even have the form in the page but maybe somewhere like the functions.php and call the form with a function? Also, do you mind explaining why no other processing files should exist, is this due to a security issue? – DᴀʀᴛʜVᴀᴅᴇʀ Aug 15 '17 at 17:32
  • In regards to the hidden I assume this is an example of what you're referring to? – DᴀʀᴛʜVᴀᴅᴇʀ Aug 15 '17 at 17:34
  • yes, but I would advise against using Admin AJAX anyway – Tom J Nowell Aug 15 '17 at 17:46
  • and no, the template that displays the form is also the template that handles it. As for standalone files, they're a massive security risk, WP is a CMS, let WP handle all the requests, and use hooks and templates for stuff. Never send stuff to a standalone PHP file in WordPress that isn't a part of WP Core – Tom J Nowell Aug 15 '17 at 17:48

Every AJAX process has 4 fundamental steps:

  1. Send a request to the server asynchronously
  2. Process the server side tasks
  3. Retrieve the response from the server
  4. Make changes to the client side based on the server's response

Creating a Request to Server

There are many ways and methods to do this. This can be done both by jQuery and JavaScript. However, since jQuery made it easier, I'm going to use that. jQuery offers 3 different functions with similar functionality. These functions are:

  • $.ajax(); - Supports both POST and GET methods
  • $.get(); - Simple GET method
  • $.post(); - Simple POST method

Since I'm not sure which method you are going to use, I will proceed with $.ajax();. I will be posting a sample code to the server, and then show a message based on server's response. Let's declare a jQuery function that handles our submission.

This function will send a username to the server.

function sendMyForm( string ) {
    // Begin an Ajax request
        type: 'POST',                   // Submission method
        url: url_object.url_string + '/wp-json/darthvader/ajax_submission', // I will explain this later
        data: { username : string },        // Data to be sent to server
        dataType: 'json',               // You should set this to JSON if you are going to use the REST api
        beforeSend: function(){         // Let's show a message while the request is being processed
        success: function ( response ) {
            // The Ajax request is successful. This doesn't mean the
            // sever was able to do the task, it just means that the
            // server did send a response. So, let's check the response
            if( response.success == true ) {
                $('#message').html('<span>' + response.message + '</span>');
            } else {
                $('#message').html('<span>' + response.message + '</span>');
        error: function(){              // The request wasn't successful for some reason
            $('#status').html('<span>Unknown error.</span>');

Okay, now we need to bind an action to our button. There are many ways to do this too. For example, prevent form submission, serialization of data, and more. I'm not even going to create a form.

I'm going to create a custom HTML div to demonstrate we don't even need a form. Here is our HTML that looks like a form:

    <p id="status"></p>
    <p id="message"></p>
    <input id="my-input" type="text"/>
    <span id="button">This is our button!</span>

Now let's bind a clink action to our button, which is actually not a button! Tricked you.

    // Get the value of our input box
    var username = $('#my-input').val();
    // Call our Ajax function and pass the username to it
    sendMyForm( string );

Alright, now the form will be submit on user's click.

Creating a Route to Process the Request

Since I've declared the data type as JSON, I'm going to use the REST API that outputs our content as JSON by default.

You can use anything you wish such as admin Ajax, as long as it's a good practice, and doesn't impact performance or cause a security issue.

Let's register a REST route for now.

add_action( 'rest_api_init','my_rest_route' ); 
function my_rest_route() {
        'darthvader',   // Base path here
        '/ajax_submission/',    // Child path
            'methods' => 'POST',
            'callback' => 'darth_contact_form'

Now it's time for the real fun. Let's handle the received data, shall we?

function darth_contact_form( \WP_REST_Request $request ){
    // Get the data that was sent to server
    $data = $request['username'];
    // Check if it's empty and send the response
    if( !empty( $data ) ){
        // Remember these from our Ajax call? This is where we set them
        $response['status'] =  true;
        $response['message'] =  'You have entered a username!';
    } else{
        $response['status'] =  false;
        $response['message'] =  'Why didn\'t you enter a username?';
    // Don't forget to return the data
    return $response;

A Final Touch

Remember this line?

url: url_object.url_string

This is the path to our REST route. Since the domain may change, it's wise to form this dynamically, by using wp_localize_script. To do this, we save all our scripts into a file named script.js and then enqueue & localize it.

wp_enqueue_script( 'my-script', get_template_directory_uri().'/js/script.js', array( 'jquery' ), '', true );

$localized = array(
    'url_string' => site_url(), // This is where that url_string came from
wp_localize_script( 'my-script', 'url_object', $localized );

Now we can access the site_url(); by using url_object.url_string in our script!

That's it for now. Click the button and have fun.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.