flaskwebgui: putting a Flask or FastAPI app into a desktop window
Create desktop applications with Flask/Django/FastAPI!
At a glance
- What is it?
- flaskwebgui turns a web application into something that looks like a desktop program by starting your WSGI or ASGI server, finding a free port and opening a browser in app mode inside a sized window. It supports Flask, Flask-SocketIO, FastAPI and Django through one FlaskUI wrapper, and the trade-off is explicit in its own documentation, since the interface is a web page and only JavaScript is available to it.
- Who is it for?
- Adopt flaskwebgui if you already have a Python web application and want to ship it as a desktop program without rewriting the interface in a native widget toolkit, especially if the UI is HTML and CSS you already maintain.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 139 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A web app in a window, not a native GUI
The project description is short: create desktop applications with Flask, Django or FastAPI. The README for the Flask variant puts it more plainly, telling the reader that if you are debugging you can do that in the browser with app.run(), and that if you want to view the flaskwebgui window you call FlaskUI(app=app, server="flask").run().
That distinction matters more than the word desktop in the tagline suggests. What flaskwebgui does is start your application server on a local port and open a browser pointed at it, in app mode, with the window sized as you asked. The user sees what looks like a program, and the developer keeps the web stack they already have.
The audience is therefore quite specific: teams with a working Flask, FastAPI or Django application and a requirement to distribute it as a desktop program, who would otherwise either rewrite the interface in a native toolkit or ship a browser tab and call it an application. The topics list the same ground with desktop-application, html-css-javascript and python3-gui.
The implementation is Python, the licence is MIT, the default branch is master, and the package requires Python 3.12 or newer according to the project metadata.
What the project is not is a widget library. There is no set of native controls, no window title bar you own, no tray icon, no file dialog that is not the browser's own. The window is a browser window in app mode, and every limitation of that arrangement is inherited by anything you build on top.
The FlaskUI wrapper and what you can configure
One class does the work, and the README lists its parameters in full, which is the best description of the mechanism available.
The server parameter is a Union of a string and a callable, and it is the function that receives server_kwargs to start the server. The string forms in the examples are flask, flask_socketio, fastapi and django, so the framework-specific startup lives in that callable while the wrapper handles the window. The server_kwargs dictionary is passed straight down to it, which is where you would configure the framework's own server options.
The app parameter takes a wsgi or asgi app, so the same wrapper works for a synchronous WSGI application and an asynchronous one. The port parameter specifies a port, and the README notes that if it is not set a free port will be chosen instead, which is what makes the port collision problem a non-issue in development.
The window itself is controlled by width, height and a fullscreen flag that defaults to True, meaning the app starts maximised. Two lifecycle hooks are available, on_startup before the browser and webserver start and on_shutdown after they stop, which is where you would put initialisation and cleanup that must not run inside a request.
The browser side has three parameters. extra_flags is a list of additional flags for the browser command. browser_path sets the path to a Chrome executable, or lets the defaults do that. browser_command is the full command line that starts Chrome in app mode, and the README gives the shape of it: an executable path, a user data directory, a new window, no default browser check, allow insecure localhost, no first run, disable sync, a window size, and an --app URL pointing at the local address.
That example is also the most important thing to read if you are evaluating, because it tells you exactly what the project assumes about the host: a Chromium-derived browser that understands app mode.
Installing it and opening your first window
The install is a single command:
pip install flaskwebguiAnd the integration is a few lines at the bottom of your existing application:
#main.py
from flask import Flask
from flask import render_template
from flaskwebgui import FlaskUI # import FlaskUI
app = Flask(__name__)
@app.route("/")
def hello():
return render_template('index.html')The window is opened from the entry point:
if __name__ == "__main__":
# If you are debugging you can do that in the browser:
# app.run()
# If you want to view the flaskwebgui window:
FlaskUI(app=app, server="flask").run()Nothing else in the application changes. The routes, the templates and the static files stay exactly as they were, which is the whole appeal: the same code serves a browser tab during development and a window in production.
One small detail worth carrying over. The README notes that to change the app icon you add favicon.ico in the head tag of your base.html, which is a neat illustration of the model. The window chrome, including the icon, comes from the page and the browser rather than from your code, so the familiar native title bar and the familiar task bar icon are the price of not writing a native application.
If you need more throughput, the README suggests installing waitress for better performance with the Flask server.
FastAPI, Django and the SocketIO variant
The same wrapper covers three more stacks, and the differences between them are mostly about how the server is started and which extra packages are needed.
For FastAPI, the README says the setup is pretty much the same. You mount static files and Jinja templates, define routes, and call FlaskUI(app=app, server="fastapi").run(). The note at the end matters: FastAPI will be served by uvicorn. So the ASGI server choice is fixed by the string you pass, and if you need a different ASGI server you would supply your own callable in the server parameter.
For Flask-SocketIO, you pass both the app and the socketio object, along with the window size:
if __name__ == '__main__':
# socketio.run(app) for development
FlaskUI(
app=app,
socketio=socketio,
server="flask_socketio",
width=800,
height=600,
).run()Here the width and height are given explicitly, which overrides the fullscreen default, and the README states that the app will be served by flask_socketio. That configuration exists because a SocketIO application needs the websocket-capable runner rather than the plain development server.
Django takes more setup, which is what you would expect from a framework with its own static file handling. The README asks for waitress and whitenoise to make it work nicely, then shows the settings for STATIC_URL, STATICFILES_DIRS, STATIC_ROOT and the MEDIA paths, and the MIDDLEWARE list with the whitenoise middleware added. Next to manage.py you create a gui.py that imports the application from the project's wsgi.py:
#gui.py
from flaskwebgui import FlaskUI
from djangodesktop.wsgi import application as app
if __name__ == "__main__":
FlaskUI(app=app, server="django").run()and then the application is started with python gui.py. The key difference from Flask is that Django's project layout stays intact and the GUI entry point sits alongside manage.py rather than replacing it.
Closing the window from a route, and the console problem
A desktop application needs a way to quit, and flaskwebgui provides one through a route.
The close_application function is imported from the package alongside FlaskUI, and you call it from any route in your web framework:
from flaskwebgui import FlaskUI, close_application
# Any python webframework routing here
@app.route("/close", methods=["GET"])
def close_window():
close_application()The link that triggers it is an ordinary anchor in your template:
<a href="/close" class="exit" role="button"> CLOSE </a>That is a complete quit path, and it is worth noticing what it implies. The window is closed by the page asking the host process to exit, so the server has to be running as part of the same process as the browser window, which is exactly what the wrapper arranges.
The second topic in this part of the README is more candid and more useful. The project shows JavaScript to prevent users from opening the browser console, blocking the F12 key and the right-click context menu:
<script>
// Prevent F12 key
document.onkeydown = function (e) {
if (e.key === "F12") {
e.preventDefault();
}
};
// Prevent right-click
document.addEventListener("contextmenu", function (e) {
e.preventDefault();
});
</script>And then it says something a buyer of a desktop toolkit would want to know: native like features are pretty hard to implement because we have access only to javascript. The README also points at issue 135 for the wider discussion and notes you can extend the script to block other browser-specific features.
So the honest summary is that this is a browser page in a window with a cosmetic set of restrictions, and the console can be reopened by a determined user. Any product that needs real confinement should not treat this script as a security boundary.
Packaging, examples and the state of the project
The repository is small and modern in its layout. The top level holds src/, examples/, pyproject.toml, uv.lock, MANIFEST.in, LICENSE, README.md and a .vscode directory, with the package declared as py-modules = ["flaskwebgui"] and the package directory mapped from src.
The single runtime dependency is psutil, which is consistent with the design, because the process has to watch for the browser window closing and shut the server down when it does. That is the one thing the browser cannot do from JavaScript, and it is the reason a Python process stays in charge of the application's lifetime.
The examples directory is the best catalogue of what the project supports. Alongside flask-desktop, django-desktop, fastapi-desktop and flask-socketio there are bottle-desktop, nicegui-desktop and torchvision-desktop, which shows the wrapper being used with other frameworks and with other kinds of workload.
On maintenance, the repository is not archived and the last push was on 2026-06-06, and there are no GitHub releases, so versions are tracked through the package metadata and the PyPI listing rather than through release tags. The pyproject.toml declares version 1.1.9, requires-python >= 3.12, and licence MIT with the licence file included in the distribution.
That Python floor is worth pausing on. A package that wraps other people's frameworks and manages processes is not hard to make work on older interpreters, so requiring 3.12 tells you the project has decided to follow current Python rather than support a wide range. That is a reasonable trade for a single-maintainer project and a real consideration for an organisation running an older fleet.
Editorial conclusion
Adopt flaskwebgui if you already have a Python web application and want to ship it as a desktop program without rewriting the interface in a native widget toolkit, especially if the UI is HTML and CSS you already maintain. Do not adopt it if your product needs native menus, file dialogs, tray integration or any window chrome the browser engine does not provide, because the application is a browser page and the README says plainly that native-like features are hard to implement with JavaScript access alone. Before you ship, test the browser_command on every platform you target, since the default assumes a Chromium-style executable in app mode, and check that your framework's server choice is one of the supported ones.
Frequently asked questions
How do I turn a Flask app into a desktop window with flaskwebgui?
Import FlaskUI from flaskwebgui and call FlaskUI(app=app, server="flask").run() in the main block of your application. The wrapper starts the server, picks a free port and opens a browser in app mode, and the README suggests installing waitress for better performance.
Which frameworks does flaskwebgui support?
The README documents Flask, Flask-SocketIO, FastAPI and Django, and the examples folder also contains bottle-desktop, nicegui-desktop and torchvision-desktop. The server parameter is a string or a callable that receives server_kwargs and starts the server.
How do I close the application window from inside the app?
Import close_application from flaskwebgui and call it from a route, then link to that route with an anchor tag. The README also shows JavaScript that blocks the F12 key and the right-click menu, and states that native-like features are hard to implement because only JavaScript is available.
What are the configuration options for FlaskUI?
The documented parameters are server, server_kwargs, app, port, width, height, fullscreen, on_startup, on_shutdown, extra_flags, browser_path and browser_command. The port is chosen automatically if not set, fullscreen defaults to True, and browser_command is the full command line that starts Chrome in app mode.
What does Django support in flaskwebgui require?
The README asks for waitress and whitenoise, a static and media configuration in settings.py, whitenoise added to MIDDLEWARE, and a gui.py next to manage.py that imports the application from wsgi.py and calls FlaskUI(app=app, server="django").run(). The app is then started with python gui.py.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/climentea-flaskwebgui)Was this useful?