What is a honeypot?
A honeypot is a hidden form field that real users never see or fill in — it is hidden via CSS — but bots do fill in automatically. Your server checks: if the field has a value, it is a bot. Reject the submission silently.
That is the entire technique. Zero friction for real users, no external services, no JavaScript requirement, and surprisingly effective against the majority of spam bots that hit PHP contact forms and comment forms.
How honeypots work
Spam bots scrape pages, find forms, and fill in every field they discover. Most bots do not execute CSS — they read the raw HTML. A field hidden with display:none or pushed off-screen with position:absolute; left:-9999px is invisible to a human looking at the rendered page, but it is fully present in the HTML source. Bots fill it.
Real users never see the field, so they never fill it. When your form processor receives a submission with a value in that hidden field, it knows with high confidence that it came from a bot. You silently discard the submission — no error, no redirect, nothing that would help a bot author diagnose what happened.
Basic PHP implementation
Add the hidden field to your form. Use a name that sounds like a real field — bots are more likely to fill plausible-sounding inputs like website, url, company, or phone.
<!-- Honeypot field — hidden from humans, bots fill it -->
<div style="display:none" aria-hidden="true">
<label for="website">Leave blank</label>
<input type="text" id="website" name="website" tabindex="-1" autocomplete="off" />
</div>
In your form processor, check for the honeypot value before doing anything else:
<?php
// Honeypot check — if the hidden field is filled, it's a bot
if (!empty($_POST['website'])) {
// Silently reject — don't tell bots what happened
http_response_code(200); // look normal to bots
exit;
}
// Continue with legitimate form processing
The http_response_code(200) line is a small detail worth keeping: returning a normal 200 response rather than a 4xx makes it harder for bot authors to identify that their submissions are being rejected.
CSS-only approach (safer than inline styles)
Some sophisticated bots parse inline style attributes and skip fields where display:none is set. Moving the hiding rule to an external CSS class is slightly harder for those bots to detect:
.hp-field {
position: absolute;
left: -9999px;
top: -9999px;
opacity: 0;
height: 0;
width: 0;
}
<div class="hp-field" aria-hidden="true">
<input type="text" name="website" tabindex="-1" autocomplete="off" />
</div>
The tabindex="-1" attribute prevents keyboard users from accidentally tabbing into the field. The aria-hidden="true" on the wrapper hides it from screen readers. Both attributes improve accessibility without affecting the anti-spam logic.
Time-based honeypot
A complementary technique: record a timestamp when the form page loads, then check the elapsed time when the form is submitted. Bots typically submit forms in under a second. Humans take several seconds at minimum to read the form and fill it in.
<?php
// On page load, store timestamp in session
session_start();
$_SESSION['form_loaded_at'] = time();
<?php
// In form processor:
session_start();
$elapsed = time() - ($_SESSION['form_loaded_at'] ?? 0);
if ($elapsed < 3) {
// Submitted in under 3 seconds — likely a bot
http_response_code(200);
exit;
}
// Clear the timestamp so it can't be reused
unset($_SESSION['form_loaded_at']);
Three seconds is a conservative threshold. You can raise it to 5 seconds if you are still seeing fast-submission spam, but keep in mind that users on autofill or users who know your form well may legitimately submit quickly.
Effectiveness
Honest assessment: honeypots stop the large majority of low-sophistication bots — form scrapers, basic spam tools, and mass-submission scripts. They do not stop:
- Headless browsers that execute JavaScript and CSS (Puppeteer, Playwright bots that actually render the page)
- Human-operated spam farms where a real person fills in the form
- Targeted attacks on your specific site where the attacker has inspected your HTML
For most contact forms and comment forms, a honeypot combined with a time-based check stops 90% or more of spam. For higher-value forms — registrations, paid actions, anything with real incentive for abuse — combine the honeypot with a proper CAPTCHA.
When to use honeypot vs CAPTCHA
| Method | User friction | Bot effectiveness | PHP complexity | External dependency |
|---|---|---|---|---|
| Honeypot | None | Medium | Trivial | None |
| Math CAPTCHA | Low | Medium | Trivial | None |
| Securimage (image CAPTCHA) | Medium | High | Medium | None (self-hosted) |
| Cloudflare Turnstile | None | Very high | Easy | Cloudflare |
Recommended approach by use case:
- Low-traffic contact form: honeypot alone is usually sufficient
- Medium-traffic form: honeypot + time-based check
- High-value form (registrations, checkout): Cloudflare Turnstile — invisible and free
- No external services allowed: honeypot + Securimage image CAPTCHA
Full working example
A complete PHP contact form with honeypot protection. Save the form page as contact.php and the processor can live in the same file or a separate handler.
<?php
// contact.php — form display + processing in one file
session_start();
$errors = [];
$success = false;
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 1. Honeypot check
if (!empty($_POST['website'])) {
http_response_code(200);
exit;
}
// 2. Time-based check
$elapsed = time() - ($_SESSION['form_loaded_at'] ?? 0);
unset($_SESSION['form_loaded_at']);
if ($elapsed < 3) {
http_response_code(200);
exit;
}
// 3. Normal validation
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$message = trim($_POST['message'] ?? '');
if ($name === '') { $errors[] = 'Name is required.'; }
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) { $errors[] = 'Valid email required.'; }
if (strlen($message) < 10) { $errors[] = 'Message is too short.'; }
if (empty($errors)) {
// Send email, save to DB, etc.
$success = true;
}
} else {
// Record when the form was first displayed
$_SESSION['form_loaded_at'] = time();
}
?>
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Contact</title></head>
<body>
<?php if ($success): ?>
<p>Thanks — your message was sent.</p>
<?php else: ?>
<?php foreach ($errors as $e): ?>
<p style="color:red"><?= htmlspecialchars($e) ?></p>
<?php endforeach ?>
<form method="post" action="">
<!-- Honeypot — hidden from humans -->
<div style="display:none" aria-hidden="true">
<input type="text" name="website" tabindex="-1" autocomplete="off" />
</div>
<label>Name <input type="text" name="name" required /></label>
<label>Email <input type="email" name="email" required /></label>
<label>Message <textarea name="message" required></textarea></label>
<button type="submit">Send</button>
</form>
<?php endif ?>
</body>
</html>
Verdict: The honeypot technique is the first spam protection you should add to any PHP form — it is free, frictionless, and stops the majority of automated spam. It takes under ten minutes to add to an existing form. Add it now; layer in a math CAPTCHA or Cloudflare Turnstile later if bots are still getting through. For WordPress sites, see the PHP form spam protection overview or jump straight to the Securimage quickstart for a self-hosted image CAPTCHA.