drabek-digital / captcha
π€ Nette Forms extension for Cloudflare Turnstile and hCaptcha integration
Requires
- php: ^8.3
- nette/di: ^3.1
- nette/forms: ^3.2
- nette/http: ^3.3
- nette/php-generator: ^3.5.4 || ^4.0
- nette/schema: ^1.2.5
- nette/utils: ^4.0.4
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/log: ^1.1 || ^2.0 || ^3.0
Requires (Dev)
- latte/latte: ^3.0
- nette/application: ^3.1
- nyholm/psr7: ^1.8
- phpstan/extension-installer: ^1.2
- phpstan/phpstan: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^11.5 || ^12.0
- spaze/phpstan-disallowed-calls: ^4.0
- spaze/phpstan-disallowed-calls-nette: ^3.0
- symplify/easy-coding-standard: ^13.0
Suggests
- ext-curl: Used for server-side verification requests, falls back to PHP streams (allow_url_fopen) without it
- latte/latte: Enables the {captchaScripts} tag (Latte 3)
Provides
None
Conflicts
None
Replaces
None
- dev-main
- v3.0.0-rc1
- v2.0.5
- v2.0.4
- v2.0.3
- v2.0.2
- v2.0.1
- v2.0.0
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- dev-test/e2e-form-validation
- dev-refactor/js-modernize
- dev-feat/latte-extension
- dev-feat/csp-nonce
- dev-feat/psr-logger
- dev-feat/http-client
- dev-feat/size-enums-schema
- dev-refactor/json-force-arrays
- dev-fix/local-javascript-read-failure
- dev-chore/dev-dependencies
- dev-ci/checkout-v5-pull-requests
- dev-fix/hcaptcha-theme-auto
- dev-chore/phpstan-config-cleanup
- dev-chore/raise-requirements
- dev-fix/js-turnstile-missing-input
- dev-fix/js-managed-messages-form-scope
- dev-old-main
- dev-fix/old-main-captcha-required-by-default
- dev-fix/captcha-required-by-default
- dev-fix-comp
This package is auto-updated.
Last update: 2026-10-26 19:44:27 UTC
README
A comprehensive Nette Forms extension for integrating Cloudflare Turnstile and hCaptcha with your Nette applications.
Features
- β Cloudflare Turnstile integration (visible, managed, invisible modes)
- β hCaptcha integration (visible, invisible modes)
- β PHP 8.3+ support
- β Server-side validation
- β Flexible configuration
- β Easy form integration
- β Customizable themes and sizes
Requirements
- PHP: 8.3+
- Nette Forms: 3.2+
- Extensions:
curl(used by default) orallow_url_fopenfor server-side verification
Installation
Install via Composer:
composer require drabek-digital/captcha
Configuration
Register the extension in your config.neon:
extensions: captcha: DrabekDigital\Captcha\DI\CaptchaExtension captcha: type: turnstile # or hcaptcha secretKey: your-secret-key siteKey: your-site-key theme: auto # light, dark, auto (optional) size: normal # normal, compact, flexible (Turnstile only) (optional)
Configuration Options
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
type |
string | No | turnstile |
Captcha provider (turnstile or hcaptcha) |
secretKey |
string | Yes | - | Your secret key from the captcha provider |
siteKey |
string | Yes | - | Your site key from the captcha provider |
verifyUrl |
string | No | - | Custom verification URL (uses default if not set) |
theme |
string | No | auto |
Theme: light, dark, or auto (hCaptcha does not support auto and uses its default light theme) |
size |
string | No | normal |
Size: normal, compact or flexible (Turnstile only) |
Usage
Basic Usage
First do not forget to render relevant JS script include in your Latte templates. With Latte 3 and nette/application the extension registers a tag that renders the provider script for the configured type together with the validation script (including the CSP nonce, when used):
{captchaScripts}
Without it, include the scripts manually:
{* This will include Turnstile or hCaptcha JS code *} {DrabekDigital\Captcha\Assets\CaptchaFrontend::getJavascriptStatic(\DrabekDigital\Captcha\Enums\CaptchaType::TURNSTILE)|noescape} {* <script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script> *} {DrabekDigital\Captcha\Assets\CaptchaFrontend::getJavascriptStatic(\DrabekDigital\Captcha\Enums\CaptchaType::HCAPTCHA)|noescape} {* <script src="https://js.hcaptcha.com/1/api.js" async defer></script> *} {* Include local JS to make forms validation (prevent submission when verification is missing) and to show managed states labels *} {* ... or manually link <LIBRARY PATH>/src/Assets/captcha-validation.js *} {DrabekDigital\Captcha\Assets\CaptchaFrontend::getLocalJavascriptStatic()|noescape}
use Nette\Application\UI\Form; $form = new Form; // Simple captcha (required by default) // The validator is automatically injected by the DI extension $form->addCaptcha('captcha', 'Verify you are human'); // Handle form submission $form->onSuccess[] = function($form) { $values = $form->getValues(); // Process form... };
Advanced Usage
// All supported method signatures: // 1. Simple required visible captcha (Turnstile + hCaptcha) $form->addCaptcha('captcha', 'Bot protection') ->setRequired(true); // 2. Simple required visible captcha with custom required message (Turnstile + hCaptcha) $form->addCaptcha('captcha', 'Bot protection') ->setRequired('Please verify you are human'); // 3. Simple managed captcha (only for Turnstile) // The control + label will be rendered but the captcha can be shown or hidden based on Turnstile decision so therefore the JS code shows these messages $form->addCaptcha('captcha', 'Bot protection') ->setManagedMessages('Check will be performed on background', Html::el('em')->setHtml('Check has been performed successfully.')); // 4. Invisible captcha (Turnstile + hCaptcha) // The control + label will be hidden, the only thing that can be shown is required message. // Turnstile: choose the Invisible widget mode for the site key in the Cloudflare dashboard. // hCaptcha: rendered as invisible and executed by the bundled JS when the form is submitted. $form->addCaptcha('captcha', 'Bot protection') ->setInvisible(true);
Manual Instantiation (Advanced)
If you need to create the control manually (e.g., for testing):
use DrabekDigital\Captcha\CaptchaControl; use DrabekDigital\Captcha\CaptchaValidator; use DrabekDigital\Captcha\Enums\CaptchaType; // Create validator manually $validator = new CaptchaValidator('secret-key', CaptchaType::TURNSTILE); // Create control (validator is the first mandatory parameter) $captcha = new CaptchaControl($validator, 'Captcha', 'site-key', CaptchaType::TURNSTILE);
Custom HTTP Client
Server-side verification uses curl when available and falls back to PHP streams (allow_url_fopen). To use your own HTTP client (proxy, retries, loggingβ¦), register any PSR-18 client as a service, it is autowired into the validator. Clients that do not implement the PSR-17 request and stream factories themselves need the factories registered too:
services: # Symfony HttpClient implements the PSR-17 factories itself - Symfony\Component\HttpClient\Psr18Client # Guzzle needs the factories - GuzzleHttp\Client - GuzzleHttp\Psr7\HttpFactory
Without DI pass them to the constructor:
$validator = new CaptchaValidator('secret-key', CaptchaType::TURNSTILE, httpClient: $client, requestFactory: $factory, streamFactory: $factory);
Logging
When a PSR-3 Psr\Log\LoggerInterface service is registered (e.g. Monolog, or Tracy via Tracy\Bridges\Psr\TracyToPsrLoggerAdapter), it is autowired into the validator:
errorβ the verification service is unavailable or the integration is misconfigured (e.g.invalid-input-secret,sitekey-secret-mismatch)infoβ the visitor's token was rejected (e.g. expired or already used)
Without a logger, errors are written to the PHP error log.
Content Security Policy
The {captchaScripts} tag adds the nonce automatically. The script helpers accept it as an argument:
{DrabekDigital\Captcha\Assets\CaptchaFrontend::getJavascriptStatic(\DrabekDigital\Captcha\Enums\CaptchaType::TURNSTILE, $nonce)|noescape} {DrabekDigital\Captcha\Assets\CaptchaFrontend::getLocalJavascriptStatic($nonce)|noescape}
Instead of the inline validation script you can serve CaptchaFrontend::getLocalJavascriptPath() as a static file (e.g. copy it to www/ during deploy) and allow it by script-src 'self'.
The providers need these sources in your policy:
- Turnstile:
script-srcandframe-srchttps://challenges.cloudflare.com - hCaptcha:
script-src,frame-src,style-srcandconnect-srchttps://hcaptcha.com https://*.hcaptcha.com
Limitations
- Only implicit rendering support for both providers (primarily due to reliability).
- For hCaptcha passive mode is not supported at all (no access).
- Custom verification endpoint was not tested.
- Other captcha providers may work but will need custom JS to ensure form validation to work.
License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request, but better to consult via issues before large efforts.
Support
For support, please create an issue on GitHub.