New features
- Add support for Python
multiprocessingin packaged Flet desktop apps built withflet build macos,flet build windows, andflet build linux.multiprocessingAPIs such asProcess,ProcessPoolExecutor, thespawn/forkserverstart methods, and the resource tracker now work in packaged desktop apps. Previously, worker processes re-executedsys.executable, which pointed to the app binary itself, causing each worker to launch another GUI app instance and hang. Flet desktop runners now detect CPython multiprocessing child command lines before Flutter starts and divert them to a headless embedded Python interpreter viadart_bridge1.5.0+. The Python bootstrap also runs the app module as the realsys.modules["__main__"]withpython -msemantics, so top-level worker functions inmain.pycan be pickled correctly. When usingmultiprocessing, your app must follow normal Python multiprocessing rules: guardft.run(...)withif __name__ == "__main__":, define worker functions at module top level, and do not access Flet UI objects from worker processes. See the new Multiprocessing cookbook page. Mobile platforms remain unsupported because iOS and Android do not allow apps to spawn arbitrary child processes (#4283, #6577) by @ndonkoHenri. - Multi-version bundled CPython support in
flet buildandflet publish. Pick the runtime your app ships with via the new--python-versionflag (3.12 / 3.13 / 3.14), or let it be derived from[project].requires-pythonin yourpyproject.toml; defaults to the latest supported stable (currently 3.14). The matching CPython-standalone build, Pyodide release (0.27.7 / 0.29.4 / 314.0.1), and Emscripten wheel platform tag are all resolved fromflet-dev/python-build's date-keyed manifest. Adding a future pre-release CPython line (e.g. 3.15 beta) is a one-row append withprerelease=True— opt-in only via an explicit--python-version 3.15orrequires-python = "==3.15.*", never the auto-resolved default. Requiresserious_python>= 4.0.0, now pinned in theflet buildtemplate. See the new Choosing a Python version docs section (#6577) by @FeodorFitsner. - Add
ft.DataChannel: dedicated byte channels for widgets that move bulk binary data (image frames, audio buffers, ML tensors) between Dart and Python, bypassing the MsgPack control protocol. The Dart side opens a channel viaFletBackend.of(context).openDataChannel()and announces it to Python by firing adata_channel_opencontrol event with{channel_name, channel_id}; the Python side declareson_data_channel_open: Optional[ft.EventHandler[ft.DataChannelOpenEvent]]and captures the channel viaself.get_data_channel(e.channel_id). Backed by a dedicatedPythonBridgeper channel in embedded native mode (4–7 GiB/s on M2 Pro) and by the defaultProtocolMuxedDataChannelFactoryin dev / web modes (raw-byte frames muxed over the active Flet protocol transport with a 1-byte type discriminator). Pyodide gets zero-copy outbound sends viapostMessageTransferable ArrayBuffer. First consumer:flet-chartsMatplotlibChartCanvas, migrated from_invoke_methodPNG dispatch to a 1-byte-opcode data channel by @FeodorFitsner. - In-process Python transport (
dart_bridgeFFI).package:fletgains a third protocol transport alongside the UDS / TCP socket servers: it can run Flet's MsgPack protocol over an in-processdart_bridgebyte channel via aFletApp(channelBuilder: …)seam (thefletpackage stays Python-independent — it doesn't depend onserious_pythonor know aboutPythonBridge; the embedder wires the channel).serious_python>= 3.0.0 uses this seam to embed the Python interpreter in-process instead of talking to it over a localhost socket, and theflet buildtemplate migrates from sockets to the FFI transport. On Android, where the OS may keep the process alive across a back-button quit and restart only the Dart VM, the transport rebinds to the new VM'sdart_bridgeports on a session-restart signal (libdart_bridge>= 1.3.0) — the Python process and its in-memory state are preserved and the Flet session is rebuilt fromREGISTER_CLIENTby @FeodorFitsner. - Add
flet cleancommand that deletes thebuilddirectory of a Flet app — the Flutter bootstrap project, cached artifacts, and generated output — in a single step (#6233) by @ndonkoHenri. - Add
compression_qualitytoFilePicker.pick_files()for selecting the image compression quality used by supported platforms (#6573) by @ndonkoHenri. - Add
ConsentManagertoflet-adsfor gathering user consent (e.g. GDPR/EEA) via Google's User Messaging Platform (UMP) before requesting ads (#6569, #6615) by @ndonkoHenri. - Add
ft.RawImage: a high-bandwidth pixel-frame display control for Pillow output, NumPy arrays, camera frames and procedural graphics. Frames stream over a dedicatedft.DataChannelinstead of the control protocol: raw premultiplied RGBA straight to a GPU texture on local transports (desktop,flet run, Pyodide), automatic PNG fallback on remoteflet-websessions. The awaitablerender(pil_or_numpy)/render_rgba(w, h, bytes)/render_encoded(png_jpeg_webp_bytes)methods resolve when the client has displayed the frame, so a plainwhile True: await ri.render(...)loop self-paces to display speed. Premultiplied-alpha conversion runs in Pillow's C loops with an opaque fast-path; Pillow and NumPy remain optional. The last frame is retained and replayed on remount, mirroringImage.src. Ships with five gallery examples (photo viewer, plasma, Pillow paint, Mandelbrot explorer, Game of Life) and a docs page withRawImagevsImageguidance (#6674) by @FeodorFitsner.
Improvements
- Swift Package Manager for iOS/macOS builds (on by default).
flet build/flet debugnow integrate the embedded Python runtime via SPM instead of CocoaPods (CocoaPods goes read-only in December 2026). Flet auto-falls back to CocoaPods when the app depends on a package that isn't SPM-ready — currentlyflet-video(media_kit) — since Flutter then builds the whole app with CocoaPods. Force CocoaPods for other non-SPM packages with--no-swift-package-manager(orswift_package_manager = falseunder[tool.flet]). Flet does not change Flutter's global SPM configuration; the setting only selects howserious_pythonstages the runtime to match. When SPM is used (it has nopod installhook),flet buildsetsSERIOUS_PYTHON_DARWIN_SPMsoserious_python'spackagestep stages the runtime (Python/dart_bridge xcframeworks, the iOS native extensions, and the stdlib/site-packages/app resources) into the plugin'sPackage.swiftlayout on the host beforeflutter build, and exports theSP_NATIVE_SETcache-bust key into the build. Requires the SPM-capableserious_pythonrelease. - Smaller Android apps with no native-packaging config.
flet build apk/aabconsume serious_python's new Android packaging: Python extension modules load memory-mapped directly from the APK (no extraction to disk), and pure Python ships in stored asset zips read viazipimport, so the standard library is no longer duplicated per ABI. Apps no longer needuseLegacyPackaging/keepDebugSymbols— theflet buildAndroid template drops them; just useminSdk 23+. New--android-extract-packagesflag and[tool.flet.android].extract_packagesship "path-hungry" packages — those that read bundled data via__file__/pkg_resourcesinstead ofimportlib.resources— extracted to disk instead of inside the zip (most packages, includingcertifi, are zip-safe and need no entry). Requiresserious_pythonwith the native-mmap packaging (dart_bridge 1.4.0). - Pyodide is no longer pre-baked into the
flet buildtemplate. Eachflet build web/flet publishrun downloads the matchingpyodide-core-<version>.tar.bz2(plus the runtimemicropipandpackagingwheels) into a per-version cache at~/.flet/pyodide/<version>/and copies the files into the build output. Subsequent builds reuse the cache; the older0.27.5bundle previously shipped in the cookiecutter template is gone (#6577) by @FeodorFitsner. - The supported Python / Pyodide / dart_bridge versions are loaded on demand from
flet-dev/python-build's date-keyedmanifest.json(fetched once and cached under~/.flet/cache), the single source of truth shared withserious_python— replacing flet's hand-mirrored version table.flet buildforwards onlySERIOUS_PYTHON_VERSIONand letsserious_pythonderive the full version / build date / dart_bridge version from its own committed snapshot. The module exposesget_supported_python_versions()/get_default_python_version()(the previousSUPPORTED_PYTHON_VERSIONS/DEFAULT_PYTHON_VERSIONconstants are removed) (#6577) by @FeodorFitsner. flet --versionshows just the Flet and Flutter versions; the staticPyodide: …line and the globalflet.version.pyodide_versionexport are removed (the supported Python / Pyodide set now lives in python-build's manifest, not the CLI output) (#6577) by @FeodorFitsner.flet --version --jsonemits a machine-readable document — Flet/Flutter versions and the Linux build dependencies — for CI to read viajqinstead of importing Flet internals withpython -c. (The supported Python/Pyodide table is no longer included; it comes from python-build's manifest.) The canonical Linux apt dependency list moved fromflet.utils.linux_deps(runtime package) toflet_cli.utils.linux_deps(build tooling) by @FeodorFitsner.client/web/python.jsand the build template'spython.jsno longer hardcodedefaultPyodideUrl.patch_index.pynow injectsflet.pyodideUrlper build (CDN URL by default, or the localpyodide/pyodide.jspath under--no-cdn) so the runtime URL always tracks the resolved Pyodide release (#6577) by @FeodorFitsner.- Stream-oriented Flet protocol transports (UDS / TCP used by
flet rundev mode) now use length-prefixed framing instead of streamingmsgpack.Unpacker.feed. Combined with a new 1-byte type discriminator at the head of every packet (0x00= MsgPack control frame,0x01= raw DataChannel frame), this unifies framing across all transports (sockets, WebSocket,dart_bridgeFFI, PyodidepostMessage).StreamingMsgpackDeserializeris removed frompackage:flet; each inbound packet is one complete MsgPack value, decoded one-shot viamsgpack.deserialize(bytes)by @FeodorFitsner. - Bump the bundled Flutter to 3.44.2 (from 3.41.7). The Flet client and the
flet buildtemplate migrate to Flutter 3.44's built-in Kotlin (the Android app no longer applies the Kotlin Gradle plugin itself) and Java 17; the client's Gradle wrapper moves to 8.14 by @FeodorFitsner. - Raw RGBA Matplotlib frames on local transports.
MatplotlibChartnow skips per-frame diffing and PNG encode/decode when the client runs on the same machine: uncompressed RGBA full frames stream straight from Agg's buffer over the chart'sDataChannel(new0x04opcode) and are displayed with a singledecodeImageFromPixels+ swap on the Dart side. A 1600×1000 @ DPR 2 figure (24 MB/frame) over a local socket goes from 7.4 to 18.7 fps, leaving matplotlib's own render as the dominant per-frame cost; remote WebSocket clients keep the compact PNG full+diff pipeline. The format is auto-selected via the newConnection.local_data_transportcapability flag (set by the socket,dart_bridgeand Pyodide transports). Also fixes O(n²) length-prefixed packet reassembly in the Dart socket transport — multi-MB frames arrive in dozens of chunks and previously re-flattened the accumulation buffer on every chunk (#6673) by @FeodorFitsner. flet build webandflet publishnow default the web renderer tocanvaskitinstead ofauto. Withauto, Chromium selects the dart2wasm/skwasm renderer, whose JS↔Dart typed-data boundary costs make byte-streaming Pyodide apps (matplotlib frames,RawImage) ~6–7x slower per frame; pass--renderer autoor set[tool.flet.web].rendererto restore the old behavior. Also fixestool.flet.web.rendererbeing ignored byflet publish(shadowed by an argparse default) (#6673) by @FeodorFitsner.- Faster mobile cold start:
import fletis now lazy. Thefletpackage previously executed its full ~270-module public API eagerly onimport flet; it now resolves public names on first access via a module-level__getattr__(PEP 562), so an app loads only the modules it actually uses. On a mid-range Android device this cutimport fletfrom ~2.0s to ~0.15s. The eager subsystem clusters thatPagepulled in (auth, components/hooks, Cupertino controls) are deferred too. Type checkers, IDEs, andfrom flet import *are unaffected (#6597) by @FeodorFitsner.
Breaking changes
- App files now ship unpacked in a read-only bundle, and the storage directories were reworked (requires
serious_python>= 4.0.0, now pinned in theflet buildtemplate). Your Python sources ship unpacked inside the app bundle next to the stdlib/site-packages (no first-launchapp.zipextraction) on macOS/iOS/Windows/Linux; on Android they ship as a storedapp.zipasset unpacked once on first launch; web is unchanged. The app directory is now read-only, so the Python program's working directory moved to a writable, app-private data dir.FLET_APP_STORAGE_DATAnow maps to the OS application support dir (adatasubdir) instead of the user's Documents folder and is the cwd;FLET_APP_STORAGE_TEMPnow points to the OS temp dir (was the cache dir) and a newFLET_APP_STORAGE_CACHEexposes the cache dir.flet runsets the dev cwd to a hidden, git-ignored<project>/.flet/storage/data. Relative reads of bundled files (open("seed.json")) must move to__file__/importlib.resourcesorassets/. See the app files unpacked / storage dirs guide by @FeodorFitsner. flet buildandflet publishnow bundle CPython 3.14 by default (previously 3.12, implicit via the old single-versionserious_python). Existing apps that depend on native wheels without 3.14 binaries should pin explicitly with--python-version 3.12(CLI),requires-python = ">=3.12,<3.13"(pyproject), orSERIOUS_PYTHON_VERSION=3.12in the build environment (#6577) by @FeodorFitsner.- Android builds now include only the ABIs the bundled Python supports, sourced per-version from python-build's manifest (
pythons.<short>.android_abis) rather than hardcoded in flet. As of python-build20260630,armeabi-v7a(32-bit ARM) is published for 3.12, 3.13 and 3.14, so all three build it by default; an explicit--arch <abi>is validated against the selected Python's supported set and fails with a clear error otherwise (#6578) by @ndonkoHenri. flet build/flet publishnow compile your app and packages to.pycby default (previously off). This removes per-launch bytecode recompilation — a significant cold-start win, especially on mobile where pure Python is imported from a stored zip (zipimport) and can't cache bytecode back to disk, so every module would otherwise recompile from source on each launch. The CLI flags gain--no-compile-app/--no-compile-packages(viaargparse.BooleanOptionalAction; the existing--compile-app/--compile-packagesstill work), and[tool.flet.compile].app/.packagesnow default totrue. Pass--no-compile-*or set them tofalseto restore the old behavior (faster iterative builds, or keeping.pysource in the bundle). Compiled web builds were verified to load in Pyodide (bundled CPython and Pyodide share the same minor version). See the compile-on-by-default guide (#6598) by @FeodorFitsner.- Flet protocol wire format on stream-oriented transports (UDS / TCP) is incompatible with pre-0.86 servers and clients. Every packet now starts with a 4-byte little-endian length prefix and a 1-byte type discriminator (
0x00= MsgPack control frame,0x01= raw DataChannel frame). WebSocket /postMessage/dart_bridgetransports keep native message boundaries and only gain the type byte. The Flet CLI dev server and the in-process Python runtime are upgraded in lockstep — runningflet runwith mismatchedfletversions across CLI and runtime is no longer supported. See the DataChannel protocol framing upgrade guide. TheMatplotlibChartCanvaswidget transports its full / diff / clear frames via aDataChannelrather than_invoke_methodarguments — visually identical, but custom code that subclassed it and overrode the apply methods may need updating by @FeodorFitsner.
Deprecations
- Deprecate the
--clear-cacheflag offlet buildandflet debug; use the newflet cleancommand instead. The flag remains functional but now emits a deprecation warning, and is scheduled for removal in0.89.0(#6233) by @ndonkoHenri.
Bug fixes
- Fix a debug-mode
'!_dirty': is not trueassertion (EXCEPTION CAUGHT BY WIDGETS LIBRARYin_BootOverlay) thrown by apps built or debugged from theflet buildtemplate when the app becomes ready. With the defaultboot_screen.fade_out_durationof 0 the overlay's zero-durationAnimatedOpacitycompleted synchronously, firingonEnd— and itssetState— in the middle of the overlay's own rebuild. The overlay is now removed in a single state update when no fade is configured; non-zero fade durations still animate as before. Debug-mode only: the assertion is compiled out of release builds (#6666) by @FeodorFitsner. - Fix
flet buildfailing on Windows when a dependency is pulled in via[tool.flet.<platform>].dev_packages(or any local-path install): the rewritten<pkg> @ file://<path>URL now usesPath.as_uri(), producing the correctfile:///D:/...three-slash form instead offile://D:\..., which pip on Windows parsed as a UNC path and aborted withOSError: [Errno 2] No such file or directory: '\\\\D:\\a\\...'(#6577) by @FeodorFitsner. - Fix
flet build web --python-version 3.13failing to match any Pyodide-built native wheel. The 3.13 row in the Python version registry was set to Pyodide platform tagpyodide-2025.0-wasm32, but Pyodide actually publishes 0.29 wheels underpyemscripten_2025_0_wasm32(thepyodide_→pyemscripten_prefix transition happened at 0.28/0.29, not at 314.0). Corrected topyemscripten-2025.0-wasm32so pip's wheel selection picks up the correct tags by @FeodorFitsner. flet buildnow cleans the build directory when the bundled Python version changes between builds, preventing stale compiled bytecode from the previous version crashing the app at runtime withImportError: bad magic numberby @FeodorFitsner.- Fix locating Flet controls by their user-assigned
keyin tests.ValueKey(control.key)was constructed asValueKey<Object>, and Flutter's runtimeType-strictValueKey.==never matches that against theValueKey<String>the rendered widget carries — sofind.byKey(Key('foo'))(flutter_test) andfind_by_key('foo')(Flet tester) located 0 widgets. TheValueKeyis now built with the value's concrete type (String →ValueKey<String>, int →ValueKey<int>, …) on both the Dart and Python sides by @FeodorFitsner. - Fix
flet build apk/flet build aabwith--archpackaging native libraries for all Android ABIs instead of only the requested ones. The requested architectures are now forwarded to Flutter as--target-platform(so--split-per-abibuilds only the requested splits), unrequested ABI directories are excluded from the artifact viapackaging.jniLibs.excludes, Android--archvalues are validated against the bundled Python's supported ABIs, multiple--archvalues now correctly reachserious_python(comma-joined), and stale artifacts from previous builds are no longer copied into the output directory (#6567, #6578) by @ndonkoHenri. - Fix repeated
--arch,--source-packagesand--permissionsflags inflet buildkeeping only the values of the last occurrence (action="extend"on each) (#6578) by @ndonkoHenri. - Fix
flet build apkfailing atmergeDebugNativeLibswithN files found with path 'lib/<abi>/libc++_shared.so'when an app combinesserious_python_androidwith another Flutter plugin that also bundles the NDK C++ runtime (#6570, #6571) by @ndonkoHenri. - Specify
handlersignatures insubscribeandsubscribe_topicmethods ofPubSubClientfor better type checking (#6549) by @Iaw4tch - Fix
FilePicker.pick_files()on web for slow network shares or slow machines: passcancel_upload_on_window_blur=Falseto prevent valid file selections from being reported as cancelled when the browser window loses focus during file picking (#771, #6573) by @ndonkoHenri. - Support
PagePlatform.ANDROID_TVinPage.get_device_info()retrieval (#6604) by @bl1nch. - Fix
ProgressRing.year_2023being ignored, so the control correctly switches between the latest and 2023 Material Design appearances (#6614) by @ndonkoHenri. flet build ipa/iosapps that ship ctypes packages with plain.dylibshared libraries (e.g.llama-cpp-python) now load them on the iOS simulator instead of failing at launch with adlopenplatform mismatch (have 'iOS', need 'iOS-simulator'); the iOS runtime also now bundles the_multiprocessingextension (importable, not spawnable). Bumps the pinned bundle toserious_python4.2.1 / python-build20260701(serious_python#223) by @ndonkoHenri, @FeodorFitsner.- Improve performance of checking added/removed controls in Session.patch_control from O(N²) to O(N) (#6651) by @davidlawson.
- Fix stateful controls inside
ResponsiveRow(video players, WebViews, scroll positions) losing their state whenever a window resize crossed a breakpoint and the layout switched between a single row and wrapping (#6661, #6663) by @FeodorFitsner.
Documentation
- Improve
FilePicker.save_file()documentation: on desktop, passingsrc_byteswrites those bytes to the selected file (#6573) by @ndonkoHenri.
New Contributors
- @EH-MLS made their first contribution in #6591
- @Iaw4tch made their first contribution in #6564
- @Federicorao made their first contribution in #6582
- @xiaocai2011 made their first contribution in #6617
- @davidlawson made their first contribution in #6651
Full Changelog: v0.85.3...v0.86.0