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.

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.dev1 — 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.dev1 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.

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.dev1

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.dev1
File Size Uploaded
desktop_flask-0.1.0.dev1.tar.gz 711.7 kB Details

Built distribution (wheel)

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

Total release size: 1.8 MB

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

Download URL desktop_flask-0.1.0.dev1.tar.gz
Size 711.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d6ad0e4f14d7fc21c8a2099ca781c21258c1f04140e343c2e36f0a6c4ca777bf
BLAKE2b-256 checksum
How to use checksums
7ebe6fd46408007f27a085b9e42b0f8a03171c50955faf1a99faae554043d6c0
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.dev1-py3-none-any.whl

Download URL desktop_flask-0.1.0.dev1-py3-none-any.whl
Size 1.0 MB
Tags Python 3
SHA-256 checksum
How to use checksums
0c634709bd989bee3db260ae3d5c9e8bace96a5719f5441d0ad01dd552e842e5
BLAKE2b-256 checksum
How to use checksums
8b6e7d473e4ceb33f48d11bc7fe3d2aa9f8cb22e365097cdaa615cdcb3beab27
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.dev1 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