turnstile_pro
A lightweight Cloudflare Turnstile
widget for Flutter, rendered through the official
webview_flutter.
Unlike some alternatives, it does not pull in flutter_inappwebview — which
keeps your dependency tree small and avoids Android Gradle Plugin (AGP 9+)
incompatibilities caused by that plugin's legacy ProGuard configuration.
Features
- ✅ Renders Turnstile via the official
webview_flutter - 📐 Size modes:
normal,compact,flexible(with sensible default heights) - 🎨 Themes:
light,dark,autoand configurablelanguage - 🔁 Auto-retry and refresh control (
retry,refreshExpired,refreshTimeout) - 👁️
appearance, and server-sideaction/cDatatagging - 🖼️ Styling via
TurnstileOptions:borderRadius,backgroundColor, appearcurve+animationDuration - ⏳ Optional
loadingWidgetshown until the challenge is ready - 🎮
TurnstileControllerwithreset()andreload() - 📣
onTokenReceived,onError(error/expired/timeout),onWidgetReady
Installation
dependencies:
turnstile_pro: ^0.1.0
Platform setup
This package uses webview_flutter, so follow its platform requirements:
- Android:
minSdkVersion 21+, internet permission in the manifest. - iOS: no extra setup for remote content.
Usage
import 'package:turnstile_pro/turnstile_pro.dart';
TurnstilePro(
siteKey: '1x00000000000000000000AA',
onTokenReceived: (token) {
// Send this token to your backend for verification.
debugPrint('token: $token');
},
onError: (error) {
// error is one of: 'error', 'expired', 'timeout', 'parse_error'
debugPrint('turnstile error: $error');
},
)
Configuring appearance & behavior
All visual/behavioral settings live in `TurnstileOptions`:
TurnstilePro(
siteKey: '...',
options: const TurnstileOptions(
size: TurnstileSize.flexible,
theme: TurnstileTheme.light,
language: 'tr',
borderRadius: BorderRadius.all(Radius.circular(10)),
curve: Curves.easeInOut,
animationDuration: Duration(milliseconds: 300),
refreshTimeout: TurnstileRefreshMode.manual,
),
loadingWidget: const Center(child: CircularProgressIndicator()),
onTokenReceived: (t) {},
)
Full-width (flexible) layout
flexible expands to the parent's width. Because a WebView has no intrinsic
width, the widget forces width: double.infinity, so place it in a container
that provides a bounded width (e.g. a Column with horizontal padding). The
widget also waits for a non-zero layout width before rendering, so flexible
reliably fills the row instead of collapsing to its minimum size:
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: TurnstilePro(
siteKey: '...',
options: const TurnstileOptions(size: TurnstileSize.flexible),
onTokenReceived: (t) {},
),
)
Programmatic control
final controller = TurnstileController();
TurnstilePro(
siteKey: '...',
controller: controller,
onTokenReceived: (t) {},
);
// Later — e.g. after a failed form submission:
await controller.reset();
Remember to dispose it:
@override
void dispose() {
controller.dispose();
super.dispose();
}
API
TurnstilePro (widget)
| Parameter | Type | Default | Description |
|---|---|---|---|
siteKey |
String |
— | Cloudflare Turnstile site key (required) |
onTokenReceived |
ValueChanged<String> |
— | Called with the verification token |
onError |
ValueChanged<String>? |
null |
error / expired / timeout / parse_error |
onWidgetReady |
VoidCallback? |
null |
Fired once the widget is rendered |
controller |
TurnstileController? |
null |
For reset() / reload() |
options |
TurnstileOptions |
TurnstileOptions() |
Appearance & behavior (see below) |
baseUrl |
String |
http://localhost/ |
Origin used for the widget |
action |
String? |
null |
Server-side action tag |
cData |
String? |
null |
Customer data passed to the challenge |
height |
double? |
per-size default | Widget height (logical px) |
loadingWidget |
Widget? |
null |
Placeholder shown until ready |
TurnstileOptions
| Field | Type | Default | Description |
|---|---|---|---|
size |
TurnstileSize |
flexible |
normal / compact / flexible |
theme |
TurnstileTheme |
auto |
light / dark / auto |
language |
String |
auto |
Widget language (e.g. tr, en) |
retryAutomatically |
bool |
true |
Retry on failed challenge |
retryInterval |
int |
8000 |
Retry interval in ms |
refreshExpired |
TurnstileRefreshMode |
auto |
Behavior when token expires |
refreshTimeout |
TurnstileRefreshMode |
auto |
Behavior on interactive timeout |
appearance |
TurnstileAppearance |
always |
always / execute / interactionOnly |
borderRadius |
BorderRadius? |
null |
Corner radius (clips the widget) |
backgroundColor |
Color |
transparent | Background color |
curve |
Curve |
Curves.easeInOut |
Appear animation curve |
animationDuration |
Duration |
300ms |
Appear animation duration |
How verification works
The widget only produces a token on the client. You must validate the token
server-side by POSTing it to
https://challenges.cloudflare.com/turnstile/v0/siteverify with your secret
key. Never trust the token without server verification.
License
MIT — see LICENSE.
Libraries
- turnstile_pro
- A lightweight,
webview_flutter-based Cloudflare Turnstile widget for Flutter — with size, theme, auto-retry/refresh options and a controller.