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.

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 entered message to an intended 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. However, sometimes, captchas are impossible to read. As a result, they create a terrible experience for legitimate users.

To avoid using a captcha while protecting the contact from spam, you can use a honeypot to trick the 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. Also, its location on the form is changed randomly.

Create the contact form

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

PHP contact form

The contact form has the following features:

  • Form validation
  • Sending message via email
  • Prevent spam
  • Prevent double submit

First, create the following folders & files:

├── config
|  └── app.php
├── css
|  └── style.css
├── inc
|  ├── footer.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)

footer.php

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 shows the contact form:

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

<form action="index.php" 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>Code language: PHP (php)

How it works.

First, display a success message if it is set:

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

Second, fill the input fields with data from the $inputs array. If the field has invalid data, show the error message from the $errors array.

Third, add a honeypot to the form:

<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>Code language: PHP (php)

post.php

The post.php handles the form submission:

<?php

// check the honeypot
$honeypot = filter_input(INPUT_POST, 'nickname', FILTER_SANITIZE_STRING);
if ($honeypot) {
    header($_SERVER['SERVER_PROTOCOL'] . ' 405 Method Not Allowed');
    exit;
}

// validate name
$name = filter_input(INPUT_POST, 'name', FILTER_SANITIZE_STRING);
$inputs['name'] = $name;
if (!$name || trim($name) === '') {
    $errors['name'] = 'Please enter your name';
}

// validate email
$email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
$inputs['email'] = $email;
if ($email) {
    $email = filter_var($email, FILTER_SANITIZE_EMAIL);
    if (!$email) {
        $errors['email'] = 'Please enter a valid email';
    }
} else {
    $errors['email'] = 'Please enter an email';
}

// validate subject
$subject = filter_input(INPUT_POST, 'subject', FILTER_SANITIZE_STRING);
$inputs['subject'] = $subject;
if (!$subject || trim($subject) === '') {
    $errors['subject'] = 'Please enter the subject';
}

// validate message
$message = filter_input(INPUT_POST, 'message', FILTER_SANITIZE_STRING);
$inputs['message'] = $message;
if (!$message || trim($message) === '') {
    $errors['message'] = 'Please enter the message';
}Code language: PHP (php)

The post.php checks the honeypot and returns the 405 HTTP status code if it detects the spambot. Otherwise, it sanitizes and validates the input fields, including name, email, subject, and message.

config.php

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

<?php

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

mail.php

The mail.php gets the email address of the receiver from the app.php configuration file. It sends to the message entered in the contact form using the mail() function:

<?php

// get email from the config file
$config = require_once __DIR__ . '/../config/app.php';
$recipient_email = $config['mail']['to_email'];

// contact information
$contact_name = $inputs['name'];
$contact_email = $inputs['email'];
$message = $inputs['message'];
$subject = $inputs['subject'];

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

mail($recipient_email, $subject, $message, $header);Code language: PHP (php)

index.php

The index.php file contains the main logic:

<?php

session_start();

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

$request_method = strtoupper($_SERVER['REQUEST_METHOD']);

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

    // show the message
    if (isset($_SESSION['message'])) {
        $message = $_SESSION['message'];
        unset($_SESSION['message']);
    } elseif (isset($_SESSION['inputs']) && isset($_SESSION['errors'])) {
        $errors = $_SESSION['errors'];
        unset($_SESSION['errors']);
        $inputs = $_SESSION['inputs'];
        unset($_SESSION['inputs']);
    }
    // show the form
    require_once __DIR__ . '/inc/get.php';
} elseif ($request_method === 'POST') {
    // check the honeypot and validate the field
    require_once __DIR__ . '/inc/post.php';

    if (!$errors) {
        // send an email
        require_once __DIR__ . '/inc/mail.php';
        // set the message
        $_SESSION['message'] =  'Thanks for contacting us! We will be in touch with you shortly.';
    } else {
        $_SESSION['errors'] =   $errors;
        $_SESSION['inputs'] =   $inputs;
    }

    header('Location: index.php', true, 303);
    exit;
}Code language: PHP (php)

How it works.

First, show the contact form if the HTTP request method is GET. Also, get the $message, $errors, and $inputs data from the $_SESSION.

Second, handle the form submission if the HTTP request method is POST, send an email if no error, and redirect to the contact form. Note that we use the PRG (Post-Redirect-Get) technique to avoid the double submit problem.

Did you find this tutorial useful?