Skip to main content

Publishing a Flet app

Flet CLI provides the flet build command to package a Flet app into a standalone executable or installable package for distribution.

Alternative: flet pack

For desktop targets, a PyInstaller-based route is also supported — see flet pack.

Prerequisites

Platform matrix

Use the following matrix to determine on which OS flet build can be run in order to target each platform:

Run onapk/aabipa/ios-simulatormacoslinuxwindowsweb
macOS
Windows✅ (WSL)
Linux

Flutter SDK

Flutter is required to build Flet apps for any platform.

If the minimum required version of the Flutter SDK is not already available in the system PATH, it will be automatically downloaded and installed (in the $HOME/flutter/{version} directory) during the first build process.

Tip

The recommended (minimum required) Flutter SDK version depends on the Flet version installed or in use.

It can be viewed by running one of the following commands:

flet --version
uv run python -c "import flet.version; print(flet.version.flutter_version)"

or the below Python code snippet:

import flet.version
print(flet.version.flutter_version)

Project structure

The flet build command assumes the following minimal Flet project structure:

README.md
pyproject.toml
src
assets
icon.png
main.py
Tip

To quickly set up a project with the correct structure, use the flet create command:

flet create <project-name>

Where <project-name> is the name for your project directory.

Using requirements.txt instead of pyproject.toml

Instead of a pyproject.toml file, you can also use a requirements.txt file to specify dependencies.

In this case, two things to keep in mind:

  • if both files are present, flet build will ignore requirements.txt.
  • don't use pip freeze > requirements.txt to generate this file or fill it with dependencies, as it may include packages incompatible with the target platform. Instead, hand-pick and include only the direct dependencies required by your app, including flet.

Choosing a Python version

flet build and flet publish bundle a specific Python release into your app. Supported versions and the matching CPython / Pyodide artifacts:

ShortCPython runtimePyodide (web)Status
3.143.14.7314.0.6default
3.133.13.150.29.4stable
3.123.12.140.27.7stable

The version is resolved in this order:

  1. --python-version X.Y — explicit override. Must match a supported short version (e.g. flet build apk --python-version 3.13).
  2. [project].requires-python in pyproject.toml — parsed as a PEP 440 specifier; the highest supported short version that satisfies it wins. requires-python = ">=3.13,<3.14" resolves to 3.13; requires-python = ">=3.13" resolves to 3.14.
  3. Default — the latest supported version (currently 3.14).

If neither the CLI flag nor requires-python selects a supported version (e.g. requires-python = ">=3.20"), the build fails with a clear error listing the versions you can choose from.

For web builds (flet build web / flet publish), the matching Pyodide runtime is downloaded into the build output and cached under ~/.flet/pyodide/<version>/ on first use, so subsequent builds reuse it. The older 0.27.5 bundle that used to ship inside the build template has been removed in favour of this per-build, versioned download.

Pre-release Python (3.15 etc.)

To bundle a pre-release Python, name it explicitly — --python-version 3.15 or requires-python = "==3.15.*". There is no separate --pre flag for this. The --pre flag on flet publish is a different, unrelated option for allowing micropip to install pre-release Python packages at runtime.

How it works

When you run flet build <target_platform>, the pipeline is:

  1. Create a Flutter project in {flet_app_directory}/build/flutter from the template. The Flutter app embeds your packaged Python app in its assets and uses flet and serious_python to run the app and render the UI. The project is cached and reused across builds for rapid iterations; run flet clean to delete the build directory and force a rebuild.
  2. Copy custom icons and splash images from assets into the Flutter project, then generate:
  3. Package the Python app using serious_python package:
  4. Run flutter build to produce the executable or installable package.
  5. Copy build outputs from Step 4 into the output directory.

How a built app terminates

A built Flet app terminates immediately. Python atexit handlers, __del__ finalizers, C++ static destructors, and buffered writes that have not yet reached the operating system are not guaranteed to run. Persist anything that matters before you exit, rather than relying on cleanup at shutdown.

This applies both when the user closes the app (desktop) and when your code calls sys.exit() (every platform).

The reason is that your Python code runs on its own thread alongside Flutter. A normal process exit runs the teardown of every loaded library - including the C extension modules imported by packages like matplotlib, numpy and Pillow - while that thread may still be executing inside one of them. The result was a segfault on exit, reported as a crash by the operating system even though the app had finished its work. Skipping the teardown removes the failure entirely, at the cost of the guarantees above.

If you need cleanup to run, do it explicitly before exiting. When your own code ends the app, finish your work first:

async def quit(e):
await save_my_state() # finish your own work first
sys.exit(0)

On desktop the user can also close the window, which the operating system initiates - your code is never asked. To get a chance to run first, intercept the close signal with Window.prevent_close and destroy the window yourself once you are done:

import flet as ft


async def main(page: ft.Page):
async def handle_window_event(e: ft.WindowEvent):
if e.type == ft.WindowEventType.CLOSE:
await save_my_state() # runs before the process goes away
await page.window.destroy()

page.window.prevent_close = True
page.window.on_event = handle_window_event

Without prevent_close, the window close goes straight through to process termination and nothing of yours runs. Keep the handler quick: it holds up the app's exit, and the OS may lose patience with an app that takes too long to quit.

Configuration options

Placeholders

Throughout this documentation, the following placeholders are used:

  • <target_platform> - one of: apk, aab, ipa, ios-simulator, web, macos, windows, linux.
  • <PLATFORM> - the config namespace under [tool.flet.<PLATFORM>]; one of: android (for apk and aab targets), ios (for ipa and ios-simulator targets), web, macos, windows, linux.
  • <python_app_path> - the path passed to flet build (defaults to the current directory).
  • <flet_app_directory> - the resolved project root for <python_app_path>; pyproject.toml and requirements.txt are read from here.
  • <flet_version> - the version of Flet in use. You can check with flet --version or uv run python -c "import flet; print(flet.__version__)".
Understanding pyproject.toml structure

Flet loads pyproject.toml as a nested dictionary and looks up settings using dot-separated paths (for example, tool.flet.web.base_url).

The two forms below are equivalent and resolve to the same key-value pair:

  • Form 1 (will be used/preferred throughout this documentation)

    [tool.flet.section]
    key = "value"
  • Form 2

    [tool.flet]
    section.key = "value"

But they are different or should not be confused with the below ("quoted keys" are literals and do not create nesting):

[tool.flet]
"section.key" = "value"

App path

Defines the root directory of your Python app within <python_app_path>. Flet looks for the entry point, the assets directory, and exclude paths relative to this directory.

Resolution order

Its value is determined in the following order of precedence:

  1. [tool.flet.app].path
  2. <python_app_path>

path is resolved relative to <python_app_path>.

Example

[tool.flet.app]
path = "src"

Entry point

This is the Python module that starts your app and contains the call to flet.run() or flet.render(). Flet uses the module stem and looks for <module>.py in your app path.

Resolution order

Its value is determined in the following order of precedence:

  1. --module-name
  2. [tool.flet.app].module
  3. "main" (entry file main.py)

Its value can either be <module> or <module>.py; both resolve to the same Python module.

Example

flet build <target_platform> --module-name app.py

Project name

The project name is the base identifier for bundle IDs and other internal names. The source value is normalized to a safe identifier: lowercased, punctuation and spaces removed or collapsed, and hyphens converted to underscores (for example, My App or my-app becomes my_app).

Resolution order

Its value is determined in the following order of precedence:

  1. --project
  2. [project].name
  3. project/app directory name

Example

flet build <target_platform> --project my_app

Product name

The display (user-facing) name shown in window titles, launcher labels, and about dialogs.

It does not control the on-disk executable or bundle name. Use the artifact name for artifact naming.

Resolution order

Its value is determined in the following order of precedence:

  1. --product
  2. [tool.flet].product
  3. --project
  4. [project].name
  5. project/app directory name

Example

flet build <target_platform> --product "My Awesome App"

Artifact name

The on-disk name for executables and/or app bundles. For example, on Windows it determines the name of the .exe file, and on macOS it sets the name of the .app bundle.

It does not affect bundle IDs or package identifiers.

It can contain spaces or accents, but keep file system restrictions in mind on your target platforms.

Resolution order

Its value is determined in the following order of precedence:

  1. --artifact
  2. [tool.flet.<PLATFORM>].artifact
  3. --project
  4. [project].name
  5. project/app directory name

Example

flet build <target_platform> --artifact "My Awesome App"

Organization name

Platform support

Android, iOS, macOS, and Linux only.

The organization name in reverse domain name notation, typically in the form com.mycompany. It is used as the prefix for the bundle ID and for package identifiers on mobile and desktop targets.

Resolution order

Its value is determined in the following order of precedence:

  1. --org
  2. [tool.flet.<PLATFORM>].org
  3. [tool.flet].org
  4. "com.flet"

Example

flet build <target_platform> --org com.mycompany

Bundle ID

Platform support

Android, iOS, macOS, and Linux only.

The bundle ID for the application, typically in the form "com.mycompany.my_app".

If not explicitly specified, it is derived from the organization name and the project name used by the build template.

Resolution order

Its value is determined in the following order of precedence:

  1. --bundle-id
  2. [tool.flet.<PLATFORM>].bundle_id
  3. [tool.flet].bundle_id

Example

flet build <target_platform> --bundle-id com.mycompany.my_app

Description

Platform support

Web and Linux only.

A short description of the application. On web builds it becomes the <meta name="description"> tag and the PWA manifest's description; on Linux it becomes the Comment of the generated desktop entry, shown as a tooltip in application menus. Other platforms have no equivalent field and ignore it.

Resolution order

Its value is determined in the following order of precedence:

  1. --description
  2. [project].description
  3. [tool.poetry].description

Example

flet build <target_platform> --description "Tracks your daily habits."

Company Name

Platform support

Windows and macOS only.

The company name displayed in about app dialogs and metadata (notably on desktop builds).

Resolution order

Its value is determined in the following order of precedence:

  1. --company
  2. [tool.flet].company
  3. Build template default (see Template Source)

Example

flet build <target_platform> --company "My Company Inc."
Platform support

Windows and macOS only.

Copyright text displayed in about app dialogs and metadata.

Resolution order

Its value is determined in the following order of precedence:

  1. --copyright
  2. [tool.flet].copyright
  3. Build template default (see Template Source)

Example

flet build <target_platform> --copyright "Copyright © 2026 My Company Inc."

Versioning

Build Number

An integer identifier used internally to distinguish one build from another.

Each new build must have a unique, incrementing number; higher numbers indicate more recent builds.

Resolution order

Its value is determined in the following order of precedence:

  1. --build-number
  2. [tool.flet].build_number
  3. Otherwise, the build number from the generated pubspec.yaml (see Template Source) will be used.
Example
flet build <target_platform> --build-number 1

Build Version

A user‑facing version string in x.y.z format. Increment this for each new release to differentiate it from previous versions.

Resolution order

Its value is determined in the following order of precedence:

  1. --build-version
  2. [project].version
  3. [tool.poetry].version
  4. Otherwise, the build version from the generated pubspec.yaml (see Template Source) will be used.
Example
flet build <target_platform> --build-version 1.0.0

Output directory

The directory where the build output is saved. If the directory already exists, it is deleted and recreated on each build.

For web builds, the app's assets directory is copied into the output directory.

Resolution order

Its value is determined in the following order of precedence:

  1. --output (or -o)
  2. <python_app_path>/build/<target_platform>

Example

flet build <target_platform> --output <path-to-output-dir>

App dependencies

These are the Python packages that your Flet app depends on to function correctly.

Resolution order

Its value is determined in the following order of precedence:

  • [tool.poetry].dependencies if present; otherwise [project].dependencies (PEP 621).
  • If [tool.flet.<PLATFORM>].dependencies is set (where <PLATFORM> corresponds to <target_platform>), its values are appended to the list above.
  • If the result of all above is empty and requirements.txt exists in <python_app_path>, it is used.
  • If the result of all the above is empty, flet==<flet_version> is used.

To use a local development version of a dependency during builds, configure [tool.flet].dev_packages or [tool.flet.<PLATFORM>].dev_packages with a package name to path mapping.

If your app uses Flet extensions (third-party packages), list them in your Python dependencies so they are packaged with the app. Examples of extensions can be found in Built-in extensions.

Example

[project]
dependencies = [
"flet",
"requests",
"flet-extension1",
"flet-extension2 @ git+https://github.com/account/flet-extension2.git", # git repo
"flet-extension3 @ file:///path/to/flet-extension3", # local package
]

[tool.flet.<PLATFORM>] # will be used/appended only if <PLATFORM> corresponds to <target_platform>
dependencies = [
"dep1",
"dep2",
]

Source packages

Platform support

Android, iOS, and Web only.

By default, packaging for mobile and web only installs binary wheels. Use source packages to allow specific dependencies to be installed from source distributions (sdists).

This can be useful for installing - pure Python - dependencies that do not have pre-built wheels for the target platform or an all-platform wheel (*-py3-none-any.whl), but instead provide a source distribution (*.tar.gz).

For more information on pure vs non-pure Python packages, see our blog post on the topic.

On desktop targets, source installs are already allowed, so this setting is mainly useful for Android, iOS, and Web (flet build web only — not flet publish).

Resolution order

Its value is determined in the following order of precedence:

  1. --source-packages
  2. [tool.flet.<PLATFORM>].source_packages
  3. [tool.flet].source_packages

Example

flet build <target_platform> --source-packages package1 package2

Icons

For most apps, you only need one image. Save it as icon.png in your app's assets directory (src/assets/icon.png in the project structure above). When you run flet build, Flet generates the icons needed for your target platform.

For the best results, use:

  • A 1024 × 1024 pixel PNG, large enough for every supported platform.
  • A transparent background, so Flet can add the background each platform needs.
  • Artwork that fills the canvas, without extra margins. Flet adds space around it where needed; margins in the source can make small icons look too small.

Here is how the same transparent image looks across platforms:

Web, Windows, LinuxiOSmacOSAndroidMaskable web icon
Artwork filling the icon
Artwork inset on an iOS icon
Artwork on a macOS tile with a shadow
Artwork inside a circular Android icon
Artwork inside a maskable web icon's safe area
Original marginsAdded margins and backgroundRounded tile and shadowSpace for the launcher's shapeSpace for the launcher's shape

A maskable web icon is used when a browser installs your app and the launcher applies its own icon shape, such as a circle or rounded square.

If you do not supply an icon, Flet uses the default Flet icons.

Background colour

To put your transparent artwork on a colour other than white, set icon_background in pyproject.toml:

[tool.flet]
icon_background = "#1a1a2e"

Flet uses this colour for iOS icons, the macOS tile, Android's adaptive icon background, and the web's maskable and Apple touch icons. Favicons, regular web icons, Windows icons, and Linux icons keep their transparency.

You can override the colour for one platform:

[tool.flet.macos]
icon_background = "#000000"

On Android, an explicit adaptive_icon_background takes precedence over icon_background. To show the background colour, keep your source artwork transparent; a fully opaque image covers it.

What a padded or opaque source does

Flet adjusts the space around transparent artwork in icon.png to suit each platform. This is called framing. It can shrink the artwork within its canvas, but does not enlarge it to remove existing margins.

The examples below show why a transparent image without extra padding is a good starting point:

Your sourceWeb, Windows, LinuxiOSAndroid
No extra padding
Transparent source with artwork filling the canvas
Artwork filling the icon
Artwork fitted for iOS
Artwork fitted for Android
Padded
Source with extra transparent margins
Small icon retaining the source margins
Padded artwork fitted for iOS
Padded artwork fitted for Android
Opaque
Source with its own solid background
Artwork and background filling a square icon
Artwork and background with rounded iOS corners
Artwork and background cropped to an Android circle

With padded artwork, the existing margins remain on web, Windows, and Linux. Flet adds more space on other platforms only if needed. If your artwork is already smaller than the target framing, it stays that small.

With opaque artwork (an image with no transparency), Flet preserves your composition, including its background. The platform can still hide the edges when it applies its icon shape, so keep logos and text near the centre. Use this approach when you want your design's own background to reach the edges.

Image sizes and formats

A 1024 × 1024 image covers the largest icons Flet generates. Larger images work too. Smaller images may look blurry when enlarged; flet build warns if your source is smaller than the largest icon needed for the target platform.

PNG is preferred. Flet also accepts .webp, .jpg, .jpeg, .gif, .bmp, .tif, and .tiff. If several files have the same base name, Flet prefers PNG. SVG is not supported; export it to PNG before building.

A non-square image is centred on a square canvas without stretching or cropping the source. The extra space is filled with icon_background for opaque outputs and left transparent elsewhere:

Your 1024 × 600 sourceiOS, opaque web icons, macOS tileFavicon, regular web icons, Windows, Linux, Android foreground
A wide source image
Extra space filled with the background colour
Extra space left transparent

The background is shown dark here to make the fill visible. On Android, the separate background layer shows through the transparent space. Flet warns about non-square sources; use a square image if you want to choose the margins yourself.

Making an icon for one platform

To use different artwork or control the margins for one platform, add a file with that platform's name to assets, such as icon_macos.png. It takes precedence over icon.png for that platform; other platforms still use icon.png.

Platform-specific files keep the margins you supply. Flet still generates the required image sizes and applies platform treatments, such as removing transparency on iOS or adding the macOS tile, but it does not automatically fit your artwork inside the platform's visible area.

icon.png: Flet adds marginsicon_macos.png: you choose the margins
Shared artwork fitted inside the macOS tile
Platform-specific artwork filling the macOS tile

Use these sizes and framing guidelines when preparing your own files. The percentages match Flet's automatic framing for transparent artwork:

FileRecommended sizeArtwork placement
icon_ios.png1024 × 1024Within the central 60% of the canvas width and height.
icon_macos.png1024 × 1024Within the central 68% of the canvas width and height, before Flet places it on the tile.
icon_android.png1024 × 1024Inside a centred circle with a diameter of about 57% of the canvas width.
icon_web.png1024 × 1024Inside a centred circle with a diameter of 80% of the canvas width for maskable icons.
icon_windows.png or icon_windows.ico256 × 256 or largerCan fill the canvas.
icon_linux.png512 × 512 or largerCan fill the canvas.

For the circular areas, keep the entire logo inside the circle, including its corners. A logo that fits a square of the same width can still extend outside it.

On Android, use transparency to let icon_background show through. An opaque icon_android.png is also supported, but its own background covers that colour.

On web, icon_web.png supplies all six icons, including maskable and Apple touch icons. Flet warns if transparent artwork extends outside the maskable safe area. To give these icons different margins, replace them individually.

Replacing a web icon directly

To replace just one web icon, put a PNG at the matching path inside assets. For example, assets/favicon.png changes the browser tab icon. These files replace the generated icons as-is, without resizing or adding margins.

Path inside assetsUsed forDisplay shape
favicon.pngBrowser tabNo mask
icons/Icon-192.png, icons/Icon-512.pngInstalled app, splash, task switcherNo mask
icons/Icon-maskable-192.png, icons/Icon-maskable-512.pngLaunchers that use maskable iconsChosen by the launcher
icons/apple-touch-icon-192.pngiOS "Add to Home Screen"Rounded corners

Use the pixel size in the filename where one is given. For maskable icons, use an opaque background and keep important artwork inside the safe area: a centred circle with a diameter of 80% of the image width.

Artwork inside the safe areaArtwork at risk of being cropped
Artwork inside the maskable safe area
Artwork extending outside the maskable safe area

The faded area outside the circle may be hidden by the launcher's shape.

Splash screen

Platform support

Android, iOS, and Web only.

The splash screen shows your app's artwork on a background colour while the app starts. By default, Flet uses icon.png from your app's assets directory, or the default Flet icon if you have not supplied one.

To use different artwork, save it as splash.png in assets (src/assets/splash.png in the project structure above). A square PNG with a transparent background is a good starting point. Flet centres the artwork on the screen and generates the image sizes needed for your target platform when you run flet build.

Splash background colors

Set the colour that fills the screen in pyproject.toml. Use color for light mode and dark_color for dark mode:

[tool.flet.splash]
color = "#ffffff"
dark_color = "#222222"

These are also the default colours. To give one platform a different background, use its own splash section:

[tool.flet.android.splash]
color = "#112233"
dark_color = "#080f17"

Command-line options --splash-color and --splash-dark-color take precedence over platform settings, followed by shared settings, then the defaults.

For dark mode, also supply a dark splash image, as described below.

Splash images

Use splash.png for shared artwork and splash_dark.png for a dark-mode version. You can override either for one platform by adding its name, such as splash_android.png or splash_dark_android.png.

Flet selects the first available image in this order. Replace <platform> with android, ios, or web:

ModeFirst choiceSecond choiceFallback
Lightsplash_<platform>.pngsplash.pngicon.png, then the default Flet icon
Darksplash_dark_<platform>.pngsplash_dark.pngThe selected light-mode image

The same image formats as app icons are supported; PNG is preferred. A non-square image is centred on a square canvas with transparent padding, without stretching or cropping the source.

Using the same artwork in both themes

If you only want to change the background in dark mode, save the same artwork as splash_dark.png. iOS needs a dark splash image to apply dark_color, and Android 12 needs one to apply icon_dark_background.

Sizing and framing

On iOS, web, and Android versions before 12, the image is displayed at a quarter of its source dimensions: a 1024 × 1024 image occupies 256 × 256 logical pixels on screen. Use a smaller image or add transparent margins to make the logo appear smaller.

Flet treats a supplied splash image and a fallback app icon differently:

SourceHow Flet places the artwork
splash.png or splash_<platform>.pngPreserves the margins you supply.
Fallback icon.pngShrinks transparent artwork, if needed, to fit within the central 60% of the canvas width and height.

Existing margins are preserved; small artwork is not enlarged to fill that 60% area. Android 12 and later use a fixed icon area with additional fitting, so source dimensions do not directly control the displayed size there.

Opaque artwork

An image with no transparency keeps its own background and composition. It is still resized to generate the required image sizes, but Flet does not add the margins it would add to a transparent logo.

On Android 12, the system's circular crop can hide the edges of this artwork. Keep important details inside a centred circle with a diameter of two thirds of the canvas width. Use a transparent image when you want the configured splash background to show around your logo.

How the Android splash is composed

The screen background and the artwork are separate layers. Before Android 12, the artwork appears centred over the background. Android 12 and later display it in a circular area, with an optional colour behind the icon:

Screen backgroundBefore Android 12Android 12+Android 12+ with icon background
The splash screen background colour
Artwork centred over the screen background
Artwork in Android 12's circular icon area
Artwork on a coloured disc over the screen background
color or dark_colorArtwork at its splash sizeArtwork fitted for the circular cropicon_background fills the disc

Use color to change the whole screen, and icon_background to add the coloured disc shown in the last example. Choose a disc colour that contrasts with your artwork so the logo remains visible.

Android 12 splash icon

These settings control the splash icon on Android 12 and later. Put them under [tool.flet.android.splash], or under [tool.flet.splash] as shared defaults:

SettingDefaultEffect
icon_backgroundNo fillAdds a background colour behind the splash icon.
icon_dark_backgroundSame as icon_backgroundSets the icon background for dark mode; requires a dark splash image.
icon_fit"contain"Reduces transparent artwork when needed to leave room for the circular crop. Use "none" to control the margins yourself.
[tool.flet.android.splash]
color = "#112233"
icon_background = "#ffffff"
icon_fit = "contain"

Here, color fills the screen and icon_background adds a white disc behind the logo. This splash setting is separate from the icon_background used for app icons.

Android uses a smaller icon area when an icon background is set. Flet generates the appropriate canvas automatically; see Android's splash screen dimensions if you are preparing artwork to fit it manually.

With icon_fit = "none", Flet preserves your artwork's margins and warns when it detects transparent artwork extending beyond the central area. Keep important details inside the central circle; the system can crop anything outside it.

Renamed in 1.0.0

icon_background, icon_dark_background, and icon_fit replace icon_bgcolor, icon_dark_bgcolor, and android_12_fit, respectively. The old names are still accepted.

Disabling splash screens

To turn off Flet's splash customization for a platform, set its value to false:

[tool.flet.splash]
android = false
ios = false
web = false

Set only the platforms you want to disable; the others remain enabled. The command-line flags --no-android-splash, --no-ios-splash, and --no-web-splash take precedence over the corresponding settings in pyproject.toml.

To customize what appears after Flutter starts while your Python app loads, see Boot screen.

Boot screen

The boot screen fills the gap between the native splash screen and your app's first frame — that is, while the Flutter app is up but your Flet/Python app is not ready yet. It is told which of two stages it is in:

  1. Preparing — the packaged app archive (app.zip) is being extracted to the app data directory (on first launch or after the app bundle changes). This stage occurs on Android only.
  2. Starting up — the Python runtime and your app are starting, until the first page is shown (all platforms). If startup fails, the error is shown on the boot screen.

A boot screen is always rendered, so this gap is a controlled background instead of a bare scaffold. By default the built-in flet boot screen shows nothing but a background color — no spinner and no message — until you configure it. You can also replace it entirely with your own widget (see Custom boot screen).

Selecting a boot screen

A boot screen is addressed by name. The default is flet (the built-in screen); custom names are provided by extensions.

[tool.flet.boot_screen] # or [tool.flet.<PLATFORM>.boot_screen]
name = "flet"

Settings under [tool.flet.<PLATFORM>.boot_screen] override the global [tool.flet.boot_screen] per key.

Built-in flet boot screen

The built-in screen is configured under a table named after it. All options are optional:

[tool.flet.boot_screen.flet]
theme_mode = "auto" # auto (default), light, or dark
bgcolor_light = "#ffffff"
bgcolor_dark = "#000000"
spinner_color_light = "blue"
spinner_color_dark = "yellow"
spinner_size = 30 # 0 or absent → no spinner
text_color_light = "#000000"
text_color_dark = "#ffffff"
prepare_message = "Preparing your app…" # Android only; empty/absent → no message
startup_message = "Starting up…" # empty/absent → no message
OptionDescription
theme_modeWhich color set to use: auto (follow the device), light, or dark. Defaults to auto.
bgcolor_light / bgcolor_darkBackground color. When omitted, follows Flet's default theme background.
spinner_color_light / spinner_color_darkSpinner color. When omitted, follows Flet's default theme primary color.
spinner_sizeSpinner diameter in logical pixels. 0 or absent hides the spinner.
text_color_light / text_color_darkMessage text color. When omitted, follows Flet's default theme on-surface color.
prepare_messageText shown during the preparing stage (Android only). Empty or absent shows no message.
startup_messageText shown during the starting up stage. Empty or absent shows no message.
fade_out_durationFade-out duration in milliseconds when the app becomes ready. Defaults to 0 (removed instantly); set a value like 300 to fade out.

Colors accept the same formats as elsewhere in Flet (hex like #ffffff or named colors like blue).

Custom boot screen

To take full control of the boot screen — including custom layouts and animations — provide your own Flutter widget from a Flet extension and reference it by name. See Boot screen in the extension authoring guide for how to implement one.

[tool.flet.boot_screen]
name = "my_screen"

[tool.flet.boot_screen.my_screen]
# arbitrary options passed to your widget
Deprecated

The older [tool.flet.app.boot_screen] and [tool.flet.app.startup_screen] settings (with show / message) are deprecated. They are still honored — and mapped onto the built-in flet boot screen — but you should migrate to [tool.flet.boot_screen].

Hidden app window on startup

Platform support

Windows, macOS, and Linux only.

A Flet desktop app (Windows, macOS, or Linux) can start with its window hidden. This lets your app perform initial setup (for example, add content, resize or position the window) before showing it to the user.

See this code example.

Resolution order

Its value is determined in the following order of precedence:

  • [tool.flet.<PLATFORM>.app].hide_window_on_start, where <PLATFORM> can be windows, macos or linux
  • [tool.flet.app].hide_window_on_start
  • FLET_HIDE_WINDOW_ON_START

Example

[tool.flet.app] # or [tool.flet.<PLATFORM>.app]
hide_window_on_start = true

Deep linking

Platform support

Android and iOS only.

Deep linking allows users to navigate directly to specific content within a mobile app using a URI (Uniform Resource Identifier). Instead of opening the app's homepage, deep links direct users to a specific page, feature, or content within the app, enhancing user experience and engagement.

  • Scheme: deep linking URL scheme, e.g. "https" or "myapp".
  • Host: deep linking URL host.

See also:

Resolution order

Its value is determined in the following order of precedence:

  1. --deep-linking-scheme and --deep-linking-host (only when both are provided)
  2. [tool.flet.<PLATFORM>.deep_linking].scheme / [tool.flet.<PLATFORM>.deep_linking].host, where <PLATFORM> can be android or ios
  3. [tool.flet.deep_linking].scheme / [tool.flet.deep_linking].host

Both scheme and host are required; if either is missing, the deep-linking entries are not added.

Example

flet build <target_platform> \
--deep-linking-scheme "https" \
--deep-linking-host "mydomain.com"
Template translation

In the Android AndroidManifest.xml, the pyproject.toml example above will be translated accordingly into this:

<meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" android:host="mydomain.com" />
</intent-filter>

In the iOS ios/Runner/Info.plist, the pyproject.toml example above will be translated accordingly into this:

<key>FlutterDeepLinkingEnabled</key>
<true />
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>mydomain.com</string>
<key>CFBundleURLSchemes</key>
<array>
<string>https</string>
</array>
</dict>
</array>

Target Architecture

Platform support

Android and macOS only.

A target platform can have different CPU architectures, which in turn support different instruction sets.

It is possible to build your app for specific CPU architectures. This is useful for reducing the size of the resulting binary or package, or for targeting specific devices.

For more/complementary information, see the specific platform guides: Android, macOS.

Resolution order

Its value is determined in the following order of precedence:

  1. --arch
  2. [tool.flet.<PLATFORM>].target_arch, where <PLATFORM> can be android or macos
  3. [tool.flet].target_arch
  4. Platform defaults for the <target_platform>

Example

flet build macos --arch arm64 x86_64

Excluding files and directories

Files and/or directories can be excluded from the build process. This can be useful for reducing the size of the resulting binary or package.

Resolution order

Its value is determined in the following order of precedence:

  1. --exclude (can be used multiple times)
  2. [tool.flet.<PLATFORM>.app].exclude (type: list of strings)
  3. [tool.flet.app].exclude (type: list of strings)

The files and/or directories specified should be provided as relative paths to the app path directory. Paths are matched exactly (no globs), and directories are excluded recursively.

By default, the build directory is always excluded. Additionally, when the target_platform is web, the assets directory is always excluded.

Example

flet build <target_platform> --exclude .git .venv

Compilation and cleanup

Flet can compile your app's .py files and/or installed packages' .py files into .pyc files during the packaging process (via python -m compileall -b). Cleanup removes known junk files and any additional globs you specify.

  1. Compilation:

    • compile-app: compile app's .py files
    • compile-packages: compile site/installed packages' .py files
  2. Cleanup:

    • cleanup-app: remove junk files from the app directory
    • cleanup-app-files: additional globs to delete from the app directory (implies cleanup-app)
    • cleanup-package-files: additional globs to delete from site-packages (implies cleanup-packages)
    • cleanup-packages: remove junk files from site-packages (defaults to true)

By default, Flet compiles both your app and the installed packages to .pyc during packaging. Shipping bytecode avoids recompiling every module on each cold start — a significant startup win on mobile, where pure Python is imported from a stored zip and cannot cache bytecode back to disk.

Pass --no-compile-app / --no-compile-packages (or set [tool.flet.compile].app / [tool.flet.compile].packages to false) to disable it — for example to speed up iterative builds, or to keep .py source in the bundle so the build still completes with syntax errors present and tracebacks show source lines.

Resolution order

The values of compile-app and cleanup-app are respectively determined in the following order of precedence:

  1. --compile-app / --cleanup-app
  2. [tool.flet.<PLATFORM>.compile].app / [tool.flet.<PLATFORM>.cleanup].app
  3. [tool.flet.compile].app / [tool.flet.cleanup].app
  4. True / empty list

The values of compile-packages and cleanup-packages are respectively determined in the following order of precedence:

  1. --compile-packages / --cleanup-packages
  2. [tool.flet.<PLATFORM>.compile].packages / [tool.flet.<PLATFORM>.cleanup].packages
  3. [tool.flet.compile].packages / [tool.flet.cleanup].packages
  4. True / True

The values of cleanup-app-files and cleanup-package-files are respectively determined in the following order of precedence:

  1. --cleanup-app-files / --cleanup-package-files
  2. [tool.flet.<PLATFORM>.cleanup].app_files / [tool.flet.<PLATFORM>.cleanup].package_files
  3. [tool.flet.cleanup].app_files / [tool.flet.cleanup].package_files
  4. False / False

Example

flet build <target_platform> \
--compile-app --compile-packages \
--cleanup-app-files "**/*.c" "**/*.h" --cleanup-package-files "**/*.pyi"

Permissions

Platform support

Android, iOS, and macOS only.

flet build allows granular control over permissions, features, and entitlements embedded into AndroidManifest.xml, Info.plist and .entitlements files.

See platform guides for setting specific iOS, Android and macOS permissions.

Predefined cross-platform permission bundles

Cross-platform permissions are named and predefined bundles that apply a baseline set of platform-specific entries required for a feature. Each bundle expands into the corresponding platform-specific equivalents. This is especially useful for beginners who may be unfamiliar with the underlying platform APIs or prefer not to interact with them directly.

Only the bundles you list are applied. If you need different wording or extra entries, set the platform-specific tables directly; those values are merged on top and can override the bundle defaults. The examples below show the exact pyproject.toml equivalents for each bundle.

Below is a list of available bundles:

  • location

    pyproject.toml equivalent
    # iOS
    [tool.flet.ios.info]
    NSLocationWhenInUseUsageDescription = "This app uses location service when in use."
    NSLocationAlwaysAndWhenInUseUsageDescription = "This app uses location service."

    # macOS
    [tool.flet.macos.info]
    NSLocationUsageDescription = "This app needs access to your location."

    [tool.flet.macos.entitlement]
    "com.apple.security.personal-information.location" = true

    # Android
    [tool.flet.android.permission]
    "android.permission.ACCESS_FINE_LOCATION" = true
    "android.permission.ACCESS_COARSE_LOCATION" = true
    "android.permission.ACCESS_BACKGROUND_LOCATION" = true

    [tool.flet.android.feature]
    "android.hardware.location.network" = false
    "android.hardware.location.gps" = false
  • camera

    pyproject.toml equivalent
    # iOS
    [tool.flet.ios.info]
    NSCameraUsageDescription = "This app uses the camera to capture photos and videos."

    # macOS
    [tool.flet.macos.info]
    NSCameraUsageDescription = "This app uses the camera to capture photos and videos."

    [tool.flet.macos.entitlement]
    "com.apple.security.device.camera" = true

    # Android
    [tool.flet.android.permission]
    "android.permission.CAMERA" = true

    [tool.flet.android.feature]
    "android.hardware.camera" = false
    "android.hardware.camera.any" = false
    "android.hardware.camera.front" = false
    "android.hardware.camera.external" = false
    "android.hardware.camera.autofocus" = false
  • microphone

    pyproject.toml equivalent
    # iOS
    [tool.flet.ios.info]
    NSMicrophoneUsageDescription = "This app uses microphone to record sounds."

    # macOS
    [tool.flet.macos.info]
    NSMicrophoneUsageDescription = "This app uses microphone to record sounds."

    [tool.flet.macos.entitlement]
    "com.apple.security.device.audio-input" = true

    # Android
    [tool.flet.android.permission]
    "android.permission.RECORD_AUDIO" = true
  • photo_library

    pyproject.toml equivalent
    # iOS
    [tool.flet.ios.info]
    NSPhotoLibraryUsageDescription = "This app saves photos and videos to the photo library."

    # macOS
    [tool.flet.macos.info]
    NSPhotoLibraryUsageDescription = "This app saves photos and videos to the photo library."

    [tool.flet.macos.entitlement]
    "com.apple.security.personal-information.photos-library" = true

    # Android
    [tool.flet.android.permission]
    "android.permission.READ_MEDIA_VISUAL_USER_SELECTED" = true
  • biometric

    pyproject.toml equivalent
    # iOS
    [tool.flet.ios.info]
    NSFaceIDUsageDescription = "This app uses biometrics to authenticate you."

    # macOS
    [tool.flet.macos.info]
    NSFaceIDUsageDescription = "This app uses biometrics to authenticate you."
Resolution order

Its value is determined in the following order of precedence:

  1. --permissions
  2. [tool.flet].permissions (type: list of strings)
  3. []
Example
flet build <target_platform> --permissions location microphone

Build template

flet build creates (and reuses) a Flutter project under <app_root>/build/flutter using a cookiecutter template. By default, the template is downloaded as a zip artifact from the matching Flet GitHub Release. The version of the template used is determined by the installed Flet version.

The cached project is refreshed when template inputs change or after you run flet clean to delete the build directory.

Template Source

Defines the location of the cookiecutter build-template to be used.

Supported values include:

  • A GitHub repository using the gh: prefix (e.g., gh:org/template)
  • A full Git URL (e.g., https://github.com/org/template.git)
  • A zip URL (e.g., https://github.com/flet-dev/flet/releases/download/v0.83.0/flet-build-template.zip)
  • A local directory path

Resolution order

Its value is determined in the following order of precedence:

  1. --template
  2. [tool.flet.template].url
  3. The default zip URL from the Flet GitHub Release matching the installed version

Example

flet build apk --template gh:my-org/my-custom-template

Template Reference

Defines the branch, tag, or commit to check out from the template source.

Resolution order

Its value is determined in the following order of precedence:

  1. --template-ref
  2. [tool.flet.template].ref
  3. <flet_version>

Example

flet build <target_platform> --template-ref main

Template Directory

Defines the relative path to the cookiecutter template. If template source is set, the path is treated as a subdirectory within its root; otherwise, it is relative to the template root.

Resolution order

Its value is determined in the following order of precedence:

  1. --template-dir
  2. [tool.flet.template].dir
  3. root of the template source

Example

flet build <target_platform> --template gh:org/template --template-dir sub/directory

Additional flutter build Arguments

During the flet build process, flutter build command gets called internally to package your app for the specified platform. However, not all flutter build arguments are exposed or usable through the flet build command directly.

For possible flutter build arguments, see Flutter docs guide. For most targets, run flutter build <target_platform> --help; for ios-simulator, run flutter build ios --simulator --help.

Important

Passing additional flutter build arguments might cause unexpected behavior. Use at your own risk, and only if you fully know what you're doing!

Resolution order

Its value is determined in the following order of precedence:

  1. --flutter-build-args (can be used multiple times)
  2. [tool.flet.<PLATFORM>.flutter].build_args
  3. [tool.flet.flutter].build_args

Example

flet build apk \
--flutter-build-args=--obfuscate \
--flutter-build-args=--export-method=development \
--flutter-build-args=--dart-define=API_URL=https://api.example.com

Flutter dependencies

When you run flet build, Flet generates a Flutter shell project and then updates its pubspec.yaml using values from pyproject.toml.

Use:

  • [tool.flet.flutter.pubspec.dependencies] for normal package declarations. (Dart docs)
  • [tool.flet.flutter.pubspec.dependency_overrides] when you must force a version or source, for example, a local path or Git fork. (Dart docs)

Values follow standard Pub dependency syntax, expressed in TOML.

Note
  • Important: In most cases, you usually do not need to add/override Flutter dependencies. We recommend doing it only if you fully know what you are doing, as it can lead to unexpected behavior.
  • If the same package appears in both pyproject.toml and the resulting pubspec.yaml, the value from pyproject.toml wins.
  • If you use { path = "..." } under [tool.flet.flutter.pubspec.dependencies] or [tool.flet.flutter.pubspec.dependency_overrides], that path is resolved by Flutter from the generated pubspec.yaml location: <flet_app_directory>/build/flutter/pubspec.yaml. This means relative paths are not resolved from your pyproject.toml file.

Example

[tool.flet.flutter.pubspec.dependencies] # or [tool.flet.flutter.pubspec.dependency_overrides]
# Version
pkg_1 = "^1.2.3"

# Local path
pkg_2 = { path = "../pkg_2" }

# Git (short form)
pkg_3 = { git = "https://github.com/org/pkg_3.git" }

# Git (expanded form: URL + ref + subdirectory)
pkg_4 = { git = { url = "https://github.com/org/mono_repo.git", ref = "main", path = "packages/pkg_4" } }

# Hosted source
pkg_5 = { hosted = { name = "pkg_5", url = "https://pub.dev" }, version = "^1.0.0" }

# SDK package (dependencies only; typically not used in dependency_overrides)
flutter_test = { sdk = "flutter" }

Verbose logging

The -v (or --verbose) and -vv flags enable detailed output from all commands during the flet build process.

Use -v for standard/basic verbose logging, or -vv for even more detailed output (higher verbosity level). If you need support, we may ask you to share this verbose log.

Console output

In packaged apps (flet build output), all output from your Python code such as print() statements, sys.stdout.write() calls, and messages from the Python logging module is redirected to a console.log file. The full path to this file is available via StoragePaths.get_console_log_filename() or the FLET_APP_CONSOLE environment variable.

Note: FLET_APP_CONSOLE is only set in production builds; in development runs, output stays in your terminal.

On Android, iOS and macOS the same output also goes to the platform log, which is usually the easier way to read it from your development machine — see "Reading your app's output" for Android, iOS and macOS. This section is about reaching it from inside your app instead.

The log file is written in an unbuffered manner, allowing you to read it at any point in your Python program using:

import os
import flet as ft

async def main(page: ft.Page):
log_file = await ft.StoragePaths().get_console_log_filename()
# or
# log_file = os.getenv("FLET_APP_CONSOLE")

with open(log_file, "r") as f:
logs = f.read()
page.add(ft.Text(logs)) # display on UI

ft.run(main)

If your program calls sys.exit(100), the complete log will automatically be shown in a scrollable window. This is a special "magic" exit code for debugging purposes:

import sys
sys.exit(100)

Calling sys.exit() with any other code will terminate the app without displaying the log.

Continuous Integration/Continuous Deployment (CI/CD)

You can use flet build command in your CI/CD pipelines to automate the build and release process of your Flet apps.

GitHub Actions

You can use GitHub Actions to build your Flet app automatically on every push, pull request, or manual run.

The recommended option is the official Flet build action, which wraps flet build, sets up the required tools, installs Linux build dependencies when needed, and creates a platform-aware archive for upload. If you need full control over every step, you can run flet build manually in the workflow instead.

name: Build Flet App

on:
push:
pull_request:
workflow_dispatch:

jobs:
build:
name: Build ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include:
- target: apk
runner: ubuntu-latest

- target: aab
runner: ubuntu-latest

- target: web
runner: ubuntu-latest

- target: linux
runner: ubuntu-latest

- target: windows
runner: windows-latest

- target: macos
runner: macos-latest

- target: ipa
runner: macos-latest

- target: ios-simulator
runner: macos-latest

steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Build app
id: build
uses: flet-dev/flet-build-action@v1
with:
target: ${{ matrix.target }}
runner-python-version: "3.14"
bundled-python-version: "3.14"
build-number: ${{ github.run_number }}

- name: Upload build archive
uses: actions/upload-artifact@v7
with:
path: ${{ steps.build.outputs.archive-path }}
archive: false
if-no-files-found: error
overwrite: false

Both workflow variants build for all major targets and upload each build output as an artifact. You can further customize the workflow for your specific needs, for example, restricting the build targets or adding signing, notarization, store upload, or deployment steps.

Troubleshooting

Prerelease compatibility

If you are using a prerelease version of the Flet Python package (for example, 0.80.6.devNNNN) to build an app, the build template may still resolve the latest stable flet Flutter package, which can lead to version incompatibility issues.

Why?: Under normal circumstances, each prerelease of the Flet Python package would require a matching prerelease of the Flutter Flet package to guarantee compatibility. However, we don't publish prerelease versions of the Flutter package to pub.dev. Because of this, the build template resolves the latest stable Flutter flet release instead.

This creates a version mismatch/incompatibility for apps packaged with flet build:

  • Your Python code may depend on newly introduced controls or features.
  • The packaged Flutter shell may still be using an older stable flet version.
  • At runtime, the app fails because the Flutter layer does not recognize the new controls/features in your prerelease flet package, leading to errors like Unknown control: <ControlName>.

Note: this issue does not affect the development workflows (ex: running an app with flet run), as the flet Flutter dependency is only resolved during the flet build process.

Solution

The rule-of-thumb is, if you are using a prerelease Flet Python package, always ensure the Flutter flet dependency is aligned with the same development version before building your app:

  1. Override the Flutter flet dependency to point to the corresponding development Git reference.
[tool.flet.flutter.pubspec.dependency_overrides]
flet = { git = { url = "https://github.com/flet-dev/flet.git", ref = "main", path = "packages/flet" } }
  1. Rebuild the app with the build cache cleared (run flet clean to delete the build directory)

To ensure reproducible builds (ex: in production or CI), prefer using a specific commit SHA, instead of a branch or tag ref.