A fully static, Nextcloud-compatible app store. You author app metadata in
hand-written YAML, run one command, and commit two generated JSON files. No
server, no database, no incoming data — Nextcloud fetches apps.json and
categories.json straight from a raw GitHub URL or GitHub Pages.
apps.yaml # hand-maintained app manifest (one entry per app)
categories.yaml # hand-maintained categories
build.py # YAML -> JSON generator + validation
sign.py # helper: sign a release archive and print its signature
api/v1/apps.json # generated output (commit this)
api/v1/categories.json # generated output (commit this)
dist/ # local release archives (gitignored)
-
Add your app entries to
apps.yaml(see thedemoappexample). -
Generate:
python3 build.py
-
Commit and push the generated
api/v1/*.json. -
Point Nextcloud at them — see Hosting.
- id: myapp # required, must match the app folder name
name: My App # required
summary: One-line pitch. # required
description: |
Full description shown in the app store.
certificate: "" # public code-signing cert (PEM). See Signing.
authors:
- name: You
mail: you@example.com
homepage: https://example.com
categories: [tools] # required, must match an id in categories.yaml
website: https://example.com
docs:
admin: https://example.com/docs
user: ""
developer: ""
discussion: ""
issueTracker: https://github.com/you/myapp/issues
screenshots:
- url: https://example.com/shot.png
smallThumbnail: https://example.com/shot-thumb.png
isFeatured: false
ratingRecent: 4.5
ratingOverall: 4.8
ratingNumRecent: 12
ratingNumOverall: 240
releases:
- version: 1.2.0 # required
phpVersionSpec: ">= 8.0"
platformVersionSpec: ">= 28, <= 30" # required
download: https://github.com/you/myapp/releases/download/v1.2.0/myapp-1.2.0.tar.gz # required
signature: "" # required for install. See Signing.
changelog: |
- Fixed the thing
- version: 1.1.0
phpVersionSpec: ">= 7.4"
platformVersionSpec: ">= 24"
download: https://github.com/you/myapp/releases/download/v1.1.0/myapp-1.1.0.tar.gz
signature: ""Nextcloud's parser only accepts two space-separated tokens. build.py
normalizes human input automatically:
| You write | Stored as |
|---|---|
* |
* (any version) |
>= 28, <= 30 |
>=28 <=30 |
>= 24 |
>=24 |
Use >= X, <= Y for a window and >= X or just * for an open or unrestricted
range.
Nextcloud refuses to install unsigned apps. Before install it:
- checks the app's
certificatewas issued by the Nextcloud Code Signing Authority (the official one — see below), - verifies the released archive's
signatureagainst that certificate (openssl_verify($archive, base64(signature), $cert, SHA512)).
Steps:
-
Register your app id on https://apps.nextcloud.com/developer — this issues a certificate signed by the Nextcloud CA for your app id. Keep the private key secret.
-
Sign a release archive:
python3 sign.py dist/myapp-1.2.0.tar.gz myapp.key
Paste the printed base64 string into
signature:for that release and the cert PEM into the app'scertificate:.Or automate it in
build.pyby giving a local archive + key directly:releases: - version: 1.2.0 file: ./dist/myapp-1.2.0.tar.gz # local archive gets signed + verified key: ./dist/myapp.key # or pass --key app.key to build.py download: https://github.com/you/myapp/releases/download/v1.2.0/myapp-1.2.0.tar.gz
python3 build.py --key dist/myapp.key
build.pycomputes and embeds the signature;opensslmust be on your PATH.
Certificates for custom stores. A self-signed certificate will NOT install on a stock Nextcloud, because the server validates the issuer chain against its bundled Nextcloud Code Signing Authority. Get the official per-app certificate from the developer registration flow, then host the releases yourself — the store data, downloads and distribution all stay fully static.
Push the repo, then set the store URL to the directory containing the JSON files:
// config/config.php
'appstoreenabled' => true,
'appstoreurl' => 'https://raw.githubusercontent.com/USER/REPO/BRANCH/api/v1',Nextcloud appends /apps.json and /categories.json to that base URL.
Enable Pages in your repo settings (deploy from the default branch), then:
'appstoreurl' => 'https://USER.github.io/REPO/api/v1',build.py validates the YAML, applies defaults, normalizes version specs and
optionally signs releases, then emits bare JSON arrays — the exact shape the
real apps.nextcloud.com/api/v1/*.json endpoints serve and the shape
Nextcloud's AppFetcher/CategoryFetcher expect. Everything is regenerated
locally; the committed JSON never changes on its own.
isNightly: truereleases are only shown/offered on beta/daily/git channels.- Releases whose version spec doesn't match the server version are filtered out
server-side; you can carry multiple
platformVersionSpecentries per app. - The
appsallowlistconfig restricts app listings to a fixed set of ids.