PHPackages                             recranet/craft-secure-forms - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Validation &amp; Sanitization](/categories/validation)
4. /
5. recranet/craft-secure-forms

ActiveCraft-plugin[Validation &amp; Sanitization](/categories/validation)

recranet/craft-secure-forms
===========================

Contact forms for Craft CMS 5 with spam protection (reCAPTCHA, Turnstile, honeypot), persisted spam scores, stored submissions, proper error reporting and an SMTP test utility

1.7.0(2w ago)015proprietaryPHPPHP &gt;=8.2

Since Jul 2Pushed 1mo agoCompare

[ Source](https://github.com/recranet/craft-secure-forms)[ Packagist](https://packagist.org/packages/recranet/craft-secure-forms)[ RSS](/packages/recranet-craft-secure-forms/feed)WikiDiscussions master Synced 1w ago

READMEChangelog (7)Dependencies (6)Versions (10)Used By (0)

Secure Forms
============

[](#secure-forms)

Contact forms for Craft CMS 5 with spam protection, stored submissions and proper error reporting. Replaces `craftcms/contact-form`, `hybridinteractive/craft-contact-form-extensions` and `recranet/craft-contact-form-recaptcha` in one plugin.

Design principles
-----------------

[](#design-principles)

- **Spam is not an error.** Real spam is stored with its score and reason, silently accepted (configurable), and never logged as an error.
- **Misconfiguration is a real error.** Missing/invalid captcha keys, a domain missing from the allowlist, an unreachable verification API or an SMTP failure are shown to the visitor, logged under the `secure-forms` category, and forwarded to Sentry when the SDK is installed. They never silently classify submissions as spam.
- **Nothing is ever lost.** Submissions are persisted before any email is attempted; send failures are recorded on the submission (`failed` status) instead of dropping the message.

Features
--------

[](#features)

- Submission storage as elements with a fixed schema — dynamic form fields are stored as JSON and **expanded back into columns on CSV export**
- Persisted spam classification: `isSpam`, `spamScore` (reCAPTCHA v3 score), `spamReason`
- Spam protection: honeypot + Google reCAPTCHA v2/v3/Enterprise or Cloudflare Turnstile (experimental)
- Notification + optional confirmation emails, rendered from site templates
- **Email / SMTP test utility** in the control panel (works with `allowAdminChanges` disabled) that surfaces full SMTP transport errors
- Control panel section with per-form sources, statuses (sent / spam / failed) and search

Installation
------------

[](#installation)

```
composer require recranet/craft-secure-forms
php craft plugin/install secure-forms
```

Usage
-----

[](#usage)

```

	{{ csrfInput() }}
	{{ actionInput('secure-forms/submit') }}
	{{ redirectInput(craft.app.request.pathInfo ~ '?submitted=true') }}
	{{ hiddenInput('formName', 'contact') }}

	{# optional overrides (hashed, tamper-proof) #}
	{{ hiddenInput('toEmail', 'sales@example.com'|hash) }}
	{{ hiddenInput('notificationTemplate', '_emails/notifications/contact'|hash) }}
	{{ hiddenInput('confirmationTemplate', '_emails/confirmations/contact'|hash) }}
	{{ hiddenInput('confirmationSubject', 'Thanks!'|hash) }}

	{# any other message[...] fields are stored as dynamic fields #}

	{{ craft.secureForms.honeypot() }}
	{{ craft.secureForms.captcha() }}

	Send

```

Error handling in the template:

```
{% set submission = craft.app.urlManager.getRouteParams().submission ?? null %}
{% if submission %}
	{{ ul(submission.getErrorSummary(true)) }}
{% endif %}
```

Configuration
-------------

[](#configuration)

All settings are read from the environment with sensible defaults, so one config file works across dev/staging/production. Create `config/secure-forms.php`:

```
