Skip to content

Flutter

Integrate EU CAPTCHA into your Flutter app using the eu_captcha_flutter package. The widget works on both Android and iOS from a single Dart codebase.

Source Platforms
github.com/Myra-Security-GmbH/eu-captcha-flutter Android API 19+ · iOS 13+

Server-side verification is identical regardless of client platform — see Server-Side Verification. For native SDKs see Android SDK and iOS SDK.


Requirements

  • Flutter 3.0+
  • Dart SDK ≥ 3.0
  • Android API 19 (KitKat) or later
  • iOS 13 or later

Installation

Add the package as a git dependency in your pubspec.yaml:

dependencies:
  flutter_inappwebview: ^6.0.0
  eu_captcha_flutter:
    git:
      url: https://github.com/Myra-Security-GmbH/eu-captcha-flutter.git

Then run:

flutter pub get

Platform configuration

Android — add the internet permission to android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET"/>

iOS — no additional configuration needed.

Basic usage

Add the EuCaptcha widget to your form. It renders at a fixed height of 70 logical pixels, constrained to a maximum width of 420:

import 'package:eu_captcha_flutter/eucaptcha.dart';

EuCaptcha(
  sitekey: 'EUCAPTCHA_SITE_KEY',
  onComplete: (token) {
    // Challenge complete — submit token to your server as "eu-captcha-response"
    submitForm(token);
  },
  onExpire: (_) {
    // Token expired — disable submit until user completes again
    setState(() => _submitEnabled = false);
  },
  onError: (_) {
    // Network or server error during verification
    showErrorSnackBar();
  },
)

Full login form example

class _LoginPageState extends State<LoginPage> {
  final _captchaKey = GlobalKey<EuCaptchaState>();
  String _captchaToken = '';
  bool _submitEnabled = false;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // ... username and password fields ...

        EuCaptcha(
          key: _captchaKey,
          sitekey: 'EUCAPTCHA_SITE_KEY',
          theme: 'auto',
          onComplete: (token) => setState(() {
            _captchaToken = token;
            _submitEnabled = true;
          }),
          onExpire: (_) => setState(() => _submitEnabled = false),
          onError: (_) => setState(() => _submitEnabled = false),
        ),

        FilledButton(
          onPressed: _submitEnabled ? _submit : null,
          child: const Text('Log in'),
        ),
      ],
    );
  }

  Future<void> _submit() async {
    final response = await http.post(
      Uri.parse('https://yourserver.example/login'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode({'eu-captcha-response': _captchaToken}),
    );
    // handle response...

    // Reset the widget after a failed attempt
    _captchaKey.currentState?.reset();
  }
}

Parameters

Parameter Type Required Default Description
sitekey String yes Your public sitekey
onComplete void Function(String) yes Called with the token when the challenge completes
onExpire void Function(String)? no null Called when the token expires
onError void Function(String)? no null Called when a network or server error occurs
theme String no 'auto' 'light', 'dark', or 'auto' (follows system theme)
lang String no 'en' BCP 47 language tag (e.g. 'de', 'fr')

Resetting the widget

After a rejected form submission, reset the widget using a GlobalKey so the user can complete a new challenge:

final _captchaKey = GlobalKey<EuCaptchaState>();

// In your widget tree:
EuCaptcha(key: _captchaKey, sitekey: 'EUCAPTCHA_SITE_KEY', onComplete: ...)

// After a failed submission:
_captchaKey.currentState?.reset();

Server-side verification

When onComplete fires, POST the token to your server as the eu-captcha-response field and verify it against the Verification API.

For ready-made server libraries, see Server-Side Verification.