Skip to main content

Packaging app for macOS

Instructions for packaging a Flet app into a macOS application bundle.

Info

This guide provides detailed macOS-specific information. Complementary and more general information is available here.

Alternative: flet pack

For a quicker, PyInstaller-based way to package desktop apps — no Flutter toolchain required — see flet pack.

Prerequisites

Rosetta 2

Flutter, which we use for packaging, requires Rosetta 2 on Apple Silicon:

sudo softwareupdate --install-rosetta --agree-to-license

Xcode

Xcode 15 or later is required to compile native Swift or Objective-C code.

CocoaPods

CocoaPods 1.16 or later is required to install and compile Flutter plugins.

flet build macos

Note

This command can be run on macOS only.

Builds a macOS application bundle from your Flet app.

Target architecture

By default, flet build macos creates a universal bundle that runs on both Apple Silicon and Intel Macs. Packaging downloads Python wheels for both arm64 and x86_64 architectures.

To limit packaging to a specific architecture, see this. This affects which Python wheels are bundled and, in turn, which CPU architectures the app will run on. You will then have to provide your users with the correct build for their Macs.

Permissions

macOS permissions are declared through Info.plist privacy usage strings and app entitlements. You can also use the cross-platform permission bundles to inject common entries, then override or extend them with platform-specific values.

Info.plist

Add or override Info.plist entries for macOS builds. These values are written to macos/Runner/Info.plist of the build project.

Resolution order

Its value is determined in the following order of precedence:

  1. --info-plist
  2. [tool.flet.macos.info]
  3. Values injected by cross-platform permission bundles, if any.

Supported value forms

Accepts repeated <key>=<value> entries. The <value> can be in one of the following forms:

  • true or false (case-insensitive) for boolean values
  • integer and real number literals, for example 32 or 0.5
  • TOML array literals, for example ["basic", "advanced"]
  • TOML inline tables, for example { NSAllowsArbitraryLoads = false }
  • any other value is treated as a string

Example

flet build macos \
--info-plist LSApplicationCategoryType="public.app-category.utilities" \
--info-plist NSSupportsSuddenTermination=true \
--info-plist ExampleInteger=32 \
--info-plist ExampleReal=0.5 \
--info-plist 'SupportedModes=["basic", "advanced"]' \
--info-plist 'FeatureFlags=[true, false]' \
--info-plist 'RetryDelays=[1, 2, 3]' \
--info-plist 'OpacitySteps=[0.25, 0.5, 0.75]' \
--info-plist 'NSAppTransportSecurity={ NSAllowsArbitraryLoads = false }' \
--info-plist 'CFBundleDocumentTypes=[{ CFBundleTypeName = "Data File", CFBundleTypeExtensions = ["dat"] }, { CFBundleTypeName = "JSON File", CFBundleTypeExtensions = ["json"] }]'
Template translation

In the macos/Runner/Info.plist, the example above will be translated accordingly into this:

<plist version="1.0">
<dict>

<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>

<key>NSSupportsSuddenTermination</key>
<true/>

<key>ExampleInteger</key>
<integer>32</integer>

<key>ExampleReal</key>
<real>0.5</real>

<key>SupportedModes</key>
<array>
<string>basic</string>
<string>advanced</string>
</array>

<key>FeatureFlags</key>
<array>
<true/>
<false/>
</array>

<key>RetryDelays</key>
<array>
<integer>1</integer>
<integer>2</integer>
<integer>3</integer>
</array>

<key>OpacitySteps</key>
<array>
<real>0.25</real>
<real>0.5</real>
<real>0.75</real>
</array>

<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>

<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Data File</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>dat</string>
</array>
</dict>
<dict>
<key>CFBundleTypeName</key>
<string>JSON File</string>
<key>CFBundleTypeExtensions</key>
<array>
<string>json</string>
</array>
</dict>
</array>

</dict>
</plist>

Entitlements

Entitlements are property-list key-value pairs that grant an executable permission to use a service or technology. The supported value type depends on the entitlement key defined in the Apple Developer Entitlements Reference.

Entitlements are written to macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements in the build template.

Resolution order

Its value is determined in the following order of precedence:

  1. --macos-entitlements

  2. [tool.flet.macos.entitlement]

  3. Values injected by cross-platform permission bundles, if any.

  4. Defaults:

    [tool.flet.macos.entitlement]
    "com.apple.security.app-sandbox" = false
    "com.apple.security.cs.allow-jit" = true
    "com.apple.security.cs.allow-unsigned-executable-memory" = true
    "com.apple.security.network.client" = true
    "com.apple.security.network.server" = true
    "com.apple.security.files.user-selected.read-write" = true
    note

    com.apple.security.cs.allow-unsigned-executable-memory is required for ctypes/cffi callbacks to work on Intel Macs when the app is signed with the hardened runtime (Apple's libffi allocates writable-and-executable closure memory on x86_64). Apple Silicon is unaffected. Set it to false if your app targets only arm64 and you want the strictest hardened runtime.

Supported value forms

Accepts repeated <key>=<value> entries. The <value> can be in one of the following forms:

  • true or false (case-insensitive) for boolean values
  • integer and real number literals, for example 32 or 0.5
  • TOML array literals, for example ["group.example.one", "group.example.two"]
  • TOML inline tables, for example { "com.apple.mail" = ["compose"] }
  • any other value is treated as a string

Example

flet build macos \
--macos-entitlements com.apple.security.network.client=true \
--macos-entitlements com.apple.developer.ubiquity-kvstore-identifier=ABCDE12345.dev.example.myapp \
--macos-entitlements ExampleInteger=32 \
--macos-entitlements ExampleReal=0.5 \
--macos-entitlements 'com.apple.security.application-groups=["group.dev.example.myapp", "group.dev.example.shared"]' \
--macos-entitlements 'ExampleBooleanArray=[true, false]' \
--macos-entitlements 'com.apple.security.scripting-targets={ "com.apple.mail" = ["compose", "send"] }' \
--macos-entitlements 'ExampleArrayOfDictionaries=[{ Name = "alpha", Enabled = true }, { Name = "beta", Enabled = false }]'
Template translation

In both macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements, the example above will be translated accordingly into this:

<key>com.apple.security.network.client</key>
<true />
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>ABCDE12345.dev.example.myapp</string>
<key>ExampleInteger</key>
<integer>32</integer>
<key>ExampleReal</key>
<real>0.5</real>
<key>com.apple.security.application-groups</key>
<array>
<string>group.dev.example.myapp</string>
<string>group.dev.example.shared</string>
</array>
<key>ExampleBooleanArray</key>
<array>
<true />
<false />
</array>
<key>com.apple.security.scripting-targets</key>
<dict>
<key>com.apple.mail</key>
<array>
<string>compose</string>
<string>send</string>
</array>
</dict>
<key>ExampleArrayOfDictionaries</key>
<array>
<dict>
<key>Name</key>
<string>alpha</string>
<key>Enabled</key>
<true />
</dict>
<dict>
<key>Name</key>
<string>beta</string>
<key>Enabled</key>
<false />
</dict>
</array>

Code signing

By default, the built app bundle is ad-hoc signed: it runs fine on the Mac that built it, but when other users download it, macOS Gatekeeper steps in. Since macOS 15 (Sequoia), there is no Control-click bypass anymore — users must approve every blocked item in System Settings → Privacy & Security → Open Anyway with an administrator password, and a Python app can trigger this per bundled library. For public distribution (excluding the Mac App Store), sign your app with a Developer ID Application certificate and notarize it.

Prerequisites

  1. An Apple Developer Program membership.

  2. A Developer ID Application certificate (create one, then install it — with its private key — into your login keychain). Verify with:

    security find-identity -v -p codesigning

Signing the app

flet build macos --macos-signing-identity "Developer ID Application: Jane Doe (TEAM123456)"

The identity may be the exact certificate name, its SHA-1 fingerprint, or a unique substring (for example, just the team ID). Passing "-" produces an explicit ad-hoc signature.

Resolution order

The signing identity is determined in the following order of precedence:

  1. --macos-signing-identity
  2. [tool.flet.macos.signing.<lane>].identity (per-lane)
  3. [tool.flet.macos.signing].identity
  4. FLET_MACOS_SIGNING_IDENTITY environment variable
  5. Default: none — a plain build keeps its ad-hoc signature and no signing step runs, while notarize and App Store builds auto-discover the certificate.

When a real identity is configured, flet build macos will, after the build:

  1. Sign every bundled binary — including the embedded Python runtime and all native modules from your dependencies — "inside out", as Apple requires, with the hardened runtime enabled and a secure timestamp (both required for notarization). Entitlements are applied to the app bundle and to helper executables and helper bundles shipped by your dependencies; frameworks and libraries are signed without entitlements, per Apple guidance.
  2. Verify the result with codesign --verify --deep --strict and check that no binary was left unsigned.

The build fails with an actionable error if the identity is not found in the keychain, if any file fails to sign, or if verification fails. An expired or revoked certificate is called out by name and status instead of appearing missing.

Identity auto-discovery

An identity counts as not configured only when the CLI option, the pyproject.toml key, and the environment variable are all unset — the resolution order above runs first, and any configured value is matched as given, never silently replaced.

With no identity configured anywhere, build modes that cannot proceed without one discover it from the keychain:

  • notarizing builds use your Developer ID Application certificate;
  • App Store builds use your Apple Distribution certificate (or its legacy equivalent, 3rd Party Mac Developer Application) for the app and your installer certificate for the .pkg.

Discovery succeeds when the keychain holds exactly one valid certificate of the required type — the chosen identity is printed in the build output. With several candidates (for example, certificates from two teams), the build fails with the candidate list; configure the certificate name or SHA-1 fingerprint explicitly. Plain builds (neither notarize nor App Store mode) never auto-discover.

Certificate types also scope explicit identities in these modes: a partial identity such as your team ID only has to be unique among certificates of the required type, not among all your certificates.

Distribution lanes

flet build macos signs and packages for one of three lanes, selected by a single setting:

  • none (default) — sign only when a signing identity is configured; without one, the app keeps its ad-hoc signature.
  • developer-id — sign with the hardened runtime, notarize and staple for direct distribution.
  • app-store — sandboxed store signing plus a signed installer .pkg for the Mac App Store.
tip

By default, all lanes write to the same output directory, so each build overwrites the existing files. To keep separate output for each lane, pass a custom output directory for each build.

Resolution order

The distribution lane is determined in the following order of precedence:

  1. --macos-distribution
  2. [tool.flet.macos.signing].distribution
  3. Default: none

Per-lane settings

Every [tool.flet.macos.signing] key except distribution may be set in a per-lane subtable, which overrides the flat key when that lane builds. This matters for the one setting whose value genuinely differs per lane — the identity, whenever auto-discovery cannot pick for you (say, certificates from several teams in one keychain):

The command line has no per-lane syntax: an invocation selects exactly one lane, so its options are inherently scoped to it — per-lane values are simply per-run values:

flet build macos --macos-distribution developer-id \
--macos-signing-identity "Developer ID Application: Jane Doe (TEAM123456)" \
--macos-notary-profile flet-notary

flet build macos --macos-distribution app-store \
--macos-signing-identity "Apple Distribution: Jane Doe (TEAM123456)" \
--macos-installer-identity "3rd Party Mac Developer Installer: Jane Doe (TEAM123456)" \
--macos-provisioning-profile path/to/File.provisionprofile \
# --output build/macos-store

Settings resolve as: CLI option → lane subtable → flat key → environment variable. For a key only one lane reads (like notary_profile on developer-id lane or provisioning_profile on app-store lane), the subtable and the flat form are equivalent — group by lane for readability, or keep them flat for less nesting. A misnamed/inexisting subtable fails the build.

Switching lanes

One pyproject.toml can hold both lanes' settings — the notary profile, provisioning profile, and installer identity are each read only by their own lane, and App Store Info.plist keys are harmless in Developer ID builds. Leave the identities to auto-discovery (a pinned identity fits only one lane), set your default lane in pyproject.toml, and flip it per build:

flet build macos --macos-distribution app-store

Notarization

A Developer-ID-signed app must also be notarized by Apple for Gatekeeper to open it without warnings. Notarization uploads the app to Apple's notary service (a malware scan, typically a few minutes), after which the resulting "ticket" is stapled to the app so it validates even offline.

Credentials

Setting up credentials takes two steps: create one with Apple, then make it available to Flet.

Creating a credential

Apple's notary service accepts two kinds — get whichever suits you:

  • App Store Connect API key (recommended; also reusable for store uploads) — in App Store Connect, open Users and Access → IntegrationsApp Store Connect APITeam Keys+. Name the key, give it the Developer role, then download the AuthKey_<KEY_ID>.p8 file — possible only once — and note the key's Key ID and the Issuer ID shown at the top of the page.
  • Apple ID + app-specific password — at account.apple.comSign-In and SecurityApp-Specific Passwords+, generate a password dedicated to notarization (your regular Apple ID password is not meant here and won't work with notarytool).

Providing it to Flet

  • Keychain profile (best for local development) — a one-time setup that saves either kind of credential into the macOS keychain under a name of your choice. With an API key:

    xcrun notarytool store-credentials flet-notary \
    --key /path/to/AuthKey_ABC123DEFG.p8 --key-id ABC123DEFG \
    --issuer 12345678-90ab-cdef-1234-567890abcdef

    or with an Apple ID (prompts for the app-specific password):

    xcrun notarytool store-credentials flet-notary \
    --apple-id you@example.com --team-id TEAM123456

    From then on, only the profile name (here flet-notary) is needed; the secrets never appear in your shell history, environment, or pyproject.toml.

  • Environment variables (best for CI) — pass an App Store Connect API key inline on each run by setting APPLE_API_KEY (path to the .p8 file), APPLE_API_KEY_ID, and APPLE_API_ISSUER. Nothing is stored on the machine, which suits ephemeral CI runners where no keychain profile exists — inject the values from your repository secrets.

Resolution order

Credentials are determined in the following order of precedence:

  1. --macos-notary-profile
  2. [tool.flet.macos.signing.developer-id].notary_profile (per-lane)
  3. [tool.flet.macos.signing].notary_profile
  4. FLET_MACOS_NOTARY_PROFILE environment variable
  5. The APPLE_API_KEY, APPLE_API_KEY_ID and APPLE_API_ISSUER environment variables (all three must be set)
  6. Default: none — notarizing builds fail without credentials.

A configured profile intentionally has precedence over the APPLE_API_* variables, which other tooling (Fastlane, CI images) may have exported for a different team.

Notarizing the app

flet build macos \
--macos-distribution developer-id --macos-notary-profile flet-notary

When notarizing, the signing identity may be omitted entirely — see Identity auto-discovery.

If notarization is rejected, the build fails and prints Apple's notarization log, which lists the exact offending files.

Distributing

flet build leaves you with a signed, notarized, and stapled .app — ship it as a single downloadable file, either a DMG (recommended) or a zip archive.

First, create the image — plain, or with a custom Finder look:

hdiutil create -volname "MyApp" -srcfolder build/macos/MyApp.app -ov -format UDZO MyApp.dmg

hdiutil create packs the app into a compressed read-only image (UDZO) containing just the app. For an Applications drop-shortcut, a background image, and icon placement, see the Custom look tab.

Then sign, notarize, and staple the image:

codesign -f --timestamp -s "Developer ID Application: Jane Doe (TEAM123456)" MyApp.dmg
xcrun notarytool submit MyApp.dmg --keychain-profile flet-notary --wait
xcrun stapler staple MyApp.dmg

The result is the conventional macOS download: users open the image and drag the app into Applications.

Signing and notarizing in CI

GitHub Actions

note

The steps below cover only signing and notarization — graft them onto a complete build workflow such as the one in the CI/CD guide.

A CI runner starts with an empty keychain, so the one-time setup is about getting your certificate and notary credentials into repository secrets:

  1. Export the Developer ID Application certificate together with its private key: in Keychain Access, under login → My Certificates, right-click the certificate → Export… and save it in the .p12 format, protected by an export password (Apple's guide). The .cer file downloadable from the developer portal is not a substitute: it holds only the public certificate, while the private key exists solely in the keychain of the Mac that created the certificate request — hence the export from Keychain Access there.

  2. Store the secrets — in the repository's Settings → Secrets and variables → Actions, or with the gh CLI. Secrets hold text, so the binary .p12 is stored base64-encoded (base64 -i path/to/certificate.p12 | pbcopy fills the clipboard), while the .p8 key is already text (PEM) and goes in as-is:

    gh secret set MACOS_CERTIFICATE_P12 --body "$(base64 -i path/to/certificate.p12)"
    gh secret set MACOS_CERTIFICATE_PASSWORD
    gh secret set MACOS_SIGNING_IDENTITY --body "Developer ID Application: Jane Doe (TEAM123456)"
    gh secret set APPLE_API_KEY_P8 < path/to/AuthKey_ABC123DEFG.p8
    gh secret set APPLE_API_KEY_ID --body "ABC123DEFG"
    gh secret set APPLE_API_ISSUER --body "12345678-90ab-cdef-1234-567890abcdef"

The workflow then imports the certificate into the runner's keychain and exposes the credentials to flet build:

- uses: apple-actions/import-codesign-certs@v3
with:
p12-file-base64: ${{ secrets.MACOS_CERTIFICATE_P12 }}
p12-password: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}

- name: Build, sign and notarize
env:
APPLE_API_KEY: ${{ runner.temp }}/AuthKey.p8
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
FLET_MACOS_SIGNING_IDENTITY: ${{ secrets.MACOS_SIGNING_IDENTITY }}
run: |
printf '%s' "${{ secrets.APPLE_API_KEY_P8 }}" > "$APPLE_API_KEY"
flet build macos --macos-distribution developer-id

Troubleshooting

SymptomCause and fix
"MyApp" is damaged and can't be opened on users' MacsThe bundle was modified after signing — most commonly the app writes next to its own files at runtime. Write user data to os.getcwd() (Flet points it at a writable location) instead of paths derived from __file__. Also triggered by building with --no-compile-app/--no-compile-packages, which lets Python create __pycache__ inside the bundle at runtime.
errSecInternalComponent when signing in CIThe keychain is locked — unlock it in the job, or use apple-actions/import-codesign-certs, which handles it.
Notarization status InvalidRead the printed notary log: typical causes are an unsigned binary that was added to the bundle after signing, or a certificate that is not a Developer ID Application certificate.
library load disallowed by system policyA native library is signed with a different Team ID than the app (or not at all). Rebuild so all binaries are re-signed together, or — if your app must load externally acquired native code at runtime — add the com.apple.security.cs.disable-library-validation entitlement.
Notarization takes very longThe first-ever submission for a new account can take up to an hour or more; subsequent submissions typically finish within minutes.
Build hangs at the signing step (codesign at 0% CPU)macOS is waiting on a keychain prompt — possibly hidden behind other windows — for permission to use the private key, common after importing a key from the terminal. Click Always Allow on the prompt, or pre-authorize codesign with security set-key-partition-list -S apple-tool:,apple: -s -k <login-password> login.keychain-db.
Warning: unable to build chain to self-signed rootApple's intermediate certificate authorities are missing from the keychain, so the signature can't chain up to Apple's root. Sign in to Xcode (Settings → Accounts), which installs them, or download them from Apple PKI.

Mac App Store

The signing support above targets direct distribution (your website, GitHub releases, etc.). For the Mac App Store — including TestFlight — flet build macos has a dedicated mode that produces a signed installer .pkg ready for App Store Connect. In this mode the app is signed with your Apple Distribution certificate — sandboxed, without the hardened runtime — your provisioning profile is embedded, the store-mandated application-identifier/team-identifier entitlements are applied (helper executables and helper bundles get the sandbox inherit pair), and every hardened-runtime exception entitlement (com.apple.security.cs.*, including the defaults) is stripped: they are meaningless without the hardened runtime and scrutinized by App Review. Notarization does not apply to store submissions — the app-store lane never notarizes.

Store prerequisites

One-time setup, requiring an Apple Developer Program membership.

Creating the distribution certificates

Store builds need two certificates. Create both under Certificates+ (if you don't have a certificate request file yet, see Generating a CSR — the process is identical for macOS):

  1. Apple Distribution — signs the app bundle.

  2. Mac Installer Distribution — signs the installer .pkg. It appears in your keychain as 3rd Party Mac Developer Installer, and because it signs packages rather than code, security find-identity -v -p codesigning does not list it. Verify it with:

    security find-identity -v -p basic

Download each certificate and double-click it to install it — with its private key — into your login keychain.

Registering an App ID

Under Identifiers+App IDs → type App, register an explicit App ID whose bundle ID exactly matches your app's (by default <org>.<project name> from pyproject.toml). No extra capabilities are needed.

Creating the provisioning profile

Under Profiles+, select Mac App Store Connect (under Distribution), then:

  1. Select the App ID registered above.
  2. Select your Apple Distribution certificate.
  3. Name the profile and click Generate.
  4. Download the .provisionprofile file and keep it with your project — it contains no secrets (it is a document signed by Apple authorizing your App ID and team), so it is safe to commit.

Creating the App Store Connect app record

In App Store ConnectMy Apps+New App: platform macOS, the bundle ID from above, any name and SKU. Then note the app's numeric Apple ID under App Information → General Information — command-line uploads are keyed to it.

Provisioning profile

The profile created above. A relative path resolves against the project directory (where pyproject.toml lives). The build embeds it at Contents/embedded.provisionprofile — sealed by the app's signature — and fails fast when the profile's App ID does not cover the app's bundle ID, a mismatch that would otherwise surface only after upload as ITMS-90889.

Resolution order

The provisioning profile is determined in the following order of precedence:

  1. --macos-provisioning-profile
  2. [tool.flet.macos.signing.app-store].provisioning_profile (per-lane)
  3. [tool.flet.macos.signing].provisioning_profile
  4. FLET_MACOS_PROVISIONING_PROFILE environment variable
  5. Default: none — App Store builds fail without one.

Installer identity

The certificate that signs the .pkg — the exact certificate name (as listed by security find-identity -v -p basic), its SHA-1 fingerprint, or a unique substring, matched only among installer certificates.

Resolution order

The installer identity is determined in the following order of precedence:

  1. --macos-installer-identity
  2. [tool.flet.macos.signing.app-store].installer_identity (per-lane)
  3. [tool.flet.macos.signing].installer_identity
  4. FLET_MACOS_INSTALLER_IDENTITY environment variable
  5. Default: none — the certificate is auto-discovered.

Building for the App Store

flet build macos --macos-distribution app-store \
--macos-provisioning-profile certs/MyApp_MacAppStore.provisionprofile \
--info-plist LSApplicationCategoryType="public.app-category.productivity" \
ITSAppUsesNonExemptEncryption=False
  • Setting LSApplicationCategoryType is required — App Store validation rejects the package without it. See supported/possible values here.
  • Setting ITSAppUsesNonExemptEncryption is optional but answers the export-compliance question once and for all. If it's not set, App Store Connect walks you through an export compliance questionnaire every time you upload a new version of your app. If set to false indicates that your app does not use encryption, which can help streamline the submission process.

Both are ordinary Info Property List keys.

Neither signing identity appears in the examples above: both are auto-discovered when not configured. To pin them explicitly, configure the signing identity for the app certificate and the installer identity for the .pkg certificate.

The lane's resolution order is shared by all distribution channels.

Uploading

Upload the .pkg with Transporter or from the command line, authenticating with the same App Store Connect API key used for notarization — altool reads the .p8 file from ~/.appstoreconnect/private_keys/:

xcrun altool --validate-app -f build/macos/MyApp.pkg -t macos \
--apiKey <KEY_ID> --apiIssuer <ISSUER_ID>
xcrun altool --upload-package build/macos/MyApp.pkg -t macos \
--apiKey <KEY_ID> --apiIssuer <ISSUER_ID> \
--apple-id <NUMERIC_APP_ID> --bundle-id <BUNDLE_ID> \
--bundle-version <BUILD_NUMBER> --bundle-short-version-string <VERSION>

<NUMERIC_APP_ID> is the app record's Apple ID noted earlier, and every upload needs a unique build number. After processing (minutes; failures arrive by email as ITMS-xxxx codes), the build appears in the TestFlight tab of your app record — internal testers can install it without beta review. Note that --validate-app does not catch everything processing checks, so a clean upload is only confirmed once processing completes.

Troubleshooting

SymptomCause and fix
ITMS-90889: Invalid Provisioning Profile after uploadThe embedded profile doesn't match the upload. flet build pre-checks that the profile covers the app's bundle ID, so this usually means the profile doesn't include the Apple Distribution certificate that signed the app — regenerate the profile selecting that certificate and rebuild.
91109: Invalid package contentscom.apple.quarantineA file in the package carries the quarantine attribute macOS puts on downloads. flet build strips it from everything it packages, so this points to files added after the build — rebuild, or clean with xattr -cr before re-packaging.
The store build can't read or write files the direct build couldThe app-store lane enables the mandatory App Sandbox: file access is confined to the app's container (~/Library/Containers/<bundle-id>). Relative paths and os.getcwd() already land there; for anything outside, let the user pick the path with FilePicker — user-selected locations are granted to a sandboxed app.
After a TestFlight install, flet build macos fails with Permission denied under build/macOS app relocation: the installer updates an existing copy with the same bundle ID wherever it finds one — including your local build products — leaving root-owned files (_MASReceipt) behind. sudo rm -rf the affected build/macos directory, and delete dev copies of the app before installing the store build.