Build your own Ajax contact form for WordPress

Many premium WordPress themes I have seen are using the plugin Contact Form 7 to offer an Ajax contact form for the template user. This works for most people very well and the user is able to change the contact form without to change any code. For most WordPress websites I have built, there was a single contact form more than enough and most of my customers never change that form on their own. So why using a plugin for a simple feature like a contact form? The new version of this website is based on a premium theme where the contact form was already built using some custom files. I really like the design, but the web form and the function that has to process the form data wasn’t the code quality I was looking for.

Ajax Contact form tutorial

This tutorial is written for the advanced WordPress user, but I’m sure the less experienced user with a little knowledge in PHP, HTML and jQuery can use the tutorial code, too. We use and explain the following features:

  • WordPress Ajax function – I’m using the native WordPress Ajax functionality and jQuery to process the contact form
  • Email address validation – Read more about our email address validator tutorial
  • Nonce value validation – Read more about securing form submissions in WordPress using nonces

If you don’t like to use this tutorial code, there is also a Ajax Contact Form plugin based on this tutorial!

For this tutorial I use a WordPress short code to create the HTML code for the web form. This is more flexible than using a new template for each WordPress theme.

function createAjaxContactForm() {
	return '
	<form id="contactform">
			<label for="name">Name</label>
			<input type="text" name="name" id="name" size="30" tabindex="1" />
			<label for="email">Email address</label>
			<input type="text" name="email" id="email" size="30" tabindex="2" />
			<label for="message">Message</label>
			<textarea  name="message" id="message" tabindex="3"></textarea>
			<input type="hidden" name="action" value="contactform_action" />
			'.wp_nonce_field('contactform_action', '_acf_nonce', true, false).'
			<input type="button" value="Submit" id="contactbutton" tabindex="4" />
	<div id="contact-msg"></div>';
add_shortcode('AjaxContactForm', 'createAjaxContactForm');

This shortcode is pretty simple and has no options or parameters. Notice the function wp_nonce_field(), this WordPress function will create the HTML code for the nonce validation we’re using later. The hidden form field with the name “action” is required for the Ajax process, you need to remember that value later. Place this code into your theme’s functions.php file. Let’s continue to the jQuery code, create a file named “contact.js”, paste the code below and save the file into the “js” sub-directory from your WordPress theme directory (create the directory if it doesn’t exists).

jQuery(document).ready(function($) {
	$('#contactbutton').click(function() {
		$('#contact-msg').html('<img src="/loading.gif" alt="Loading...">');
		var message = $('#message').val();
		var name = $('#name').val();
		var email = $('#email').val();
		if (!message || !name || !email) {
			$('#contact-msg').html('At least one of the form fields is empty.');
			return false;
		} else {
				type: "GET",
				url: '',
				data: { address: email, api_key: '>>> ADD HERE YOUR MAILGUN PUBLIC API KEY <<<' },
				dataType: "jsonp",
				crossDomain: true,
				success: function(data, status_text) {
					if (data['is_valid']) {
							type: 'POST',
							url: ajax_object.ajax_url,
							data: $('#contactform').serialize(),
							dataType: 'json',
							success: function(response) {
								if (response.status == 'success') {
					} else {
						if (data['did_you_mean']) {
							$('#contact-msg').html('Error, did you mean <em>' +  data['did_you_mean'] + '</em>?');
						} else {
							$('#contact-msg').html('The entered mail address is invalid.');
						return false;
				error: function(request, status_text, error) {
					$('#contact-msg').html('Error occurred, unable to validate your email address.');
					return false;

Okay, this a lot of code and I will explain the most important parts. There are two jQuery Ajax calls in this script. If you don’t know how this jQuery functions  works, please check their manual, too. If the visitor has clicked the button (#contactbutton), the loading image is fired and the form field values are passed to the variables “message”, “name” and “email”. The following IF statement is a simple test that all three form field values are not empty. If “false” an error message shows up and otherwise the first Ajax function is called. The function has some attributes (I explain the important ones):

  • The value for url points to the Mailgun API (you need a Mailgun account, get one here it’s free)
  • The attribute data is used to send the email address and the public API key (you need to enter your own key) to the Mailgun API endpoint
  • The dataType is set to “jsonp” (note the “p” for parentheses)
  • If the Ajax call to the Mailgun API was successful the function for the attribute success is executed
  • The code for the attribute error is used to show some message if something went wrong.

If the API call was successful we can use an object named data and that is passed to the function. If “data.is_valid” is true, the next Ajax call is executed. If not we get two different messages: For that case that the Mailgun validator has returned a value for “data.did_you_mean”, the script will show a message including a suggestion for a correct email address and otherwise the script shows a generic error message. The second Ajax function is used to post the form data to the WordPress Ajax URL. This is the value “ajax_object.ajax_url” and we define this one later. For the data attribute we use the jQuery “serialize” function to create a data set from our web form form (#contactform). If the Ajax response is successful the web form becomes a reset and we return the message we got inside the response.

Important: The Mailgun account is still free, but since some time you need to pay for the email validation requests. 

Ajax in WordPress

If you use the jQuery Ajax function you need to define a target URL. That was for the Mailgun API call an external URL and for the form data we use an object with the name ajax_object. Now we need to define the values for the object URL.

function contactform_add_script() {
	if (is_page('contact')) {
		wp_enqueue_script( 'contactform-script', get_template_directory_uri().'/js/contact.js', array('jquery') );
		wp_localize_script( 'contactform-script', 'ajax_object', array( 'ajax_url' => admin_url( 'admin-ajax.php' ) ) );
add_action('wp_enqueue_scripts', 'contactform_add_script');

This custom function will include the “contact.js file into your site’s header. The condition is_page() will allow the code only for a page with the slug “contact”. The function wp_localize_script() creates for the same page the WordPress Ajax URL and our jQuery code will use that value as the target for the form data. The PHP code belongs into your functions.php file. Now we need a function that will email the form data to the WordPress site admin.

function ajax_contactform_action_callback() {
	$error = '';
	$status = 'error';
	if (empty($_POST['name']) || empty($_POST['email']) || empty($_POST['message'])) {
		$error = 'All fields are required to enter.';
	} else {
		if (!wp_verify_nonce($_POST['_acf_nonce'], $_POST['action'])) {
			$error = 'Verification error, try again.';
		} else {
			$name = filter_var($_POST['name'], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_LOW);
			$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
			$subject = 'A messsage from your website\'s contact form';
			$message = stripslashes($_POST['message']);
			$message .= PHP_EOL.PHP_EOL.'IP address: '.$_SERVER['REMOTE_ADDR'];
			$message .= PHP_EOL.'Sender\'s name: '.$name;
			$message .= PHP_EOL.'E-mail address: '.$email;
			$sendmsg = 'Thanks, for the message. We will respond as soon as possible.';
			$to = get_option('admin_email'); // If you like change this email address
			// replace "" with your real email address
			$header = 'From: '.get_option('blogname').' <>'.PHP_EOL;
			$header .= 'Reply-To: '.$email.PHP_EOL;
			if ( wp_mail($to, $subject, $message, $header) ) {
				$status = 'success';
				$error = $sendmsg;
			} else {
				$error = 'Some errors occurred.';

	$resp = array('status' => $status, 'errmessage' => $error);
	header( "Content-Type: application/json" );
	echo json_encode($resp);
add_action( 'wp_ajax_contactform_action', 'ajax_contactform_action_callback' );
add_action( 'wp_ajax_nopriv_contactform_action', 'ajax_contactform_action_callback' );

First we take a look at the last two rows from this snippet. Do you remember the hidden form tag with the name action? The value is the same as the second part of the hook “wp_ajax_contactform_action” or “wp_ajax_nopriv_contactform_action”. The value “contactform_action” is used to identify that the custom function is called if an Ajax call is done.

Why two of them? The second is used for those users are logged in and the first for all others. Okay let’s go back to the first rows from this function. There is another IF statement that is responsible for a check that all none of the $_POST vars is empty. If this check is passed, the function will check the nonce and continues only if the nonce is valid. Now we will filter the $_POST values to remove possible mail header injections. The PHP constant variable PHP_EOL is used to create a new lines (like \n and/or \r).

Before you can use the function you need to update code to create a valid FROM mail header. We use an IF statement for the function wp_mail() to check that the mail was send successfully. If yes the variable $status is set to “true” and a friendly message is passed to the variable “$error”. The last rows inside the function will create a JSON string from an array and the response for the Ajax call is done by using a simple “echo”. Add this code to your theme’s functions.php file and you’e done.

The tutorial code should work out of the box (don’t forget to change the API key and the FROM mail header), but I’m sure you like to use different or more form fields and maybe other messages. Start your modifications carefully and update always the form field and variable names on all three places: the form shortcode, the contact.js file and the email function. Don’t forget to style the form in your CSS style-sheet!

You can download the tutorial code here. If you have questions about this tutorial, please use the comment form below. Thanks for sharing!

Published in: WordPress Development


  1. Over the last year I’ve experienced with different methods to build my own contact form so I could skip another plugin. On some websites (read: too many) the output mails weren’t delivered. Not sure if it was the clients mailclient or the webserver that was blocking the output mails. On other sites my form was working great… After too many struggles I switched back to a plugin again, in my case Contact Form 7.

    I guess you don’t have any problems receiving the output mail?

    1. Hi eaglejohn,
      There are several reasons why a email doesn’t arrive. First, use for websites always a professional SMTP provider like Mailgun. Use for the sender’s domain SPF and DKIM validation. For several mail servers you need to use an existing email address for the sender. Do you tried all these optimizations?

  2. Hi there, I have similar situation but i don’t have to create the shortcode function from the start; what i need to do is call a shortcode function provided by an installed plugin once i do a click on certain button. I downloaded the “css&javascript toolbox” plugin that lets me write javascrip code, I can call php code like echoes and so but when i call a shortcode with “echo do_shortcode(the shortcode)” it desnt work. I also don’t know how to call a specific function on a php file, i only know how to call all the php file result

    Could you tell me how to call a do_shortcode with jquery in wordpress?

    1. Hi Francisco,
      about your problem with the do_shortcode() function (from the codex):

      Searches content for shortcodes and filters shortcodes through their hooks.

      I think you don’t need to use that function, just call the PHP function if your script has access. For example if you do a Ajax request using jQuery, include the code and call the function right away. The function do_shortcode() is great if you need to use it in themes or for filters on your content.

  3. Thanks for the tutorial. I tried your script but keep getting a verification error even if the everything is filled out correctly.

    I figured out the issue which is wp_verify_nonce is not verifying correctly. When I remove the ! from !wp_verify_nonce, the script works and emails are sent and received.

    Any advice on why this isn’t working? I copied your code verbatim.

    Also, any suggestions on code to send a confirmation email to the person that filled out the contact form as well. For example, sending something like, “We received your message and someone will get back to shortly.”

    1. Hi Mike,
      do you use a cache plugin? I use WP Super Cache for all my websites and if a nonce is used for a web form, I need to setup the garbage collector to collect “trash” twice a day.
      I will try to add a conformation mail feature to the WordPress plugin the next weeks. I send all my form mail to HelpScout and setup a email responder using their functions. Looks much better than an email in WordPress :) Try the contact form on this website.

  4. @ Mike

    I’ve had the same problem and it seems that the script has an error. In HTML both hidden “action” input and hidden nonce fields should have the same name. In the example it’s different. The correct one is below.

    ‘.wp_nonce_field(‘contactform_action’, ‘_acf_nonce’, true, false).’

  5. Hi Olaf

    Great tutorial! Thank you for sharing your knowledge. I took this code to create my first ajax form where user can choose a travel destination and book via custom ajax form.

    Parallel to the email notification I integrated the popular ContacForm DB Plugin which stores every sent form in the DB. Following page was helpful for the integration:

    Wish you all the best!


    1. Hi Philipp,

      great that my example helped! I use similar Ajax forms in many WordPress projects.
      For the plugin Contact Form DB, you can store the form data right in your PHP callback function (I place this code before the email is send)

      $data = (object) array('from_name'=>$name, 'from_email'=>$email, 'subject'=>$subject, 'message'=>$message), 'uploaded_files' => null);
      do_action_ref_array( 'cfdb_submit', array( &$data ) );

      The Contact Form DB website has some information about it too:

  6. Hello I keep getting the error “entered mail address is invalid”. I just followed the tutorial but I end up getting this error.

Comments are closed.