Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

English · 简体中文

desktop_flask

Run a Flask app in a desktop window: WebView2 renders the UI, an in-process WSGI bridge serves it, and no TCP port is ever opened.

Vibe Coding — this project is built by a human and an AI together. The author sets the requirements, the architecture decisions and the acceptance criteria, and does the real-machine verification; an AI agent writes most of the code, tests and documentation. Nothing here is "generated and shipped unseen": every non-obvious claim was measured on a real machine and the measurements were kept. See How this was built.

desktop_flask is a standalone extension library — it does not modify Flask's source. You keep from flask import Flask and add one line:

from flask import Flask
import desktop_flask

app = Flask(__name__)
desktop_flask.init_app(app)          # ← the only wiring step

@app.get("/")
def get_index():
    return render_template("index.html")

if __name__ == "__main__":
    app.run_desktop(title="My tool")

Or without any injection:

desktop_flask.run_desktop(app, title="My tool")

From the command line:

flask --app myapp desktop                      # via the flask.commands entry point
flask --app myapp desktop --self-check         # built-in WebView2 self check (S1–S19)

desktop-flask myapp:app --title "My tool"      # launcher, no Flask CLI needed
python -m desktop_flask myapp:create_app()     # the same thing

Status: 0.1.0.dev2 — developer preview. The bridge core, the Flask integration, the WebView2 host and the packaging story are done and tested on a real machine, but the API and the DESKTOP_* keys may still shift before 0.1.0. Desktop-specific optimisations are still ahead. Breaking changes will be listed in CHANGELOG.md.

Install

pip install desktop_flask

That is enough on Windows: pythonnet is a base dependency (sys_platform == 'win32'), so run_desktop() works right after installing. desktop_flask[desktop] is still accepted as an alias for older instructions.

  • The WebView2 Runtime is required at run time. Windows 11 has it; older Windows 10 may not. run_desktop(install_runtime=True) can offer to install it.
  • The WebView2 managed DLLs and native loader are bundled in the package, so there is no separate download step.
  • Windows only for now. There is no macOS/Linux backend — run_desktop() says so instead of silently opening a port.

⚠️ Do not put a version range in your requirements for this pre-release: desktop_flask>=0.1 does not resolve, because pip excludes pre-releases when the requirement carries a specifier. Plain pip install desktop_flask and pip install desktop_flask==0.1.0.dev2 both work. Once 0.1.0 is published, ranges start working and upgrades move to the final release automatically.

What it changes

Ordinary app.run() desktop_flask
Transport TCP socket on 127.0.0.1 direct app.wsgi_app(environ, start_response) call
Reachable from other processes yes no — requests never leave the process
Port conflicts / firewall prompts possible impossible
Page origin http://127.0.0.1:5000 df://app (registered custom scheme, tuple origin)
Cookies Chromium the bridge's own cookie jar (Chromium cookies do not work on custom schemes)
WebSocket / SSE yes not supported — realtime uses postMessage (desktop_flask.emit)
Backend code — unchanged

Packaging your own app into an exe

pip install "desktop_flask[desktop,build]"
desktop-flask-build app.py

You get build/app.dist/ — ship that whole directory. Nuitka also needs a C compiler (MSVC or MinGW64); pip cannot install it, and the tool's preflight will tell you if it is missing before starting a ten-minute compile. See docs/packaging.md for the two traps that make a build succeed and still not run, and for the distribution checklist.

Reading the docs offline

The documentation ships inside the package, so it is readable without any network access:

desktop-flask-docs                  # list the pages (Chinese, the fuller set)
desktop-flask-docs --en             # English pages
desktop-flask-docs packaging --cat  # print one page

What's inside

Transport WSGIBridge — builds a WSGI environ from a WebView2 request, calls app.wsgi_app, captures the response. No socket, no port.
Worker pool DESKTOP_WORKERS (4 by default) — request handling does not occupy the UI thread. The reason is throughput, not "freezing": WebView2 renders in its own process.
Static asset cache in-memory LRU (32 MiB default) with strict HTTP semantics and ETag/304 revalidation
Realtime channel postMessage message bus (desktop_flask.emit() / desktopFlask.invoke()) — the WebSocket replacement
Single instance DESKTOP_SINGLE_INSTANCE — a named kernel mutex keyed on the userDataFolder, so "the second copy is silently broken" becomes a readable message
Runtime detection + guided install registry-based detection, actionable instructions, and an opt-in (DESKTOP_INSTALL_RUNTIME) download + silent install of Microsoft's Evergreen bootstrapper
Crash reports sys.excepthook + threading.excepthook + sys.unraisablehook + faulthandler, each writing an editable report with an environment block
userDataFolder management %LOCALAPPDATA%\<app-id>\WebView2, overridable, with DESKTOP_APP_ID as the identity knob
Packaging desktop-flask-build — Nuitka standalone/onefile builds with the vendored DLLs, the icon, and the flags that would otherwise fail silently
Icon drawn in pure Python by scripts/make_icon.py; used for both the exe and the window
Built-in self check --self-check — S1–S19 in a real WebView2 window, on your machine

Documentation

Document Contents
docs/README.md documentation index (English); Chinese version under docs/zh/
docs/configuration.md every DESKTOP_* key, init_app()/run_desktop()/CLI option, and the reasoning behind the defaults
docs/troubleshooting.md the failures that actually happen, with the fix that worked
docs/packaging.md Nuitka packaging, why not PyInstaller, the frozen-path traps, distribution checklist
docs/zh/python-3.14-free-threading.md R17: what free-threading (PEP 703/779) would and would not buy, and the prep needed to test it — Chinese only

Developer-facing design notes (architecture, measured findings, ADRs) live in the carrier repository this library was developed in: DESKTOP_MODE_DESIGN.md and the m0_spike/, m1_bridge/, m3_host/, m4_engineering/ conclusion documents. They are not part of this package.

Boundaries (by design, not bugs)

  • No WebSocket. The constructor itself refuses a non-http(s)/ws(s) URL on a custom scheme. Use desktop_flask.emit() / desktopFlask.invoke().
  • No SSE / streaming responses. WebView2 requires the complete body before the deferral completes. Push progress through the message bridge instead.
  • Cookies are the bridge's, not Chromium's. document.cookie stays empty — that is expected. localStorage / IndexedDB work normally.
  • No Service Worker (Blink requires http/https).
  • Windows only for now; the interface is designed to be extended.

How this was built (Vibe Coding)

This project is a human–AI collaboration, and it is explicit about which part is whose.

Who Did what
Human author the goal and the requirements; the architecture decisions (a custom df:// scheme instead of a local port, bridge-managed cookies, no WebSocket, Nuitka instead of PyInstaller); the acceptance criteria; every real-machine check; the release
AI agent most of the implementation, the test suite, the documentation in both languages, and the release scripts

Why it can still be trusted:

  • Nothing was accepted because it looked right. Every non-obvious decision names its evidence — a measurement, an official document quote, or a real-machine run that failed and forced a change. The WebView2 quirks that cost the most time are recorded rather than hidden: --include-data-dir silently dropping .dll; Nuitka not setting sys.frozen; pythonnet converting bytes element-by-element at ~1000× the cost of one Marshal.Copy.
  • The gates are real. 942 tests in the development tree, 450 in an environment with the unmodified official Flask, ruff + mypy --strict, and a packaged build that passes the built-in self check 15 passed / 0 failed / 4 info on a real Windows desktop.
  • The limits are stated, not papered over. Windows only, no WebSocket/SSE, no streaming responses, cookies owned by the bridge — all listed under Boundaries.

What to expect because of it:

  • The comments and docs are unusually dense; an AI writes more explanation than a human usually bothers to. That is deliberate here, but it is a style you may not like.
  • The English documentation is a translation: Chinese is the first-class language of this project and the fuller version.
  • If you find a mistake that looks AI-shaped — confidently wrong, or copied from a similar-looking case — an issue is the fastest fix. That is exactly the kind of report this mode of development needs.

Not part of Flask

desktop_flask is an independent extension library: it is not part of Flask and is not affiliated with, or endorsed by, the Pallets organisation. It redistributes Microsoft's WebView2 assemblies verbatim under their own license. See LICENSE.txt (BSD-3-Clause) and NOTICE.md for the full attribution list.

Layout

This directory is the library's repository root — src/, tests/, scripts/, docs/ and examples/ are siblings here, so the whole directory can be pushed as a standalone repository.

src/desktop_flask/          the library
├── bridge.py               WSGI bridge: environ, WSGI call, response capture
├── adapter.py              makes url_for(_external=True) produce df:// URLs
├── cookies.py              bridge-managed cookie jar
├── bodystore.py            memory vs. temp file for request/response bodies
├── cache.py                static asset LRU + HTTP conditional requests
├── pipeline.py             cache + bridge, host-independent
├── workers.py              request thread pool + deferral lifetime
├── runtime.py              single instance, crash reports, Runtime detection/install
├── _frozen.py              "is this a frozen build?" (Nuitka does not set sys.frozen)
├── origin.py               origin strategies (df://app, http://df.localhost)
├── compat.py               legacy-origin forwarding, body rewriting, navigation policy
├── messages.py             postMessage message bus (the WebSocket replacement)
├── selfcheck.py            built-in S1–S19 self check page
├── server.py               DebugHttpServer (development only; opens a port)
├── integration.py          init_app(): runtime wiring, no Flask patch
├── packaging.py            desktop-flask-build (Nuitka)
├── docs.py                 desktop-flask-docs (the bundled documentation)
├── cli.py                  `flask desktop`
├── run.py / __main__.py    `desktop-flask` launcher
├── assets/                 icon (generated by scripts/make_icon.py)
├── _vendor/webview2/       WebView2 managed DLLs + native loader (vendored)
└── platforms/windows.py    WebView2 host (pythonnet + WinForms)
docs/                       documentation (English)
docs/zh/                    documentation (Simplified Chinese)
scripts/                    fetch_webview2_dlls.py, make_icon.py, build_nuitka.py, frozen_probe.py
tests/                      the library's test suite
examples/desktop_demo/      a demo app showing cache / workers / message bridge
examples/tutorial/          the official Flask tutorial, run unchanged (acceptance)
pyproject.toml              distribution metadata
LICENSE.txt / NOTICE.md     license and third-party attribution
CHANGELOG.md                what changed, and the version policy

Metadata

Release files for desktop_flask 0.1.0.dev2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for desktop_flask 0.1.0.dev2
File Size Uploaded
desktop_flask-0.1.0.dev2.tar.gz 716.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for desktop_flask 0.1.0.dev2
File Interpreter ABI Platform
desktop_flask-0.1.0.dev2-py3-none-any.whl Python 3 none any Details

Total release size: 1.8 MB

Release files / desktop_flask-0.1.0.dev2.tar.gz

Download URL desktop_flask-0.1.0.dev2.tar.gz
Size 716.0 kB
Tags Source
SHA-256 checksum
How to use checksums
34e87f9a06a90fc2710709275dc377bb55ca2e22f93a6d25bf3e59889c00db02
BLAKE2b-256 checksum
How to use checksums
025aa3f735ffefbee13938663cee082eb0878e75d7de4b080da5fa99e128d5db
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / desktop_flask-0.1.0.dev2-py3-none-any.whl

Download URL desktop_flask-0.1.0.dev2-py3-none-any.whl
Size 1.0 MB
Tags Python 3
SHA-256 checksum
How to use checksums
f12f4933fff6c1f8577b580bd19780733c4681eb096918f7d030224ea58e7889
BLAKE2b-256 checksum
How to use checksums
b0cf27d514a76a0f160fc9d13fbda2db272f42bf7d83bf4c9a0aec1ab0fdac9d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.10.2 {"installer":{"name":"uv","version":"0.10.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

This release

0.1.0.dev2 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page