Onsite Spam Guard

Description

Onsite Spam Guard blocks spam on the forms your visitors actually use — WordPress comments, Contact Form 7, WooCommerce product reviews, Jetpack contact forms and WP Job Manager job submissions, plus account registration and BuddyPress private messages if you want them — without sending anything to a third-party service, requiring an API key, or putting a CAPTCHA in front of your users.

Protection is built from a pipeline of independent guards. Each guard is a small, focused check (a hidden honeypot field, a submit-speed gate, a keyword filter, and so on). Every enabled guard looks at every submission, and one objection is enough to block it; the log records every guard that objected, so you can see exactly why. Every guard can be toggled and tuned from a single settings page — for the whole site, or form by form — and every block can be logged for review.

Spam guards

  • Honeypot — a hidden field that bots fill in but humans never see.
  • Duplicate detection — rejects identical submissions sent within a short window.
  • Time gate — rejects submissions completed faster than a human could plausibly type.
  • Signature — requires a server-signed token proving the form was served by this site, deterring automated cross-site posting.
  • Rate limit (optional) — throttles repeated submissions from the same sender within a short window, keyed on the logged-in user when there is one and the connection IP otherwise.
  • Link limit — flags submissions that contain too many URLs.
  • Keyword block — rejects submissions matching a configurable blocklist of words or phrases.
  • Behavioral analysis (optional) — scores mouse movement, clicks, and time on page to spot bot-like interaction.

Why you might choose it

  • No external services. Nothing leaves your site. No accounts, no API keys, no per-submission fees.
  • No CAPTCHA. Protection is invisible to legitimate visitors.
  • Try before you enforce. Monitor mode records what would have been blocked without blocking anything, for the whole site or a single form, and the Per-form tab sums up what enforcing a form would catch before you switch it on.
  • Tuned per form. A contact form can be stricter about links than a comment thread, and a form such as a job listing starts with limits that suit it.
  • Allowlist. Trusted IPs, CIDR ranges, email addresses, and email domains bypass every guard.
  • Logging with retention. Blocked submissions are recorded in a dedicated table with a paginated admin viewer, and old entries are pruned automatically on a schedule you control.
  • Privacy-aware. The plugin registers suggested privacy-policy text describing exactly what it records.
  • Modern, dependency-free code. PHP 8.2+, vanilla front-end JavaScript (no jQuery), and no runtime third-party libraries.

Works with

  • WordPress comments (always).
  • WooCommerce product reviews (when WooCommerce is active).
  • Jetpack contact form blocks (when Jetpack is active).
  • WP Job Manager job submissions (when WP Job Manager is active).
  • Contact Form 7 forms (when Contact Form 7 is active).
  • BuddyPress private messages (when BuddyPress is active; off by default).
  • Account registration forms — WordPress, WooCommerce My Account and BuddyPress signup (off by default).

Screenshots

Installation

  1. Upload the onsite-spam-guard folder to /wp-content/plugins/, or install it through Plugins Add New.
  2. Activate the plugin through the Plugins menu in WordPress.
  3. Go to Spam Guard Settings to choose which form types to protect and to enable or tune individual guards.
  4. Review anything that gets blocked under Spam Guard Spam Logs.

No further configuration is required — sensible defaults are applied on activation.

FAQ

Does this send my data to any external service?

No. Every check runs on your own server. Nothing about a submission is sent anywhere outside your site.

Will legitimate visitors see a CAPTCHA or extra step?

No. All protection is invisible. The honeypot field is hidden, and the timing and behavioral checks happen in the background.

What does it store, and for how long?

When a submission is blocked, or would have been under monitor mode (and logging is enabled), the plugin records when it happened, every guard that objected, the form, the reason, whether it was blocked or let through, a short excerpt of the content, the visitor IP address, and the browser user-agent. Entries older than the retention window (default 30 days, configurable; set to 0 to keep them indefinitely) are pruned automatically. The plugin also registers suggested privacy-policy text you can add to your site’s policy.

Can AI assistants and other tools read the spam log?

On WordPress 6.9 and later, the plugin offers two read-only « abilities » through WordPress’s Abilities API: a summary of the last 7 days, and a list of recent blocks. Tools that use the API — REST clients, MCP connectors, and AI features built on WordPress’s AI Client — can read them, but only when acting for someone who can manage the site’s settings: the same people who can open the Spam Logs screen.

They leave out personal data. There are no IP addresses, no browser user-agents and none of the submitted text: only when a submission was blocked, on which form, which checks objected, and why. The plugin sends nothing anywhere itself; the abilities answer only when something on your site asks. On earlier versions of WordPress they are not registered and nothing changes.

I’m behind Cloudflare or a load balancer and the wrong IP is logged.

By default the plugin uses the direct connection IP, because forwarded headers can be forged. If your site sits behind a reverse proxy, enable Trust proxy headers for IP detection under Spam Guard Settings Allowlist.

The visitor’s address is then read from the entry your proxy added to the X-Forwarded-For header, which a visitor cannot forge. That relies on your server accepting traffic only from the proxy: if it can also be reached directly, a visitor who goes around the proxy can still supply an address of their choosing. With Cloudflare, that means restricting your server to Cloudflare’s published address ranges.

To check your setup, look at What this server sees beneath the setting. It shows the address your request arrived from, whether a forwarded header was present, and the address the plugin is using, with a plain verdict on whether the setting matches. The same check runs under Tools Site Health, and is marked critical when the setting is on, an allowlist is in use, and a request reached the server without passing through a proxy.

If your site sits behind two proxies — Cloudflare in front of a load balancer, for example — every visitor will appear to come from a Cloudflare address. A developer can correct that with the simple_spam_shield_trusted_proxy_hops filter, returning the number of proxies in front of the site.

A legitimate submission was blocked. What do I do?

By default a blocked comment or review is placed in the spam queue (Comments Spam) rather than being rejected outright, so you can restore a false positive with one click — nothing is lost. Open Spam Guard Spam Logs to see which guard blocked it and why, then loosen that guard on the settings page — for example, raise the link limit, lower the behavioral threshold, or add the sender to the allowlist. If you would rather reject blocked comments with an error message, enable that option under Spam Guard Settings General.

Can I limit how often one person can submit?

Yes. Enable Rate limit on the Guards tab, then set the maximum number of submissions and the window they are counted over — 20 per hour and 5 per minute are both expressible. It counts per sender — the logged-in user where there is one, otherwise the connection IP — and each form type is counted separately. It is off by default, because on sites where many visitors share an address (an office, a school, or mobile carrier NAT) an IP-based limit can catch people who are not doing anything wrong. Set the maximum to 0 to disable it without turning the guard off.

Does this work with Contact Form 7?

Yes. Every Contact Form 7 form on the site is protected the moment the plugin is active, and a Contact Form 7 forms switch appears under Protection targets on the General tab.

There is nothing to add to your forms. You do not place a tag in each form, and there is no per-form setup — forms you build later are covered the same way, with no step to forget.

What runs is the full set of guards rather than a single trick: a hidden field bots fill in, a check that the form was not submitted faster than a person could type, a signature proving the submission came from your site, duplicate detection, an optional rate limit, and your own keyword and link rules. A bot written to step around any one of those still has to get past the rest.

A blocked submission is marked as spam, so the visitor sees the message you configured for spam on that form, and the reason is recorded in Contact Form 7’s own spam log next to the plugin name.

Because Contact Form 7 lets you name fields whatever you like, nothing here matches on field names. It reads the types you built the form from: the message and text fields are screened for content — including the subject line, which spam often targets — and the email field is used as the sender’s address. A field holding nothing but a web address is not counted as a link, whether you built it with a URL field or a plain text one, so asking visitors for their website cannot trip the link limit just by being filled in correctly. Links written among your visitor’s words still count, which is what spam actually looks like.

Does this work with WP Job Manager?

Yes. When WP Job Manager is active the frontend job submission form is protected automatically — including the « save as draft » path — and a WP Job Manager job submissions switch appears under Protection targets on the General tab. WP Job Manager’s own answer to submission spam is Google reCAPTCHA; this gives you the same protection without the third-party service or the puzzle your posters have to solve.

Job listings run longer than comments and legitimately carry more links, so the form gets its own section on the Per-form tab and its own starting link limit: 10 links, where comments get 3. An ordinary job description linking to an about page, a benefits page, a team page and an application page already has four. If you have changed the site-wide link limit on the Guards tab, your setting applies to job listings too; set a limit on the Per-form tab to give them their own. It is also worth raising the minimum submit time there, well above the site-wide default — nobody writes a job description in three seconds, so a longer minimum costs genuine posters nothing and stops scripted submissions. The company website, video and Twitter fields are never counted as content links, so filling them in normally cannot trip the limit.

Does it replace WordPress’s built-in comment moderation?

No — it complements it. Onsite Spam Guard’s guards run before WordPress’s own comment checks, and those built-ins still run underneath: the duplicate-comment check, the comment flood throttle, the Disallowed Comment Keys blocklist, and the « hold a comment with this many links » setting (all under Settings Discussion). Its Keyword, Link limit, and Duplicate guards overlap those, so you can rely on either or both. What it adds on top is the honeypot, timing, signature, and behavioral checks core has no equivalent for, one settings screen with logging, and protection for WooCommerce reviews and Jetpack contact forms — not just comments.

You can also go the other way and put WordPress’s list to work everywhere: enable Also apply WordPress’s Disallowed Comment Keys on the Guards tab and the plugin runs every protected submission through core’s own blocklist — so the list you already maintain under Settings Discussion starts covering reviews, Jetpack forms, and any form added through the plugin’s API, not only comments.

Can it stop fake account registrations?

Yes. Turn on Account registration forms under Protection targets on the General tab. It covers the WordPress registration form, the WooCommerce My Account registration form, and BuddyPress signup — whichever your site has.

It is off by default, because a mistake here stops a real person creating an account, which is worse than a rejected comment. The safest way to switch it on is to set that form to Monitor on the Per-form tab first, watch the spam log for a few days, and enforce it once you are happy with what it would catch.

WooCommerce checkout is deliberately left alone. Accounts created while placing an order, after purchase, or through the WooCommerce REST API are never screened, so a customer is never turned away at the point of paying.

Could this lock me out of my site?

No. Onsite Spam Guard never touches logging in: it adds nothing to the login form and does not check login attempts at all.

A refused registration, a blocked comment or a rejected form submission is not a failed login, so none of them can count towards a limit on failed login attempts that you might have from another plugin. You can also always add accounts yourself under Users Add New, which is never screened, and neither is account creation at WooCommerce checkout or by administrators.

The one thing worth knowing concerns the optional rate limit, which is off by default. It counts submissions per visitor address. If your site sits behind a proxy or CDN that the plugin is not set up for, every visitor can appear to come from the same address — so with the rate limit on, one busy minute could refuse everyone’s submissions to your protected forms until that minute passes. It does not affect logging in. What this server sees, beneath Trust proxy headers on the Allowlist tab, tells you whether your site is in that situation.

If you have been locked out of logging in by another plugin before, a shared address is a common cause there too: every failed attempt, from anyone, counts against the one address everybody appears to have. Adding your own IP address to that plugin’s allow list is the usual safeguard.

Can I try a change before it starts blocking anything?

Yes. Turn on Monitor mode on the General tab and the plugin keeps checking every submission and recording what it would have blocked, but lets everything through. The spam log labels those entries Would have blocked, so after a few days you can see exactly what a new setting would have caught — including anything it would have caught by mistake — before you rely on it.

You can also monitor a single form while the rest stay protected: on the Per-form tab, set that form’s mode to Monitor. This is the safest way to switch on protection for a new form.

When you are ready to decide, the Per-form tab sums it up beside each monitored form: how long it has been monitored, how many submissions would have been blocked and by which checks, a link to those entries in the spam log, and an Enforce this form link that switches it on.

While monitor mode is on for the whole site, a notice on every admin screen reminds you that nothing is being blocked.

Does it work with caching plugins?

Yes. The timing and authenticity checks use a token whose signature does not expire (unlike a WordPress nonce, which would go stale on a cached page and block legitimate visitors), so full-page caching does not produce false positives.

Does removing the plugin clean up after itself?

By default, yes — deleting the plugin (not just deactivating it) drops its database table, removes all of its options, clears its scheduled task, and purges its transients, on every site of a multisite network. If you would rather keep your settings and logs (for example, before reinstalling), turn off Delete all plugin data when this plugin is deleted under Spam Guard Settings Logging first.

Reviews

There are no reviews for this plugin.

Contributors & Developers

“Onsite Spam Guard” is open source software. The following people have contributed to this plugin.

Contributors

Translate “Onsite Spam Guard” into your language.

Interested in development?

Browse the code, check out the SVN repository, or subscribe to the development log by RSS.

Changelog

1.7.0

  • Monitored forms now come with a review on the Per-form tab: how long the form has been monitored, how many submissions would have been blocked and by which checks, a link to those entries in the spam log, and an Enforce this form link that switches it on when you are ready.
  • Changed: WP Job Manager job listings now start with a limit of 10 links instead of 3. An ordinary listing linking to an about page, a benefits page, a team page and where to apply was being rejected. If you have changed the site-wide link limit, your setting still applies to job listings.
  • Each blank field on the Per-form tab now says what it inherits: the site-wide value, or a starting value the form brings with it.
  • On WordPress 6.9 and later, tools that use WordPress’s Abilities API — REST clients, MCP connectors and AI features — can read a summary of the last 7 days and a list of recent blocks. Only for administrators, and with no personal data: no IP addresses, browser details or submitted text.
  • A Settings link in the plugin’s row on the Plugins screen.
  • For developers: a plugin that registers its form through the simple_spam_shield_contexts filter can give it its own starting thresholds.
  • Fixed: the « submissions blocked » count at the top of the settings page included submissions that monitor mode let through.

1.6.0

  • Monitor mode: try the plugin, or a change to its settings, without blocking anyone. Every check still runs and anything it would have blocked is logged as « Would have blocked », but the submission goes through. Turn it on for the whole site on the General tab, or for one form on the Per-form tab while the rest stay protected. While it is on for the whole site, a notice on every admin screen says so, so it cannot be forgotten.
  • Account registration forms can now be protected, off by default: WordPress’s own registration form, WooCommerce’s My Account registration, and BuddyPress signup. Accounts created at WooCommerce checkout, and accounts you add yourself under Users Add New, are never screened. Monitor mode is a good way to trial it first.
  • The Allowlist tab now shows what your server sees — the connecting address, any forwarded header, and the address the plugin will use — and whether Trust proxy headers is set correctly for how your site is reached. Site Health reports the same.
  • The spam log records whether each submission was blocked or only would have been, and the 7-day summary counts the two separately. The log table is upgraded; existing entries are kept.
  • Changed: blocked keywords now match words joined by underscores, as in usernames and email addresses, so « casino » matches « casino_bonus_77 ». Words run together, such as « casinobonus », still do not match.
  • Changed, for developers: the simple_spam_shield_blocked action no longer fires for a submission let through by monitor mode.
  • Fixed: after an automatic update that changed the log table, logging stopped until someone opened the WordPress admin. Spam was still blocked, but not recorded. The table is now upgraded on the first request of any kind.

1.5.1

  • Fixed: with Trust proxy headers enabled, the visitor’s address is now read from the entry your proxy added, which a visitor cannot set. Previously it could be taken from a value supplied by the visitor, which could affect the allowlist, the rate limit and duplicate detection. Updating is recommended for every site using this setting. The setting’s description and the FAQ now also explain what it relies on: your server accepting traffic only from the proxy.
  • Fixed: blocked keywords now match in every script. A single-word keyword written in Cyrillic, Greek, Chinese, Japanese or another non-Latin script never matched before, and upper- and lower-case versions of non-Latin text were treated as different. If you block words in a language other than English, they will start working with this update.
  • Fixed: « Clear all logs » could leave a blank, half-drawn admin page on some servers instead of returning to the log with a confirmation. The logs were still cleared.
  • Fixed: two different comments could occasionally be mistaken for duplicates of each other when their text differed only in where the author’s name ended and the message began.

1.5.0

  • Contact Form 7 forms are now protected. Every form is covered the moment the plugin is active — there is no tag to add to each form and no per-form setup, so forms you build later are protected too. A blocked submission is marked as spam, so the visitor sees the spam message you configured for that form, and the reason is recorded in Contact Form 7’s own spam log.
  • WP Job Manager job submissions are now protected, including the « save as draft » path. WP Job Manager’s built-in answer to submission spam is Google reCAPTCHA; this is the same protection with no third-party service and no puzzle for the person posting the job.
  • BuddyPress private messages can now be protected, off by default. BuddyPress applies WordPress’s blocklists to the activity stream but nothing at all to private messages. The checks that suit private messaging are the rate limit and duplicate detection, keyed on the sender rather than on what they wrote, and moderators are never throttled. Message bodies are never written to the spam log — a block records who sent it, when, and which check objected, so private correspondence does not accumulate in a table for an administrator to browse.
  • Each newly protected form gets its own section on the Per-form tab, so a contact form, a job listing and a comment thread can have thresholds suited to each.
  • Fixed: the link limit counted a linked web address twice whenever the link text was the address itself — which is what happens when a visual editor turns a pasted address into a link. An ordinary job description or comment with four links could be counted as five and rejected. Repeated addresses now count once.
  • Fixed: a field holding nothing but a web address is no longer counted as a link in Contact Form 7 forms, whether it was built as a URL field or a plain text one. Asking visitors for their website could otherwise push a genuine message over the link limit.

1.4.0

  • The hidden honeypot field now has a different name on every site, derived from the site’s own secret. Previously every installation used the same name, so a spammer who learned it once could skip that field on every site running this plugin. Nothing to configure, and forms already open in a visitor’s browser keep working.
  • New Per-form settings tab. Guard thresholds used to apply identically to every form on the site, so making a contact form stricter also made the comment form stricter. Thresholds can now be set per form — a comment thread can allow several links while a contact form allows one, and a long application form can require a longer fill time than a comment box. Leave a field blank to use the global value.
  • Plugins that protect their own forms through this plugin can register them so they appear on the Per-form tab. A form that does not register is still protected; it uses the global thresholds.
  • The duplicate-detection window and the rate-limit window are now settable on the Guards tab. Both were fixed at 60 seconds and could only be changed by editing a file inside the plugin, which an update overwrote. A rate limit of « 20 per hour » is now expressible, and a busy comment thread where several people legitimately post a short reply within a minute can use a shorter duplicate window instead of turning the guard off.
  • The rate-limit maximum is no longer labelled « per minute », since the window is no longer always a minute.
  • Number settings are now range-checked when saved. Previously a value outside a field’s range was stored as given, and a negative number was stored as its positive equivalent.

1.3.0

  • The spam log now records every guard that matched a blocked submission, not just the first one. The guard that decided the outcome is still shown on its own line, with any additional matches listed beneath it.
  • Fixed: a submission rejected by one guard was recorded as « seen » by the duplicate guard, so a visitor who corrected the problem and resubmitted was refused a second time — told they had submitted a duplicate rather than what was actually wrong.
  • Fixed: the rate limit was not counting submissions that another guard had already rejected, which is most of what a spam bot sends. A bot could keep submitting indefinitely without ever reaching the limit, while a legitimate visitor who tripped a guard once did count against it. Rejected attempts now count.
  • For developers: other plugins can now register their own guard through the simple_spam_shield_guards filter. A registered guard runs in the pipeline, appears in the log, and gets its own toggle on the Guards tab. A new simple_spam_shield_blocked action fires whenever a submission is blocked. See the readme in the plugin’s repository.

1.2.1

  • Fixed: allowlist entries using an IPv6 range (for example 2001:db8::/32) never matched. IPv4 ranges, exact addresses and email rules were unaffected.
  • Fixed: rate-limit data left rows behind in the database when the plugin was deleted.
  • Fixed: the honeypot guard’s field name is no longer presented as configurable, since changing it disabled the guard rather than renaming the field.

1.2.0

  • New Rate limit guard: throttles repeated submissions from the same sender (the logged-in user where there is one, otherwise the connection IP). Off by default.
  • New option to also apply WordPress’s Disallowed Comment Keys (Settings Discussion) to every protected form, extending that one blocklist beyond comments to reviews, Jetpack forms, and forms added through the plugin’s API.

1.1.3

  • Tested with WordPress 7.1. No functional changes.

1.1.2

  • Renamed to Onsite Spam Guard. The previous name was too generic and overlapped existing plugins in the directory. Your settings and logs are unaffected by the rename.
  • Security hardening: the spam-log bulk-delete action now checks the current user’s capability directly, in addition to verifying the nonce.

1.1.1

  • Fixed: Jetpack contact form submissions could be wrongly flagged. The plugin inspected everything Jetpack passes its spam filter, which includes site and server metadata — the site address, the referrer, the page permalink, and request headers. Those count as links and text, so a legitimate submission containing no links of its own could still exceed the link limit, and keywords could match against the browser’s user-agent string. Only what the visitor actually submitted is inspected now.
  • Fixed: the sender’s name and email were not read from Jetpack submissions, so the keyword guard never checked them and duplicate detection was less accurate.

1.1.0

  • Settings are now organized into tabs (General, Guards, Allowlist, Logging) to reduce scrolling.
  • New setting to control whether all plugin data is removed when the plugin is deleted (on by default).
  • Uninstall now removes the plugin’s table, options, transients, and scheduled task on every site of a multisite network, not just the main site.

1.0.1

  • simple_spam_shield_check() accepts the hidden honeypot/token/behavioral fields explicitly, so REST/AJAX endpoints (JSON body, empty $_POST) can pass them from the request.
  • The time-gate and signature guards skip rather than block when no token is supplied for a custom context, so content-only integrations are not falsely rejected. Built-in comment and review forms still require the token.

1.0.0

  • Initial release.
  • Guard pipeline: honeypot, duplicate detection, time gate, signature, link limit, keyword block, and optional behavioral analysis.
  • Integrations for WordPress comments, WooCommerce product reviews, and Jetpack contact form blocks.
  • Allowlist supporting IPs, CIDR ranges, email addresses, and email domains, with an optional trusted-proxy mode for IP detection.
  • Blocked comments and reviews are routed to the spam queue by default (recoverable), with an option to reject them outright instead.
  • Database-backed logging with a paginated admin viewer and a configurable auto-purge retention window.
  • Public integration API (simple_spam_shield_check / simple_spam_shield_protect_selector / simple_spam_shield_field_markup) so other plugins can protect their own forms.
  • Suggested privacy-policy content and a clean uninstall routine.
  • Developed by Jerome Wincek, with engineering assistance from Anthropic’s Claude.