flutpak

Pub Version

A Dart CLI tool that automates Flatpak packaging for Flutter applications. Describe your config once in pubspec.yaml, run flutpak init once to scaffold your template manifest, then run flutpak generate on every CI build to produce a fully-substituted, flatpak-builder-ready output.

Highlights

  • init + generate split — one-time scaffold vs. every-build generation; the editable template is committed to git, the substituted output is gitignored
  • No Python — pure Dart, compiles to a single native binary for CI
  • Config in pubspec.yaml — same pattern as msix_config, flutter_native_splash
  • Placeholder substitution__FLATPAK_TAG__ and __FLATPAK_COMMIT__ are resolved by generate at build time, never baked into the template
  • Patches registry — known packages (e.g. objectbox_flutter_libs) get their patches resolved automatically
  • Validation on every rungenerate errors early if the template is missing placeholders or its fields diverge from config
  • Retry on transient errors — pub.dev and Flutter artifact downloads retry on 429/5xx

Table of Contents


Prerequisites

Tool When needed Notes
Linux Always flutpak targets Linux Flatpak packaging only
git Always Version detection and commit-hash resolution
Internet access init, generate, sources, flutter Downloads from pub.dev and storage.googleapis.com
Flutter SDK Flutter projects Path via $FLUTTER_ROOT or --sdk; pure-Dart projects can omit this
Dart SDK ≥ 3.0 Building from source only Not required when using a pre-built binary
flatpak-builder Local Flatpak builds Only needed when actually building the Flatpak, not during flutpak generate
org.freedesktop.Sdk Local Flatpak builds Install via flatpak install flathub org.freedesktop.Sdk//25.08

Installation

Download the binary for your platform from the releases page and place it somewhere on your $PATH:

sudo mv flutpak /usr/local/bin/

Via setup-flutpak action (CI)

The repository ships a composite action that compiles and installs flutpak automatically. See CI/CD integration for full usage.

- uses: o-murphy/flutpak/.github/actions/setup-flutpak@main

From source

Using make:

git clone https://github.com/o-murphy/flutpak.git
cd flutpak
make build           # regenerates version.dart + compiles binary
sudo mv flutpak /usr/local/bin/

Without make:

dart run tool/update_version.dart
dart compile exe bin/flutpak.dart -o flutpak

Step-by-step setup

Step 1 — Add config to pubspec.yaml

Add a flutpak: section to your app's pubspec.yaml (or create a standalone flutpak.yaml — but not both).

Minimum viable config — only app_id and finish_args are truly required:

flutpak:
  manifest:
    app_id: io.github.YourOrg.YourApp
    finish_args:
      - --share=ipc
      - --socket=wayland
      - --device=dri

Everything else is either optional or auto-detected at runtime. For Flutter projects, add the SDK path:

flutpak:
  flutter:
    sdk: $FLUTTER_ROOT        # or absolute path; omit for pure-Dart projects
  manifest:
    app_id: io.github.YourOrg.YourApp
    finish_args:
      - --share=ipc
      - --socket=wayland
      - --device=dri

See Full config reference for all options.


Step 2 — Create required asset files

flutpak init validates that these files exist before generating anything. Create and commit them first:

<your-project>/
├── pubspec.yaml
├── pubspec.lock
└── app/
    └── share/
        ├── metainfo/
        │   └── <app_id>.metainfo.xml
        ├── applications/
        │   └── <app_id>.desktop
        └── icons/
            └── hicolor/
                └── 256x256/
                    └── apps/
                        └── <app_id>.png    # minimum 256x256 required by Flathub

Minimal <app_id>.desktop:

[Desktop Entry]
Name=Your App
Exec=yourapp
Icon=io.github.YourOrg.YourApp
Type=Application
Categories=Utility;

Minimal <app_id>.metainfo.xml:

<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
  <id>io.github.YourOrg.YourApp</id>
  <metadata_license>MIT</metadata_license>
  <project_license>MIT</project_license>
  <name>Your App</name>
  <summary>Short one-line description</summary>
  <releases>
    <release version="0.1.0" date="2025-01-01"/>
  </releases>
</component>

For full requirements on these files, see the Flathub app-author documentation.


Step 3 — First run

flutpak init

This is a one-time command. It generates the following files and then immediately runs generate to populate flatpak/generated/:

flatpak/
├── <app_id>.yml          # editable template manifest — commit this
├── <name>-wrapper.sh     # Flutter launcher wrapper — commit this
├── flathub.json          # Flathub submission config — commit this
├── .gitignore            # contains: generated/
└── generated/            # gitignored, ready for flatpak-builder
    ├── <app_id>.yml
    ├── flathub.json
    ├── generated-sources.json
    └── patches/

Commit flatpak/<app_id>.yml, flatpak/<name>-wrapper.sh, flatpak/flathub.json, and flatpak/.gitignore. Do not commit flatpak/generated/ — it is gitignored by design.

flutpak init errors if the template already exists. Use --force to overwrite an existing scaffold.


Step 4 — Review and customize the template

Open flatpak/<app_id>.yml and adjust build-commands, sdk-extensions, finish-args, and any other fields for your project. The template is yours — flutpak never overwrites it on subsequent runs.

Important: keep __FLATPAK_TAG__ and __FLATPAK_COMMIT__ in the sources section. generate requires both placeholders and will error if either is missing.

Section Safe to edit?
finish-args: Yes
build-commands: Yes
build-options: Yes
sdk-extensions: Yes
modules: Yes
sources: (extra archives, patches) Yes
__FLATPAK_TAG__ / __FLATPAK_COMMIT__ in sources Do not remove — required by generate

Step 5 — Build locally

Point flatpak-builder at the generated manifest:

flatpak-builder --repo=repo --force-clean build-dir \
  flatpak/generated/<app_id>.yml

See Validating and building locally for the full lint and validation workflow.


Step 6 — CI: generate on each release

On every release build, run generate with the release tag:

flutpak generate --tag ${{ github.ref_name }}

This reads the committed template, resolves the tag and commit SHA, generates generated-sources.json, substitutes __FLATPAK_TAG__ and __FLATPAK_COMMIT__, and writes everything to flatpak/generated/.

See CI/CD integration for a complete GitHub Actions workflow.


Step 7 — Submit to Flathub

Open a PR to your app's Flathub repository. Copy the contents of flatpak/generated/ into the repo root:

<flathub-repo>/
├── <app_id>.yml               # flatpak/generated/<app_id>.yml
├── generated-sources.json     # flatpak/generated/generated-sources.json
├── flathub.json               # flatpak/generated/flathub.json
└── patches/                   # flatpak/generated/patches/ (if any)

For complete submission requirements see the Flathub app-author documentation.


Full config reference

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

Location Key
pubspec.yaml flutpak: section
flutpak.yaml standalone file

Complete YAML with all options

# pubspec.yaml — flutpak: section (or standalone flutpak.yaml)
flutpak:
  output: flatpak                    # default

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

  flutter:
    sdk: /home/user/flutter          # or set $FLUTTER_ROOT; omit for pure-Dart
    patch: flatpak/patches/flutter/shared.sh.patch  # optional

  patches:                           # optional project-level patches
    - package: objectbox_flutter_libs
      path: flatpak/patches/objectbox.patch
      dest_subpath: linux

  manifest:
    app_id: io.github.YourOrg.YourApp  # required
    runtime_version: '25.08'           # default: '25.08'
    finish_args:                        # required
      - --share=ipc
      - --socket=wayland
      - --device=dri
    sdk_extensions:
      - org.freedesktop.Sdk.Extension.llvm20

    # Auto-detected if omitted (info printed on init):
    command: yourapp                  # default: last segment of app_id
    repo_url: https://github.com/... # default: git remote get-url origin

    # Asset source paths (relative to project root):
    metainfo_path: app/share/metainfo/<app_id>.metainfo.xml   # default
    desktop_entry_path: app/share/applications/<app_id>.desktop  # default
    icons:                            # default: single 256x256 entry
      - size: 256x256                 # required when icons: key is present
        path: app/share/icons/hicolor/256x256/apps/<app_id>.png
      # optional additional sizes:
      - size: 512x512
        path: app/share/icons/hicolor/512x512/apps/<app_id>.png
      - size: scalable
        path: app/share/icons/hicolor/scalable/apps/<app_id>.svg

    # Other optional fields:
    extra_modules:
      - flatpak/modules/bclibc.yml
    extra_sources: []
    env: {}
    build_options:
      append_path: /custom/bin
      prepend_ld_library_path: /custom/lib
      env:
        MY_VAR: value

  flutter_version_file: flatpak/flutter.version  # default when flutter configured

Field reference table

Field Type Default Notes
output string flatpak Output directory for generated files
pub.locks list [pubspec.lock] Lock files to scan; $ENV vars expanded
flutter.sdk string $FLUTTER_ROOT Flutter SDK path; omit for pure-Dart
flutter.patch string Custom patch for setup-flutter.sh
flutter_version_file string <output>/flutter.version Written when Flutter is configured
patches list [] Project-level package patches
manifest.app_id string required Reverse-DNS app ID
manifest.runtime_version string 25.08 Freedesktop runtime version
manifest.command string last segment of app_id Executable name inside /app/bin/
manifest.repo_url string git remote get-url origin Source git URL written into template
manifest.finish_args list required Flatpak sandbox permissions
manifest.sdk_extensions list [] e.g. llvm20, rust
manifest.metainfo_path string app/share/metainfo/<id>.metainfo.xml Validated on init and generate
manifest.desktop_entry_path string app/share/applications/<id>.desktop Validated on init and generate
manifest.icons list [{size: 256x256, path: app/share/icons/…}] Must include 256x256 if key is present
manifest.extra_modules list [] YAML files included verbatim in modules
manifest.extra_sources list [] Verbatim flatpak source entries
manifest.env map {} Build-time env vars (shorthand)
manifest.build_options.append_path string Appended to PATH during build
manifest.build_options.prepend_ld_library_path string Prepended to LD_LIBRARY_PATH
manifest.build_options.env map {} Merged with top-level env:

Commands reference

init

flutpak init [--config <path>] [--sdk <path>] [--force]

One-time setup. Generates the editable template manifest, wrapper script, flathub.json, and flatpak/.gitignore, then immediately runs generate.

  • Errors if the template (flatpak/<app_id>.yml) already exists — use --force to overwrite.
  • Validates that all asset files (metainfo, desktop entry, icons) exist.
  • Auto-detects repo_url from git remote get-url origin.
Flag Description
--config Path to config file (default: auto-detected pubspec.yaml or flutpak.yaml)
--sdk Flutter SDK path; overrides flutter.sdk: in config and $FLUTTER_ROOT
--force Overwrite existing template and wrapper

generate

flutpak generate [--tag v1.2.3] [--commit sha] [--config <path>] [--sdk <path>]
                 [--no-sources] [--pub-only] [--flutter-only] [--dry-run]

Every-build command. Reads the committed template, validates it against config, generates generated-sources.json, substitutes __FLATPAK_TAG__ and __FLATPAK_COMMIT__, and writes everything to flatpak/generated/.

  • Errors if the template does not exist (run flutpak init first).
  • Errors if __FLATPAK_TAG__ or __FLATPAK_COMMIT__ are missing from the template.
  • Errors if app-id, command, or runtime-version in the template differ from config.
  • Errors if any asset file (metainfo, desktop entry, icon) does not exist.
Flag Description
--tag Git tag embedded in the manifest (e.g. v1.2.3)
--commit Full git commit SHA; defaults to git rev-parse HEAD
--config Path to config file
--sdk Flutter SDK path
--no-sources Skip source generation; copy template only
--pub-only Generate only pub package sources
--flutter-only Generate only Flutter SDK sources
--dry-run Print what would be written without writing any files

pub

flutpak pub [--lock <path>] [--output <dir>]

Generate pub package sources only. Reads pubspec.lock (and any additional lock files specified via --lock) and writes generated-sources.json.

flutter

flutpak flutter [--sdk <path>] [--output <dir>]

Generate Flutter SDK sources only. Reads engine version from the SDK and writes artifact entries to generated-sources.json.

sources

flutpak sources [--lock <path>] [--sdk <path>] [--output <dir>]

Generate all sources (pub + Flutter SDK) combined into a single generated-sources.json.

sdk-ext

flutpak sdk-ext [--sdk <path>] [--output <dir>]

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


--tag / --commit behavior

--tag --commit Tag in manifest Commit in manifest
v1.2.3 v1.2.3 Resolved via git rev-parse v1.2.3^{}
abc123 abc123 abc123
HEAD SHA HEAD SHA
v1.2.3 abc123 v1.2.3 abc123 (explicit override)

When neither flag is passed, the current HEAD SHA is used for both values so flatpak-builder can fetch and build the revision without a real release tag.

For release builds, always pass --tag. --tag requires the tag to exist locally — use fetch-depth: 0 in your checkout step so the tag is available.


Manifest lifecycle

The architecture separates the editable template (committed to git) from the generated output (gitignored):

flatpak/
├── <app_id>.yml          <- editable template — committed to git
│                            contains __FLATPAK_TAG__ and __FLATPAK_COMMIT__
├── <name>-wrapper.sh     <- committed to git
├── flathub.json          <- committed to git
├── patches/              <- patch files — committed to git
├── .gitignore            <- contains: generated/
└── generated/            <- gitignored, never commit this
    ├── <app_id>.yml      <- final manifest (placeholders substituted)
    ├── flathub.json      <- copy
    ├── generated-sources.json
    └── patches/          <- copy

Phase 1 — flutpak init (run once)

Creates flatpak/<app_id>.yml with __FLATPAK_TAG__ and __FLATPAK_COMMIT__ in the app git source block. Commit this file — it is the starting point that reviewers inspect and that generate reads on every subsequent build.

Phase 2 — flutpak generate --tag vX.Y.Z (run on every CI build)

Reads the committed template, resolves the tag and commit SHA, generates generated-sources.json, substitutes the placeholders, and copies everything to flatpak/generated/. The template is never modified. flatpak-builder consumes flatpak/generated/<app_id>.yml.


CI/CD integration

setup-flutpak action

- uses: o-murphy/flutpak/.github/actions/setup-flutpak@main
  # Builds from the same ref as the 'uses:' directive by default.
  # Pin to a specific release:
  # with:
  #   version: v0.4.0-beta.1
Input Description Default
version Release tag, commit SHA, or branch to compile ref pinned in uses: directive
install_dir Directory where the flutpak binary is placed /usr/local/bin

The repository also ships a flutter composite action with runner.arch-aware cache keys — it works correctly on both ubuntu-latest (x86_64) and ubuntu-24.04-arm (aarch64) runners:

- uses: o-murphy/flutpak/.github/actions/flutter@main
  with:
    flutter-version: stable   # or read from flatpak/flutter.version
    cache: true
Input Description Default
flutter-version Version tag (e.g. 3.41.6), stable, or beta stable
cache Cache the SDK between runs true
Output Description
flutter-path Absolute path to the installed Flutter SDK

The action also exports FLUTTER_ROOT and FLUTTER_HOME env vars and adds flutter/bin to $PATH, so flutpak generate picks up the SDK automatically without an explicit --sdk flag.

Release workflow example

name: Flatpak release

on:
  push:
    tags: ['v*']

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0          # required for --tag to resolve the commit

      - uses: o-murphy/flutpak/.github/actions/setup-flutpak@main

      - uses: o-murphy/flutpak/.github/actions/flutter@main
        with:
          flutter-version: stable
          cache: true

      - run: flutter pub get

      - name: Generate Flatpak manifest
        run: flutpak generate --tag ${{ github.ref_name }}

      - name: Upload generated artifacts
        uses: actions/upload-artifact@v4
        with:
          name: flatpak-generated
          path: flatpak/generated/
          retention-days: 90
          if-no-files-found: error

flatpak-builder is then pointed at flatpak/generated/<app_id>.yml.

For a full workflow that includes linting, building, exporting a .flatpak bundle, and uploading a Flathub submission artifact, add the steps described in Validating and building locally.


Validating and building locally

flutpak generates sources and manifests. The actual lint, build, and validation steps use the official Flatpak toolchain.

Install toolchain

# 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

# Install the Freedesktop runtime
flatpak install flathub org.freedesktop.Sdk//25.08

1. Validate metainfo

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/generated/<app_id>.yml

--exceptions applies the built-in Flathub exception list. Run this before building to catch manifest errors early.

Important

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

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.

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

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)

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

Why include flutter_tools/pubspec.lock?

Before any flutter command runs inside the Flatpak sandbox, the Flutter tooling bootstraps itself by running pub get inside packages/flutter_tools/. This requires the 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 in the two lock files (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 sources

For each hosted package in your lock file(s), flutpak fetches the SHA-256 checksum from the pub.dev API and generates two flatpak-builder source entries:

[
  {
    "type": "archive",
    "url": "https://pub.dev/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 is missing even when the archive is present.

Flutter SDK sources

When flutter.sdk: is configured, flutpak reads the engine version from the local Flutter install (bin/internal/engine.version, etc.) and constructs download URLs for each artifact — Dart SDK, engine binaries, fonts, and the Gradle wrapper — for both x86_64 and aarch64. 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.
  • setup-flutter.sh — wires the downloaded artifacts together inside the Flatpak sandbox.

flutpak determines the Flutter version from the SDK path using the following fallback chain:

  1. <sdk>/version — flat version file present in Flutter < 3.32
  2. git describe --tags --abbrev=0 — works for any tagged git clone
  3. <sdk>/packages/flutter/pubspec.yaml — inner package version field

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

Registry entries may specify a version constraint (semver range) to limit which package versions the patch applies to. Project-level patches: entries always take priority over registry entries for the same package.

The built-in Flutter bootstrap patch (shared.sh.patch) replaces pub upgrade (requires network) with pub get --offline inside the Flutter SDK's shared.sh bootstrap script.

Wrapper script

For Flutter apps, init generates flatpak/<name>-wrapper.sh:

#!/bin/sh
# Generated by flutpak — https://github.com/o-murphy/flutpak
APP=/app/<name>
export LD_LIBRARY_PATH="$APP/lib:${LD_LIBRARY_PATH:-}"
exec "$APP/<name>" "$@"

This bridges Flatpak's command: entry (pointing to /app/bin/<name>) to the Flutter bundle at /app/<name>/<name>, setting LD_LIBRARY_PATH for shared libraries bundled with the app.

Template vs generated

The template (flatpak/<app_id>.yml) is a valid Flatpak manifest with two placeholder strings:

  • __FLATPAK_TAG__ — replaced by the git tag (e.g. v1.2.3) or HEAD SHA
  • __FLATPAK_COMMIT__ — replaced by the full commit SHA

generate copies the template to flatpak/generated/<app_id>.yml and substitutes the placeholders. The template is never modified; the generated file is gitignored and rebuilt on every CI run.

generate also validates consistency between the template and config before writing anything:

  • app-id in template must match manifest.app_id in config
  • command in template must match manifest.command in config (or its default)
  • runtime-version in template must match manifest.runtime_version in config

Building from source / contributing

git clone https://github.com/o-murphy/flutpak.git
cd flutpak
make test        # run test suite
make build       # regenerate version.dart + compile binary
make version     # regenerate version.dart only

Bumping the version

  1. Update version: in pubspec.yaml
  2. Run make version (or dart run tool/update_version.dart) — rewrites lib/src/version.dart
  3. Commit both files

During a release, release.yml performs steps 1–3 automatically when a v* tag is pushed, so the compiled binaries always report the correct version.


License

MIT

Libraries

flutpak
Flatpak source manifest generator for Flutter/Dart applications.