Skip to main content

fpack 1.1.12 copy "fpack: ^1.1.12" to clipboard
fpack: ^1.1.12 copied to clipboard

Package Flutter apps into release artifacts for every platform (apk, aab, ipa, dmg, exe, msix, deb, rpm, AppImage, web) with one command. Native Go core, thin Dart launcher.

fpack #

English · 中文

📖 Documentation → matkurban.github.io/fpack #

Guides for every platform (signing, notarization, installers), every command and config key, CI recipes and troubleshooting. A complete sample app with a commented fpack.yaml lives in example/.

One command turns a Flutter project into release artifacts for every platform. Android (APK / per-ABI APKs / AAB), iOS (IPA), macOS (.app zip / DMG / pkg installer, signed + notarized), Windows (zip / Inno Setup installer / MSIX), Linux (tar.gz / deb / rpm / AppImage / Snap) and Web (zip).

dart pub global activate fpack    # install (git / local path also work, see below)
cd my_flutter_app
fpack doctor                      # what can this machine build? what is missing and how to install it?
fpack build apk aab               # → dist/1.0.0+1/my_app-1.0.0+1-android-universal.apk …
fpack build --all                 # everything this machine can build; the rest is skipped with a reason
  • Works with zero configuration: reads pubspec.yaml, Gradle and the Xcode projects; the fpack.yaml generated by fpack init is entirely optional.
  • Never modifies your project files: fpack only drives the Flutter toolchain and packaging tools. Signing data is injected through environment variables, Gradle files are never edited. The only file it ever writes into the project is the fpack.yaml from fpack init; artifacts go to dist/, temporary files to build/fpack/.
  • Native core: the core is written in Go and runs as a native binary (≈10 ms startup, no runtime dependencies); the Dart package is a thin launcher that finds or prepares the binary of exactly the same version.
  • Everything is configurable: 211 fpack.yaml keys (installer metadata, signing, DMG layout, Inno Setup languages/privileges, MSIX, deb/rpm metadata and scripts, hooks, file name templates…), any value can come from the environment (${VAR} / ${VAR:-default}, also for booleans and numbers) and the build, signing and notarization settings have dedicated FPACK_* variables; the configuration is strictly validated (unknown keys get a "did you mean …", type errors point to the line) and a JSON Schema gives editor completion.
  • Built for humans: colored output and spinners (plain text in CI), failures show the key error excerpt + a fix + the full log path, --dry-run shows every exact command, --json for machines, English/Chinese chosen automatically.

Contents #


Install #

Requires Dart 3.8+ (bundled with Flutter). Pick one:

Method Command Notes
pub.dev dart pub global activate fpack recommended
git dart pub global activate --source git https://github.com/Matkurban/fpack the first run downloads the core from the GitHub Release (SHA-256 verified); builds it with local Go only if the download is unavailable
local path dart pub global activate --source path /path/to/fpack works offline: the release tarball contains prebuilt binaries for all 6 platforms

Make sure ~/.pub-cache/bin (Windows: %LOCALAPPDATA%\Pub\Cache\bin) is on your PATH:

echo 'export PATH="$PATH:$HOME/.pub-cache/bin"' >> ~/.zshrc && source ~/.zshrc
fpack --version

How the launcher finds the native core #

On every run fpack looks for an fpack-core whose version exactly matches the Dart package (checked with fpack-core --core-version), in this order. A verified prebuilt binary always wins; a local build is only the last resort:

  1. the FPACK_CORE environment variable (development)

  2. the user cache: macOS ~/Library/Caches/fpack/<version>/<os>-<arch>/, Linux $XDG_CACHE_HOME/fpack/… (default ~/.cache/fpack), Windows %LOCALAPPDATA%\fpack\… (override with FPACK_HOME)

  3. the bundled prebuilt binary prebuilt/<os>-<arch>/ (when present): verified against the SHA-256 in prebuilt/manifest.json, then copied into the cache

  4. a download of this platform's binary (≈4 MB) from the GitHub Release, verified against checksums.txt — a mismatching file is never executed

  5. only if the download fails or is disabled: a local build from the bundled Go sources (go build -trimpath, dependencies are vendored, no network needed), with a clear note that it fell back to a local build and which Go it used:

    fpack: note: no verified prebuilt core available (download: …connection refused…);
    fpack: falling back to a local build from the bundled sources with go1.27.2 (/usr/local/go/bin/go) – first run only, ~30s…
    

Controls:

Variable Effect
FPACK_NO_DOWNLOAD=1 offline / air-gapped: never download; use the bundled binary or build locally with Go
FPACK_DOWNLOAD_URL download from a mirror (a directory with checksums.txt and fpack-core-<os>-<arch> (.exe on Windows), laid out like the GitHub Release)
FPACK_GO=/path/to/go / FPACK_GO=none the Go used for the fallback build / never build locally
FPACK_REBUILD=1 development: ignore the cache and build with local Go first (after changing the Go sources)
FPACK_HOME cache directory

A cached core is reused until the version changes; to replace a locally built core with the downloaded binary, delete the cache directory (fpack --wrapper-info shows its path).

fpack --wrapper-info shows each step's path and the binary that is finally used.

pub.dev package size trade-off: the binaries for 6 platforms are ≈23 MB (≈10 MB compressed). Bundling them in the pub package → works offline right after install, but every user downloads every platform's binary; not bundling → a tiny package (≈350 KB) that downloads this platform's ≈4 MB binary from the GitHub Release on first run (and only builds with local Go, ≈30 s, if that is unavailable). The release workflow's repository variable BUNDLE_BINARIES decides; the fpack-vX.Y.Z.tar.gz on the GitHub Release always contains all binaries for offline / intranet use.


Quick start #

cd my_flutter_app
fpack doctor                     # checks the environment, with an install/fix command per item
fpack build apk --dry-run        # shows the commands that would run (runs nothing)
fpack build apk                  # actually build
fpack init                       # (optional) commented fpack.yaml, interactive; --yes uses the defaults

Artifacts go to dist/<version>+<build>/ by default, together with SHA256SUMS (check with shasum -a 256 -c SHA256SUMS):

dist/1.0.0+1/
  my_app-1.0.0+1-android-universal.apk
  my_app-1.0.0+1-android-arm64-v8a.apk
  my_app-1.0.0+1-android.aab
  my_app-1.0.0+1-ios-arm64.ipa
  my_app-1.0.0+1-macos-universal.dmg
  my_app-1.0.0+1-macos.pkg
  SHA256SUMS

Existing artifacts are never overwritten by default: bump the version, use --build-number N, or pass --force.


Commands #

fpack build [targets…] [options] [-- args passed to flutter]
fpack doctor          check the prerequisites of every target, with fixes
fpack list            list all targets, their artifact formats and whether this machine can build them
fpack init            write an fpack.yaml listing every key with English/Chinese comments (-y/--yes uses the
                      detected defaults; if it exists, writes fpack.yaml.new and shows a diff, --force overwrites)
fpack schema          print the JSON Schema of fpack.yaml (-o FILE writes a file)
fpack notarize status [dir|ID]   query the state of macOS notary submissions (reads dist/…/notarization.json)
fpack notarize finish [dir]      wait for the notary result, staple the ticket, update SHA256SUMS
fpack clean           remove build/fpack; --dist also removes dist/; --flutter-clean runs flutter clean; --all everything
fpack version         version information (same as --version / -V)
fpack help <command>  help for a command

Global options: -C, --project DIR, --config FILE, --flutter SDK, --lang zh|en, -v, --verbose, --json, --no-color, -y, --yes.

build options #

Option Description
-a, --all build every target this machine can build; skip the rest with a reason
-n, --dry-run print the full plan and exact commands, run nothing
-m, --mode / --release --profile --debug build mode (default release)
--flavor NAME Android productFlavor / Xcode scheme. A name that is not among the detected flavors is an error (exit 3, with a suggestion); if no flavors are detected it is only a warning
-t, --target FILE entry point, e.g. lib/main_prod.dart
--dart-define K=V (repeatable) / --dart-define-from-file FILE compile-time variables
--build-name X.Y.Z / --build-number N override the pubspec version
--split-per-abi[=true|both] / --abis LIST per-ABI APKs; both also keeps the universal APK
--obfuscate / --split-debug-info DIR obfuscation; symbols default to <output>/debug-info/<platform>
-o, --output DIR / -f, --force output directory / allow overwriting
--export-method M / --export-options-plist FILE / --no-codesign iOS export
--sign --no-sign --sign-identity ID --installer-identity ID --notarize --no-notarize --notarize-no-wait --notary-profile NAME --dmg-tool T macOS signing (app / pkg), notarization (--notarize-no-wait: submit without waiting), DMG tool
--base-href PATH / --wasm Web
-- … everything after is passed to flutter build, e.g. -- --no-tree-shake-icons

Examples:

fpack build apk aab --flavor prod --dart-define-from-file env/prod.json
fpack build apk --split-per-abi=both
fpack build ipa --export-method ad-hoc
fpack build ipa --no-codesign                # unsigned IPA (Payload layout)
fpack build macos dmg                        # signs/notarizes per fpack.yaml macos.sign (unsigned if not configured)
fpack build dmg --no-notarize                # quick local package
fpack build dmg --notarize-no-wait           # submit and exit; later: fpack notarize finish
fpack build macos dmg pkg                    # zip + DMG + pkg installer from a single flutter build
fpack build --all --json > result.json
fpack -C apps/client build web --base-href /app/

Precedence: command-line flags > FPACK_* environment variables > fpack.yaml > defaults.

No targets: fpack build without targets builds build.targets from fpack.yaml; without that key it stops with exit code 2 and asks which targets to build.

Interrupting: one Ctrl-C stops the running flutter/gradle/xcodebuild gracefully (the whole process group), a second one kills it; exit code 130.

Monorepos: in a project subdirectory fpack walks up to pubspec.yaml; at the repository root it lists the Flutter projects it found — pick one with -C <dir>.

Flutter SDK lookup: --flutter → FPACK_FLUTTER → flutter.sdk in fpack.yaml → FVM (.fvm/flutter_sdk, .fvmrc) → FLUTTER_ROOT → PATH → common install locations (~/develop/flutter, ~/flutter, …).


Targets and artifacts #

The default file name template is {app}{-flavor}-{version}{+build}-{platform}{-arch}{-variant}{-mode} (no mode for release).

Target Host OS Artifact Tools
apk any -android-universal.apk, -android-arm64-v8a.apk … flutter, apksigner (signature check)
aab any -android.aab flutter, keytool (signature check)
ipa macOS -ios-arm64.ipa (unsigned: -ios-arm64-unsigned.ipa) flutter/xcodebuild, ditto with --no-codesign
macos macOS -macos-universal.zip flutter, codesign, ditto, notarytool
dmg macOS -macos-universal.dmg hdiutil or create-dmg, codesign, notarytool, stapler
pkg macOS -macos.pkg pkgbuild + productbuild (installs to /Applications), Developer ID Installer signing, notarytool, stapler
windows Windows -windows-x64-portable.zip flutter
exe Windows -windows-x64-setup.exe Inno Setup (ISCC)
msix Windows -windows-x64.msix the msix dev dependency (dart run msix:create)
linux Linux -linux-x64.tar.gz flutter
deb Linux -linux-x64.deb dpkg-deb
rpm Linux -linux-x64.rpm rpmbuild
appimage Linux -linux-x64.AppImage appimagetool (skipped by --all when missing)
snap Linux -linux-x64.snap snapcraft (skipped by --all when missing)
web any -web.zip flutter

Flutter cannot cross-compile iOS/macOS/Windows/Linux desktop apps. fpack build --all skips targets this machine cannot build and says why; naming one explicitly (e.g. fpack build ipa on Linux) is an error that points you to a CI runner of that OS. --dry-run still shows a reference plan for such targets.

One flutter build is shared per run: macos + dmg + pkg build once, linux + deb + rpm + appimage + snap build once, windows + exe + msix build once.

Target aliases work on the command line and in build.targets: bundle/appbundle → aab, android → apk, ios → ipa, mac/osx/app → macos, win/zip/portable → windows, setup/installer/inno → exe, tar/tgz/tar.gz → linux, debian → deb, fedora → rpm, image/AppImage → appimage, snapcraft → snap.

fpack doctor lists a target as "ready to build" only when nothing it needs is missing; for apk/aab that includes Java (JDK 17+), even though a build still tries without it (Gradle may find a JDK fpack cannot see).


Configuration: fpack.yaml #

📖 Full configuration reference (every fpack.yaml key, every FPACK_* variable, every flag, precedence rules and a complete example): doc/configuration.md · website

Every key is optional. fpack init writes a file that lists every key, each with an English (--lang zh: Chinese) comment describing what it does, its allowed values, default, an example and the matching environment variable / flag, pre-filled with values detected in the project (application ID, flavors, version, team ID, Inno AppId…); leave the keys you do not need commented out.

  • Validation: unknown keys (with "did you mean …"), type errors (line 12: android.signing.v1: expected true or false, got "maybe"), allowed values and ranges, conflicting combinations; files used by the selected targets are checked before building.
  • Editor completion: the first line of the generated file is # yaml-language-server: $schema=…/schema/fpack.schema.json, so VS Code (YAML extension) / JetBrains give completion, hover docs (English/Chinese) and validation. fpack schema -o fpack.schema.json exports a local copy.
  • Environment variables: ${VAR} / ${VAR:-default} are expanded on load in any value (obfuscate: ${OBF:-false} works too); build, signing and notarization settings also have dedicated FPACK_* variables (see the full list).
# yaml-language-server: $schema=https://raw.githubusercontent.com/Matkurban/fpack/main/schema/fpack.schema.json
app:
  display_name: XueHua IM
  publisher: XueHua Tech             # default: CompanyName in windows/runner/Runner.rc
  homepage: https://xuehua.example.com
  license: MIT

build:
  targets: [apk, aab, ipa, dmg]      # built by `fpack build` without targets
  flavor: prod
  dart_define_from_file: [config/prod.json]

output:
  dir: "dist/{version}{+build}"
  names: { exe: "{app}-setup-{version}", web: "{app}-web" }   # per-target names; {target} {date} also work
  checksum_algorithm: sha512         # SHA512SUMS

hooks:
  pre_build: [dart run build_runner build --delete-conflicting-outputs]
  post_package: { dmg: ['./scripts/upload.sh "$FPACK_ARTIFACT"'] }

android:
  split_per_abi: both
  signing:
    store_file: ~/keys/upload.jks
    store_password: ${FPACK_ANDROID_KEYSTORE_PASSWORD}
    key_alias: upload
    v4: true                         # any of v1–v4 re-signs with apksigner; v4 also writes the .idsig

ios:
  export_method: ad-hoc
  team_id: ABCDE12345                # fpack then generates ExportOptions.plist
  provisioning_profiles: { com.xuehua.im: XueHua AdHoc }

macos:
  sign:
    identity: "Developer ID Application: XueHua Tech (ABCDE12345)"
    notary_profile: XueHua           # or notary_api_key/_id/_issuer, or notary_apple_id/_team_id/_password
  notarize:
    wait: true                       # false: submit without waiting (staple later with fpack notarize finish)
  dmg: { background: macos/dmg/bg.png, window_size: [660, 400], format: ULFO }
  pkg: { min_os: "11.0", postinstall: macos/installer/postinstall.sh }

windows:
  inno_setup: { languages: [zh-CN, en], privileges: ask, desktop_icon: checked }
  sign: { certificate: C:/certs/codesign.pfx, password: ${WINDOWS_CERT_PASSWORD} }
  msix: { publisher: "CN=XueHua Tech", capabilities: [internetClient] }

linux:
  categories: [Network, InstantMessaging]
  metainfo: linux/com.xuehua.im.metainfo.xml
  deb: { depends: ["libgtk-3-0 | libgtk-3-0t64"], postinst: linux/postinst }
  rpm: { requires: [gtk3], license: MIT }

web:
  base_href: /app/
  source_maps: true

Hooks (hooks): pre_build (once before the first flutter build; a failure stops the run), pre_package.<target> / post_package.<target> (around packaging, with FPACK_TARGET, FPACK_ARTIFACT, FPACK_ARTIFACTS), post_build (once at the end, with FPACK_ARTIFACTS, FPACK_SUCCESS = 1/0; it also runs after build failures, but is skipped when every target already failed its checks, e.g. a missing tool or unknown flavor). All hooks run in the project root through sh -c (Windows: cmd /S /C, so quoted paths work as typed) with FPACK_PROJECT_ROOT, FPACK_OUTPUT_DIR, FPACK_VERSION, FPACK_BUILD_NUMBER, FPACK_MODE, FPACK_FLAVOR; write $VAR (Windows cmd: %VAR%) for variables that only exist while the hook runs (${VAR} is expanded when the configuration is loaded). A failing hook means exit code 1.


Signing #

Android signing #

fpack does not edit Gradle files. It signs release builds through the Android Gradle plugin's standard android.injected.signing.* properties (passed as ORG_GRADLE_PROJECT_* environment variables); passwords never appear on the command line, in logs or in --dry-run output (shown as ***).

export FPACK_ANDROID_KEYSTORE=~/keys/upload-keystore.jks
export FPACK_ANDROID_KEYSTORE_PASSWORD='…'
export FPACK_ANDROID_KEY_ALIAS=upload
export FPACK_ANDROID_KEY_PASSWORD='…'       # defaults to the keystore password
fpack build apk aab
  • In CI, put the base64 of the keystore into FPACK_ANDROID_KEYSTORE_BASE64; fpack writes it to build/fpack/secrets/ (mode 0600) and deletes it after the build.
  • Before building, keytool checks the password and alias and tells you which one is wrong.
  • After building, apksigner / keytool -printcert verify the artifacts and show the signer; a remaining Android Debug certificate triggers a warning.
  • If the project already has its own key.properties + signingConfigs, just don't configure fpack signing and the project's setup is used.
  • Without signing configured and with release using the debug key (the flutter create default) fpack notes: fine for testing, Google Play will reject it.

iOS export and signing #

fpack build ipa                                   # Flutter default (App Store Connect)
fpack build ipa --export-method ad-hoc
fpack build ipa --export-options-plist ios/ExportOptions.plist
fpack build ipa --no-codesign                     # unsigned IPA for re-signing later

doctor checks Xcode, CocoaPods (when there is a Podfile), the project's DEVELOPMENT_TEAM and signing certificates; on failure the key Xcode errors (certificates, provisioning profiles, team, pods…) are translated into concrete fixes.

macOS signing and notarization #

For distribution outside the App Store (Developer ID). Enabled only through fpack's own settings (low → high precedence):

  1. macos.sign in fpack.yaml (fpack init writes a commented section with placeholders and lists the Developer ID certificates found in your keychain)
  2. environment variables FPACK_MACOS_SIGN, FPACK_MACOS_SIGN_IDENTITY, FPACK_MACOS_INSTALLER_IDENTITY, FPACK_MACOS_NOTARIZE, FPACK_MACOS_NOTARY_PROFILE
  3. flags --sign/--no-sign, --sign-identity, --installer-identity, --notarize/--no-notarize, --notary-profile
macos:
  sign:
    identity: "Developer ID Application: Your Name (TEAMID)"   # turns signing on
    notary_profile: XueHua                                     # turns notarization on

One notary credential (highest priority first): keychain profile notary_profile (recommended locally) → App Store Connect API key notary_api_key + notary_api_key_id (+ notary_api_issuer, recommended for CI) → Apple ID notary_apple_id + notary_team_id + notary_password (app-specific password, redacted in logs). Signing uses the Hardened Runtime by default (hardened_runtime, required for notarization).

Rules: an identity turns signing on; any notary credential turns notarization on; turning signing off also turns notarization off; --sign without an identity picks the first "Developer ID Application" certificate in the keychain; --notarize (or notarize: true) without any credential uses the keychain profile NotaryProfile; a missing identity lists the available ones. With nothing configured the .app keeps the Xcode project's own signature, the DMG is unsigned, and the artifact notes explain how to configure signing. fpack does not read other tools' settings in pubspec.yaml (such as the dmg: section of the dmg package).

DMG flow: copy the .app → codesign --deep the nested code → re-sign the app with the Hardened Runtime + macos/Runner/Release.entitlements → verify → hdiutil (or create-dmg, with background image, window size/position, icon size and positions, volume icon, license; format/filesystem work with both tools) builds the DMG → sign the DMG → xcrun notarytool submit → wait for the result → stapler staple → spctl assessment.

Waiting for notarization: Apple usually needs a few minutes. As soon as the upload has a submission ID, fpack writes NOTARIZATION.md (language follows --lang) and notarization.json into the output directory: artifact path and SHA-256, submission ID, submit time, the credential used (keychain profile name / API key ID / Apple ID — never a password) and copy-paste commands (xcrun notarytool info/wait/log …, xcrun stapler staple/validate …, spctl -a -vv …, fpack notarize status/finish), and prints the file's path. While waiting it shows the elapsed time and the hint: Notarization usually takes a few minutes. Ctrl-C stops waiting — Apple keeps processing; query it later with the commands in NOTARIZATION.md. The file is updated with the final state (Accepted / Invalid); on rejection fpack downloads Apple's log (notary-log-<file>.json), summarizes the issues and suggests fixes.

  • No waiting: macos.notarize.wait: false (or FPACK_NOTARIZE_WAIT=false, --notarize-no-wait) submits, writes the record and finishes; the summary and --json report the artifact's notarization state as submitted (not stapled yet).
  • Continue later: fpack notarize status [dir|ID] asks Apple about all pending submissions; fpack notarize finish [dir] waits for the results, staples accepted DMGs/pkgs (an app zip is unpacked, the .app stapled and re-zipped), rewrites SHA256SUMS and updates the record; rejected ones get their log downloaded and summarized. Also works after a Ctrl-C.

How to obtain the certificates and notary credentials is described in the next section, Getting macOS signing certificates and notarization credentials.

fpack build macos produces a signed zip; with notarization on, the zip is notarized too (submit the zip → staple the .app → re-zip).

Getting macOS signing certificates and notarization credentials #

Distributing a macOS app outside the App Store needs a Developer ID Application certificate (signs the .app / zip / DMG), optionally a Developer ID Installer certificate (signs the pkg), and one kind of notarization credential. All of this requires a paid Apple Developer Program membership, and Developer ID certificates can only be created by the team's Account Holder.

1. Create the Developer ID certificates (on a Mac)

  • Xcode → Settings… → Accounts → select the team → Manage Certificates… → + → Developer ID Application (and Developer ID Installer if you build pkgs). Certificate and private key go straight into the login keychain.

  • Or on the web: Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority… (save the CSR to disk) → developer.apple.com/account/resources/certificates → + → Developer ID Application / Developer ID Installer → upload the CSR → download the .cer and double-click it — on the Mac that created the CSR, which holds the private key.

  • Check:

    security find-identity -v -p codesigning   # "Developer ID Application: Your Name (ABCDE12345)"
    security find-identity -v -p basic         # also lists "Developer ID Installer: …"
    
  • Tell fpack (use the quoted names exactly as printed; the 10 characters in parentheses are your Team ID):

    Purpose fpack.yaml Environment Flag
    App signing identity macos.sign.identity FPACK_MACOS_SIGN_IDENTITY --sign-identity
    pkg signing identity macos.sign.installer_identity FPACK_MACOS_INSTALLER_IDENTITY --installer-identity

2. Notarization credentials (pick one)

a) Keychain profile (recommended on your own Mac): Apple ID + app-specific password + Team ID, stored once.

  1. account.apple.com → Sign-In and Security → App-Specific Passwords → generate one (looks like abcd-efgh-ijkl-mnop).

  2. Team ID: developer.apple.com/account → Membership details, or the 10 characters in the certificate name.

  3. Store it in the keychain (omit --password to be prompted):

    xcrun notarytool store-credentials fpack-notary \
      --apple-id you@example.com --team-id ABCDE12345 --password abcd-efgh-ijkl-mnop
    xcrun notarytool history --keychain-profile fpack-notary   # check
    
  4. fpack: macos.sign.notary_profile: fpack-notary (or FPACK_MACOS_NOTARY_PROFILE, --notary-profile).

Without a stored profile you can pass the Apple ID directly: notary_apple_id / notary_team_id / notary_password (FPACK_NOTARY_APPLE_ID, FPACK_NOTARY_TEAM_ID, FPACK_NOTARY_PASSWORD). Keep the password in an environment variable or CI secret; fpack redacts it in logs, --dry-run output and NOTARIZATION.md.

b) App Store Connect API key (recommended for CI): not tied to a person's Apple ID, revocable at any time.

  1. App Store Connect → Users and Access → Integrations → App Store Connect API → Team Keys → +, access Developer (or higher).

  2. Download AuthKey_<KEY_ID>.p8 — it can be downloaded only once, keep it safe — and note the Key ID and the Issuer ID shown on the page (individual keys have no issuer; leave it out).

  3. fpack:

    Value fpack.yaml Environment
    path of the .p8 file macos.sign.notary_api_key FPACK_NOTARY_API_KEY
    Key ID macos.sign.notary_api_key_id FPACK_NOTARY_API_KEY_ID
    Issuer ID macos.sign.notary_api_issuer FPACK_NOTARY_API_ISSUER

    Check: xcrun notarytool history --key AuthKey_ABC123DEF4.p8 --key-id ABC123DEF4 --issuer <Issuer ID>.

Priority: notary_profile → API key → Apple ID; any of them turns notarization on.

3. Export the certificates (.p12) for CI

  1. Keychain Access → login keychain → My Certificates → select "Developer ID Application: …" (expand it: the private key must be underneath) → File → Export Items… → format Personal Information Exchange (.p12) → choose an export password. Repeat for "Developer ID Installer: …" if you build pkgs (or select both and export them into one .p12).

  2. Base64-encode it into CI secrets (e.g. MACOS_CERTS_P12_BASE64, MACOS_CERTS_P12_PASSWORD), the API key likewise (NOTARY_API_KEY_P8_BASE64):

    base64 -i DeveloperID.p12 | pbcopy
    base64 -i AuthKey_ABC123DEF4.p8 | pbcopy
    
  3. Import them into a temporary keychain on the runner (GitHub Actions):

    - name: import Developer ID certificates
      env:
        P12_BASE64: ${{ secrets.MACOS_CERTS_P12_BASE64 }}
        P12_PASSWORD: ${{ secrets.MACOS_CERTS_P12_PASSWORD }}
        KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}   # any random string
        API_KEY_BASE64: ${{ secrets.NOTARY_API_KEY_P8_BASE64 }}
      run: |
        KEYCHAIN="$RUNNER_TEMP/signing.keychain-db"
        echo "$P12_BASE64" | base64 --decode > "$RUNNER_TEMP/certs.p12"
        security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
        security set-keychain-settings -lut 21600 "$KEYCHAIN"
        security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
        security import "$RUNNER_TEMP/certs.p12" -k "$KEYCHAIN" -P "$P12_PASSWORD" \
          -T /usr/bin/codesign -T /usr/bin/productbuild -T /usr/bin/pkgbuild
        security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
        security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
        security find-identity -v -p codesigning "$KEYCHAIN"
        echo "$API_KEY_BASE64" | base64 --decode > "$RUNNER_TEMP/AuthKey.p8"
        rm "$RUNNER_TEMP/certs.p12"
    - name: fpack build macos dmg pkg
      env:
        FPACK_MACOS_SIGN_IDENTITY: "Developer ID Application: Your Name (ABCDE12345)"
        FPACK_MACOS_INSTALLER_IDENTITY: "Developer ID Installer: Your Name (ABCDE12345)"
        FPACK_NOTARY_API_KEY: ${{ runner.temp }}/AuthKey.p8
        FPACK_NOTARY_API_KEY_ID: ${{ secrets.NOTARY_API_KEY_ID }}
        FPACK_NOTARY_API_ISSUER: ${{ secrets.NOTARY_API_ISSUER }}
      run: fpack build macos dmg pkg
    - name: clean up keychain
      if: always()
      run: security delete-keychain "$RUNNER_TEMP/signing.keychain-db" || true
    

    set-key-partition-list lets codesign / productbuild use the private key without a prompt; -lut 21600 keeps the keychain unlocked for 6 hours. Never commit certificates, .p12/.p8 files or passwords.

macOS installer (pkg) #

fpack build pkg uses pkgbuild to create a component package that installs the app into /Applications (not relocatable, so upgrades always replace the copy in /Applications), then productbuild to create a distribution package (Apple silicon and Intel, no Rosetta prompt). macos.pkg sets identifier, install_location, title and the installer's welcome / readme / license / conclusion pages (.html/.rtf/.txt) and background (image).

  • Signing: a pkg needs a separate "Developer ID Installer" certificate (not the "Developer ID Application" one that signs the app), set with macos.sign.installer_identity / FPACK_MACOS_INSTALLER_IDENTITY / --installer-identity; the signed pkg is checked with pkgutil --check-signature. Without it an unsigned pkg is produced with a note; --no-sign turns pkg signing off as well.
  • App signing: with macos.sign.identity set, the app inside the pkg is re-signed with Developer ID just like for the zip/DMG.
  • Notarization: shares the DMG settings; only signed pkgs are submitted and stapled, then assessed with spctl --assess --type install.
  • More options: min_os (refuse to install below this version; default is the project's MACOSX_DEPLOYMENT_TARGET), require_restart, relocatable, preinstall / postinstall scripts (made executable automatically), version.
  • doctor checks pkgbuild/productbuild and lists the installer certificates in the keychain.

Windows signing and installers #

Code signing is optional. Choose one certificate source in windows.sign:

  • a .pfx file: certificate + password (FPACK_WINDOWS_CERTIFICATE / FPACK_WINDOWS_CERTIFICATE_PASSWORD; the password is never printed),
  • the certificate store: thumbprint (/sha1) or subject (/n), with store (/s, default My) and machine_store: true (/sm),
  • a signtool plugin for cloud signing, e.g. Azure Trusted Signing: dlib + dlib_metadata (/dlib / /dmdf).

Right after flutter build windows, fpack signs files of the Release folder (default *.exe and *.dll), so the portable zip, the installer and the MSIX all contain signed binaries. The Inno Setup installer and uninstaller are signed through SignTool=; the MSIX is signed by msix:create with the same certificate (with dlib, fpack signs the finished MSIX). Every signature uses SHA-256 and an RFC 3161 timestamp (timestamp_url, default http://timestamp.digicert.com), with description (/d) and url (/du); afterwards signtool verify /pa checks it (verify: false turns that off; a failure, e.g. a self-signed test certificate, is only a warning). signtool is found on PATH or in the newest Windows SDK (or set signtool); fpack doctor reports it and how to install the SDK (winget install Microsoft.WindowsSDK.10.0.26100).

windows:
  sign:
    certificate: ${WINDOWS_PFX}            # or thumbprint: / subject: / dlib:
    password: ${WINDOWS_PFX_PASSWORD}
    url: https://example.com

Inno Setup installers support publisher/URL, copyright, install directory, Start menu group, desktop shortcut, license/info pages, icon and wizard images, minimum Windows version, privileges (user without admin / admin / ask) and multiple languages (languages: [zh-CN, en], the first is the default; fpack ships the Chinese language file older Inno Setup versions lack). MSIX display name, publisher, capabilities, file associations, protocols, version etc. can be set directly in windows.msix and take precedence over pubspec's msix_config.

Linux packages #

deb/rpm/AppImage install into linux.prefix (default /opt/<package>, with a symlink in /usr/bin); the .desktop file has Name/GenericName/Comment/Categories/Keywords/MimeType/StartupWMClass, the icon is scaled into the hicolor theme per icon_sizes, and an AppStream metainfo and copyright can be included. deb supports Depends/Recommends/Suggests/Conflicts/Section/Priority and maintainer scripts, rpm supports Requires/Group/License/URL and %pre/%post/%preun/%postun, and AppImage supports embedded update information (update_information, with the .zsync file written to the output directory).

The snap target packs the already built bundle with snapcraft. fpack generates snap/snapcraft.yaml (base core24, strict confinement, the gnome extension for GTK, plugs desktop, desktop-legacy, wayland, x11, opengl, audio-playback, network, home, and a dump part over the bundle, so Flutter is not rebuilt inside snapcraft) plus a .desktop file and the icon in snap/gui/. Name, summary, description, base, grade, confinement, license, plugs, extensions, stage packages, extra apps and the .desktop file can be set under linux.snap; linux.snap.template uses your own snapcraft.yaml instead. linux.snap.build_mode (or FPACK_SNAP_BUILD_MODE) chooses how snapcraft builds: destructive (directly on this machine, which must run the Ubuntu release of the base, 24.04 for core24; fastest, good for CI; snapcraft installs build dependencies here, so fpack runs it with sudo -n unless you are root: passwordless sudo as on CI, or run sudo -v first), lxd or multipass (isolated build environments). The default auto uses destructive on a matching Ubuntu and lxd elsewhere. Install snapcraft with sudo snap install snapcraft --classic, then test the snap with sudo snap install --dangerous <file>.snap.


CI example #

jobs:
  android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: subosito/flutter-action@v2
      - run: dart pub global activate fpack 1.1.12 && echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
      - run: fpack build apk aab --split-per-abi=both
        env:
          FPACK_ANDROID_KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
          FPACK_ANDROID_KEYSTORE_PASSWORD: ${{ secrets.KEYSTORE_PASSWORD }}
          FPACK_ANDROID_KEY_ALIAS: upload
      - uses: actions/upload-artifact@v7
        with: { name: android, path: dist/ }
  apple:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v7
      - uses: subosito/flutter-action@v2
      - run: dart pub global activate fpack 1.1.12 && echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
      - run: fpack build ipa dmg --json > result.json
  windows:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v7
      - uses: subosito/flutter-action@v2
      - run: dart pub global activate fpack 1.1.12
      - run: fpack build windows exe
  linux:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: subosito/flutter-action@v2
      - run: sudo apt-get install -y ninja-build libgtk-3-dev rpm
      - run: dart pub global activate fpack 1.1.12 && echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH
      - run: fpack build linux deb rpm web

For signed + notarized macOS builds in CI, see the certificate import steps above.

Without a TTY, spinners and colors are turned off and a heartbeat line is printed every 60 seconds so CI does not time out on silence.


AI agent skill #

fpack ships a package skill, fpack-cli, that teaches AI coding agents (Cursor, Claude Code, Gemini, Copilot, Cline, Codex…) how to use fpack: install, init, doctor, build targets, config keys and FPACK_* variables, signing, macOS notarization, CI and troubleshooting. Install it into your project's agent folder:

dart pub add dev:fpack                  # skills are installed from dependencies
dart run skills@ get --package fpack    # choose your agent, or add --agent cursor|claude|codex|…
dart run skills@ get --package fpack --skill fpack-cli   # non-interactive (CI/scripts)

Re-run after upgrading fpack. Details: AI agent skill.


Environment variables #

Full details (type, default, affected targets): doc/configuration.md.

Variable Effect
FPACK_FLUTTER Flutter SDK path
FPACK_CONFIG configuration file path
FPACK_MODE FPACK_FLAVOR FPACK_ENTRY FPACK_BUILD_NAME FPACK_BUILD_NUMBER build parameters
FPACK_OUTPUT_DIR FPACK_OVERWRITE FPACK_SPLIT_PER_ABI FPACK_OBFUSCATE output / Android / obfuscation
FPACK_ANDROID_KEYSTORE FPACK_ANDROID_KEYSTORE_BASE64 FPACK_ANDROID_KEYSTORE_PASSWORD FPACK_ANDROID_KEY_ALIAS FPACK_ANDROID_KEY_PASSWORD Android signing
FPACK_IOS_EXPORT_METHOD FPACK_IOS_EXPORT_OPTIONS_PLIST FPACK_IOS_CODESIGN iOS export
FPACK_MACOS_SIGN FPACK_MACOS_SIGN_IDENTITY FPACK_MACOS_INSTALLER_IDENTITY FPACK_MACOS_NOTARIZE FPACK_MACOS_NOTARY_PROFILE FPACK_NOTARIZE_WAIT FPACK_DMG_TOOL macOS
FPACK_NOTARY_APPLE_ID FPACK_NOTARY_TEAM_ID FPACK_NOTARY_PASSWORD FPACK_NOTARY_API_KEY FPACK_NOTARY_API_KEY_ID FPACK_NOTARY_API_ISSUER macOS notary credentials (Apple ID / API key)
FPACK_WINDOWS_CERTIFICATE FPACK_WINDOWS_CERTIFICATE_PASSWORD FPACK_WINDOWS_CERT_THUMBPRINT FPACK_WINDOWS_CERT_SUBJECT FPACK_WINDOWS_SIGN_DLIB FPACK_WINDOWS_SIGN_DLIB_METADATA Windows code signing (.pfx, certificate store, or a signtool dlib such as Azure Trusted Signing)
FPACK_LANG zh / en (default: the OS language – macOS UI language, Windows display language, Linux LC_ALL/LC_MESSAGES/LANGUAGE/LANG; Chinese → Chinese, anything else → English; details)
NO_COLOR / FPACK_NO_COLOR / FORCE_COLOR colors
FPACK_CORE use this native core binary (development)
FPACK_HOME core cache directory
FPACK_REBUILD=1 build the core with local Go first (after changing the Go sources)
FPACK_GO the Go used for the fallback build (none disables local builds)
FPACK_NO_DOWNLOAD=1 / FPACK_DOWNLOAD_URL never download (build locally) / download mirror (intranet); see how the launcher finds the native core

Exit codes #

Code Meaning
0 success (--dry-run: the plan was produced)
1 at least one target failed to build/package, or a hook failed; fpack doctor: problems were found; fpack notarize: a submission was rejected or a query failed
2 usage or configuration error (unknown flag or target, no targets and no build.targets, invalid fpack.yaml, no Flutter project found)
3 prerequisites missing / artifact already exists / nothing buildable / Flutter SDK not found; fpack notarize: no notarization record found
130 interrupted by Ctrl-C (an interrupted notarization wait keeps processing on Apple's side)

With --json, stdout carries only the JSON result (targets, states, artifact paths, sizes, SHA-256/512, notarization state notarization.state, durations, error excerpts, fixes); all human-readable output goes to stderr.


FAQ #

How do I read a failed build? fpack shows the key error excerpt (Gradle's "What went wrong", Xcode error lines, …), one concrete fix, and the full log path build/fpack/logs/…. Add -v to stream the full output.

A path dependency is missing (e.g. xue_hua_sdk: path: ../xue_hua_sdk): fpack checks before building and names the missing package; put it at the expected location relative to the project.

macOS says the developer cannot be verified: fpack removes the quarantine attribute when it installs the core binary; if it is still blocked, run FPACK_REBUILD=1 fpack --version to build it with local Go.

With a local path install every run starts with "Resolving dependencies…": that is pub's fixed behavior for path-activated packages (git / pub.dev installs don't do it). If another program consumes the --json output, install from git or pub.dev, or filter those lines.

Will it change my project? No. fpack only reads project files; flutter_launcher_icons.yaml, flutter_native_splash.yaml, distribute_options.yaml, package_rename_config.yaml, pubspec.yaml and the like are never modified. Consider adding dist/ to .gitignore.


Architecture #

fpack/
  bin/fpack.dart            Dart entry point (pub executable)
  lib/src/                  launcher: platform detection, core resolution (cache → bundled → download → local go build), SHA-256, signal forwarding
  go/                       native core (Go, dependencies vendored)
    cmd/fpack-core/         main
    internal/cli            argument parsing, commands, help
    internal/build          orchestration: classify → preflight → shared flutter steps → package → checksums → summary
    internal/targets        per-target preflight, flutter args, artifact lookup, packaging commands (dry-run and real run share the code)
    internal/config         key registry (drives env vars, validation, JSON Schema, init template and doc tables), fpack.yaml parsing
    internal/project        pubspec / Gradle / Xcode project info (read-only)
    internal/flutter        Flutter SDK lookup (FVM / PATH / …)
    internal/runner         subprocesses (process groups, Ctrl-C, logs, secret redaction)
    internal/hints          common errors → fixes
    internal/ui, i18n, pack, host, doctor, version
  schema/                   fpack.schema.json (generated from the key registry)
  prebuilt/                 prebuilt binaries + manifest.json (release artifacts, not in git)
  scripts/                  build_binaries.sh, check_versions.sh, package_dist.sh, e2e_*.py + fake-apple/ (E2E)
  website/                  documentation site (Jaspr; generated tables come from the Go tests)
  .github/workflows/        ci.yml (tests + lint), e2e.yml, docs.yml, release.yml

Development and releases #

cd go && go vet ./... && go test ./...      # Go core
FPACK_UPDATE=1 go test ./internal/config    # after changing the key registry: regenerate the schema and doc/configuration.md tables
dart pub get && dart analyze && dart test   # Dart launcher
scripts/build_binaries.sh                   # cross-compile 6 platforms → prebuilt/
scripts/package_dist.sh ../fpack-dist.tar.gz  # self-contained offline bundle
FPACK_CORE=$PWD/prebuilt/darwin-arm64/fpack-core fpack doctor   # debug with a specific core

Releasing: bump the version in pubspec.yaml, lib/src/version.dart and go/internal/version/version.go together (scripts/check_versions.sh checks them), update CHANGELOG.md, push a vX.Y.Z tag. release.yml cross-compiles, creates the GitHub Release (binaries + checksums.txt + offline bundle) and optionally publishes to pub.dev.

License #

MIT

1
likes
0
points
893
downloads

Documentation

Documentation

Publisher

verified publisherjsontodart.cn

Weekly Downloads

Package Flutter apps into release artifacts for every platform (apk, aab, ipa, dmg, exe, msix, deb, rpm, AppImage, web) with one command. Native Go core, thin Dart launcher.

Repository (GitHub)
View/report issues

Topics

#flutter #build #packaging #cli #release

License

unknown (license)

More

Packages that depend on fpack