flutpak

Pub Version

A Dart CLI tool that automates Flatpak packaging for Flutter applications. Analogous to flutter pub run build_runner build — describe your config once in pubspec.yaml and let flutpak prepare generate everything needed for a Flathub-compatible offline build.

  • One commandflutpak prepare generates sources, resolves patches, and creates/updates the manifest
  • No Python — pure Dart, compiles to a single native binary for CI
  • Config in pubspec.yaml — same pattern as msix_config, flutter_native_splash
  • Manifest generation — writes flatpak/<app_id>.yml with __FLATPAK_TAG__ / __FLATPAK_COMMIT__ placeholders that CI patches
  • Patches registry — known packages (e.g. objectbox_flutter_libs) get their patches resolved automatically
  • Retry on transient errors — pub.dev and Flutter artifact downloads retry on 429/5xx

Installation

dart pub global activate flutpak

Or compile a native binary (for CI without a Dart SDK):

git clone --depth 1 https://github.com/o-murphy/flutpak.git /tmp/flutpak_src
cd /tmp/flutpak_src && dart pub get
dart compile exe bin/flutpak.dart -o /tmp/flutpak

Quick start

1. Add config to pubspec.yaml

flutpak:
  output: flatpak
  flutter_version_file: flatpak/flutter.version  # optional

  pub:
    locks:
      - pubspec.lock
      - $FLUTTER_ROOT/packages/flutter_tools/pubspec.lock

  flutter:
    sdk: $FLUTTER_ROOT

  manifest:
    app_id: io.github.YourOrg.YourApp
    runtime_version: "25.08"
    command: yourapp
    repo_url: https://github.com/YourOrg/YourApp.git
    finish_args:
      - --share=ipc
      - --socket=wayland
      - --device=dri

2. First run — generate everything

FLUTTER_ROOT=/path/to/flutter flutpak prepare

This creates:

  • flatpak/io.github.YourOrg.YourApp.yml — manifest with __FLATPAK_TAG__ / __FLATPAK_COMMIT__ placeholders
  • flatpak/generated-sources.json — pub packages + Flutter SDK artifacts
  • flatpak/patches/ — patch files from the built-in registry (if needed)
  • flatpak/flutter.version — pinned Flutter version string (if flutter_version_file set)

Commit the generated files to git. The manifest is a generated artifact (like *.g.dart from build_runner) that you check in for Flathub review.

3. CI: pin tag/commit and regenerate sources

flutpak prepare \
  --tag "$TAG" \
  --commit "$COMMIT_SHA" \
  --sdk "$FLUTTER_ROOT"

This replaces __FLATPAK_TAG__ / __FLATPAK_COMMIT__ in the manifest and regenerates generated-sources.json.

Full config reference

Config lives in one of two places (error if both exist):

Location Key
pubspec.yaml flutpak: section
flutpak.yaml standalone file
flutpak:
  output: flatpak   # output directory for all generated files (default: flatpak)

  flutter_version_file: flatpak/flutter.version  # optional: write Flutter version here

  pub:
    locks:
      - pubspec.lock
      - $FLUTTER_ROOT/packages/flutter_tools/pubspec.lock  # $ENV vars are expanded

  flutter:
    sdk: $FLUTTER_ROOT
    patch: flatpak/patches/flutter/shared.sh.patch  # optional: custom shared.sh patch

  patches:                    # project-level patches for pub packages
    - package: objectbox_flutter_libs
      path: flatpak/patches/objectbox_flutter_libs/CMakeLists.txt.patch
      dest_subpath: linux     # optional: subdir within the pub package root
      # version: 5.3.1        # optional: auto-resolved from pubspec.lock if omitted

  manifest:
    app_id: io.github.YourOrg.YourApp
    runtime_version: "25.08"
    sdk_extensions:
      - org.freedesktop.Sdk.Extension.llvm20   # auto-adds llvm bin/lib to PATH
    command: yourapp
    repo_url: https://github.com/YourOrg/YourApp.git
    finish_args:
      - --share=ipc
      - --socket=fallback-x11
      - --socket=wayland
      - --device=dri
    extra_modules:
      - flatpak/modules/some-native-dep.yml    # included verbatim in modules list
    env:                                       # build-options env vars (shorthand)
      MY_VAR: value
    build_options:
      append_path: /custom/bin               # appended to PATH
      prepend_ld_library_path: /custom/lib   # prepended to LD_LIBRARY_PATH
      env:                                   # merged with top-level env:
        ANOTHER_VAR: value
    extra_sources:                           # verbatim flatpak sources (arch-specific archives, etc.)
      - type: archive
        only-arches:
          - x86_64
        url: https://github.com/example/lib/releases/download/v1.0/lib-linux-x64.tar.gz
        sha256: abc123...
        dest: lib-prebuilt
        strip-components: 0
      - type: archive
        only-arches:
          - aarch64
        url: https://github.com/example/lib/releases/download/v1.0/lib-linux-aarch64.tar.gz
        sha256: def456...
        dest: lib-prebuilt
        strip-components: 0

Required project structure

The generated manifest expects the following files to be present in your repository. flutpak prepare installs them into /app/share/ inside the Flatpak sandbox using install -Dm644, so a missing file will cause the build to fail with a clear error.

app/
└── share/
    ├── applications/
    │   └── <app_id>.desktop          # XDG desktop entry
    ├── icons/
    │   └── hicolor/
    │       └── 512x512/
    │           └── apps/
    │               └── <app_id>.png  # application icon (512×512 PNG)
    └── metainfo/
        └── <app_id>.metainfo.xml     # AppStream metainfo

These files must be committed to git — they are read directly from the source tree during the Flatpak build. flutpak does not generate them.

Patches registry

flutpak ships a built-in registry of patches for packages that need special handling inside the Flatpak sandbox. Patches are applied automatically when the package is found in any of your lock files.

Package What the patch does
objectbox_flutter_libs Replaces prebuilt binary download with a local objectbox-c archive
sqflite_common_ffi Adjusts CMake for sandbox builds

Project-level patches: entries always take priority over registry entries for the same package.

Commands

One-shot command — generates sources, resolves patches, and creates or updates the manifest.

# First run: generate everything from scratch
flutpak prepare

# CI: update placeholders + regenerate sources
flutpak prepare --tag v1.2.3 --commit abc1234567890 --sdk "$FLUTTER_ROOT"
Flag Description
--tag Git tag embedded in the manifest (e.g. v0.1.14). Omit to remove the tag: line.
--commit Full git commit SHA. Defaults to git rev-parse HEAD.
-s, --sdk Flutter SDK path. Defaults to $FLUTTER_ROOT.
--no-sources Skip source regeneration (manifest update only).
--pub-only Skip Flutter SDK sources, generate only pub packages.
--flutter-only Skip pub sources, generate only Flutter SDK artifacts.
-n, --dry-run Print what would be done without writing any files.
-c, --config Path to config file (default: flutpak.yaml).

Workflow:

pubspec.yaml  [flutpak: ...]
      ↓
flutpak prepare                    # first run: generates everything
      ↓
flatpak/<app_id>.yml               # manifest with __FLATPAK_TAG__ / __FLATPAK_COMMIT__
flatpak/generated-sources.json     # pub packages + Flutter SDK artifacts
flatpak/patches/                   # patches from registry (if applicable)
      ↓
git commit + push
      ↓
CI: flutpak prepare --tag $TAG --commit $SHA --sdk $FLUTTER_ROOT
      ↓
flatpak-builder build --repo=...

sources

Generates generated-sources.json combining pub packages and Flutter SDK.

flutpak sources \
  --lock pubspec.lock \
  --lock "$FLUTTER_ROOT/packages/flutter_tools/pubspec.lock" \
  --sdk "$FLUTTER_ROOT" \
  --output flatpak

generated-sources.json is always written as <output>/generated-sources.json.

Flag Description
-l, --lock pubspec.lock path (repeatable, $ENV expanded)
-s, --sdk Flutter SDK path
-o, --output Output directory (default: flatpak)
--pub-only Skip Flutter SDK sources
--flutter-only Skip pub sources

pub

Generates sources for pub packages only.

flutpak pub --lock pubspec.lock --output flatpak

flutter

Generates sources for Flutter SDK artifacts only.

flutpak flutter --sdk "$FLUTTER_ROOT" --output flatpak

sdk-ext

Generates a Flathub SDK Extension manifest (org.freedesktop.Sdk.Extension.flutter3) to share the Flutter SDK across multiple apps on the same machine.

flutpak sdk-ext \
  --sdk "$FLUTTER_ROOT" \
  --runtime-version 25.08 \
  --output org.freedesktop.Sdk.Extension.flutter3.json

CI/CD integration

The pin-manifest composite action handles Flutter SDK setup, binary download, and flutpak prepare in one step. Add it to your release workflow:

      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Pin Flatpak manifest
        uses: o-murphy/flutpak/.github/actions/pin-manifest@main
        with:
          tag_name: ${{ github.ref_name }}        # e.g. v1.2.3
          flutter_version_file: flatpak/flutter.version  # committed version file
          # flutter_version: stable               # or explicit version
          # config: flutpak.yaml                  # default: auto-detected
Input Description
tag_name Tag to embed in the manifest (required)
flutter_version Flutter SDK version (e.g. stable, 3.41.9)
flutter_version_file Path to committed version file (one of these two required)
commit Commit SHA (default: resolved from tag)
config Path to config file (default: auto-detected from CWD)
flutpak_version flutpak release tag to download (default: latest)
cache Cache Flutter SDK between runs (default: true)

GitHub Actions — manual

      - name: Build flutpak
        run: |
          git clone --depth 1 https://github.com/o-murphy/flutpak.git /tmp/flutpak_src
          cd /tmp/flutpak_src && dart pub get
          dart compile exe bin/flutpak.dart -o /tmp/flutpak

      - name: Prepare Flatpak sources and manifest
        run: |
          REF_TYPE="${{ github.ref_type }}"
          TAG=""
          if [ "$REF_TYPE" = "tag" ]; then TAG="${{ github.ref_name }}"; fi
          /tmp/flutpak prepare \
            --tag "$TAG" \
            --commit "${{ github.sha }}" \
            --sdk "$FLUTTER_ROOT"

Building and validating with official Flatpak tools

flutpak generates sources and manifests. The actual build, lint, and validation steps use the official Flatpak toolchain — org.flatpak.Builder (installed as a Flatpak) and appstreamcli (system package).

Prerequisites

# Install appstreamcli (Debian/Ubuntu)
sudo apt-get install appstream

# Install org.flatpak.Builder (required for build + lint)
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install --user flathub org.flatpak.Builder

1. Validate metainfo (AppStream XML)

appstreamcli validate --explain --no-net app/share/metainfo/<app_id>.metainfo.xml

2. Lint the manifest

dbus-run-session flatpak run \
  --filesystem=host \
  --command=flatpak-builder-lint \
  org.flatpak.Builder \
  --exceptions \
  manifest flatpak/<app_id>.yml
  • --exceptions — apply the built-in Flathub exception list
  • --filesystem=host — lets the sandboxed linter read files from the host
  • Run this before building to catch manifest errors early

3. Build with flathub-build

flathub-build is the same script Flathub CI uses. It runs flatpak-builder with --sandbox, --install-deps-from=flathub, --repo=repo, and other Flathub-specific flags. The built app is exported to repo/ automatically.

dbus-run-session flatpak run --command=flathub-build org.flatpak.Builder \
  flatpak/<app_id>.yml

Note: do not set a top-level branch: in your manifest — flatpak-builder-lint flags this as toplevel-unnecessary-branch.

4. Lint the built repo

dbus-run-session flatpak run \
  --filesystem=host \
  --command=flatpak-builder-lint \
  org.flatpak.Builder \
  --exceptions \
  repo repo

5. Export a single-file bundle (optional, for local testing)

flatpak build-bundle repo myapp.flatpak <app_id>
flatpak install --user myapp.flatpak
flatpak run <app_id>

Full GitHub Actions example

      - name: Install flatpak and appstream
        run: sudo apt-get install -y flatpak appstream

      - name: Validate metainfo
        run: |
          appstreamcli validate --explain --no-net \
            app/share/metainfo/<app_id>.metainfo.xml

      - name: Cache org.flatpak.Builder
        uses: actions/cache@v5
        with:
          path: ~/.local/share/flatpak
          key: flatpak-builder-25.08-${{ runner.arch }}-v1

      - name: Install org.flatpak.Builder
        run: |
          flatpak remote-add --user --if-not-exists flathub \
            https://dl.flathub.org/repo/flathub.flatpakrepo
          dbus-run-session flatpak install --user -y --noninteractive flathub \
            org.flatpak.Builder

      - name: Lint manifest
        run: |
          dbus-run-session flatpak run \
            --filesystem=host \
            --command=flatpak-builder-lint \
            org.flatpak.Builder \
            --exceptions \
            manifest flatpak/<app_id>.yml

      - name: Build Flatpak
        run: |
          dbus-run-session flatpak run --command=flathub-build \
            org.flatpak.Builder \
            flatpak/<app_id>.yml

      - name: Lint repo
        run: |
          dbus-run-session flatpak run \
            --filesystem=host \
            --command=flatpak-builder-lint \
            org.flatpak.Builder \
            --exceptions \
            repo repo

      - name: Export bundle
        run: |
          flatpak build-bundle repo myapp.flatpak <app_id>

Why include flutter_tools/pubspec.lock?

Before any flutter command runs, the Flutter tooling bootstraps itself by running pub get inside packages/flutter_tools/. This requires flutter_tools dependencies to be present in the offline pub cache.

Pass both lock files so the generated sources cover the app and the tool:

pub:
  locks:
    - pubspec.lock
    - $FLUTTER_ROOT/packages/flutter_tools/pubspec.lock

When the same package appears at different versions (e.g. yaml 3.1.2 in the app and yaml 3.1.3 in flutter_tools), both versions are included — deduplication is by (name, version) pair.

How it works

Pub packages

For each hosted package, flutpak fetches the SHA-256 from the pub.dev API and generates two flatpak source entries:

[
  {
    "type": "archive",
    "url": "https://pub.dartlang.org/packages/yaml/versions/3.1.2.tar.gz",
    "sha256": "abc123...",
    "dest": ".pub-cache/hosted/pub.dev/yaml-3.1.2",
    "strip-components": 0
  },
  {
    "type": "inline",
    "contents": "abc123...",
    "dest": ".pub-cache/hosted-hashes/pub.dev",
    "dest-filename": "yaml-3.1.2.sha256"
  }
]

Both entries are required: pub get --offline checks the hash file and fails if it's missing even when the archive is present.

Flutter SDK artifacts

Reads version files from a local Flutter install (bin/internal/engine.version, etc.) and constructs download URLs for each artifact (Dart SDK, engine binaries, fonts, Gradle wrapper). SHA-256 checksums are cached in ~/.cache/flutpak/ by URL hash to avoid redundant downloads across runs.

Two extra entries are always added:

  • sky_engine/pubspec.yaml (inline)packages/sky_engine/ was removed from the Flutter git tree in Flutter 3.x. Written inline so pub get --offline resolves it.

  • shared.sh.patch — Flutter's bootstrap script runs pub upgrade (requires network). The built-in patch replaces it with pub get --offline. Written to flatpak/patches/flutter/shared.sh.patch next to the output file.

License

MIT

Libraries

flutpak
Flatpak source manifest generator for Flutter/Dart applications.