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 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)
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.php
Code 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 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)