Turnstile Contact Form
A plugin that adds a simple contact us form and validator with cloudflare turnstile widgets
by Derryn Simmons · github.com/derryn-simmons/wordpress-cloudflare-turnstile-contact · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/derryn-simmons/wordpress-cloudflare-turnstile-contact/archive/refs/heads/main.zipA lightweight WordPress contact form block protected by Cloudflare Turnstile, the privacy-friendly CAPTCHA alternative.
Messages are emailed to you and stored as private records in wp-admin, so nothing is lost if email delivery fails.
Authored by Derryn Simmons — derrynsimmons.com
Features
- Contact form block (
ds-contact/form) for the block editor, rendered server-side. Its only front-end script is the Turnstile widget, which loads only on pages that contain the form. - Cloudflare Turnstile verification on the server, which fails closed and checks the action and hostname.
- Layered spam protection: honeypot field, per-IP rate limit (5 messages per hour), nonce check and Turnstile.
- Message log: every submission is saved under Contact → Messages, visible to administrators only, and shows whether the email was sent.
- Works without JavaScript in the form itself. It uses a standard POST and a redirect, and if there are errors the visitor's input is kept.
- Privacy: visitor IPs are stored only as salted hashes, never in full.
- Follows your theme: styles use the theme's
theme.jsoncolour, spacing and font presets, with neutral fallbacks. - Accessible: labelled fields, inline errors linked with
aria-describedby, and status messages announced to screen readers.
Requirements
- WordPress 6.4+
- PHP 8.1+
- A free Cloudflare account (for Turnstile keys). Your site does not need to be proxied through Cloudflare.
Installation
- Download or clone this repository into
wp-content/plugins/:cd wp-content/plugins git clone https://github.com/derryn-simmons/Wordpress-Cloudflare-Turnstile-Contact.git turnstile-contact-form - Activate Turnstile Contact Form under Plugins.
- Configure it under Settings → Contact (see below).
- Add the Contact form block to any page.
No build step and no Composer install are needed. The plugin has its own autoloader.
Configuration
1. Create a Turnstile widget
- Sign in to the Cloudflare dashboard.
- Go to Turnstile → Add widget.
- Add your site's hostname, for example
example.com. - Choose widget mode Managed.
- Copy the Site key and Secret key.
2. Plugin settings (Settings → Contact)
| Setting | Description |
|---|---|
| Site key | Public Turnstile site key. |
| Secret key | Turnstile secret key. It is write-only: once saved it is never shown again, and leaving the field blank keeps the stored value. |
| Widget theme | Auto (matches the visitor's system), Light or Dark. |
| Cloudflare proxy | Tick this only if your site's DNS is proxied through Cloudflare (orange cloud). The plugin then uses the CF-Connecting-IP header to identify visitors for rate limiting. Leave it unticked otherwise, because that header can be forged. |
| Send messages to | Recipient address. Defaults to the site admin email. |
Development / staging
If no keys are set and wp_get_environment_type() is anything other than production, the plugin uses Cloudflare's always-pass test keys, so the form works in local development straight away.
In production with no keys set, the form is hidden from visitors. Administrators see a notice with a link to the settings page.
Block options
| Option | Default |
|---|---|
| Heading | Send a message (leave empty to hide) |
| Show subject field | On |
How a submission is processed
Form block → POST admin-post.php?action=ds_contact_submit
→ nonce check + input sanitising
→ honeypot (bots receive a fake success)
→ field validation
→ rate limit (5 per hour per hashed IP)
→ Turnstile siteverify (server-side)
→ store as a private "Message" record
→ wp_mail() to the recipient, with Reply-To set to the sender
→ redirect back to the form (Post/Redirect/Get)
The message is stored before the email is sent, so a mail failure never loses it. The message list shows whether each email was sent.
Developer hooks
/**
* Fires after a message is stored (and emailed, if possible).
*
* @param int $post_id Stored message ID (post type `ds_contact_message`).
* @param array $data { name, email, subject, message }
*/
add_action( 'ds_contact_message_received', function ( int $post_id, array $data ) {
// e.g. forward to Slack, a CRM, etc.
}, 10, 2 );
Uninstalling
Deleting the plugin removes its settings. Stored messages are kept by default. To delete them too, run this before uninstalling:
update_option( 'ds_contact_keep_messages', 0 );
Email deliverability
The plugin sends mail with WordPress's wp_mail(). For reliable delivery, configure your site to send through an SMTP or transactional mail service. Every message is also saved under Contact → Messages regardless.
Development
composer install # PHPCS + WordPress Coding Standards
composer lint # phpcs
composer lint:fix # phpcbf
License
MIT — see LICENSE.
© Derryn Simmons — derrynsimmons.com