Skip to content

Flask — greenfield or existing app

Use hedron-flask for Flask-native apps. Scaffold with the hedron CLI (hedron new --flask); runtime is hedron-flask + hedron-core (no FastAPI in the app process).

Install: pip install "hedron-flask>=1.0.0" (or uv add "hedron-flask>=1.0.0"). Requires Python 3.11–3.14. See Installation.

Flask supports pages, fragments, and CSRF. Progressive FastAPI facades (@app.view, @app.action, and related symbols) are FastAPI-only — see the host matrix on What’s ready.

Golden path (scaffold + Refresh)

Same success criteria as FastAPI: open the app, see Hello, click Refresh, watch the status region update without a full page reload. The scaffold uses Flask blueprints and raw hx-* attributes, not FastAPI status.refresh_button(...).

# Need uv? https://docs.astral.sh/uv/getting-started/installation/
uvx --from "hedron>=1.0.0" hedron new my-flask-app --flask
cd my-flask-app && uv sync && uv run flask --app app run --port 8000

Open http://127.0.0.1:8000 — you should see Hello from hedron new --flask. Click Refresh; the panel timestamp updates via HTMX into the declared #panel region.

Port 8000

Hedron Flask samples and docs use port 8000 (same as FastAPI) so you can switch examples without changing the browser URL. Plain flask run defaults to 5000 if you omit --port.

The scaffold includes page + fragment regions under security="standard". Set HEDRON_SESSION_SECRET before production.

refresh_button vs raw hx-*

FastAPI scaffolds use status.refresh_button(...). Flask/Django getting-started samples may show raw hx-get / hx-target attributes on a button — both are valid. Prefer the host’s scaffold helpers when present; raw HTMX attrs remain portable across adapters.

Try without local setup

Open the monorepo in Codespaces / Dev Container, then scaffold with hedron new my-app --flask or run examples/flask-reference.

Alternate: manual factory (with Refresh)

python -m venv .venv && source .venv/bin/activate
python -m pip install "hedron-flask>=1.0.0"

Save as app.py:

import os
from datetime import datetime, timezone

from flask import Flask

from hedron_core import FragmentRegion, InteractionResult, Page, Text, html
from hedron_core.interaction import InteractionPolicy
from hedron_flask import HedronBlueprint, HedronFlask

hedron = HedronFlask()
ui = HedronBlueprint("ui", __name__)

PANEL = FragmentRegion(id="panel", selector="#panel")


def panel_body() -> object:
    stamp = datetime.now(timezone.utc).strftime("%H:%M:%S UTC")
    return html.div(Text(f"Flask status · {stamp}"), id="panel")


@ui.page("/")
def home() -> Page:
    return Page(
        html.div(
            Text("Hello Flask"),
            panel_body(),
            html.button(
                Text("Refresh"),
                **{
                    "hx-get": "/status",
                    "hx-target": "#panel",
                    "hx-swap": "outerHTML",
                },
            ),
        ),
        title="Home",
    )


@ui.view("/status", fragment_regions=(PANEL,))
def status() -> InteractionResult:
    return InteractionResult(
        content=panel_body(),
        region_id="panel",
        policy=InteractionPolicy(declared_regions=(PANEL,)),
    )


def create_app() -> Flask:
    app = Flask(__name__)
    app.secret_key = os.environ.get("HEDRON_SESSION_SECRET", "replace-in-production")
    hedron.init_app(app)
    app.register_blueprint(ui)
    return app


app = create_app()
flask --app app:app run --debug --port 8000
# uv users: uv run flask --app app:app run --debug --port 8000

Constructor style (HedronFlask(__name__)) still works when you do not need a factory (see the scaffold app.py).

CSRF

Safe GETs issue the hedron_csrf cookie. Unsafe methods on hedron_route and HedronFlask.respond require a matching X-CSRF-Token header or csrf_token form field.

Next

Stay on Flask: extend the scaffold HedronBlueprint / HedronFlask using view / action and keep POST field csrf_token. Job status: prefer bounded polling (FastAPI SSE helpers are experimental).

FastAPI-only continuation

HTMX interactions and Minimal form assume Hedron() / @app.view / status.refresh_button(...) / CsrfField(). Use them after you switch hosts, not as the Flask next step.