How To Build Your Own WordPress Contact Form and Why

I’m a massive fan of building stuff from the ground up. There are loads of plugins around for everything you could ever wish for in a WordPress blog, but I feel both rewarded and secure when I build features from scratch.

Why bother? Most of the time I don’t want to mess around with a plugin’s UI, or the plugin does way more than I actually want it to do. Without hacking the core files, plugins can be very rigid with what you’re allowed to do.

The solution? Roll your own! It’s a great learning experience, you know what’s going on under the hood and you’re gaining skills for another time. And trust me, you will need these skills down the track. I’ve rarely learnt how to do something with WordPress that I haven’t ended up repeating in another project.

Why Don’t I Just Use a Plugin?

Here are four good reasons why plugins are bad for simple contact forms:

  1. Plugins pack in way more than you (should) care for
  2. Using a UI to build a simple contact form is overkill
  3. Knowing what’s under the hood is good for your knowledge, and your peace of mind
  4. Default stylesheets kill me slowly from the inside

I could probably go on.

I hate bulky UIs with a passion. I hate bad UX and I especially hate too much functionality. Hate is a very strong word, but these days most forms that boast contact forms are in competition with each other to create the best, most feature rich plugin. And it ends up looking like this

Or even worse, this

You hated scrolling past that as much as I did!

But I Need All Those Features!!1!

Really? This isn’t Web 2.0 anymore. Complex forms scare customers.

How many times have you gone to fill out a form, only to be daunted by the amount of personal data you have to put in? It happens all the time. People even get halfway and are then distracted by something else. People on the web have a very short attention span. So in order for you to make it as easy as possible for your potential clients and customers to get in touch with you, you really should be stripping to the bone.

So What’s Absolutely Essential?

Name, email, message and human verification. But I think you knew that already.

These four small bits of information are as simple as commenting on a blog — why would you want to make getting in touch any more difficult? I don’t think I’ve ever built a form with more information than that, unless absolutely required.

If people want to leave their phone number and address, they will do so in the message. Visual cues such as asking them in a short paragraph above the form is enough!

Surely It’s a Pain Coding Everything From scratch!?

Not really. I mean, if you’re patient you’ll smash through this tutorial in under half an hour. All it really consists of is:

  1. A custom template file
  2. Some PHP

Yep, that’s it. You can handle displaying the form, human verification, validation, emailing the admin and thanking the reader for getting in touch all in one file. In fact, here it is.

Getting started

As per usual, I’m starting with a fresh WordPress installation. If you’re unsure how, educate yourself on the Codex.

To follow good practice, I’m going to create a child theme of TwentyTwelve — available here — with just a stylesheet and our custom page template. You can take out the custom page template and drop it into any of your current themes, and it should work flawlessly (bar some CSS modifications).

In wp-content/themes, add a new folder called “contact-form.” Within that, create two new files — style.css and page-contact-us.php.

Inside style.css, paste the following:

/*
Theme Name:     Contact Form Page Template
Theme URI:      http://premium.wpmudev.org/blog/
Description:    A super simple, self-contained contact form you can drop into your existing theme.
Author:         Harley
Author URI:     http://premium.wpmudev.org/
Template:       twentytwelve
Version:        0.1
*/

@import url("../twentytwelve/style.css");

Ignore my shameless premium plug — I work here. It’s a requirement!

By now, you should be able to go ahead and activate the theme on your test site, and be ready to roll.

The next step is to create a page, aptly-named “Contact Us.” This name is special. It relates immediately to the template file we made previously called page-contact-us.php. This solution for creating a custom page template is not a catch-all, just a convention WordPress provides and I like. Page-about-us.php would automatically be used to display a page titled “About Us.” It’s kinda handy!

If you’ve created page-contact-us.php and left it empty, visiting your newly created “Contact Us” page will yield a blank page.

That’s alright though! Quickly pad it out with the following, and it will look just like a normal page again.

<?php get_header(); ?>

  <div id="primary" class="site-content">
    <div id="content" role="main">

      <?php while ( have_posts() ) : the_post(); ?>

          <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>

            <header class="entry-header">
              <h1 class="entry-title"><?php the_title(); ?></h1>
            </header>

            <div class="entry-content">
              <?php the_content(); ?>

              <p>FORM CODE GOES HERE</p>

            </div><!-- .entry-content -->

          </article><!-- #post -->

      <?php endwhile; // end of the loop. ?>

    </div><!-- #content -->
  </div><!-- #primary -->

<?php get_sidebar(); ?>
<?php get_footer(); ?>

Alrighty. Now we’ve got the base page in, it’s time to drop in a very simple form.

Replace the FORM CODE GOES HERE paragraph with the following:

<style type="text/css">
  .error{
    padding: 5px 9px;
    border: 1px solid red;
    color: red;
    border-radius: 3px;
  }

  .success{
    padding: 5px 9px;
    border: 1px solid green;
    color: green;
    border-radius: 3px;
  }

  form span{
    color: red;
  }
</style>

<div id="respond">
  <?php echo $response; ?>
  <form action="<?php the_permalink(); ?>" method="post">
    <p><label for="name">Name: <span>*</span> <br><input type="text" name="message_name" value="<?php echo esc_attr($_POST['message_name']); ?>"></label></p>
    <p><label for="message_email">Email: <span>*</span> <br><input type="text" name="message_email" value="<?php echo esc_attr($_POST['message_email']); ?>"></label></p>
    <p><label for="message_text">Message: <span>*</span> <br><textarea type="text" name="message_text"><?php echo esc_textarea($_POST['message_text']); ?></textarea></label></p>
    <p><label for="message_human">Human Verification: <span>*</span> <br><input type="text" style="width: 60px;" name="message_human"> + 3 = 5</label></p>
    <input type="hidden" name="submitted" value="1">
    <p><input type="submit"></p>
  </form>
</div>

Excuse the inline style! This is purely for error/success messages, and to put the whole thing in one file. Feel free to put it in your style.css.

There are a number of things going on here. Firstly, we echo the $respond variable, which we’ll look at a bit later.

The rest is just form tags, but you’ll note that the value of them echoes a $_POST variable. This is so that in the case the form is submitted incorrectly, all data is retained so the user doesn’t have to type out everything from scratch.

There’s also a sneaky “hidden” form input, that allows us to check if a user has posted anything or not. I’ll get to that later though.

I’ve added an asterix next to each input, to signify they are required.

If you save that and refresh, you’ll have a nice looking contact form!

The logic

The logic that follows upon form submission is simple. There are several components that need to be built.

  1. Response generation and messages (based on validation)
  2. Validation
  3. Sending an email

Response Generation

To save having messy PHP code, we’ve placed the $response variable within the form. That’s cool, keeping logic mostly separate from views. How do we set this though?

It’s pretty easy really, we need to write a function that fills the $response variable with the feedback we get from validation. This must contain messages to address each validation requirement.

At the top of your page-contact-us.php before get_header(), paste the following.

<?php

  //response generation function
  $response = "";

  //function to generate response
  function my_contact_form_generate_response($type, $message){

    global $response;

    if($type == "success") $response = "<div class='success'>{$message}</div>";
    else $response = "<div class='error'>{$message}</div>";

  }
?>

Cool. This doesn’t do a whole lot yet, but if you have basic PHP knowledge you’ll understand that we a) create an empty $response variable, and b) fill it with the $message passed depending on its $type, which will either be error or success.

Now when we build the validation, we can call generate_response() at each case (whether it be a success or failure), pass a message (i.e. Email address invalid), and it will be spat out right above the form!

A few variables to get us going

There are 3 groups of variables we need for a successful contact form.

  • Validation Messages
  • Form Input Variables
  • PHP Mailer Variables

The validation process will use all three of these groups to validate, send an email and notify the user of a sent message.

Below the my_contact_form_generate_response() function, write up the following twelve variables:

//response messages
$not_human       = "Human verification incorrect.";
$missing_content = "Please supply all information.";
$email_invalid   = "Email Address Invalid.";
$message_unsent  = "Message was not sent. Try Again.";
$message_sent    = "Thanks! Your message has been sent.";

//user posted variables
$name = $_POST['message_name'];
$email = $_POST['message_email'];
$message = $_POST['message_text'];
$human = $_POST['message_human'];

//php mailer variables
$to = get_option('admin_email');
$subject = "Someone sent a message from ".get_bloginfo('name');
$headers = 'From: '. $email . "rn" .
  'Reply-To: ' . $email . "rn";

Each group is fairly straight forward. The Response Message group stores all our feedback messages in variables.

The User Posted Variables pick up what has been put into the form, and places them in nicely named variables to save having to access the $_POST variable over and over. $_POST contains all information submitted via a form.

Finally, we set up a couple of variables that are required by PHP’s mail() function, which tell it who to go to, a subject line, and some headers that specify from and reply-to addresses.

Validation

This is the meaty bit. We want to check for all possible problems, in the most succinct way possible. This requires several nested if/else conditional statements, but not enough to blow out your brains.

In order, we check for

  1. If the user has submitted the form (using the hidden input)
  2. If not, check the human verification passes (i.e. the user can do math properly!)
  3. If passed, validate the email address
  4. If passed, check for presence of a name and message (as they’re required)
  5. If all the above passes, send an email!

So let’s get stuck into it!

Human verification

This is pretty simple really. We want to test to see if the form user has entered some math correctly, to ensure they are human. If you look back at our form field, you’ll notice the equation is ? + 3 = 5. So obviously, we want to test firstly if the value is NOT 2, spit out an error. If it is 2, we then proceed. Below the variable groups, paste the following.

if(!$human == 0){
  if($human != 2) my_contact_form_generate_response("error", $not_human); //not human!
  else {

    //validate email
    //validate presence of name and message
    //send email
  }
}
else if ($_POST['submitted']) my_contact_form_generate_response("error", $missing_content);

Here, we first check if the human verification is not equal to 0. If it is, then check if it’s incorrect. If so, generate a matching response. The last line checks if the hidden field has been submitted, and if it has, throw another error.

This tests for two cases: empty content and human verification.

If you try submit the form while it’s empty, you should get a nice error message.

Cool! If you enter anything other than “2″ into the human verification, you’ll also get an error.

The next part of validation is email validation. Now we know they’re human, we want to be sure they’re supplying a legit reply address you can contact them with.

To do so very easily, we use a PHP function called filter_var() which has a number of validation models. Obviously, we’ll use the FILTER_VALIDATE_EMAIL model to validate email.

Replace the three lines of comments smack bang in the middle of our validation with the following.

//validate email
if(!filter_var($email, FILTER_VALIDATE_EMAIL))
  my_contact_form_generate_response("error", $email_invalid);
else //email is valid
{
  //validate presence of name and message
  //send email
}

Here, we filter the $email variable checking if it’s a valid email address. If not, throw an error. Else, continue!

One line email validation!

Checking for presence of a name and message is relatively easy. We just use PHP’s empty() function, which returns true or false depending whether the variable you pass to it is empty.

//validate presence of name and message
if(empty($name) || empty($message)){
  my_contact_form_generate_response("error", $missing_content);
}
else //ready to go!
{
  //send email
}

Finally, the last piece of our puzzle is sending the email. Once all our validation has passed, we use the mail() function PHP has built in to swiftly send an email. We’ve also got to generate a response on success, and on failure as well.

Replace //send email with:

$sent = wp_mail($to, $subject, strip_tags($message), $headers);
if($sent) my_contact_form_generate_response("success", $message_sent); //message sent!
else my_contact_form_generate_response("error", $message_unsent); //message wasn't sent

Believe it or not, you’re done! You’ve now got a fully functional contact form.

If you fill everything out properly, you should get a happy success message that says your email has been sent.

Success!

And that’s it. Rolling your own contact page isn’t hard, and it keeps things as fast as possible without any overhead. Depending on your skills, you can of course build on top of this. It’s a great way to learn new things as well as build on your experience. Let alone know exactly what happens under the hood!

Extra Credit

If you want to take this further, try getting the form to insert each entry as a custom post type post. You’ll need to use nonces, and wp_insert_post(). You can then even turn it into a completely user-driven testimonials engine!

Download Source Code

Comments (49)

  1. I am finding that spammers seem to blow right by captchas and email verification. That is why I chose one of the plugins you depict above, to be able to use Akismet. However one site that is a huge target for spam uses BuddyPress plugin and Custom Community theme and the contact form plugin is incompatible. I actually ended up sending users to a different web site to submit comments.

    I was wondering if you have used this approach successfully for BuddyPress/Custom Community sites?

    • Fair enough, I guess you could increase the complexity of the verification. I.e. ask for two numbers that equal something, or “x + 4 = y”. If they fail this test (the first test) the script halts execution.

      It’d be worth a shot!

  2. Unfortunately that kind of test only checks for bots. With the economy being so bad for the past few years, it is now cheaper to hire live people to post comment spam and make bogus registrations.

    So there are two issues:

    (1) BuddyPress incompatibilities (which is why I posted the original question)

    (2) Testing for “humans” is annoying to legit users but not particularly effective for screening

  3. Thank you for this very useful information, But just one thing I wanted to ask you sir that Can you show us how to implement SMTP mail functionality in this code. My hosting server has blocked mail() function and that forces me to use those plugins.
    So please please help me in adding SMTP options for sending mails.

    Thank you

  4. This is a very insightful tutorial!

    I wonder, is it possible to change “admin_email” to an email that is stored in a custom field?

    So the scenario would be:

    1. Have a post
    2. Have a custom field (say, of a person mentioned in the post) that contains an email address.
    3.Have the above contact form but it sent to the email in the custom field

  5. Hi Harley, I too share your disdain of the overcomplex, oftentimes ungainly presentation of front and back-end contact formage or is that contact fromage. However unlike you, I totally suck at this stuff @_@

    I dropped the page-contact-us.php into my own childtheme folder and renamed the aforementioned php file to page-write.php as write is the name of my contact page. However nothing appears on my write page. I haven’t changed any of the code in the file. I am using a childtheme of twentytwelve. I gave it some thought but i can’t see what i’m doing wrong.

    Thanks ahead.

  6. I “pinned” this tutorial in chrome cuz I like it so much! ;-)

    I have a question about this line:

    if(!$human == 0)

    Can you describe the difference in your case between that ‘if’ statement and this one?

    if($human != 0)

    or this one:

    if(empty($human))

    Thanks!

  7. zanematthew,

    Thank you for that Type Comparison’s chart! Handy!

    I guess I’m confused about how what is entered in _POST['message_human'] will effect the value assigned to !$human.

    What risks would I run if I chose to use

    if ($human != “”)

    instead of

    if(!human == 0)

    Also, if I use 0 instead of “” do *all* strings return the ‘not human’ error message because the comparison is looking for an integer?

  8. Found this very useful as I’ve been wanting to get rid of Contact Form 7 on my own, and clients sites for a while.

    I’ve added this form to a test site and it’s working just fine except for a couple of minor issues.

    1. The ‘Name’ field data isn’t being included in the email the form sends out. It’s always nice to know the name of the person using the form so it would be good to have that included.

    2. The submit button text reads ‘Submit Query’ rather than the usual ‘Submit’. Not a big issue but if it’s possible to change this it would be handy.

    Other than that, it’s great!

    • Firstly, thanks Harley and great work. I implemented mine as a custom page template and it works fine. Just threw a few undefined index notices when I turned WP_DEBUG on but from what I can gather this isn’t a real issue.

      Mark, I got the name AND email to display in the email body. Not sure if this is a good way to do it (perhaps Harley could offer his feedback) but this is how I did it.

      Directly under the User Posted Variables ad the line,

      $body = “Name: $name \n\nEmail: $email \n\nComments: \n$message”;

      Then in the line that reads,

      $sent = wp_mail($to, $subject, strip_tags($message), $headers);

      change out $message for $body, end result is a nicely formatted email that looks something like,

      Name:

      Email:

      Comments:

      Hope this helps.

      One more thing, I think this would benefit from something that generates a random math problem when it loads. Anyone want to have a crack at it and post the code?

      Something like $numa= random between 1 and 9, $numb= random between 1 and 9, $numc= $numa + $numb,

      print math problem, “text field + ” $numb ” = ” $numc

      User inputs $numd. if $numd = $numa then test passed else get lost.

      • Ok I’ve almost got this random math problem thing working but now I’m stuck and need some help.

        I set some variables, $numa and $numb that both get a random number between 1 and 9. $numc is the sum of $numa and $numb.

        I changed things so now the code checks to see if $human is (or isn’t) equal to $numa.

        Problem is that when you hit the submit button it seems that the page begins to reload and as such $numa generates a new number and the code is comparing $human to the new $numa and not the one that existed when the contact page originally loaded. Or it seems that this is what is happening because the form only submits if by chance $numa generates the exact same number it originally had. Otherwise it throws the human verification error.

        Any ideas people? I’ll share if I can get it working :)

  9. It’s a pity to me that so many people find this to be such an interesting tutorial. Not because it’s badly written, not because its core idea is bad, but because it promotes WordPress development that’s bad for compatibility and security and overall just promotes bad practices.

    First of all, you’re not sanitizing any of your data. Not only are you not sanitizing it before sending it by e-mail, you’re not even sanitizing the POST-data before using it in the input again!

    Secondly, why are you not prefixing your functions? Why don’t you call your function mytheme_generate_response or something similar (i.e. prefixed)? I fully understand that this is just an example, but you’re making it seem as if using un-prefixed function names is common or good practice.

    • You’re exactly right it definitely opens up the door for a lot of spam and a lot of bad things to happen. However what you don’t understand his people want other people to convert and conversion rate optimization you’ll have to basically be a business. What does that mean to me spending money on a real email system that actually has a enterprise grade spam filter and virus filter. Go ahead and say that this opens up the door to million problems. Good night but there are other ways around this using different technologies that cost quite a bit of money and look at the way automatic is hosting their VIP customers blogs they’re all login with go off or something like that so in my opinion this is the Future.

  10. Hello there, excelent tutorial, thanks for this.
    I have an issue, I use on my blog wp touch plugin for mobile devices acces, but this contact form doesn´t work propperly. In fact, it doesn`t load. What can I do to fix it… I’m practicing and learning with this tutorial how to make static pages with forms, because later I have to deploy other membership afiliation form and access to data in mysql, and is very important to access from mobile devices. Thanks a lot again!

  11. Hello there, excelent tutorial, thanks for this.
    I have an issue, I use on my blog wp touch plugin for mobile devices acces, but this contact form doesn´t work propperly. In fact, it doesn`t load. What can I do to fix it… I’m practicing and learning with this tutorial how to make static pages with forms, because later I have to deploy other membership afiliation form and access to data in mysql, and is very important to access from mobile devices. Thanks a lot again!

    • Hi Mark,
      1) You can replace
      $message = $_POST['message_text'];

      on

      $message = “Message from: “.$name.”\r\n”;
      $message .= “Message text: “.$_POST['message_text'].”\r\n”;

      2) Just add attribute value=”Submit”

      • Hi

        Could you explain a bit fuller what I need to do to get this working. I’ve replaced

        $message = $_POST['message_text'];

        with

        $message = “Message from: “.$name.”\r\n”;
        $message .= “Message text: “.$_POST['message_text'].”\r\n”;

        in the user posted variables section but I didn’t understand what you meant by ‘ Just add attribute value=”Submit” ‘

        Was I right to replace the $message code with the code above, and what do I need to do with the second instruction concerning the attribute value?

        Sorry – relative php newbie and have spent hours trying to figure this out on my own!

  12. Perfect! Many thanks for that. I’d not noticed the second ‘input’ tag at the bottom of the form.

    I’ll be using this in my sites from now on.

    Thanks Guys!

  13. I have a newbie question, do I save the new folder called “contact-form” in wp-content/themes parent or child folders?
    Thank you and please excuse my ignorance ;)

  14. Hi Guys

    I need a formbuilder that clients can login and update their information.
    I then need to be able to view the information on an Interface
    Can someone help
    Thanks.

  15. Akismet Along with the honey pot project, should always be used in preference to capitula or any other manual spam technique. I have not confirmed this myself however I’ve heard from multiple sources that adding a do not click here if it is NOT spam button works extremely well.
    I use gravity forms which are incorporate everything I’ve already talked about. I get roughly 7000 hits a day and I don’t get much spam at all that good actually gets through to me. I would have to say it is for the best systems of all. Don’t waste your time building a homemade contact form From a form framework called contact seven which exists already. I realize that you want to cut down on the amount of coding on your blog however gravity forms is probably one of the most highly respected and best coded plug-ins available on WordPress.

  16. Hi everyone,

    I’m working now in this way and until now works great, but now i get stuck into a problem when trying to download files, seems like wordpress do not like my path or is not recognized, here is a little of my code, i appreciate if you have any idea why is that happening,

    I got this from Chrome: Resource interpreted as Document but transferred with MIME type application/octet-stream:

    An my file downloads incomplete

    A part of my code is:

    $path = $_SERVER['SERVER_NAME'].”:8080/projects/sports/wp-content/whitepapers/logo_america.jpg”;

    send_download($path);

    function send_download($path)
    {
    $file = basename($path);
    $length = filesize($path);

    header(‘Content-Description: File Transfer’);
    header(‘Content-Type: application/octet-stream’);
    header(‘Content-Disposition: attachment; filename=”‘ . $file . ‘”‘);
    header(‘Content-Transfer-Encoding: binary’);
    header(‘Expires: 0′);
    header(‘Pragma: public’);
    header(‘Content-Length: ‘ . $length);
    header(‘Accept-Ranges: bytes’);
    ob_clean(); //cleans the output buffer
    flush();

    readfile($path);

    }

    P.D I made a separe example outside wordpress and works fine.

    Thank’s in advance guys!!

  17. I tried to put something like:

    $path = get_template_directory_uri().”/images/banner.jpg”;

    But it does not work either. My God!!!

    Please any ideas!

    Harley, do you ever did something like this?

    Thank’s in advance !!

    • A friend of mine (Alexis Mejia “the master”) helped me to find the solution, before you have to include this

      require_once(‘/../../../wp-load.php’);

      $file = “img.jpg”

      $path = TEMPLATEPATH.”/../admin/images/”.$file;

      obviously everything depends on your project structure!

      SOLVED!

  18. Well, this tutorial exposes us on the process yet it missed a couple of things.

    1- Checking if there is a $_POST variable before setting the other variables with values from it.

    2- it needs a better validation method, that highlights the missing form elements

    3- the most important thing is making this in AJAX, that would be cool

  19. You don’t explain how this works on localhost. I’m using WAMP and whenever I try to send an email, it tells me that my address is invalid. I’m assuming I’m getting this error because I’m working from localhost. If this is the case, you should probably explain how to fix this issue.

  20. Hi all. This worked well in my child theme. Two questions.

    a. How do I add my own email address to the PHP to have the form get to me (i.e the admin email).

    b. How do I incorporate this form in my live WordPress theme.

    Many thanks,

    David

    • If you read the tutorial you will see the form sends messages to the admin email by deafult, hence:

      //php mailer variables $to = get_option(‘admin_email’);

      As for how to incorporate it into your WordPress theme, again read the tutorial. It’s all explained in easy to follow steps and the source code can also be downloaded.

  21. So tutorial seems great.

    However, I have a less intelectual question than posts above. I setup as per your download file. However, I am unable to access this on the front end. I am not sure where to view this contact page.

    I didnt create an actual page so I am unsure where to view this

    Thanks

  22. Hey,
    How can I add the function to the function.php file instead of having it above the get_header()? Sorry for a silly question but it is not working for me….

    Hannes

  23. Hi,
    I tried to put the form in a sidebar but I can’t get it to work.
    Is it at all possible?

    Thanks for the post. I’ve used it in several pages and popups already.

  24. Hi
    We have a domain http://www.surfs-sup.co.uk that was using Ninja forms but I am trying to cut down resource use on server as we also use woocommerce.

    I have followed this tutorial and setup as follows:

    1) taken the style.css info and put it into our active themes style.css file
    2) taken the file called page-contact-us.php and put it into our active theme folder
    3) we have a page called Contact us

    If you load http://www.surfs-sup.co.uk/contact-us/ nothing is coming up?

    Any tips to get this to load please

    • In relation to my previous comment , I now have the form coming in . I simply used the method of adding this template by using the Page Template drop down menu in the page itself.

      New problem is it says email sent but nothing is turning up???

  25. Hey

    This has been a great script for me and I have used it many times. However today I found out it stopped working on one site. Not sure why? Has anyone else found this?

    Could it be something the hosting did in an update?

    Appreicate the help, thanks

Participate