flutpak
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+generatesplit — 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 asmsix_config,flutter_native_splash - Placeholder substitution —
__FLATPAK_TAG__and__FLATPAK_COMMIT__are resolved bygenerateat build time, never baked into the template - Patches registry — known packages (e.g.
objectbox_flutter_libs) get their patches resolved automatically - Validation on every run —
generateerrors 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
- flutpak
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
Pre-built binary (recommended)
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--forceto overwrite. - Validates that all asset files (metainfo, desktop entry, icons) exist.
- Auto-detects
repo_urlfromgit 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 initfirst). - Errors if
__FLATPAK_TAG__or__FLATPAK_COMMIT__are missing from the template. - Errors if
app-id,command, orruntime-versionin 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 sopub get --offlineresolves 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:
<sdk>/version— flat version file present in Flutter < 3.32git describe --tags --abbrev=0— works for any tagged git clone<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-idin template must matchmanifest.app_idin configcommandin template must matchmanifest.commandin config (or its default)runtime-versionin template must matchmanifest.runtime_versionin 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
- Update
version:inpubspec.yaml - Run
make version(ordart run tool/update_version.dart) — rewriteslib/src/version.dart - 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.