Publishing guide
Any app with a public download is welcome — FlatPark does not host builds. As long as the app ships an official installer or prebuilt archive at a stable, public release URL (extra-data style), it can be added here. FlatPark fetches it at build, pins it, and signs the result.
Add an app
Create one directory under registry/ named exactly for the app id:
registry/com.example.App/
flatpark.yml # the descriptor (below)
com.example.App.yml # the Flatpak manifest
com.example.App.metainfo.xml
com.example.App.svg
resolve-update.sh # optional: upstream update resolver
Then validate and build locally:
node scripts/read-descriptor.mjs registry/com.example.App/flatpark.yml
./scripts/publish.sh --verify com.example.App
Test without polluting your everyday Flatpak
publish.sh --verify adds a local file:// remote named flatpark to your
--user installation and installs the app there. The scratch repo
(out/repo) is rebuilt on every run, so its commits drift from whatever you
have installed — and if you also run the real FlatPark remote in that same
installation, flatpak update eventually fails with Update is older than current version and leaves orphaned refs. Keep test builds in a separate,
throwaway installation so they never touch your normal Flatpak state:
# one-time: create an isolated 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
# install the freshly built app into it, then wipe it when done
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
Open a PR. pr-checks validates the descriptor, runs the test suite, checks for
dead links, and (for same-repo PRs) builds the app. On merge, publish builds
and publishes it.
flatpark.yml schema
id: com.example.App # required — must match the directory name
name: Example App # required
summary: One-line description # required
website: https://example.com/ # optional
source_url: https://github.com/you/packaging # optional
build:
manifest: com.example.App.yml # required — relative to this directory
branch: stable # optional (default: stable)
mode: extra-data # packaging mode (internal label)
catalog: # optional — drives the catalog page
category: Productivity
tags:
- Example
- Demo
update: # optional — enables auto pin-bump PRs
command: ./resolve-update.sh
policy: # optional — informational
proprietary: true
extra_data_first: true
dangerous_permissions: []
Only id, name, summary, and build.manifest are required.
Auto-updating (optional)
Version checking is always a script — there are no declarative checker types
to learn. Point update.command at a resolve-update.sh that figures out the
current release however it likes (a JSON/HTML endpoint, the GitHub API, a fixed
URL, whatever) and prints this JSON to stdout (logs go to stderr):
{
"version": "1.2.3",
"releaseDate": "2026-06-19",
"sources": [
{ "filename": "installer.sh", "url": "https://example.com/installer-1.2.3.sh" }
]
}
The script does no hashing — it just resolves the version and the real
download URL(s). FlatPark downloads each source, computes sha256/size, and
rewrites the manifest’s managed block (mark it with these comments so FlatPark
knows what to rewrite):
# BEGIN MANAGED EXTRA-DATA
- type: extra-data
filename: installer.sh
only-arches:
- x86_64
url: https://example.com/installer-1.2.3.sh
sha256: <computed by FlatPark>
size: <computed by FlatPark>
# END MANAGED EXTRA-DATA
Where the version lives: in the AppStream metainfo <releases>, not in
extra-data (Flatpak has no version field there). The latest <release version>
is the comparison anchor: each day update-check runs your resolver, and only
when its version differs from the metainfo does it download, re-pin, prepend a
new <release>, and open a PR. A maintainer merges it, which rebuilds and
republishes just that app.
Resolver templates
GitHub releases (pick the right asset):
#!/usr/bin/env bash
set -euo pipefail
repo="owner/name"
rel="$(curl -fsSL ${GITHUB_TOKEN:+-H "Authorization: Bearer $GITHUB_TOKEN"} \
"https://api.github.com/repos/$repo/releases/latest")"
version="$(jq -r '.tag_name | ltrimstr("v")' <<<"$rel")"
url="$(jq -r '.assets[]|select(.name|test("linux.*x86_64.*\\.tar\\.gz$")).browser_download_url' <<<"$rel")"
date="$(jq -r '.published_at' <<<"$rel" | cut -c1-10)"
jq -n --arg v "$version" --arg d "$date" --arg u "$url" \
'{version:$v,releaseDate:$d,sources:[{filename:"app.tar.gz",url:$u}]}'
A vendor JSON endpoint (version in one field, URL in another):
#!/usr/bin/env bash
set -euo pipefail
meta="$(curl -fsSL https://vendor.example/latest.json)"
version="$(jq -r '.version' <<<"$meta")"
url="$(jq -r '.assets[]|select(.name|test("linux-x86_64\\.deb$")).url' <<<"$meta")"
date="$(jq -r '.published_at // ""' <<<"$meta" | cut -c1-10)"
jq -n --arg v "$version" --arg d "$date" --arg u "$url" \
'{version:$v,releaseDate:$d,sources:[{filename:"app.deb",url:$u}]}'
A fixed URL that simply embeds the version:
#!/usr/bin/env bash
set -euo pipefail
version="$(curl -fsSL https://vendor.example/latest.txt)"
jq -n --arg v "$version" \
'{version:$v,sources:[{filename:"app.bin",url:("https://vendor.example/app-"+$v+".bin")}]}'
Sandbox & permissions
Prefer the tightest finish-args that still work. Avoid --filesystem=home and
other broad grants; FlatPark surfaces permissions on each app’s detail page, and
broad grants will be questioned in review.
What we review (and what gets a PR rejected)
Every PR is checked against the full review runbook. To pre-empt the common rejections, make sure your submission:
- Pins every remote source —
extra-data/archiveneedsha256(andextra-dataa non-zerosize);gitneeds an immutablecommit. (type: filepackaging files need no pin.) - Downloads only from the official channel — the vendor’s own domain or the genuine upstream repo, never a personal account or a mirror.
- Repackages the official build unmodified —
build-commandsonly install the wrapper/desktop/metainfo/icon and anapply_extrathat unpacks the download; don’t patch, recompile, or change the app’s behavior. - Uses a plain resolver —
update.commandis a simple relative script path like./resolve-update.sh(it runs in CI). - Declares its
policy— setproprietaryhonestly and list any high-risk permissions indangerous_permissions. - Doesn’t fetch-and-run arbitrary code — a vendor’s own self-updater writing into the app’s data directory is fine; downloading and executing unpinned third-party code is not.
- Avoids sandbox-escape permissions — no
--filesystem=host,--filesystem=/, or--talk-name=org.freedesktop.Flatpak. - Ships an accepted artifact — tarball,
.deb,.rpm, zip, or an official installer. AppImage is not accepted. - Has a legitimate purpose — non-FOSS is fine; piracy, malware, and trademark impersonation are not.
Non-FOSS commercial apps (e.g. brokers) are welcome on the same bar: official source, unmodified, pinned.