Free plan250 submissions a month, spam filtering included, no card.See pricing →
Guide · Spam

Stop contact form spam without punishing your visitors

CAPTCHA is the reflex answer, and it is the wrong one to reach for first. It taxes every honest visitor to stop a bot that a hidden input would have caught for free, and screen-reader users, people with motor impairments, and anyone on a bad connection pay the highest price. Below are the layers that actually work, in the order you should add them.

4 min readAny formLast reviewed

On this page

Your options, compared

The usual defences, weighed by what they cost the people you actually want to hear from.

ApproachCost to visitorsAccessibilityStops botsSetup
Visual CAPTCHA puzzleA puzzle every timeHard for manyMostWidget and keys
Invisible challenge, such as TurnstileUsually noneGoodMostWidget and keys
Honeypot, timing and server filteringNoneUnaffectedThe large majorityOne hidden field

The minimal integration

A honeypot is a normal input, hidden from people and left empty by them. Bots fill every field they find, so a non-empty value is a reliable spam signal. Never hide it with type="hidden". Bots skip those; hide it with CSS and keep it out of the tab order.

HTML
<form action="https://mtform.co/f/your-form-key" method="POST">
  <label>
    Name
    <input name="name" autocomplete="name" required>
  </label>

  <label>
    Email
    <input type="email" name="email" autocomplete="email" required>
  </label>

  <label>
    Message
    <textarea name="message" required></textarea>
  </label>

  <!-- Spam trap: hidden from people, tempting to bots. Leave it empty. -->
  <input
    type="text"
    name="_mtf_honeypot"
    tabindex="-1"
    autocomplete="off"
    aria-hidden="true"
    style="position:absolute;left:-9999px"
  >

  <button type="submit">Send</button>
</form>

The _mtf_honeypot field is already in it. MakeTheForm drops any submission that fills it, and still answers with success so the bot learns nothing.

Hidden visually and from assistive technology, and removed from the tab order. It has no label and a name no autofill recognises, because browsers ignore autocomplete="off" and will fill a field labelled "Company" on a real visitor’s behalf.

Honeypot field, done correctly
<form action="https://mtform.co/f/your-form-key" method="POST">
  <label for="email">Email</label>
  <input id="email" name="email" type="email" required>

  <label for="message">Message</label>
  <textarea id="message" name="message" required></textarea>

  <!-- Honeypot: invisible to humans, irresistible to bots. -->
  <div aria-hidden="true" style="position:absolute;left:-9999px;">
    <input name="_mtf_honeypot" type="text" tabindex="-1" autocomplete="off">
  </div>

  <button type="submit">Send</button>
</form>

Bots post the instant they parse the page. Stamping render time and rejecting sub-two-second submissions costs a human nothing. Nobody reads and fills a contact form that fast.

Time-to-submit check
<input type="hidden" name="_rendered_at" value="">
<script>
  document.currentScript.previousElementSibling.value = Date.now();
</script>

Step by step

  1. 1

    Add a honeypot field

    Add a text input that humans never see, positioned off-screen with CSS and marked aria-hidden with tabindex="-1". Any submission that fills it is automated.

  2. 2

    Add a time-to-submit check

    Record when the page rendered in a hidden field. Treat submissions that arrive within a couple of seconds as automated, because no human completes a form that quickly.

  3. 3

    Filter server-side, never in the browser

    Any check that lives only in client JavaScript is trivially bypassed by posting directly to your endpoint. The decision has to happen on the server that receives the POST.

  4. 4

    Lock the endpoint to your own origins

    Restrict which domains may submit to your form. A public endpoint that accepts posts from anywhere is a spam magnet regardless of the other layers.

  5. 5

    Review what was caught, do not just delete it

    Keep filtered submissions in a spam view for a retention window so you can recover a false positive. A filter you cannot audit will eventually eat a real customer enquiry without you ever knowing.

Why a type="hidden" honeypot doesn’t work

It is the obvious way to hide a field, and it is the one bots check for. Scripted spam leaves hidden inputs alone, so the trap is never sprung. Hide the field with CSS instead and keep it out of the tab order.

A honeypot bots skip · don’t ship this
<!-- Bots know to leave hidden inputs empty. -->
<input type="hidden" name="_mtf_honeypot" value="">

Common pitfalls

Hiding the honeypot with type="hidden"

Bots specifically skip hidden inputs because they know the trick. The field must be a normal visible-type input that CSS moves off-screen, otherwise it catches nothing.

Using display:none on the honeypot

Some bots parse computed styles and skip anything with display:none. Off-screen positioning is harder to detect and behaves identically for real users.

Silently discarding suspected spam

Every filter has false positives. Discarding without a reviewable spam folder means a legitimate enquiry disappears with no trace and no way to recover it.

Relying on client-side validation alone

A bot posts straight to your endpoint URL and never runs your JavaScript. If the check is not on the server, it is decoration.

Troubleshooting

SymptomWhat’s happeningFix
still spammedVolume continues after adding a honeypot.Check the field is not type="hidden" and not display:none, and confirm the server actually rejects on a non-empty value rather than only logging it.
false positivesReal enquiries never arrive, and Spam shows them refused for filling the hidden field.Browser autofill or a password manager is filling the honeypot. autocomplete="off" does not stop it. Remove any label or id that reads like a real field (company, name, website, phone) and name it _mtf_honeypot.
403Submissions rejected after adding origin rules.The posting domain is not on the allowed list. Add every origin you submit from, including the www and non-www variants.

Frequently asked questions

Do I need a CAPTCHA to stop contact form spam?

Usually not. A honeypot field, a time-to-submit check, and server-side filtering stop the large majority of automated spam. CAPTCHA adds friction for every legitimate visitor and creates real accessibility barriers, so it is worth trying as a last resort rather than a first line.

Is a honeypot field bad for accessibility?

Not when implemented correctly. Position it off-screen with CSS, set aria-hidden="true" on its wrapper, and give it tabindex="-1" so keyboard and screen-reader users never encounter it. Avoid display:none, which some bots detect.

Why am I getting spam when my form has JavaScript validation?

Because bots post directly to your form endpoint and never load your page or run your scripts. Client-side validation improves the experience for real users but provides no spam protection at all. The filtering has to happen server-side.

What is a good reCAPTCHA alternative for a contact form?

Start with the layers that cost visitors nothing: a honeypot field, a time-to-submit check, and server-side filtering. If a form is still targeted after that, add an invisible challenge such as Cloudflare Turnstile, which most people never see. MakeTheForm verifies Turnstile tokens for you when you turn it on for a form.

Should spam be deleted automatically?

No. Keep it in a reviewable spam view for a retention window. Every filter produces false positives eventually, and without a spam folder a legitimate enquiry is lost silently.

Keep going

Ship this form for real.

Create an endpoint, paste it in and watch the first submission land in your inbox.

Create free endpoint