If your inbox is filling with junk through a contact form you already trust, you do not need a new form platform. Detected form widget honeypot spam protection is a layered approach you add to the markup you already have. The Detected Form widget connects an existing HTML form to WidgetJar so you can capture consent alongside submissions, but spam filtering still starts with the form itself. This guide walks through three client-side layers — a hidden honeypot field, a timing check and native HTML validation — plus the consent checkbox and a test path you can run before launch.
What the Detected Form widget does — and what it doesn't
The detected form widget connects an existing HTML form to WidgetJar. That is the important framing: you keep your own <form>, your own fields and your own endpoint, and the widget links that form to the platform. You are not rebuilding your contact page and you are not moving to a different form service.
A few practical consequences follow:
- It is not an anti-spam guarantee. Nothing in an embeddable widget stops a bot that submits directly to your form
action. The filtering described below lives in your form. - It does not replace your endpoint. Whatever receives the data — a mail handler, a serverless function, a third-party form endpoint — still needs its own checks.
- It does help with consent. You can capture when a visitor gave consent as part of the submission, so you have a record separate from the message body.
If you are starting from a static page whose form currently goes nowhere, the adjacent guide on how to make an HTML form work without a backend covers the hand-off before you worry about spam.
Why a plain contact form attracts spam
Bots do not need to understand your business. They read the HTML, find the <form>, and post to whatever the action attribute points at. As MDN's explanation of how browsers send form data describes, a form is essentially a way to configure an HTTP request: the action defines where the data goes and the method defines how it is sent. A public action URL plus a POST body is all a script needs.
Because the browser sends every named control, a bot that fills all fields will also fill any field you did not expect it to touch. That quirk is the basis of the honeypot technique below.
Layer 1: a honeypot field real visitors never fill
A honeypot is a form control that is hidden from people but present in the HTML. A human never sees it and leaves it blank. Automated scripts that fill every input will populate it, and you can reject those submissions.
<form id="contact" action="/contact-handler" method="post">
<div style="position:absolute;left:-9999px" aria-hidden="true">
<label for="company_website">Leave this field empty</label>
<input type="text" id="company_website" name="company_website"
tabindex="-1" autocomplete="off">
</div>
<!-- real fields -->
<label for="email">Email</label>
<input type="email" id="email" name="email" required>
<button type="submit">Send</button>
</form>
Four details matter here:
- Hide it visually, not with
type="hidden". Atype="hidden"input is usually skipped by bots and carries no signal. Off-screen positioning or adisplay:nonewrapper keeps the field in the submission. aria-hidden="true"on the wrapper keeps screen readers from announcing the decoy to assistive technology users.tabindex="-1"keeps keyboard users from tabbing into a field they cannot see.autocomplete="off"discourages browsers from autofilling a value that would look like spam.
On the receiving side, reject any submission where company_website is not empty. Do not send a helpful error — the sender is not a person who needs to fix anything.
Layer 2: a timing check for suspiciously fast submissions
A real visitor takes time to read the page and type a message. Most scripts submit almost instantly after the document loads. Record a timestamp when the form is rendered and compare it at the moment of submission.
(function () {
var form = document.getElementById('contact');
var renderedAt = Date.now();
var minimumMs = 3000; // adjust after watching real submissions
form.addEventListener('submit', function (event) {
var elapsed = Date.now() - renderedAt;
if (elapsed < minimumMs) {
event.preventDefault();
return; // silently drop, or show a neutral message
}
// Optional: attach the elapsed time for your handler to review
var field = document.createElement('input');
field.type = 'hidden';
field.name = 'elapsed_ms';
field.value = String(elapsed);
form.appendChild(field);
});
})();
Two cautions:
- Pick the threshold by watching your real submissions, not by guessing. If genuine returning visitors paste a pre-written message in two seconds, a high threshold punishes them. A few seconds is usually enough to catch naive bots.
- Treat this as a signal, not proof. A patient bot (or a human spammer) will wait. Combine it with the honeypot rather than relying on it alone.
Also remember that if JavaScript fails to load, the timing check simply does not run. The form should still submit — the check is an additional filter, not a hard dependency.
Layer 3: native HTML validation before anything is sent
Before your endpoint sees anything, the browser can reject obviously malformed input. MDN's client-side form validation guide lists the built-in constraints: required, minlength and maxlength, min, max and step, type, and pattern. It also notes that HTML validation is faster than JavaScript and recommends beginning with robust HTML features, then enhancing with JavaScript where needed.
That matches the layered order here. Add sensible constraints to the fields you already have:
<label for="name">Name</label>
<input type="text" id="name" name="name" required maxlength="80" autocomplete="name">
<label for="email">Email</label>
<input type="email" id="email" name="email" required autocomplete="email">
<label for="message">Message</label>
<textarea id="message" name="message" required minlength="10" maxlength="2000"></textarea>
Clear labels and messages matter as much as the constraints themselves. If you are tightening your fields, the companion articles on HTML form labels for lead forms and contact form error messages cover wording and accessible error placement.
The receiving endpoint still matters
The same MDN validation page is direct about this: client-side validation “should not be considered an exhaustive security measure.” It advises performing validation, including security checks, on any form-submitted data on the server side as well, because client-side validation is easy to bypass.
Everything in layers 1–3 runs in the visitor's browser. A script posting directly to your action URL skips all of it. So keep a check where the data lands:
- Reject submissions where the honeypot field has a value.
- Reject submissions that are missing required fields.
- Sanitize and length-limit any text you pass into email templates or a database.
- Keep any confidential values — passwords, card numbers, ID documents — out of the form entirely. MDN also warns against sending sensitive data with GET, because it appears in the URL bar; for anything sensitive, POST over HTTPS is the baseline.
The widget's role is to connect the form and capture consent; it does not turn unreliable client-side filtering into a server-side guarantee.
Adding the consent checkbox with the Detected Form widget
Where consent applies (for example, storing a message and using the contact details to reply or follow up), add an explicit unchecked checkbox rather than pre-ticked wording:
<label>
<input type="checkbox" name="consent" required>
I agree that my details may be used to respond to this enquiry.
</label>
Marking it required means the browser blocks submission until it is ticked, and the field arrives at your handler with a clear yes. Record the time of consent alongside the submission so your record shows when it was given.
When you connect the form to the widget, review which fields are recorded and keep sensitive fields out of that capture. The form connects; it does not need to copy everything.
If your conversion path also includes a popup, accessibility work carries over. The W3C's modal dialog pattern describes how focus should move into a dialog and stay contained, and the accessible popup checklist applies the same thinking to lead capture. Hiding a honeypot correctly is the same discipline in miniature: keep decoys out of the tab order and out of what assistive technology announces.
Test the whole path before launch
Run all four checks, and inspect what your handler actually receives each time (your browser's Network tab shows the request payload, as MDN describes):
- Legitimate entry. Fill the visible fields, leave the honeypot untouched, take more than a few seconds, tick consent. Submission should succeed and arrive with all expected values.
- Filled honeypot. Use developer tools to put a value in the decoy field and submit. Your handler should reject or discard it.
- Too-fast submission. Submit immediately after load. The page should block it without a confusing error for a real visitor.
- Keyboard and screen reader pass. Tab through the form. Focus should skip the decoy entirely and land on visible fields in order. Confirm no honeypot label is announced.
Pre-launch checklist
- Honeypot field present in the HTML and hidden visually, with
aria-hidden="true"on its wrapper andtabindex="-1"on the input - Handler rejects submissions where the honeypot is filled
- Timing threshold chosen from real observations, not a guess
- Form still submits if the timing script fails to load
-
required,type,pattern,minlengthandmaxlengthapplied where relevant - Visible required-field indicators, per accessibility guidance
- Endpoint re-validates every field it receives
- Consent checkbox unchecked by default, marked
required, and its timestamp recorded - No passwords, card numbers or identity documents captured by the form or the widget
- All four test submissions inspected in the network panel
Next step
You already have the form; the fastest way to add consent capture without touching your backend is the Detected Form widget. Connect your existing markup, review which fields are recorded, then run the four tests above. The spam layers stay in your form where they belong, and the widget handles the connection and the consent record.