Contact form with captcha
Two layers, in the order you should add them. The honeypot is invisible, costs nothing and stops most automated submissions on its own. The captcha goes on top for the ones that get past it.
Replace YOUR_FORM_ID with a form of your own and this sends to your inbox.
<form action="https://submit.formpost.ai/YOUR_FORM_ID" method="POST">
<h2>Contact us</h2>
<p class="lede">We read every message. Bots do not get through.</p>
<label for="name">Name</label>
<input id="name" type="text" name="name" required>
<label for="email">Email</label>
<input id="email" type="email" name="email" required>
<label for="message">Message</label>
<textarea id="message" name="message" rows="3" required></textarea>
<!-- Layer one: invisible to people, irresistible to bots -->
<input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off">
<!-- Layer two: the widget renders itself into this div -->
<div class="cf-turnstile" data-sitekey="YOUR_TURNSTILE_SITE_KEY"></div>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<button type="submit">Send message</button>
</form>body {
margin: 0;
padding: 32px 16px;
background: #fafafa;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
color: #171717;
}
form {
display: flex;
flex-direction: column;
max-width: 430px;
margin: 0 auto;
padding: 28px;
background: #fff;
border: 1px solid #ededed;
border-radius: 14px;
}
h2 {
margin: 0 0 4px;
font-size: 20px;
}
.lede {
margin: 0 0 20px;
font-size: 14px;
color: #737373;
}
label {
margin-bottom: 6px;
font-size: 13px;
font-weight: 600;
color: #404040;
}
input,
textarea {
box-sizing: border-box;
font: inherit;
margin-bottom: 16px;
padding: 10px 12px;
color: #171717;
background: #fff;
border: 1px solid #d4d4d4;
border-radius: 8px;
}
input:focus,
textarea:focus {
border-color: #171717;
outline: 2px solid rgba(23, 23, 23, 0.12);
}
textarea {
resize: vertical;
}
button {
padding: 11px 16px;
font: inherit;
font-weight: 600;
color: #fff;
background: #171717;
border: 0;
border-radius: 8px;
cursor: pointer;
}
.hint {
margin: -8px 0 16px;
font-size: 12px;
color: #a3a3a3;
}Start with the honeypot
A field people never see and bots fill in anyway, because most of them populate every input they find. Anything arriving with it filled is discarded. It costs one line, it never asks a real visitor to do anything, and it removes the bulk of automated spam before you have made anyone look at a puzzle.
<input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off">The tabindex and autocomplete attributes matter: without them a keyboard user can tab into an invisible field, and a password manager can fill it. Both would flag a genuine visitor as a bot.
Then add the captcha
Turnstile, hCaptcha and reCAPTCHA all work the same way here — render the widget as their documentation describes, then save the matching secret key in your form settings. The token is verified server-side before the submission is accepted, which is the part that makes it worth anything: a captcha checked only in the browser stops nobody.
- Turnstile is usually the right default — no puzzles for most visitors, and no visual challenge unless something looks wrong.
- hCaptcha if you need a privacy-focused provider with a free tier and are willing to show interactive challenges.
- reCAPTCHA if you are already invested in it. Be aware of what it means for your privacy policy in the EU.
- Whichever you pick, the secret key goes in your form settings and never into your markup. Only the site key is public.
Do not reach for a captcha first
Every captcha costs you real submissions. People abandon forms over them, some fail them repeatedly through no fault of their own, and screen reader users have the worst time of anyone. Add the honeypot, launch, and see whether you have a problem before you make every visitor prove they are human.
Questions about this contact form
- Which captcha should I use on a contact form?
- Cloudflare Turnstile for most sites — it is free, usually invisible, and does not show a puzzle unless something looks suspicious. hCaptcha and reCAPTCHA are both supported if you prefer them.
- Do I need a captcha at all?
- Probably not on day one. A honeypot field stops the majority of automated submissions and costs a real visitor nothing. Add a captcha when you can see spam arriving, not before.
- Where does the secret key go?
- In your form settings, never in your HTML. Only the site key belongs in the markup; the secret is what proves the token is genuine when it is verified server-side.
- Does the honeypot field hurt accessibility?
- Not if you set tabindex="-1" and autocomplete="off" as shown. Without those a keyboard user can tab into it or a password manager can fill it, and a genuine visitor gets treated as a bot.
Related examples
Contact form with validation
Native constraints, custom messages, and errors a screen reader announces.
PHP contact form
The mail() version, and the reason most people should delete it.
Contact us page
The whole page: address, phone and hours beside the form, in two columns.
Or go back to all 21 contact form examples.
Give this form an endpoint
Formpost takes the submission, emails it to you and keeps a searchable copy. Free for 250 messages a month, with unlimited forms and no card.