Submitting an app
Anyone can add an app: write the package, build it, use the app from the build, and open a pull request. Before you start, check the app can be packaged; then write the files described in what a package is for the download you picked (choosing a format).
- Build the package locally.
- Install it and use it. Launch the app and try its main feature. A package that only builds is not tested, and a PR without this is not reviewed.
- Open the PR, saying what you tested and what you couldn’t.
CI then checks the descriptor, builds the app (forks included, once a maintainer approves the run), and a reviewer goes through the review runbook. On merge, the app is published.
Build
Use the Flathub build of flatpak-builder. It carries a fixed SDK toolchain
(bsdunzip, appstreamcli, node), so the result doesn’t depend on your
distribution:
flatpak install flathub org.flatpak.Builder
node scripts/read-descriptor.mjs registry/com.example.App/flatpark.yml
node scripts/audit-descriptor.mjs registry/com.example.App/flatpark.yml
./scripts/check-apply-extra.sh com.example.App
./scripts/publish.sh --verify com.example.App
publish.sh --verify builds the app, installs it from a temporary local remote
to prove the repo is installable, then removes both.
check-apply-extra.sh unpacks the real download the way a system-wide install
does, which a --user install never shows. Both must pass.
Test
Keep the build installed and use it:
FLATPARK_VERIFY_KEEP=1 ./scripts/publish.sh --verify com.example.App
flatpak --user run com.example.App
# … use the app's main feature, quit, start it again: are settings still there?
flatpak kill com.example.App
flatpak --user uninstall -y com.example.App
flatpak --user remote-delete --force flatpark-local
rm -rf ~/.var/app/com.example.App
Check at least:
- the app starts and its window looks right;
- its main feature works;
- settings and data survive a restart;
- nothing new appears in your home directory that shouldn’t.
Open the PR
- Branch:
add/<app-id>for a new app,fix/<slug>for a change. - Commit subject: Conventional Commits,
feat(<short-name>): add <App>, <what it is> (<app-id>)for a new app,fix(<short-name>): <what changed>for a fix. The body says why, not which files. - PR title: the commit subject.
- PR body:
- What: the app and its id.
- Packaging: the download used, the runtime, anything around the vendor’s build (wrapper settings, prebuilt stacks, extra downloads).
- Sandbox: the permissions granted and why; the ones left out, with the
flatpak overridelines in the metainfo that turn them on. - Tested: what you did with the app, and what you couldn’t (an account, hardware, a desktop you don’t run).
- Flathub: if the app is on Flathub, why it should be here too (rules).
If you are the app’s developer, say so in the PR; it is how the developer-approved shield gets added (upstream approvals).
For agents
Building without the Flathub builder
A host flatpak-builder is used in preference if installed; that is what CI
does, because the sandboxed builder needs a session bus to reach the host’s
Flatpak and a headless runner has none. The sandboxed builder has its own
/tmp, so a REPO_DIR, OUT_DIR or GNUPGHOME_DIR you set yourself has to be
under $HOME or the repo; the scripts stop with an explanation if it isn’t.
A host flatpak-builder needs bsdunzip (package libarchive-tools) to unpack
a zip source; without it the build fails on the zip, not on your manifest.
Keeping test installs apart
publish.sh --verify adds a remote named flatpark-local to your --user
installation and never touches your real flatpark remote (it refuses to run if
the names collide). If the app was already installed from another remote, it is
moved aside with --reinstall and restored afterwards. The scratch repo is
rebuilt on every run, so don’t leave flatpark-local around between sessions.
For longer testing, use a separate installation:
# once: an installation named "test"
sudo install -d /etc/flatpak/installations.d
printf '[Installation "test"]\nPath=%s/.local/share/flatpak-test\nDisplayName=FlatPark test\n' \
"$HOME" | sudo tee /etc/flatpak/installations.d/test.conf >/dev/null
flatpak --installation=test remote-add --no-gpg-verify flatpark "file://$PWD/out/repo"
flatpak --installation=test install flatpark com.example.App
flatpak --installation=test uninstall --all
Without root, use your --user installation with a remote name of its own:
flatpak --user remote-add --if-not-exists --no-gpg-verify test-tmp "file://$PWD/out/repo"
flatpak --user install -y test-tmp com.example.App
# … test …
flatpak kill com.example.App
flatpak --user uninstall -y com.example.App
flatpak --user remote-delete test-tmp
rm -rf ~/.var/app/com.example.App
Don’t use FLATPAK_USER_DIR for isolation. That installation isn’t
registered in /etc/flatpak/installations.d, so the host’s Flatpak portal
doesn’t know it exists, and anything relying on flatpak-spawn fails. That
includes glycin, the image loader of the GNOME 48+ runtimes, so the app aborts
at start with Failed to load .../image-missing.png: Loader process exited early — which looks exactly like the package crashing, and isn’t.
flatpak install --reinstall from a local file:// remote can keep the old
commit; uninstall and install again after a rebuild. timeout flatpak run
stops only the client, so run flatpak kill <id> after every test.
Checks CI runs
pr-checks: validates the descriptor, runsaudit-descriptor.mjsand the test suite, checks links, runscheck-apply-extra.shwhen a pin changed, and builds the changed app. A fork’s build runs without secrets, signed with a throwaway key.audit-descriptor.mjsfails a package whose scripts inject code, run host commands or turn off an inner sandbox without declaring it, and a package without<id>.pngor<id>.svg.
What review checks
The review runbook is the full list. The common reasons a PR is sent back:
- Not run. The PR doesn’t say what was tested and what wasn’t.
- Optional permissions granted by default instead of documented as overrides; see permissions.
- The wrong kind of source. The app and anything it needs come in as
extra-data, withsha256andsize. Anarchivesource is only for a prebuilt stack the catalog already consumes that way.gitsources aren’t accepted. - Download not from the official channel: the vendor’s domain or the real upstream repository, never a personal account or mirror.
- The vendor’s build changed without declaring it, or at all for an open-source app (packaging levels).
- A dependency compiled in the manifest instead of an extra download or a prebuilt stack.
- Downloading and running unpinned code at run time. A vendor’s own updater writing into the app’s data directory is fine.
policy.proprietarynot set honestly.- No legitimate purpose: piracy, malware or trademark impersonation.