Introducing flutpak - automating Flathub submission for Flutter apps

Introducing flutpak - automating Flathub submission for Flutter apps

Repo: GitHub - o-murphy/flutpak: Automates Flatpak packaging and offline builds for Flutter apps from pubspec.yaml. · GitHub
Version: 0.4.0-rc.2 (pre-1.0, breaking changes expected until stable)


The problem

Flathub requires every source to be declared upfront with a SHA-256 checksum -
no network access at build time. For a typical Flutter project that means:

  • Every pub.dev dependency needs two source entries in
    generated-sources.json: an archive download and an inline hash file that
    pub get --offline looks for separately. Miss the hash file and the build
    fails even if the archive is present.
  • The Flutter SDK brings its own set of artifacts - engine binaries, Dart SDK,
    material fonts, Gradle wrapper - each with architecture-specific variants for
    x86_64 and aarch64, all requiring correct checksums at a specific engine
    revision.
  • Packages like objectbox_flutter_libs require patch sources whose dest:
    path embeds the exact package version
    (.pub-cache/hosted/pub.dev/objectbox_flutter_libs-5.3.1). Update the
    package and that path silently goes stale, producing a manifest that passes
    linting but breaks at build time.
  • The Flutter SDK bootstrap script (shared.sh) calls pub upgrade, which
    requires network. It needs to be patched to pub get --offline for every
    Flatpak build, every Flutter version.
  • None of the existing generator tooling (flatpak-pip-generator and its
    siblings) understands the Dart/Flutter ecosystem.

Doing all of this by hand for a non-trivial app means tracking hundreds of
source entries, re-verifying checksums on every dependency bump, and
re-hunting Flutter artifact URLs on every Flutter release. This is the main
reason Flutter apps are underrepresented on Flathub - not lack of interest, but
the sheer amount of manual work required just to get to the point where you can
open a submission PR.


What flutpak targets

flutpak is not a general Flatpak build tool. It is specifically aimed at
producing the output that a Flathub submission expects:

  • A generated-sources.json in the format Flathub reviewers are familiar with
  • A manifest that passes flatpak-builder-lint --exceptions without manual
    tweaks
  • A flathub.json with correct submission metadata
  • Metainfo and desktop entry validation via appstreamcli as part of the
    generate step
  • GitHub Actions that mirror the flathub-build pipeline used in Flathub CI,
    so what passes locally also passes in review

The goal is to reduce the gap between “my app builds with Flutter” and “my app
is published on Flathub” to a config file and two commands.


Where flutpak started

The first version (0.1.0, May 2026) was a single prepare command that
generated generated-sources.json from a lock file and patched a manifest
using __FLATPAK_TAG__ / __FLATPAK_COMMIT__ placeholder strings. It also
generated metainfo XML and .desktop files from config, and had lint,
validate, and export wrapper commands around the official Flatpak toolchain.

It worked for one app. It was also immediately clear what was wrong with it:

  • The metainfo and desktop file generators were fragile and duplicated what
    appstreamcli and the Flathub submission process already validate. Removed
    in 0.2.8.
  • The lint and validate wrappers around official tools added complexity
    without value. Removed in 0.2.5-0.2.6.
  • Patch sources were baked into generated-sources.json at init time. The
    dest: path included the package version, so any version bump silently
    produced a broken manifest. Fixed in 0.4.0-beta.2 by moving patch injection
    to generate time, where the current pubspec.lock is always available.
  • The manifest template carried __FLATPAK_TAG__ and __FLATPAK_COMMIT__
    strings that generate replaced with sed. Any YAML structural change near
    those strings risked breaking substitution. Replaced in 0.4.0-rc.1 with
    direct yaml_edit injection into the correct YAML node - no strings to
    maintain, no fragile text replacement.
  • Config keys were inconsistently cased (app_id, runtime_version, etc.)
    and mixed flutpak-specific fields with Flatpak manifest fields in the same
    section. Cleaned up in 0.4.0-rc.1: all keys are now kebab-case, and
    flutpak-specific fields (repo-url, metainfo-path, icons) live at the
    root level rather than inside manifest:.

The general direction has been: remove everything that duplicates official
tooling, automate everything that is mechanical and error-prone, and keep
the config minimal.


What it looks like now

Two commands cover the full workflow:

flutpak init - run once. Generates the editable template manifest, a
Flutter wrapper shell script, flathub.json, and .gitignore. Validates that
your metainfo, desktop entry, and icon files exist before writing anything.

flutpak generate --tag vX.Y.Z - run on every CI build. Reads the
committed template, writes tag: and commit: directly into the git source
block via yaml_edit, generates generated-sources.json, and copies
everything to flatpak/generated/ ready for flatpak-builder.

The minimum config to get started:

# flutpak.yaml
flutter:
  sdk: $FLUTTER_ROOT
manifest:
  app-id: io.github.YourOrg.YourApp

Everything else - command name, repo URL, flutter version file path, sandbox
permissions - is either auto-detected or written into the template on init
with sensible defaults. You edit the template directly; flutpak never
overwrites it.

The known-patches/ directory ships reference patch files and companion
Flatpak module files for packages that commonly need offline build
modifications. For example, objectbox_flutter_libs needs a CMakeLists.txt
patch to use a prebuilt library instead of fetching it at build time. Copying
the relevant files and adding two lines to config is enough - generate handles
the rest, including the version-stamped dest: path and stripping CRLF line
endings if the upstream archive has them.

GitHub Actions composite actions are provided for CI, mirroring the Flathub
build pipeline:

- uses: o-murphy/flutpak/.github/actions/generate@v0.4.0-rc.2
  with:
    tag: ${{ github.ref_name }}
    metainfo-path: app/share/metainfo/<app-id>.metainfo.xml
- uses: o-murphy/flutpak/.github/actions/build-flatpak@v0.4.0-rc.2
  with:
    manifest: flatpak/generated/<app-id>.yml
    app-id: io.github.YourOrg.YourApp

Dogfooding

I am using flutpak to package ebalistyka
for Flathub. The tool exists because I needed it and nothing else filled this
gap. That submission is still in progress - which is exactly why the API is not
stable yet. Working through a real Flathub submission is what surfaces the
remaining rough edges.


Where community help matters most

known-patches/ is where the most useful community contributions can land.
If you use a Flutter package that requires a patch source - pre-built native
libraries, CMake modifications, anything that needs a type: patch or
type: shell source in the manifest - contributing a patch file and companion
module there makes it work automatically for everyone using that package.

Beyond patches:

  • Bug reports and edge cases from real apps are the most valuable feedback at
    this stage
  • Feedback on the manifest structure that flutpak init generates
  • Packages that require source handling not yet covered

Issue tracker: Issues · o-murphy/flutpak · GitHub


*Pre-1.0 means breaking changes are possible between minor versions. The
changelog documents them. If you are packaging a Flutter app for Flathub - or
have been putting it off because the source generation work seemed too painful

  • this is worth a look.*

There is a flatpak-flutter project already which a bunch of Flutter apps on Flathub are successfully using. I suggest collaborating with them in this effort.

I would normally highlight community projects in the readme here GitHub - flatpak/flatpak-builder-tools: Various helper tools for flatpak-builder · GitHub once there are some submissions or apps use it, but looking at your repo it is made using Claude which means I have to review it fully…

Anyway, if you want it to be added to the readme there feel free to open a PR with some examples of which apps are using it, then we can consider what to do there.

I don’t mind, I did take theirs project as a reference, but I left them an issue about this, unfortunately they don’t respond to issues and the project is poorly maintaining.

Also their solution is different. Mine doesn’t require anything but dart, simplifies preparing submission assets to a few lines in pubspec.yaml. more focused on the flutter ecosystem, supports pub package patches, and native modules. Generates all on the fly and ready for CI.

I propose you too visit the repo and compare approaches. Will happy to see ya.

Update: flutpak just passed Flathub CI on the first try (Proof of Concept)

Hey everyone! Quick update on flutpak (the tool automating Flathub submissions for Flutter apps).

Two days ago, I shared the tool while my own app’s submission (io.github.o_murphy.ebalistyka) was still in progress. Today, I’m thrilled to report that the Flathub test build has successfully passed on the very first attempt!

Build succeeded for both x86_64 and aarch64.

What’s under the hood?

The application is now built properly from source, except for objectbox-c dependency which is distributed only as pre-compiled binaries under the Apache License.

Key improvements in the manifest:

  • .desktop, .metainfo.xml, and icon files have been moved upstream to the main repository (no longer committed directly to the Flathub repo)
  • Screenshots in the metainfo file now point to an immutable release tag instead of the main branch tip
  • Updated runtime to 25.08 with adjusted permissions

Why is this a big deal?

  • Zero manual manifest tweaks: The entire generated-sources.json (with hundreds of lines of pub.dev dependencies) and the manifest were generated entirely by flutpak generate.
  • Handled complex native dependencies: The known-patches/ system worked flawlessly. It successfully injected the offline patch for objectbox_flutter_libs, mapping the correct version-stamped paths without breaking the strict offline network restriction of the Flathub compiler.

The PR is now clean, passes all linters, and is currently awaiting final human review.

Pull Request: https://github.com/flathub/flathub/pull/8889

If you’ve been holding back on bringing your Flutter app to Linux because of the painful manual packaging process, flutpak is officially proven to close that gap.

Check out the repo, try it on your app, and if you use packages that require native C-bindings or offline patches, contributions to known-patches/ are highly welcome!

Repo: https://github.com/o-murphy/flutpak

flutpak — major update (0.8.0): Rust/Cargo support, flatpak-flutter-compatible registry, no local SDK required

Since the original post (and the first successful Flathub CI run) a lot has landed. I want to give a proper update, especially on two points raised in the thread.

A note on the proof-of-concept submission

The previous update in this thread reported that PR #8889 passed the Flathub test build on the first try — that part is still true: the build succeeded for both x86_64 and aarch64. However, the PR was subsequently closed for reasons unrelated to the packaging itself:

  1. Proprietary dependencyobjectbox-c is distributed as a prebuilt binary under the Apache License with no source code available. @hfiguiere flagged this as incompatible with the app’s GPL-3.0 license.
  2. Generative AI policy — Flathub’s requirements restrict AI-generated code in submissions. Claude was used during development of the app, which made the PR ineligible.

Neither of these is a flutpak issue — the tool produced a correct, buildable manifest. The rejection is about the specific app’s dependencies and its codebase, not the packaging toolchain. I mention it for transparency since this thread had the earlier “CI passed” update and it would be misleading to leave it without follow-up.


Registry now compatible with flatpak-flutter

@bbhtt raised the point about flatpak-flutter. Since then the foreign deps registry format has been aligned to be fully schema-compatible with flatpak-flutter’s foreign_deps.json. Every entry from the flatpak-flutter registry can be used in flutpak with no changes. flutpak extends the schema by one optional field (crlf: on patch sources); flatpak-flutter silently ignores it.

The registry currently covers 19 packages:

  • objectbox_flutter_libs / objectbox_sync_flutter_libs
  • sqlite3 / sqlite3_flutter_libs / sqlcipher_flutter_libs
  • simple_secure_storage_linux
  • audiotags, flutter_webrtc, media_kit_libs_linux, pdfium_flutter,
    printing, flutter_new_pipe_extractor, fvp, powersync
  • rhttp, metadata_god, super_native_extensions, flutter_discord_rpc,
    flutter_vodozemac (cargokit/Rust — see below)

All native archive sources, patches, and version-stamped dest: paths are
resolved and injected automatically by flutpak generate. No manual source
hunting per release.

Version matching is : a registry entry for 1.0.0 covers 1.2.3,
1.5.0, etc., reducing churn when upstream bumps patch/minor versions.


Rust / Cargo offline builds (0.8.0)

Flutter plugins using cargokit
(rhttp, metadata_god, super_native_extensions, etc.) now work out of the
box. Add a rust: section to flutpak.yaml:

rust:
  version: 1.85.0
  rustup-path: /var/lib/rustup

generate will:

  1. Extract Cargo.lock from pub.dev archives, fetch SHA-256 checksums from the
    crates.io sparse registry index, emit cargo-sources.json
  2. Generate rustup-<version>.json: downloads the channel manifest,
    rustup-init binaries, and a minimal toolchain (rustc, cargo, rust-std) for
    both x86_64 and aarch64; sets RUSTUP_DIST_SERVER to the pre-downloaded
    static directory so nothing touches the network at build time
  3. Insert the rustup module before the app module in the generated manifest
  4. Wire CARGO_HOME, RUSTUP_HOME, and PATH into the app module’s
    build-options

Known limitation: git-sourced crates (git+https://...) are skipped with a
warning — they require cloning at generate time, which is out of scope.
Crates.io dependencies are fully supported.


No local Flutter SDK required for generate (0.7.0)

flutpak generate no longer reads from a local Flutter installation. Engine
versions (engine binary, Dart SDK, fonts, Gradle wrapper checksums) are fetched
from the GitHub raw API using flutter.ref:

flutter:
  ref: "3.44.1" # tag, branch, or commit SHA

The Flutter SDK sources are emitted as a standalone module
(flutter-sdk-<version>.json) — separate from pubspec-sources.json. Pre-built
module JSONs for recent Flutter releases are cached in the flutpak repo and
fetched on first use, avoiding regeneration across multiple projects on the same
SDK version.


LLVM SDK extension auto-injected (0.5.0)

The correct org.freedesktop.Sdk.Extension.llvmXX is now selected automatically
based on runtime-version (25.08 → llvm20, 24.08 → llvm19, 23.08 → llvm17) with
append-path / prepend-ld-library-path wired up. No manual extension entry
needed.


Breaking changes since the original post

# before
flutter:
  sdk: $FLUTTER_ROOT
  manifest:
    app-id: io.github.YourOrg.YourApp

# after (0.8.0)
flutter:
ref: "3.29.3"
app-id: io.github.YourOrg.YourApp
  • generated-sources.json → renamed to pubspec-sources.json (update !include references)
  • Flutter SDK sources moved out of pubspec-sources.json into a separate module
  • Re-run flutpak init --force after upgrading to 0.8.x to get a clean template

Other notable changes

Version Change
0.8.0 flutpak cache clear — wipes ~/.cache/flutpak/
0.7.0 subdir: — Flutter project in a monorepo subdirectory
0.7.0 flutpak sdk-mod — standalone Flutter SDK module JSON for !include
0.6.0 patches[].use-gitgit apply instead of patch -p1
0.5.0 --binary on every type: patch entry — deterministic CRLF handling
0.5.0 .gitattributes with *.patch -text generated by init
0.4.0 yaml_edit injection — tag:/commit: written directly into the git source block
0.4.0 actions/generate + actions/build-flatpak composite CI actions
0.4.0 Retry on 429/5xx for all network fetches

Current status

The core workflow is stable. The demo app (examples/demo_app/) builds
sqlite3 + rhttp end-to-end inside the Flatpak sandbox on both architectures —
the CI workflow is a working reference.
The most impactful contribution right now: if you maintain or package a Flutter
app with a native dep not yet in the registry, a PR adding it to
foreign_deps/foreign_deps.json makes it work for everyone. The format is
identical to flatpak-flutter, so existing entries can be copied directly.

@hfiguiere / @bbhtt — a clarification question before I close this out

Regarding the objectbox-c rejection: I want to make sure I understand the
policy correctly, because I’m seeing something inconsistent.

flatpak-flutter’s foreign_deps.json — which @bbhtt recommended — ships
prebuilt native binaries for at least two packages: pdfium_flutter
(prebuilt PDFium from pdfium-binaries,
Apache 2.0) and audiotags (prebuilt linux.tar.gz from the audiotags GitHub
releases). Several apps listed in the
flatpak-flutter README
as successfully published on Flathub use those entries.

objectbox-c is not in flatpak-flutter’s registry, so I’m not claiming
exact equivalence — but the broader pattern is the same: prebuilt native
binaries distributed under permissive licenses, bundled via Flatpak sources.

On reflection I’m also dropping the “Mere Aggregation” argument I made in the
PR — the GPL FAQ is clear that modules running linked together in a shared
address space “almost surely means combining them into one program”, and the
FAQ explicitly states that containers do not change this analysis. So
dynamically linking libobjectbox.so into a GPL-3.0 app does constitute a
combined work, and the GPL’s source-availability requirement applies to the
whole. Since objectbox-c has no source available, that requirement can’t be
satisfied. The license conflict is real.

That said, the question about pdfium_flutter and audiotags still stands —
both ship prebuilt binaries (Apache 2.0) via flatpak-flutter and those apps
pass review. Is the difference simply that those libraries are also dynamically
linked but their permissive license doesn’t create a conflict with the app’s
license? Or is there something else I’m missing about how Flathub evaluates
prebuilt binary dependencies?

Knowing the answer would help anyone packaging a Flutter app that uses
ObjectBox and trying to understand what’s actually allowed on Flathub.