Docs menu

Install StageWhisper in Flask

Install StageWhisper in Flask by adding one async script tag to your Jinja2 base template — every template that extends it inherits the widget, so one edit covers your whole app. No extension, no blueprint, no config key.

Prerequisites

  • A StageWhisper site created in the dashboard.
  • Your app’s origin on the site’s origin allowlist — enable the localhost toggle while you test with flask run locally.

Add the loader to your base template

  1. Open templates/base.html — the template every page {% extends %} — so the widget rides along on every page without per-route wiring.
  2. Paste the loader tag inside the <head>, and swap pub_your_site_key for the public site key shown on your site’s install page in the dashboard:
    templates/base.html
    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <title>{% block title %}My app{% endblock %}</title>
        <script async src="https://cdn.stagewhisper.co/loader.js" data-sw-key="pub_your_site_key"></script>
        {% block extra_head %}{% endblock %}
      </head>
      <body>
        {% block content %}{% endblock %}
      </body>
    </html>
    

The tag is async, so it never blocks your page render — the widget mounts quietly once the page is up.

Strict CSP with flask-talisman

Serving a strict Content Security Policy with flask-talisman? Pass your policy to Talisman and list the directives that should carry a per-request nonce in content_security_policy_nonce_in:

app.py
# app.py
from flask import Flask
from flask_talisman import Talisman

app = Flask(__name__)

csp = {
    "default-src": "'self'",
    "script-src": ["'self'", "https://cdn.stagewhisper.co"],
    "style-src": "'self'",
    "connect-src": ["'self'", "https://api-staging.stagewhisper.co"],
}

Talisman(
    app,
    content_security_policy=csp,
    content_security_policy_nonce_in=["script-src", "style-src"],
)

Then carry the nonce on the loader tag itself with nonce="{{ csp_nonce() }}" — flask-talisman registers csp_nonce() as a Jinja2 global automatically, so it works in any template with nothing extra to import:

Loader tag with nonce
<script async src="https://cdn.stagewhisper.co/loader.js" data-sw-key="pub_your_site_key" nonce="{{ csp_nonce() }}"></script>

The Content Security Policy guide has the full directive list, including the Turnstile origins to allow if you enable it.

Verify

Refresh your app, then watch the site’s install status in the dashboard — it flips the moment the first ping arrives.

Widget not showing up? Head to Troubleshooting for the usual suspects — blocked origins, CSP, localhost, and stale config.