PHP Contact Form

Summary: in this tutorial, you’ll learn how to build a contact form in PHP that includes form validation, sending email, honeypot, etc.

Introduction to the PHP contact form #

A contact form allows visitors of a website to leave messages. Typically, a contact form has the name, email, subject, and message input fields:

PHP contact form

The visitors need to fill out these fields and click the submit (or send) button to send a message. In PHP, you can validate the form data and send the message to an email address.

The contact form is a target for spammers who use spambots to send unsolicited messages for advertising, phishing, spreading malware, etc.

A spambot is software that automates the spam activities like filling out and submitting contact forms automatically.

To build a spam-free contact form, you can add a captcha to it. Sometimes, captchas are very difficult to read. Hence, they do not create a good experience for legitimate users.

To avoid using a captcha while protecting the contact form from spam, you can use a honeypot to trick spambots.

A honeypot is a field on the form that the visitor cannot see, but spambots can. When a spambot sees the honeypot field, it fills the field with a value. In PHP, you can check if the honeypot has a value and skip sending the message.

To create a honeypot, you need to have a CSS class that completely hide the honeypot field as follows:

.user-cannot-see {
    display:none
}Code language: CSS (css)

And you have a honeypot field on the form:

<label for="nickname" aria-hidden="true" class="user-cannot-see"> Nickname
    <input type="text" 
           name="nickname" 
           id="nickname" 
           class="user-cannot-see" 
           autocomplete="off" 
           tabindex="-1">
</label>Code language: HTML, XML (xml)

Note that the name of the honeypot should look legitimate. Recently, the spambot has become smarter that can detect the honeypot.

To deal with these smart spambots, you need a smart honeypot. For example, a smart honeypot may have a different name for each request. Additionally, its location on the form is changed randomly.

Creating the contact form #

We’ll build a contact form as shown in the following picture:

The contact form has the following features:

  • Form validation
  • Sending message via email.
  • Prevent spam using the honeypot technique.
  • Prevent double submit.

Here’s the complete contact form:

<?php

session_start();

function check_honeypot(){
    // check the honeypot
    if(filter_has_var(INPUT_POST, 'honeypot')){
        $honeypot = trim($_POST['honeypot']);
        if ($honeypot) {
            header($_SERVER['SERVER_PROTOCOL'] . ' 405 Method Not Allowed');
            exit;
        }
    }
}

function send_email($from_email, $message, $subject, $recipient_email) {
    // Email header
    $headers[] = 'MIME-Version: 1.0';
    $headers[] = 'Content-type: text/html; charset=utf-8';
    $headers[] = "To: $recipient_email";
    $headers[] = "From: $from_email";
    $header = implode('\r\n', $headers);

    // send email
    mail($recipient_email, $subject, $message, $header);
}

function validate() {

    $inputs = [];
    $errors = [];   

    // validate name
    if(filter_has_var(INPUT_POST, 'name')) {
        $inputs['name'] = trim($_POST['name']);
        if (trim($inputs['name']) === '') {
            $errors['name'] = 'Please enter your name';
        }    
    }

    // validate email
    if(filter_has_var(INPUT_POST, 'email')) {
        $inputs['email'] = trim($_POST['email']);
        // validate email
        if (!filter_var($inputs['email'], FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = 'Please enter a valid email address';
        }
    } else {
        $errors['email'] = 'Please enter your address';
    }


    // validate subject
    if(filter_has_var(INPUT_POST, 'subject')) {
        $inputs['subject'] = trim($_POST['subject']);
        if (trim($inputs['subject']) === '') {
            $errors['subject'] = 'Please enter a subject';
        }    
    } else{
        $errors['subject'] = 'Please enter a subject';
    }

    // validate message
    if(filter_has_var(INPUT_POST, 'message')) {
        $inputs['message'] = trim($_POST['message']);
        if (trim($inputs['message']) === '') {
            $errors['message'] = 'Please enter a message';
        }    
    } else {
        $errors['message'] = 'Please enter a message';
    }

    return [$inputs, $errors];
}


$request_method = $_SERVER['REQUEST_METHOD'];


if($request_method === 'POST') {
   
    $config = [
        'mail' => [
            'to_email' => '[email protected]'
        ]
    ];

    // check honeypot
    check_honeypot();

    // validate inputs
    [$inputs, $errors] = validate();

    if(empty($errors)) {
        // send email
        $from_email = $inputs['email'];
        $subject = $inputs['subject'];
        $message = nl2br(htmlspecialchars($inputs['message']));
        
        send_email($from_email, $message, $subject, $config['mail']['to_email']);

        // success message
        $_SESSION['success_message'] =  'Thanks for contacting us! We will be in touch with you shortly.';

    } else {

        $_SESSION['error_message'] =  'Please fix the following errors';
        $_SESSION['errors'] =   $errors;
        $_SESSION['inputs'] =   $inputs;
        
    }

    header('Location: ' . $_SERVER['PHP_SELF'], true, 303);
    exit;   
    

} if($request_method === 'GET') {

    if (isset($_SESSION['success_message'])) {
        $success_message = $_SESSION['success_message'];
        unset($_SESSION['success_message']);

    } elseif (isset($_SESSION['inputs'],$_SESSION['errors'])) {
        $error_message = $_SESSION['error_message'];
        $errors = $_SESSION['errors'];
        $inputs = $_SESSION['inputs'];
        unset($_SESSION['errors'], $_SESSION['inputs'], $_SESSION['error_message']);
    }
}

?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="css/style.css">
    <title>PHP Contact Form</title>
</head>
<body>
    <main>
        <?php if (isset($success_message)) : ?>
        <div class="alert alert-success">
            <?= $success_message ?>
        </div>
        <?php endif ?>

         <?php if (isset($error_message)) : ?>
        <div class="alert alert-error">
            <?= $error_message ?>
        </div>
        <?php endif ?>

        <form action="<?= htmlspecialchars($_SERVER['PHP_SELF']) ?>" method="post">
            <header>
                <h1>Send Us a Message</h1>
            </header>

            <div>
                <label for="name">Name:</label>
                <input type="text" value="<?= $inputs['name'] ?? '' ?>" name="name" id="name" placeholder="Full name">
                <small><?= $errors['name'] ?? '' ?></small>
            </div>

            <div>
                <label for="email">Email:</label>
                <input type="email" name="email" id="email" value="<?= $inputs['email'] ?? '' ?>" placeholder="Email address">
                <small><?= $errors['email'] ?? '' ?></small>
            </div>

            <div>
                <label for="subject">Subject:</label>
                <input type="subject" name="subject" id="subject" value="<?= $inputs['subject'] ?? '' ?>" placeholder="Enter a subject">
                <small><?= $errors['subject'] ?? '' ?></small>
            </div>

            <div>
                <label for="message">Message:</label>
                <textarea id="message" name="message" rows="5"><?= $inputs['message'] ?? '' ?></textarea>
                <small><?= $errors['message'] ?? '' ?></small>
            </div>

            <label for="nickname" aria-hidden="true" class="user-cannot-see"> Nickname
                <input type="text" name="nickname" id="nickname" class="user-cannot-see" tabindex="-1" autocomplete="off">
            </label>

            <button type="submit">Send Message</button>
        </form>
    </main>
</body>
</html>Code language: PHP (php)

How it works.

The $errors array stores the error messages. Each item has a key that is the field name and a value which is an error message. For example, the $erros['name'] stores the error message of the name field:

$errors['name'] = 'Please enter your name';Code language: PHP (php)

The $inputs array stores the input values. Each item is a key-value pair of the input name and value.

For each field in the HTML form, we display the input value and the validation error message if it has. For example, the following display the name input field with a value that comes from the $inputs array and error message that comes from the $errors array:

<div>
    <label for="name">Name:</label>
    <input type="text" value="<?= $inputs['name'] ?? '' ?>" name="name" id="name" placeholder="Full name">
    <small><?= $errors['name'] ?? '' ?></small>
</div>Code language: PHP (php)

Notice that we use the null coalescing operator (??) to display the input value and error message if it is set and not null.

When the users submit the form sucessfully, we display the success messasge on the top of the contact form:

<?php if (isset($success_message)) : ?>
   <div class="alert alert-success">
       <?= $success_message ?>
   </div>
<?php endif ?>Code language: PHP (php)

If there are errors, we display the error message:

<?php if (isset($error_message)) : ?>
        <div class="alert alert-error">
            <?= $error_message ?>
        </div>
<?php endif ?>Code language: PHP (php)

On top of the file, we start the session by calling the session_start() function:

session_start();Code language: PHP (php)

The check_honeypot() function checks the honeypot and returns 405 HTTP status code if the honeypot has a value:

function check_honeypot(){
    if(filter_has_var(INPUT_POST, 'honeypot')){
        $honeypot = trim($_POST['honeypot']);
        if ($honeypot) {
            header($_SERVER['SERVER_PROTOCOL'] . ' 405 Method Not Allowed');
            exit;
        }
    }
}Code language: PHP (php)

The validate() function validates each field in the form and returns an array that has two items: $inputs and $errors:

function validate() {

    $inputs = [];
    $errors = [];   

    // validate name
    if(filter_has_var(INPUT_POST, 'name')) {
        $inputs['name'] = trim($_POST['name']);
        if (trim($inputs['name']) === '') {
            $errors['name'] = 'Please enter your name';
        }    
    }

    // validate email
    if(filter_has_var(INPUT_POST, 'email')) {
        $inputs['email'] = trim($_POST['email']);
        // validate email
        if (!filter_var($inputs['email'], FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = 'Please enter a valid email address';
        }
    } else {
        $errors['email'] = 'Please enter your address';
    }


    // validate subject
    if(filter_has_var(INPUT_POST, 'subject')) {
        $inputs['subject'] = trim($_POST['subject']);
        if (trim($inputs['subject']) === '') {
            $errors['subject'] = 'Please enter a subject';
        }    
    } else{
        $errors['subject'] = 'Please enter a subject';
    }

    // validate message
    if(filter_has_var(INPUT_POST, 'message')) {
        $inputs['message'] = trim($_POST['message']);
        if (trim($inputs['message']) === '') {
            $errors['message'] = 'Please enter a message';
        }    
    } else {
        $errors['message'] = 'Please enter a message';
    }

    return [$inputs, $errors];
}Code language: PHP (php)

The send_email() function sends an email to a $recipient_email:

function send_email($from_email, $message, $subject, $recipient_email) {
    // Email header
    $headers[] = 'MIME-Version: 1.0';
    $headers[] = 'Content-type: text/html; charset=utf-8';
    $headers[] = "To: $recipient_email";
    $headers[] = "From: $from_email";
    $header = implode('\r\n', $headers);

    // send email
    mail($recipient_email, $subject, $message, $header);
}Code language: PHP (php)

We store the HTTP request in the $request_method variable:

$request_method = $_SERVER['REQUEST_METHOD'];Code language: PHP (php)

If the HTTP request is POST, we check the honeypot, validate data, and send email if there is no error. We use sessions to store messages, $errors and $inputs:

if($request_method === 'POST') {
   
    $config = [
        'mail' => [
            'to_email' => '[email protected]'
        ]
    ];

    // check honeypot
    check_honeypot();

    // validate inputs
    [$inputs, $errors] = validate();

    if(empty($errors)) {
        // send email
        $from_email = $inputs['email'];
        $subject = $inputs['subject'];
        $message = nl2br(htmlspecialchars($inputs['message']));
        send_email($from_email, $message, $subject, $config['mail']['to_email']);
        
        $_SESSION['success_message'] =  'Thanks for contacting us! We will be in touch with you shortly.';

    } else {

        $_SESSION['error_message'] =  'Please fix the following errors';
        $_SESSION['errors'] =   $errors;
        $_SESSION['inputs'] =   $inputs;
        
    }

    header('Location: ' . $_SERVER['PHP_SELF'], true, 303);
    exit; 
}Code language: PHP (php)

If the HTTP request is GET, we get values out of the $_SESSION and assign them to corresponding variables:

elseif($request_method === 'GET') {
    if (isset($_SESSION['success_message'])) {
        $success_message = $_SESSION['success_message'];
        unset($_SESSION['success_message']);

    } elseif (isset($_SESSION['inputs'],$_SESSION['errors'])) {
        $error_message = $_SESSION['error_message'];
        $errors = $_SESSION['errors'];
        $inputs = $_SESSION['inputs'];
        unset($_SESSION['errors'], $_SESSION['inputs'], $_SESSION['error_message']);
    }
}Code language: PHP (php)

Download the contact form

Organize the code #

First, create the following folders & files:

├── config
|  └── app.php
├── css
|  └── style.css
├── inc
|  ├── footer.php
|  ├── form.php
|  ├── get.php
|  ├── header.php
|  ├── mail.php
|  └── post.php
└── index.phpCode language: PHP (php)

header.php #

The header.php file contains the header part of the contact form:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="css/style.css">
    <title>PHP Contact Form</title>
</head>
<body>
    <main>Code language: HTML, XML (xml)

The footer.php file contains the enclosing tags of the header’s tags:

    </main>
</body>
</html>Code language: HTML, XML (xml)

get.php #

The get.php file handle when the HTTP request is GET:

<?php

if (isset($_SESSION['success_message'])) {
    $success_message = $_SESSION['success_message'];
    unset($_SESSION['success_message']);
} elseif (isset($_SESSION['inputs'], $_SESSION['errors'])) {
    $error_message = $_SESSION['error_message'];
    $errors = $_SESSION['errors'];
    $inputs = $_SESSION['inputs'];
    unset($_SESSION['errors'], $_SESSION['inputs'], $_SESSION['error_message']);
}Code language: PHP (php)

post.php #

The post.php handles the form submission and sending email:

<?php

$config = require_once __DIR__ . '/../config/app.php';

// Check honeypot
check_honeypot();

// Validate inputs
[$inputs, $errors] = validate();

if (empty($errors)) {
    // Send email
    $from_email = $inputs['email'];
    $subject = $inputs['subject'];
    $message = nl2br(htmlspecialchars($inputs['message']));
    send_email($from_email, $message, $subject, $config['mail']['to_email']);

    $_SESSION['success_message'] = 'Thanks for contacting us! We will be in touch with you shortly.';
} else {
    $_SESSION['error_message'] = 'Please fix the following errors';
    $_SESSION['errors'] = $errors;
    $_SESSION['inputs'] = $inputs;
}

header('Location: ' . $_SERVER['PHP_SELF'], true, 303);
exit;Code language: PHP (php)

app.php #

The app.php stores the configuration information e.g., the receiver’s email address:

<?php

return [
    'mail' => [
        'to_email' => '[email protected]'
    ]
];Code language: PHP (php)

functions.php #

<?php

function check_honeypot() {
    if(filter_has_var(INPUT_POST, 'honeypot')){
        $honeypot = trim($_POST['honeypot']);
        if ($honeypot) {
            header($_SERVER['SERVER_PROTOCOL'] . ' 405 Method Not Allowed');
            exit;
        }
    }
}


function validate() {

    $inputs = [];
    $errors = [];   

    // validate name
    if(filter_has_var(INPUT_POST, 'name')) {
        $inputs['name'] = trim($_POST['name']);
        if (trim($inputs['name']) === '') {
            $errors['name'] = 'Please enter your name';
        }    
    }

    // validate email
    if(filter_has_var(INPUT_POST, 'email')) {
        $inputs['email'] = trim($_POST['email']);
        // validate email
        if (!filter_var($inputs['email'], FILTER_VALIDATE_EMAIL)) {
            $errors['email'] = 'Please enter a valid email address';
        }
    } else {
        $errors['email'] = 'Please enter your address';
    }


    // validate subject
    if(filter_has_var(INPUT_POST, 'subject')) {
        $inputs['subject'] = trim($_POST['subject']);
        if (trim($inputs['subject']) === '') {
            $errors['subject'] = 'Please enter a subject';
        }    
    } else{
        $errors['subject'] = 'Please enter a subject';
    }

    // validate message
    if(filter_has_var(INPUT_POST, 'message')) {
        $inputs['message'] = trim($_POST['message']);
        if (trim($inputs['message']) === '') {
            $errors['message'] = 'Please enter a message';
        }    
    } else {
        $errors['message'] = 'Please enter a message';
    }

    return [$inputs, $errors];
}

function send_email($from_email, $message, $subject, $recipient_email) {
    // Email header
    $headers[] = 'MIME-Version: 1.0';
    $headers[] = 'Content-type: text/html; charset=utf-8';
    $headers[] = "To: $recipient_email";
    $headers[] = "From: $from_email";
    $header = implode('\r\n', $headers);

    // send email
    mail($recipient_email, $subject, $message, $header);
}Code language: PHP (php)

index.php #

The index.php file contains the main logic:

<?php

session_start();

require_once __DIR__ . '/inc/functions.php';

$request_method = strtoupper($_SERVER['REQUEST_METHOD']);
 
if ($request_method === 'POST') {
    require_once __DIR__ . '/inc/post.php';
} elseif ($request_method === 'GET') {
    require_once __DIR__ . '/inc/get.php';
} 


require_once __DIR__ . '/inc/header.php';
require_once __DIR__ . '/inc/form.php';
require_once __DIR__ . '/inc/footer.php';Code language: PHP (php)

Download the contact form

Did you find this tutorial useful?