Flask-Turnstile
A Cloudflare Turnstile extension for Flask based on flask-recaptcha.
Install
pip install flask-turnstile
Usage
Implementation view.py
from flask import Flask
from flask_turnstile import Turnstile
app = Flask(__name__)
turnstile = Turnstile(app=app)
#or
turnstile = Turnstile()
turnstile.init_app(app)
In your template: {{ turnstile }}
Inside of the form you want to protect, include the tag: {{ turnstile }}
It will insert the code automatically
<form method="post" action="/submit">
... your field
... your field
{{ turnstile }}
[submit button]
</form>
Verify the captcha
In the view that's going to validate the captcha
from flask import Flask
from flask_turnstile import Turnstile
app = Flask(__name__)
turnstile = Turnstile(app=app)
@app.route("/submit", methods=["POST"])
def submit():
if turnstile.verify():
# SUCCESS
pass
else:
# FAILED
pass
Api
turnstile.init(app, site_key, secret_key, is_enabled=True)
turnstile.get_code()
Returns the HTML code to implement. But you can use {{ turnstile }} directly in your template
turnstile.verify()
Returns bool
turnstile.get_response()
Returns Cloudflare's raw siteverify response as a dict, including success and,
on failure, error-codes (plus challenge_ts and hostname when provided).
Returns an empty dict when validation is disabled or the request fails. Useful
for logging the reason a challenge was rejected:
result = turnstile.get_response()
if not result.get("success"):
app.logger.warning("Turnstile failed: %s", result.get("error-codes"))
In Template
Just include {{ turnstile }} wherever you want to show the captcha
Config
Flask-Turnstile is configured through the standard Flask config API. These are the available options:
TURNSTILE_ENABLED: Bool - True by default, when False it will bypass validation
TURNSTILE_SITE_KEY : Public key
TURNSTILE_SECRET_KEY: Private key
The following are Optional arguments.
TURNSTILE_ENABLED = True
TURNSTILE_SITE_KEY = ""
TURNSTILE_SECRET_KEY = ""
Widget options
Any of the Cloudflare Turnstile widget options can be set, either through Flask config as TURNSTILE_<OPTION> or as a keyword argument to Turnstile(...). Each is rendered as the matching data-* attribute on the widget. When an option is left unset it is omitted entirely (so Cloudflare's default applies, e.g. theme follows the visitor's system preference).
| Flask config | Constructor kwarg | Rendered attribute | Common values |
|---|---|---|---|
TURNSTILE_THEME |
theme |
data-theme |
auto, light, dark |
TURNSTILE_SIZE |
size |
data-size |
normal, flexible, compact |
TURNSTILE_LANGUAGE |
language |
data-language |
auto, ISO code (e.g. en, es) |
TURNSTILE_APPEARANCE |
appearance |
data-appearance |
always, execute, interaction-only |
TURNSTILE_ACTION |
action |
data-action |
string (max 32 chars) |
TURNSTILE_CDATA |
cdata |
data-cdata |
string (max 255 chars) |
TURNSTILE_TABINDEX |
tabindex |
data-tabindex |
integer |
TURNSTILE_RETRY |
retry |
data-retry |
auto, never |
TURNSTILE_RETRY_INTERVAL |
retry_interval |
data-retry-interval |
ms (default 8000) |
TURNSTILE_REFRESH_EXPIRED |
refresh_expired |
data-refresh-expired |
auto, manual, never |
TURNSTILE_REFRESH_TIMEOUT |
refresh_timeout |
data-refresh-timeout |
auto, manual, never |
TURNSTILE_EXECUTION |
execution |
data-execution |
render, execute |
TURNSTILE_RESPONSE_FIELD |
response_field |
data-response-field |
bool |
TURNSTILE_RESPONSE_FIELD_NAME |
response_field_name |
data-response-field-name |
string |
TURNSTILE_FEEDBACK_ENABLED |
feedback_enabled |
data-feedback-enabled |
bool |
TURNSTILE_CALLBACK |
callback |
data-callback |
JS function name |
TURNSTILE_ERROR_CALLBACK |
error_callback |
data-error-callback |
JS function name |
TURNSTILE_EXPIRED_CALLBACK |
expired_callback |
data-expired-callback |
JS function name |
TURNSTILE_TIMEOUT_CALLBACK |
timeout_callback |
data-timeout-callback |
JS function name |
TURNSTILE_BEFORE_INTERACTIVE_CALLBACK |
before_interactive_callback |
data-before-interactive-callback |
JS function name |
TURNSTILE_AFTER_INTERACTIVE_CALLBACK |
after_interactive_callback |
data-after-interactive-callback |
JS function name |
TURNSTILE_UNSUPPORTED_CALLBACK |
unsupported_callback |
data-unsupported-callback |
JS function name |
Example:
TURNSTILE_THEME = "dark"
TURNSTILE_SIZE = "flexible"
(c) 2015 Mardix (c) 2023-2026 Kristian
Release files for Flask-Turnstile 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| flask_turnstile-0.2.0.tar.gz | 6.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| flask_turnstile-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size:12.7 kB
Release files / flask_turnstile-0.2.0.tar.gz
| Download URL | flask_turnstile-0.2.0.tar.gz |
|---|---|
| Size | 6.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e8e4cf22187e55049675a0e9607c1b9823b5b222b6d4f0825e97dfcf634d0a88
|
|
BLAKE2b-256 checksum How to use checksums |
1304ed96e780c7d4a1b5ea3ceefaaa10f850149a08b4ba6227e0a58448872201
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.
Transparency logRelease files / flask_turnstile-0.2.0-py3-none-any.whl
| Download URL | flask_turnstile-0.2.0-py3-none-any.whl |
|---|---|
| Size | 6.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4ea676c2d317fc10689b7d9d0c062945510d253fda64993354fe78206e2ab1be
|
|
BLAKE2b-256 checksum How to use checksums |
b023cee94036efe279e15c63fe36f2eeca0d4162a215a84c231fbf7ebed4068b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jun 1, 2026.
Transparency log