Form Armour
Download free

Docs

Everything you need to install Form Armour, protect your forms and fix the common problems. Written for version 0.6.8. Can't find it here? Email support@hyperpixel.com.au.

Contents Requirements Install and protect your first form Form plugins Custom forms Appearance Protection modes How verification works Settings and stats For developers Troubleshooting Pro, trial and licences Changelog Removing the plugin

Requirements

  • WordPress 6.9 or later (tested up to 7.1).
  • PHP 7.4 or later. PHP 8.2 or later is recommended.
  • HTTP or HTTPS, with nothing to set up on your server. HTTPS is still worth adding when you can, because it protects what visitors type.
  • First-party cookies and JavaScript in the visitor's browser.
  • One of the supported form plugins, or a form you built yourself: Contact Form 7, WPForms, Gravity Forms, Fluent Forms, Elementor Pro.

Multisite and network-wide activation are not supported. WordPress login, comments, WooCommerce and external endpoints are not protected automatically.

Install and protect your first form

  1. Test on a staging site before using Form Armour on a live site. A staging site is a private copy of your site, so a problem with a form cannot affect real enquiries.
  2. In WordPress, go to Plugins, Add New, Upload Plugin, choose the Form Armour zip and activate it.
  3. Open Form Armour, Forms. Your forms are listed in one card per form plugin.
  4. Set a form's protection to Local and click Save. The slider appears on that form straight away; you do not add a field in the form editor.
  5. Choose whether to turn on Block pasting for that form (see Troubleshooting before you do).
  6. Clear any page cache, open the page and send yourself a test message.

The Form Armour menu has five screens: Dashboard, Forms, Appearance, Stats and Settings. Every screen has a Help tab in the top right with a quick start, custom-form steps and troubleshooting notes.

Forms you have not chosen stay off. Discovery scans up to 500 records per form plugin. If a form is missing, use Add a form on the Forms screen to register it by its ID.

Form plugins

Contact Form 7

Form Armour hooks into Contact Form 7's spam check and adds a safeguard before mail is sent. When a submission is blocked, Contact Form 7 may show its own generic error rather than your Form Armour message.

WPForms

Standard WPForms forms are protected through its rendering and validation hooks. Conversational forms, uploads, payments and registration add-ons need testing on a staging site first.

Gravity Forms

Each validation step is checked, including page changes in multi-page forms and API validation. Entries inserted directly by privileged code are outside its scope. Test multi-page, payment and API workflows on staging.

Fluent Forms

Standard Fluent Forms rendering and validation are protected. Conversational forms and advanced add-ons need testing first.

Elementor Pro

The Elementor Pro Form widget is supported, including forms in pop-ups and saved templates. Third-party Elementor form widgets are not covered. Elementor forms are identified as post-ID:widget-ID if you register one by hand.

Custom forms

A form you built yourself needs two things: the slider inside the form, and a check in the code that handles the submission. The slider alone does not protect anything.

  1. On the Forms screen, under Add a form, register the form with the provider Custom forms and a slug such as contact-enquiry. It is set to Local protection.
  2. Render the slider inside your <form>, before its submit button, with either of these:
    [form_armour id="contact-enquiry"]
    <?php echo form_armour_render('contact-enquiry'); ?>
  3. In your submission handler, stop when validation fails, before sending email, saving, uploading or charging anything:
    // After your normal method, authorisation and CSRF checks,
    // and before any side effects:
    if (!function_exists('form_armour_validate') || !form_armour_validate('contact-enquiry')) {
        wp_send_json_error(['message' => 'Please complete verification again.'], 422);
    }
Hard-code the slug on the server. Never take it from submitted data. An unknown slug fails validation, and a registered form set to Off passes by your choice. The function_exists guard keeps your handler closed if the plugin is deactivated.

Send all the hfg_* fields with your other fields as an ordinary form-encoded or multipart POST to the same site, so the session cookie is sent. JSON request bodies are not read. Each attempt uses up its ticket, so for an AJAX form, reset the slider after every response, including validation errors:

window.HumanFormGuard?.reset(document.querySelector('#your-form-id'));

Your form still needs its own CSRF protection, permissions, validation and escaping. The older names hfg_render(), hfg_validate() and [human_form_guard] still work.

Appearance

Open Form Armour, Appearance. Settings are in five tabs beside a live preview: Layout, Slider, Colours, Visitor messages and Extras. Drag the preview to see each state, use Show error state, and switch the preview background between light and dark to check contrast. Saving keeps every tab's settings.

SettingOptionsPlan
Alignment and widthLeft, centre or right; a set width or the full form widthFree
When to show itIn the form, or in a pop-up when the visitor submitsFree
"Verify without dragging" buttonOff by default. Runs the same checksFree
Card style and handle shapeSoft card, outline or minimal; rounded with arrow, circle or squareFree
SizeWidth 240 to 800 px, height 44 to 88 px, corner radius 0 to 44 pxFree
Accent, background and text coloursAny colour. The default accent is blue, #2563ebFree
Visitor messagesSlider label, before verifying, checking, success, failure, expiry, button label, pop-up title and cancel, paste blockedFree
ExtrasVibrate on success (on by default), "Protected by Form Armour" link (off by default)Free
Theme presetsEmerald, Midnight, Slate, Sunset, MonoPro
Handle iconMatch the shape, double chevron, shield, padlock or nonePro
Success, error and drag-trail coloursAny colourPro
Per-form stylingChoose a form under Editing to override the global settingsPro

Handle icons are white on dark accent colours and turn dark on light ones, such as lime, so they stay readable. The success tick follows the success colour the same way. Visitors who ask their device for reduced motion get the same states without animation.

Protection modes

Local

Needs no keys and makes no outside requests. A signed, single-use ticket tied to the form and the visitor's browser, a hidden field, a background puzzle, a minimum fill time, replay protection and a per-address limit. It stops scripts, replays and form-filling bots. It cannot stop a bot written specifically for this plugin.

Local + Turnstile (Pro)

Adds Cloudflare Turnstile's invisible bot check, with every token verified by your server against the hostname, action and ticket. Use it for forms that attract targeted abuse. To set it up:

  1. In Cloudflare, open Turnstile (a free account is enough), click Add widget, enter your site's domain and choose Managed.
  2. Copy the site key and secret key into Form Armour, Settings and save.
  3. Switch the forms you want to Local + Turnstile on the Forms screen.

To keep the secret out of the database, define it in wp-config.php instead:

define('HFG_TURNSTILE_SECRET', 'your-secret-key');

If Pro ends, Turnstile forms fall back to Local, so they stay protected. A key or Cloudflare failure never silently falls back to Local: the form fails closed until it is fixed.

How verification works

When a visitor first touches the form, their browser fetches a small single-use puzzle and solves it in the background. Dragging the slider trades the solved puzzle for a signed ticket. When the form is sent, your server runs these checks in order and stops at the first one that fails:

  1. Within the submission limit for this address.
  2. The hidden field is empty.
  3. The ticket is signed by your site, for this form.
  4. Not sent too fast (at least 3 seconds after the visitor started), and the ticket is less than five minutes old.
  5. Sent from the browser that earned the ticket.
  6. The slider was completed in the browser.
  7. The ticket has never been used before.

Each decision is recorded with one of these reasons:

ReasonMeaning
verifiedPassed every check.
rate_limitToo many submissions from this address in ten minutes.
honeypot_or_missingThe hidden field was filled in or missing.
invalid_ticketNo ticket, a forged one, or one for another form or mode.
expired_ticketThe ticket was more than five minutes old.
too_fastSent less than the minimum fill time after the visitor started.
session_mismatchThe ticket was earned in a different browser.
interaction_missingThe slider was not completed.
replayThe ticket had already been used.
token_missingTurnstile mode, but no Turnstile token was sent.
turnstile_rejectedCloudflare rejected the Turnstile token (Pro).
token_context_mismatchThe Turnstile token was for another site, action or ticket (Pro).
verification_unavailableCloudflare could not be reached. Counted as an error, not a block (Pro).
verification_configurationCloudflare reported a problem with the secret key or the request, not with the visitor. Counted as an error (Pro).
configurationTurnstile is selected without keys. Counted as an error.
storage_failureThe database could not record the ticket. Counted as an error.
The honest limit. A bot written for this plugin can solve the puzzle, wait out the fill time and send the right fields. Local mode slows that down but cannot stop it. Use Local + Turnstile for forms that attract that kind of attention.

Settings and stats

  • Submissions allowed per address every 10 minutes: 5 to 200, default 20. Visitors over the limit are blocked until the window passes. Shared networks, like offices and schools, may need a higher number.
  • Keep statistics for: 7 to 90 days, default 30. An hourly WordPress cron job removes old tickets, limits and statistics, so make sure cron runs reliably.

No form contents and no raw IP addresses are stored. Address limits use salted, time-windowed hashes. The visitor's browser gets one session cookie, hfg_session_<site-id>, which is HttpOnly, Secure and SameSite=Strict. Mention it in your own site's cookie notice.

With Pro, the Dashboard shows totals and the Stats tab has the daily chart, details and CSV export. On the free plan, statistics are still recorded and those screens show a labelled sample. Counts are server-side decisions, not confirmed bots or delivered messages, and dates use UTC.

For developers

FilterDefaultUse
hfg_always_enqueuefalseLoad the scripts on every page, for forms injected later by AJAX or pop-ups.
hfg_min_fill_seconds3Minimum seconds between the visitor starting and the submission. 0 to 30.
hfg_work_bits15Puzzle difficulty in leading zero bits. 8 to 22. Each step doubles the work for visitors' browsers.
add_filter('hfg_always_enqueue', '__return_true');
add_filter('hfg_min_fill_seconds', fn() => 5);

The browser script exposes two methods for custom AJAX forms:

  • HumanFormGuard.reset(form) after every completed request, so the visitor can verify again.
  • HumanFormGuard.reportError(form) when your server reports a verification failure. It opens the pop-up again.

Keep the REST endpoint /wp-json/form-armour/v1/challenge uncached and reachable by same-origin POST. Page HTML can be cached, because tickets are fetched separately.

Troubleshooting

Start with the Health check on the Settings screen. It shows "All good" and "Ready to protect your forms", plus any notes that apply to your site, such as "Your site is on HTTP" or "Limits are per browser here".

Every submission fails

Check that the challenge endpoint is not cached and that the form posts to the same address as your site. Visitors also need first-party cookies and JavaScript turned on.

Everyone is rate-limited together

Behind a proxy on your host's network, Form Armour reads the visitor's address automatically. A CDN with a public address still looks like one visitor until the real address is restored. Restore it at the web server, for example with mod_remoteip or nginx real_ip. Forwarding headers are never trusted blindly.

The slider does not appear

Check the form is saved as Local or Local + Turnstile, then clear page and plugin caches. If the form loads in a pop-up or by AJAX, turn on hfg_always_enqueue (see For developers).

A visitor cannot drag

The slider works with a keyboard. You can also turn on the Verify without dragging button under Appearance, Layout. It runs the same checks.

Pasting is blocked

Block pasting makes visitors type instead of pasting. It slows copy-paste spam but can get in the way of password managers and assistive tools, so use it only where it helps.

"Checking..." takes a few seconds

If someone verifies within 3 seconds of starting on the form, the slider holds the checking state until the minimum fill time has passed, rather than letting the submission be refused.

Pro, trial and licences

Start a free 3-day trial of Pro from the Upgrade screen inside Form Armour. No card is needed. The trial starts once you confirm it from the email Freemius sends; until then the screen says "Trial requested. Check your email." with a link to send it again. The badge in the Form Armour header shows Free, Trial pending, the days left in your trial, or Pro. Pro costs US$24.99 for one site or US$49.99 for up to three sites, paid once.

Pro adds Turnstile mode, per-form styling, theme presets, handle icons, success, error and drag-trail colours, the stats dashboard with daily chart and CSV export, and priority email support.

When a trial ends or a licence is removed, the site returns to Free. Turnstile forms fall back to Local so they stay protected, saved Pro styling is kept but not applied, and statistics keep being recorded.

Licences, trials, payments and updates are handled by Freemius, which sells Pro on Hyperpixel Studio's behalf. Freemius runs in anonymous mode in this plugin: there is no usage-tracking opt-in. Refunds are covered on the Refunds page.

Changelog

0.6.8

  • The Health check explains why HTTPS is still worth adding, for your forms and your site. Form Armour keeps working either way.
  • Added a reminder to test on a staging site first, with a link to our Terms.

0.6.7

  • Form Armour now works on plain HTTP as well as HTTPS, with nothing to change on your server or host.
  • Visitor addresses are worked out automatically behind a proxy on your host's network.
  • The Health check no longer asks you to contact your host. It shows what Form Armour found and confirms it's ready.

0.6.6

  • The new Form Armour logo now appears across the plugin's screens.
  • The Upgrade page fits better on narrow screens.

0.6.5

  • The Health check now explains each problem in plain language, with what it means for your forms and what to ask your host.
  • Your trial status shows in the header while a trial is pending or running.
  • The slider's trail now runs a little ahead of the handle as you drag.
  • Rounder alerts and a clearer “Start free trial” confirmation.

0.6.4

  • A new Stats tab. With Pro, it has the daily chart, details and CSV export.
  • Other plugins' notices now wait in a Notices tray, so Form Armour screens stay focused.
  • Form rows line up in columns, so names, protection settings and checkboxes are easier to scan.
  • “Block pasting” now explains what it does and when to use it.

0.6.3

  • Appearance settings are now tabs beside the live preview, with Save always in view.
  • The Forms screen compares the two protection modes in one place and groups your forms by form plugin.

0.6.2

  • A fresh look for the Form Armour screens: blue buttons and tabs, rounder corners and calmer fields.
  • New installs use a blue slider by default. Colours you have already saved are kept.
  • The Upgrade screen now matches the rest of Form Armour.

0.6.1

  • Handle icons turn dark on light accent colours, such as lime, so the arrow and spinner stay easy to see. The live preview follows as you edit colours.

0.6.0

  • Stronger protection: each visitor's browser quietly solves a small puzzle before a ticket is issued, so bots that only post the form are stopped.
  • Submissions sent within 3 seconds of starting the form are blocked as too fast. Real visitors just see “Checking...” for a moment longer.

0.5.0

  • A getting-started checklist, Help tabs and setup guides.
  • Drag feedback: a progress trail, snap-back, and clear checking, success and error states, with optional vibration.
  • Pro: handle icons, success and error colours, drag-trail colour, theme presets and a daily activity chart.
  • An optional “Protected by Form Armour” link, off by default.

Removing the plugin

Deactivating stops protection and the scheduled clean-up, and keeps your settings and statistics. To delete everything when you uninstall, add this to wp-config.php first, then remove it afterwards:

define('HFG_DELETE_DATA', true);

Cloudflare Turnstile keys are not revoked automatically. Remove the widget in Cloudflare if you no longer need it.