vuillaume-agency / symfony-turnstile
Symfony bundle for Cloudflare Turnstile - a privacy-friendly, GDPR-compliant CAPTCHA alternative. Drop-in reCAPTCHA replacement with zero user friction.
Package info
github.com/vuillaume-agency/symfony-turnstile
pkg:composer/vuillaume-agency/symfony-turnstile
Requires
- php: >=8.2
- symfony/form: ^7.4|^8.0
- symfony/framework-bundle: ^7.4|^8.0
- symfony/http-client: ^7.4|^8.0
- symfony/twig-bundle: ^7.4|^8.0
- symfony/validator: ^7.4|^8.0
- symfony/yaml: ^7.4|^8.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.65
- phpstan/phpstan: ^2.1
- phpstan/phpstan-symfony: ^2.0
- phpunit/phpunit: ^11.5
- rector/rector: ^2.0
- symfony/phpunit-bridge: ^8.0
README
A Symfony bundle to integrate Cloudflare Turnstile on your forms. Turnstile is a privacy-preserving alternative to CAPTCHA that doesn't require user interaction.
Features
- Zero user friction — No puzzles, no clicking on traffic lights
- GDPR-friendly — No cookie consent required, privacy-first design
- 22 languages included — All major European languages supported
- Symfony 8 ready — Full support for Symfony 7.4 LTS and 8.x
- Customizable messages — Override error messages via form options or translations
- Easy theming — Light, dark, or auto theme support
- Fully tested — PHPUnit tests and PHPStan static analysis
Quick Start
composer require vuillaume-agency/symfony-turnstile
# .env TURNSTILE_KEY="your-site-key" TURNSTILE_SECRET="your-secret-key"
// In your form ->add('captcha', TurnstileType::class)
Get your free keys at Cloudflare Dashboard.
Why Turnstile over reCAPTCHA?
| Feature | Turnstile | reCAPTCHA |
|---|---|---|
| Usually invisible (no puzzles) | Yes | Yes (v3 only) |
| GDPR compliant by design | Yes | No (requires consent) |
| Uses cookies | No | Yes |
| Data used for advertising | No | Unclear |
| Script size | ~30KB | ~80KB |
| Free unlimited | Yes | Yes (with limits) |
GDPR considerations
For European websites, reCAPTCHA poses significant compliance challenges:
- Cookies: reCAPTCHA sets cookies that require user consent under GDPR
- Data transfer: User data is sent to Google servers in the US
- Consent required: Must obtain explicit consent before loading reCAPTCHA
- Fines: French DPA (CNIL) has fined companies for improper reCAPTCHA implementation
Turnstile is designed with privacy in mind:
- No cookies: Doesn't set any cookies, no consent banner needed
- Minimal data: Only collects what's necessary for bot detection (IP, TLS fingerprint)
- No advertising: Cloudflare explicitly prohibits using collected data for ads or tracking
- GDPR-ready: Can be used without additional consent mechanisms
About this fork
Originally a fork of pixelopen/cloudflare-turnstile-bundle by Pixel Développement.
What's new:
- Symfony 7.4 LTS and 8.x support
- 22 languages for error messages
- Customizable error messages via form options
disable_submit_until_verifiedoption to prevent premature form submission- PHP 8.2+ with modern features (readonly, constructor promotion)
- Active maintenance and community contributions welcome
Requirements
| Requirement | Version |
|---|---|
| PHP | >= 8.2 |
| Symfony | >= 7.4 |
Installation
Step 1: Install the package
composer require vuillaume-agency/symfony-turnstile
Step 2: Register the bundle
Add the bundle to your config/bundles.php:
return [ // ... VuillaumeAgency\TurnstileBundle\VuillaumeAgencyTurnstileBundle::class => ['all' => true], ];
Step 3: Add your Cloudflare credentials
Get your keys from the Cloudflare Dashboard and add them to your .env file:
TURNSTILE_KEY="your-site-key" TURNSTILE_SECRET="your-secret-key"
That's it! The bundle automatically reads from these environment variables.
Optional: Custom configuration
If you need to customize the bundle, create config/packages/vuillaume_agency_turnstile.yaml:
vuillaume_agency_turnstile: key: '%env(TURNSTILE_KEY)%' # Default: reads from TURNSTILE_KEY env var secret: '%env(TURNSTILE_SECRET)%' # Default: reads from TURNSTILE_SECRET env var enable: true # Default: true disable_submit_until_verified: false # Default: false
| Option | Type | Default | Description |
|---|---|---|---|
key |
string | %env(TURNSTILE_KEY)% |
Your Turnstile site key (public) |
secret |
string | %env(TURNSTILE_SECRET)% |
Your Turnstile secret key (private) |
enable |
boolean | true |
Enable/disable validation |
disable_submit_until_verified |
boolean | false |
Disable submit buttons until Turnstile verification completes |
Usage
Adding Turnstile to a form
Use TurnstileType in your form builder:
<?php namespace App\Form; use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\EmailType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\FormBuilderInterface; use VuillaumeAgency\TurnstileBundle\Type\TurnstileType; class ContactType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('email', EmailType::class) ->add('message', TextareaType::class) ->add('captcha', TurnstileType::class, [ 'label' => false, ]) ->add('submit', SubmitType::class); } }
Widget customization
Customize the Turnstile widget appearance:
->add('captcha', TurnstileType::class, [ 'label' => false, 'attr' => [ 'data-theme' => 'dark', // 'light', 'dark', or 'auto' 'data-size' => 'compact', // 'normal' or 'compact' 'data-action' => 'contact', // Custom action name for analytics ], ])
See Cloudflare Turnstile documentation for all available options.
Disable submit until verified
To prevent form submission before Turnstile completes, enable automatic submit button disabling:
# config/packages/vuillaume_agency_turnstile.yaml vuillaume_agency_turnstile: disable_submit_until_verified: true
When enabled:
- Submit buttons are disabled on page load (server-side)
- JavaScript enables them after Turnstile verification succeeds
- Buttons are re-disabled if verification expires or fails
This improves UX by preventing users from clicking submit before the challenge is ready.
Important: This feature requires using
SubmitTypein your form builder. Raw HTML<button>elements in Twig templates will not be affected.
use Symfony\Component\Form\Extension\Core\Type\SubmitType; $builder ->add('captcha', TurnstileType::class) ->add('submit', SubmitType::class, ['label' => 'Send']);
Custom error messages
Pass custom messages directly as form options:
->add('captcha', TurnstileType::class, [ 'label' => false, 'missing_response_message' => 'Please verify you are human.', 'verification_failed_message' => 'Verification failed, please try again.', ])
Or override translations in translations/validators.{locale}.yaml:
turnstile.missing_response: Please verify you are human. turnstile.verification_failed: Verification failed. Please try again.
Translations
The bundle provides error messages in 22 European languages:
| Language | Code | Language | Code | |
|---|---|---|---|---|
| English | en |
Hungarian | hu |
|
| French | fr |
Swedish | sv |
|
| Spanish | es |
Danish | da |
|
| German | de |
Finnish | fi |
|
| Italian | it |
Norwegian | nb |
|
| Portuguese | pt |
Greek | el |
|
| Dutch | nl |
Czech | cs |
|
| Polish | pl |
Slovak | sk |
|
| Romanian | ro |
Slovenian | sl |
|
| Bulgarian | bg |
Lithuanian | lt |
|
| Croatian | hr |
Latvian | lv |
|
| Ukrainian | uk |
Estonian | et |
|
| Turkish | tr |
Migrating from pixelopen/cloudflare-turnstile-bundle
This bundle is a modernized fork. Here's how to migrate:
Step 1: Replace the package
composer remove pixelopen/cloudflare-turnstile-bundle composer require vuillaume-agency/symfony-turnstile
Step 2: Update bundles.php (this is probably automatically done by the composer remove and require)
// config/bundles.php // Remove: // PixelOpen\CloudflareTurnstileBundle\PixelOpenCloudflareTurnstileBundle::class => ['all' => true], // Add: VuillaumeAgency\TurnstileBundle\VuillaumeAgencyTurnstileBundle::class => ['all' => true],
Step 3: Delete old config
rm config/packages/pixel_open_cloudflare_turnstile.yaml
Step 4: Update imports in your code
Find and replace in your project:
| Old | New |
|---|---|
PixelOpen\CloudflareTurnstileBundle\Type\TurnstileType |
VuillaumeAgency\TurnstileBundle\Type\TurnstileType |
PixelOpen\CloudflareTurnstileBundle\Validator\CloudflareTurnstile |
VuillaumeAgency\TurnstileBundle\Validator\CloudflareTurnstile |
# Quick find/replace with sed (Linux/macOS) find src -name "*.php" -exec sed -i '' 's/PixelOpen\\CloudflareTurnstileBundle/VuillaumeAgency\\TurnstileBundle/g' {} +
Step 5: Clear cache
php bin/console cache:clear
Migrating from reCAPTCHA
Switching from Google reCAPTCHA is straightforward:
- Remove your reCAPTCHA bundle and configuration
- Install this bundle (see Installation)
- Replace
RecaptchaTypewithTurnstileTypein your forms - Remove reCAPTCHA from your cookie consent banner
- Update your privacy policy (simpler now!)
No changes needed in your controllers — validation works the same way.
Testing
During development, use Cloudflare's test credentials instead of real keys. This is the recommended approach as it keeps validation active while providing predictable behavior.
Test site keys
| Site key | Behavior |
|---|---|
1x00000000000000000000AA |
Always passes (recommended) |
2x00000000000000000000AB |
Always blocks |
3x00000000000000000000FF |
Forces an interactive challenge |
Test secret keys
| Secret key | Behavior |
|---|---|
1x0000000000000000000000000000000AA |
Always passes (recommended) |
2x0000000000000000000000000000000AA |
Always fails |
3x0000000000000000000000000000000AA |
Returns "token already spent" |
Example dev configuration
# .env.local (for development) TURNSTILE_KEY="1x00000000000000000000AA" TURNSTILE_SECRET="1x0000000000000000000000000000000AA"
Alternative: Disabling validation
You can also disable validation entirely, but using test keys is preferred as it keeps your code paths consistent between environments:
# config/packages/dev/vuillaume_agency_turnstile.yaml vuillaume_agency_turnstile: enable: false
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes
- Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please ensure tests pass (vendor/bin/phpunit) and code follows standards (vendor/bin/php-cs-fixer fix).
License
The MIT License (MIT). See LICENSE for more information.