Overview
Add the SpamBlock pixel to any static site or custom JavaScript application. The script protects native HTML forms by default and exposes hooks if you need more control.
Prerequisites
- Ability to edit the HTML template or bundle a JavaScript asset
- Forms that render as native
<form>elements in the DOM - Optional: build tooling if you prefer to load the script from a module bundler
Setup
Quick start
<script src="https://api.spamblock.io/sdk/pixel/v1.js" defer></script>
<form data-block-spam action="/api/contact" method="post">
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
Add data-block-spam to any form you want protected. If you omit the attribute, all forms on the page are protected automatically.
Configuration
All configuration options are set via data-* attributes on the script tag. See the complete configuration reference for all available options and their defaults.
Advanced usage
Manual protection
window.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form');
if (form) {
form.setAttribute('data-block-spam', 'true');
}
});
React example
import { useEffect, useRef } from 'react';
export function ContactForm() {
const formRef = useRef(null);
useEffect(() => {
if (formRef.current) {
formRef.current.setAttribute('data-block-spam', 'true');
}
}, []);
return (
<form ref={formRef} action="/api/contact" method="post">
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
);
}
Form Submission Methods
SpamBlock supports two submission patterns: native form submission (default) and JavaScript-based submission (for SPAs and custom handling).
Native Form Submission (Default)
By default, SpamBlock uses native form submission via form.requestSubmit(). This preserves browser validation, form actions, and submitter buttons. The form will navigate to the URL specified in the action attribute.
<form data-block-spam action="/api/contact" method="post">
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
</form>
When to use:
- Traditional server-rendered forms
- Forms that submit to server endpoints
- When you want browser-native validation and navigation
- Simple, zero-configuration setups
How it works:
- SpamBlock intercepts the submit event
- Checks the submission with the API
- If allowed: calls
form.requestSubmit()to trigger native submission - Browser navigates to the form's
actionURL
JavaScript-Based Submission
For single-page applications (SPAs) or when you need custom submission handling, you need to prevent the pixel's default native submission and handle it manually. Here's how:
Important: The pixel calls form.requestSubmit() immediately after emitting the spamblock:allowed event. To prevent native submission, you have two options:
Option 1: Remove the action attribute (Recommended)
<form data-block-spam id="contact-form">
<!-- No action attribute - form won't navigate -->
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
<div id="status-message"></div>
</form>
<script>
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form');
// Intercept allowed event to handle submission manually
form.addEventListener('spamblock:allowed', async (event) => {
// Get form data
const formData = new FormData(form);
// Submit via fetch (or your preferred method)
try {
const response = await fetch('/api/contact', {
method: 'POST',
body: formData
});
if (response.ok) {
document.getElementById('status-message').textContent = '✅ Message sent!';
form.reset();
} else {
throw new Error('Submission failed');
}
} catch (error) {
document.getElementById('status-message').textContent = '❌ Error sending message';
}
});
// Handle blocked submissions
form.addEventListener('spamblock:blocked', (event) => {
const response = event.detail.response;
document.getElementById('status-message').textContent =
`❌ Submission blocked: ${response?.reasons?.join(', ') || 'Spam detected'}`;
});
});
</script>
Option 2: Intercept the original submit event
<form data-block-spam id="contact-form" action="/api/contact" method="post">
<input type="email" name="email" required />
<textarea name="message" required></textarea>
<button type="submit">Send</button>
<div id="status-message"></div>
</form>
<script>
document.addEventListener('DOMContentLoaded', () => {
const form = document.getElementById('contact-form');
let shouldSubmitNatively = false;
// Intercept submit BEFORE SpamBlock to prevent native submission
form.addEventListener('submit', (event) => {
// Only allow native submission if we explicitly set the flag
if (!shouldSubmitNatively) {
event.preventDefault();
}
}, { capture: true }); // Capture phase - runs before SpamBlock's handler
// Handle SpamBlock allowed event
form.addEventListener('spamblock:allowed', async (event) => {
// Prevent the pixel's requestSubmit() from actually submitting
// by ensuring preventDefault was already called
const formData = new FormData(form);
try {
const response = await fetch('/api/contact', {
method: 'POST',
body: formData
});
if (response.ok) {
document.getElementById('status-message').textContent = '✅ Message sent!';
form.reset();
}
} catch (error) {
document.getElementById('status-message').textContent = '❌ Error sending message';
}
});
form.addEventListener('spamblock:blocked', (event) => {
const response = event.detail.response;
document.getElementById('status-message').textContent =
`❌ Submission blocked: ${response?.reasons?.join(', ') || 'Spam detected'}`;
});
});
</script>
When to use:
- Single-page applications (React, Vue, Angular)
- Forms that need to stay on the same page
- Custom submission logic (API calls, analytics, etc.)
- When you need full control over the submission flow
Key differences:
- Native: Form navigates to
actionURL, browser handles everything - JavaScript: Form stays on page, you handle submission and UI
Listening for results
The pixel dispatches custom DOM events you can hook into:
// Submission was allowed - form will submit natively by default
form.addEventListener('spamblock:allowed', (event) => {
const response = event.detail.response;
console.log('Submission allowed', response);
// response contains: { allow: true, score: 0-60, reasons: [], latencyMs: 123 }
});
// Submission was blocked - form will NOT submit
form.addEventListener('spamblock:blocked', (event) => {
const response = event.detail.response;
console.warn('Spam detected', response);
// response contains: { allow: false, score: 61+, reasons: ['disposable_domain'], latencyMs: 123 }
});
Event details:
event.detail.response- The SpamBlock API responseevent.detail.latencyMs- Time taken for the check (milliseconds)event.detail.site- The site domain that was checked
Preventing default submission:
The pixel automatically calls form.requestSubmit() after emitting spamblock:allowed. To prevent this:
- Option 1 (Recommended): Remove the
actionattribute from your form. Without an action,requestSubmit()won't navigate anywhere. - Option 2: Intercept the original
submitevent in the capture phase (before SpamBlock) and callpreventDefault()to stop all submission attempts.
Testing
- Enable debug logging by setting
data-debug="true"on the script tag. - Submit a disposable address like
[email protected]to trigger the disposable domain signal. - Try spam-heavy text ("casino winner viagra") to trigger profanity detection.
- Use browser devtools to ensure the honeypot field
_sb_hpis present and left blank.
For more testing tips and configuration options, see the documentation.
Troubleshooting
| Symptom | Fix |
|---|---|
| Form never submits | Check for competing JavaScript that also prevents submission. |
| Legitimate submissions blocked | Adjust data-max-score or inspect console logs (with data-debug="true") for the reasons array. See configuration options. |
| No spam being caught | Confirm the pixel loads on the page and the <form> has data-block-spam. |
For more troubleshooting tips and configuration options, see the documentation.