Building a Desktop App#
This tutorial walks you through building and running an existing Jac full-stack
app as a native desktop app. The desktop target turns your app into one
jac nacompiled binary plus the OS's own web engine - no Rust toolchain, no
PyInstaller, and no separate backend process. It builds the same cl frontend
the web target produces, then compiles a native host that embeds CPython to serve
that bundle and renders it in the OS-native webview (WebKitGTK on Linux,
WKWebView on macOS, WebView2 on Windows).
Status
jac build --client desktop produces a working, self-contained desktop
binary that renders your cl UI. Wiring the sv backend/walkers onto the
embedded interpreter, HMR dev mode, and per-OS installers/signing are in
progress - see issue #6436.
Prerequisites
- Completed: Project Setup - you have a working
jac startweb app- Installed:
pip install jac-client jac-desktop- Installed: the OS web engine + a C toolchain (the native host links a small
libwebview.so, built on first use). On Debian/Ubuntu:sudo apt-get install -y build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev- No Rust toolchain required.
1. Configure the window#
Add a [plugins.desktop] section to your jac.toml (all fields optional):
[plugins.desktop]
name = "my-app"
[plugins.desktop.window]
title = "My App"
width = 1000
height = 700
There is no jac setup desktop step - the native host is generated at build time.
2. Build the desktop app#
This:
- builds your
clcodespace with the standard Vite pipeline (.jac/client/dist/), - generates a native host that embeds CPython to serve that bundle on a loopback port and renders it in the OS webview,
- compiles the host with
jac nacompileinto a single binary.
The output lands in .jac/client/desktop/:
.jac/client/desktop/
my-app # the native binary
dist/ # the served cl bundle
libwebview.so # the OS-webview wrapper (resolved via $ORIGIN)
The directory is relocatable - the binary finds its sibling dist/ and
libwebview.so relative to itself.
3. Run it#
Or run the built binary directly:
A native window opens showing your cl UI, served in-process - no localhost you
manage, no second process.
How it differs from the web target#
| web | desktop | |
|---|---|---|
| output | bundle served by a host you run | one self-contained binary |
| UI runtime | a browser you point at the server | the OS-native webview |
| backend transport | HTTP to a remote server | embedded CPython, in-process |
The same cl/sv source builds for both - only the target changes.