Skip to content

Honeypot Spam Protection

Protect your forms from spam bots with an invisible honeypot field.

Note: Forminit includes built-in spam protection by default. Honeypot is an optional extra layer of protection for forms that receive high volumes of spam.


Honeypot protection exploits a key difference between humans and bots:

  1. Humans don’t see or interact with hidden fields
  2. Bots automatically fill out every field they find, including hidden ones

When a submission includes a value in the honeypot field, Forminit flags it as spam and rejects the submission. Legitimate users will never trigger this because they can’t see or fill the hidden field.


Add a hidden honeypot field to your form:

<form id="contact-form">
  <input type="text" name="fi-sender-firstName" placeholder="First name" required />
  <input type="text" name="fi-sender-lastName" placeholder="Last name" required />
  <input type="email" name="fi-sender-email" placeholder="Email" required />
  <textarea name="fi-text-message" placeholder="Message" required></textarea>
  
  <input type="hidden" name="_gotcha" style="display:none !important" />
  
  <button type="submit">Send</button>
</form>

That’s it! Any bot that fills in the _gotcha field will have their submission automatically rejected.


AttributeValueRequired
typehiddenYes
name_gotcha (default)Yes
styledisplay:none !importantRecommended
valueEmpty (no value)Yes

Important notes:

  • The field must have no value — leave it empty
  • Use both type="hidden" and style="display:none !important" for maximum protection
  • Do not add required attribute to the honeypot field

By default, the honeypot field is named _gotcha. Some sophisticated bots recognize common honeypot names and avoid them. You can use a custom field name for better protection.

  1. Go to your Form Settings
  2. Navigate to Honeypot
  3. Enter your custom field name
  4. Save changes

After changing the field name in settings, update your HTML to match:

<input type="hidden" name="website_url" style="display:none !important" />

Tips for custom field names:

  • Use names that look like real fields (e.g., website, company_url, fax_number)
  • Avoid obvious names like honeypot, trap, or bot_check
  • Don’t use the fi- prefix for honeypot fields

<form id="contact-form">
  <input type="text" name="fi-sender-firstName" placeholder="First name" required />
  <input type="text" name="fi-sender-lastName" placeholder="Last name" required />
  <input type="email" name="fi-sender-email" placeholder="Email" required />
  <textarea name="fi-text-message" placeholder="Message" required></textarea>
  
  <input type="hidden" name="_gotcha" style="display:none !important" />
  
  <button type="submit">Send</button>
</form>

<p id="form-result"></p>

<script src="https://forminit.com/sdk/v1/forminit.js"></script>
<script>
  const forminit = new Forminit();
  const FORM_ID = 'YOUR_FORM_ID';
  
  document.getElementById('contact-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    
    const formData = new FormData(e.target);
    const { data, error } = await forminit.submit(FORM_ID, formData);
    
    if (error) {
      document.getElementById('form-result').textContent = error.message;
      return;
    }
    
    document.getElementById('form-result').textContent = 'Message sent!';
    e.target.reset();
  });
</script>
'use client';

import { useState } from 'react';
import { Forminit } from 'forminit';

export function ContactForm() {
  const [status, setStatus] = useState<'idle' | 'loading' | 'success' | 'error'>('idle');
  const [error, setError] = useState<string | null>(null);
  const forminit = new Forminit({ proxyUrl: '/api/forminit' });

  async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
    e.preventDefault();
    setStatus('loading');
    setError(null);

    const formData = new FormData(e.currentTarget);
    const { data, error } = await forminit.submit('YOUR_FORM_ID', formData);

    if (error) {
      setStatus('error');
      setError(error.message);
      return;
    }

    setStatus('success');
    e.currentTarget.reset();
  }

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" name="fi-sender-firstName" placeholder="First name" required />
      <input type="text" name="fi-sender-lastName" placeholder="Last name" required />
      <input type="email" name="fi-sender-email" placeholder="Email" required />
      <textarea name="fi-text-message" placeholder="Message" required />
      
      <input type="hidden" name="_gotcha" style={{ display: 'none' }} />
      
      {status === 'error' && <p className="error">{error}</p>}
      {status === 'success' && <p className="success">Message sent!</p>}
      
      <button type="submit" disabled={status === 'loading'}>
        {status === 'loading' ? 'Sending...' : 'Send'}
      </button>
    </form>
  );
}
<script setup lang="ts">
import { ref } from 'vue';
import { Forminit } from 'forminit';

const FORM_ID = 'YOUR_FORM_ID';

const status = ref<'idle' | 'loading' | 'success' | 'error'>('idle');
const errorMessage = ref<string | null>(null);
const formRef = ref<HTMLFormElement | null>(null);

const forminit = new Forminit({ proxyUrl: '/api/forminit' });

async function handleSubmit() {
  if (!formRef.value) return;
  
  status.value = 'loading';
  errorMessage.value = null;

  const formData = new FormData(formRef.value);
  const { data, error } = await forminit.submit(FORM_ID, formData);

  if (error) {
    status.value = 'error';
    errorMessage.value = error.message;
    return;
  }

  status.value = 'success';
  formRef.value.reset();
}
</script>

<template>
  <form ref="formRef" @submit.prevent="handleSubmit">
    <input type="text" name="fi-sender-firstName" placeholder="First name" required />
    <input type="text" name="fi-sender-lastName" placeholder="Last name" required />
    <input type="email" name="fi-sender-email" placeholder="Email" required />
    <textarea name="fi-text-message" placeholder="Message" required />
    
    <input type="hidden" name="_gotcha" style="display: none !important" />
    
    <p v-if="status === 'error'" class="error">{{ errorMessage }}</p>
    <p v-if="status === 'success'" class="success">Message sent!</p>
    
    <button type="submit" :disabled="status === 'loading'">
      {{ status === 'loading' ? 'Sending...' : 'Send' }}
    </button>
  </form>
</template>

When building forms programmatically on the server, include the honeypot field:

import express from 'express';
import { Forminit } from 'forminit';

const app = express();
app.use(express.urlencoded({ extended: true }));

const forminit = new Forminit({
  apiKey: process.env.FORMINIT_API_KEY,
});

const FORM_ID = 'YOUR_FORM_ID';

app.post('/submit', async (req, res) => {
  const formData = new FormData();
  
  // Add form fields
  formData.append('fi-sender-firstName', req.body.firstName);
  formData.append('fi-sender-lastName', req.body.lastName);
  formData.append('fi-sender-email', req.body.email);
  formData.append('fi-text-message', req.body.message);
  
  if (req.body._gotcha) {
    formData.append('_gotcha', req.body._gotcha);
  }
  
  const { data, error } = await forminit.submit(FORM_ID, formData);
  
  if (error) {
    return res.status(400).json({ success: false, message: error.message });
  }
  
  res.json({ success: true, submissionId: data.hashId });
});

app.listen(3000);

For maximum spam protection, combine honeypot with other methods:

MethodBest ForUser Impact
HoneypotBasic bot protectionNone (invisible)
reCAPTCHAAdvanced bot protectionLow (invisible v3) to Medium (checkbox)
hCaptchaPrivacy-focused protectionMedium (requires interaction)
Rate LimitingPreventing abuseNone (automatic)

Example combining honeypot with reCAPTCHA:

<form id="contact-form">
  <input type="text" name="fi-sender-firstName" placeholder="First name" required />
  <input type="email" name="fi-sender-email" placeholder="Email" required />
  <textarea name="fi-text-message" placeholder="Message" required></textarea>
  
  <!-- Honeypot -->
  <input type="hidden" name="_gotcha" style="display:none !important" />
  
  <!-- reCAPTCHA -->
  <div class="g-recaptcha" data-sitekey="YOUR_SITE_KEY"></div>
  
  <button type="submit">Send</button>
</form>

If legitimate submissions are being blocked:

  1. Check for autofill — Some password managers or browser extensions might fill hidden fields
  2. Verify the field is hidden — Ensure both type="hidden" and style="display:none" are set
  3. Check for default values — Make sure the honeypot field has no value attribute

Sophisticated bots may avoid honeypot fields. Consider:

  1. Use a custom field name — Change from the default _gotcha to something less obvious
  2. Add reCAPTCHA or hCaptcha — Combine honeypot with CAPTCHA for stronger protection
  3. Use CSS hiding — Some bots only check for type="hidden", not CSS visibility

Verify your implementation:

<!-- ✅ Correct -->
<input type="hidden" name="_gotcha" style="display:none !important" />

<!-- ❌ Wrong - has fi- prefix -->
<input type="hidden" name="fi-_gotcha" style="display:none !important" />

<!-- ❌ Wrong - has a value -->
<input type="hidden" name="_gotcha" value="test" style="display:none !important" />

<!-- ❌ Wrong - missing style -->
<input type="hidden" name="_gotcha" />