How building works
pyzed build turns a Pyzed project into an app that runs on machines without Python:
a folder (or a single file) with the Python interpreter, pyzed-ui, your dependencies, your backend code and your
forms. It uses PyInstaller or Nuitka underneath, run inside the
project’s own environment, so nothing has to be installed globally.
Build in three steps
Section titled “Build in three steps”-
Open a terminal in the project folder (the one with
pyproject.toml). If you do not have a project yet, create one withpyzed new(see Create a project). -
Run the build:
Windows, macOS and Linux uv run pyzed buildmacOS and Linux source .venv/bin/activatepyzed build # or: python -m pyzed buildWindows (PowerShell) .venv\Scripts\Activate.ps1pyzed build # or: py -m pyzed build -
Open the result in
dist/:Platform Result Run it Windows dist\Shop\Shop.exe(ordist\Shop.exewith--onefile)Double-click it. macOS dist/Shop.appOpen it from Finder. Linux dist/Shop/Shop(ordist/Shopwith--onefile)./dist/Shop/Shop
What happens when you build
Section titled “What happens when you build”flowchart LR
P["pyproject.toml<br/>[tool.pyzed.build]"] --> B["pyzed build"]
U["ui/ forms + handlers"] --> B
S["src/ backend"] --> B
B --> L["launcher script<br/>build/pyzed/"]
B --> I["PyInstaller or Nuitka<br/>(uv run --with ...)"]
L --> I
I --> D["dist/<br/>app folder, .exe or .app"]
pyzed buildreads[tool.pyzed]and[tool.pyzed.build]frompyproject.toml.- It writes a small launcher (
build/pyzed/<name>_launcher.py) that opens the main form ([tool.pyzed] main) from inside the bundle, with the window icon and the splash screen. - It makes the icon files (
.ico,.icns,.png) from your picture, and the Windows version file. - It scans the
.pyfiles of the UI folder with Python’sastmodule to find what they import. - It runs the backend in the project’s environment with the right options, and the app lands in
dist/. Last, it fills in the macOSInfo.plistand packs the archive, if you asked for one.
What gets bundled
Section titled “What gets bundled”Forms load their handlers from .py files when they open, so PyInstaller cannot follow their imports by itself.
pyzed build fills the gaps:
- The UI folder is shipped as data, keeping its layout (
ui/main.pzform,ui/main.py, helper modules…). - Every module imported by the UI is passed as a hidden import, so the backend in
src/, modules next topyproject.tomland third-party packages are bundled with all their dependencies. - The project’s own package (
src/<name>) is collected whole, including modules imported dynamically. - Pyzed itself with its data files (themes, icons, templates).
ui/,src/and the project root are added to PyInstaller’s search path.
Code outside the project, such as a shared library in a sibling folder, is bundled like any other dependency once it is installed in the environment:
pyzed add --editable ../billing # then `from billing import invoice` in a handlerOptions
Section titled “Options”Command line flags win over [tool.pyzed.build] in pyproject.toml:
| Flag | Effect |
|---|---|
--backend nuitka |
Build with Nuitka instead of PyInstaller (see Choosing a backend). |
--onefile |
A single executable instead of a folder. |
--console |
Keep the console window (useful to see tracebacks). |
--name Shop |
Name of the executable or bundle. |
--archive zip |
Also pack the app in a zip (or tar.gz) named for the self-updater. |
--target linux-x64 |
Check that this computer builds that target (CI uses it); it does not cross-compile. |
--dry-run |
Print the backend’s command and stop. |
--workflow |
Write a GitHub Actions workflow for the project’s targets instead of building (see CI). |
All the settings of a project live in pyproject.toml (the Export dialog of Pyzed Design
writes the same table):
[tool.pyzed.build]name = "Shop" # executable / bundle name (default: the project name)backend = "pyinstaller" # or "nuitka"onefile = false # one self-extracting file instead of a folderwindowed = true # no console window on Windows and macOSicon = "assets/icon.png" # one square picture; .ico and .icns are made from itversion = "1.2.0" # default: [project] versionpublisher = "Acme" # the company in the Windows file propertiesidentifier = "com.acme.shop" # macOS bundle id and Linux app idcopyright = "© 2026 Acme"optimize = 0 # 1 drops asserts, 2 also docstringsslim = true # leave out tkinter, test, idlelib... (unused by Pyzed apps)exclude = ["numpy.tests"] # more modules to leave outinclude = ["assets", "data/prices.csv"] # extra files and folders, same relative path in the bundlehidden-imports = ["shop.plugins.csv"] # modules imported with importlib or by namecollect = ["babel"] # packages to ship whole: code, data files and binariesarchive = "zip" # also pack the app: shop-windows-x86_64.zip...targets = ["windows-x64", "macos-arm64", "linux-x64"] # what the CI workflow buildsargs = [] # anything else, passed to the backend as is
[tool.pyzed.build.splash]image = "assets/splash.png"duration = 1.5 # seconds, at leasttext = "Loading…"native = true # also the backend's own splash while the app unpacks
[tool.pyzed.build.nuitka] # options for one backend only ([tool.pyzed.build.pyinstaller] too)args = ["--lto=yes"]| Setting | Default | Notes |
|---|---|---|
name |
the project name | Also the name of the folder in dist/. |
backend |
"pyinstaller" |
"nuitka" compiles to C: see below. |
onefile |
false |
A single file starts slower (it unpacks itself each time) and cannot update itself. |
windowed |
true |
false (or --console) shows a terminal next to the app. |
icon |
none | A PNG becomes .ico, .icns and the window icon; see Icons and metadata. |
version, publisher, identifier, description, copyright |
from [project] |
Written into the Windows file properties and the macOS Info.plist. |
splash |
none | A splash screen. |
optimize |
0 |
Python’s -O (1) or -OO (2). |
slim |
true |
Leaves out tkinter, test, idlelib, lib2to3, ensurepip, pydoc_data, turtle and turtledemo. |
strip, upx |
true, false |
Strip debug symbols (Linux and macOS); compress with UPX if installed. |
exclude |
[] |
Modules to leave out. |
include |
[] |
Paths relative to the project root. |
hidden-imports |
[] |
Modules the backend cannot see. |
collect |
[] |
Packages with data files or plugins. |
archive |
none | "zip" or "tar.gz". |
targets |
this computer | For --workflow: windows-x64, windows-arm64, macos-arm64, macos-x64, linux-x64, linux-arm64. |
args |
[] |
Any other option of the backend (PyInstaller, Nuitka). |
Choosing a backend
Section titled “Choosing a backend”| PyInstaller | Nuitka | |
|---|---|---|
| How | Bundles your .py files as they are, next to Python |
Compiles your code and its dependencies to C, then to machine code |
| Build time | Seconds | Minutes |
| Speed | Normal Python | Compiled: faster pure-Python code |
| Size of a single file | Larger | About 15% smaller (better compression) |
| Size of a folder | Smaller | Larger (compiled code takes more room than .pyc) |
| Needs | Nothing (uv installs it) | A C compiler |
Measured with a one-form app on Linux x64: PyInstaller made a 57 MB folder or a 25 MB single file in about 25
seconds; Nuitka a 72 MB folder or a 21 MB single file in about 3 minutes. Most of the size is Pyzed’s engine (about
33 MB) and Python itself, so slim, exclude and strip matter more than the backend.
Start with PyInstaller while you develop, and try Nuitka for releases. Nuitka needs a C compiler: on Linux gcc
(sudo apt install gcc), on macOS the Xcode command line tools (xcode-select --install), on Windows Visual Studio’s
Build Tools, or Nuitka downloads a compiler the first time. pyzed build adds patchelf (Linux) and zstandard
(single files) itself.
Splash screen
Section titled “Splash screen”With [tool.pyzed.build.splash], the app opens a frameless window with your image, centered on the screen, before the
main form. It stays up for duration seconds at least and until the main form has loaded, then closes. text is
written over the bottom of the image. With native = true, the backend’s own splash also shows before Python starts
(PyInstaller on Windows and Linux, which needs tkinter in the project’s Python; Nuitka for single files on Windows),
useful for single-file apps that take a moment to unpack.
The same splash works in an app written in code, through App.splash:
import pyzed as pz
class Shop(pz.App): splash = pz.Splash("assets/splash.png", duration=1.5, text="Loading…")Finding your data files
Section titled “Finding your data files”Files listed in include end up under the bundle root, with the same relative path. Find them from your code with:
import sysfrom pathlib import Path
ROOT = Path(getattr(sys, "_MEIPASS", Path(__file__).resolve().parents[1]))prices = ROOT / "data" / "prices.csv"sys._MEIPASS is the bundle folder of a PyInstaller app. In a Nuitka app and while you develop, the fallback is used:
from a handler in ui/ it points at the bundle (or the project) root; adjust parents[1] to where the file that runs
this lives.
Where the files go
Section titled “Where the files go”The intermediate files go to build/pyzed/ and the result to dist/; both are in the .gitignore of new projects.
Each build starts clean, so delete nothing by hand.
Apps written in code
Section titled “Apps written in code”pyzed build starts the project’s main form. For an app written fully in code (a pz.App class and
pz.run(...)), run PyInstaller on your script yourself, collecting Pyzed’s data files. pyzed build --dry-run prints
the exact command pyzed build would run, which you can adapt:
uv run --with pyinstaller pyinstaller main.py --name Shop --windowed --collect-data pyzed --collect-submodules pyzedpython3 -m pip install pyinstallerpython3 -m PyInstaller main.py --name Shop --windowed --collect-data pyzed --collect-submodules pyzedpy -m pip install pyinstallerpy -m PyInstaller main.py --name Shop --windowed --collect-data pyzed --collect-submodules pyzedWith Nuitka:
uv run --with nuitka --with patchelf python -m nuitka --mode=standalone --include-package-data=pyzed main.py- Per platform: Windows, macOS, Linux.
- Build every platform in CI and ship updates.
- Something wrong? Troubleshooting.