Skip to content

Publish an app

SDK 1.xWritten for core 1.53+ and gui 1.23+.

Goal: Your app listed in the app catalog, so idf.py app-install --app my-app works for everyone, with a release for every version you push.

You need: Start here done, an app made with Start from the template or from an example (Make an example your own), and gh auth login run once (the steps below create and edit GitHub repos with gh).

Run every command from the project root (~/CrossPad, Windows C:\CrossPad\firmware) with ESP-IDF loaded as in 2. Build the firmware. Used the one-line installer? Use crosspad-idf in place of idf.py. In the commands, you stands for your GitHub user name and my-app for your app’s id.

The listing rule: repos in the CrossPad org are found by the topic crosspad-app; public repos anywhere else are added to external-apps.json in CrossPad/crosspad-apps. Either way the catalog reads crosspad-app.json from the repo’s default branch on GitHub (an entry in external-apps.json may name another branch with "branch") and skips a repo without a readable one. Step 4 has one tab per route: pick the one that fits your repo.

  1. Create the app with its own repo

    Made your app from an example? It has its public repo already (Make an example your own, step 6): go on to step 2.

    The catalog lists GitHub repos, so your app needs a public one. --public creates the repo, pushes the new app to it and adds it to the project as a submodule; add --owner CrossPad for an org repo.

    Terminal window
    idf.py app-new --app my-app --name "My App" --public

    You should see:

    … file(s) generated
    git repository initialised
    pushed to you/crosspad-my-app (public)
    added as a submodule at components/crosspad-my-app
    my-app ready at components/crosspad-my-app

    Made the app without --public? Its folder is already a git repo of its own; commit your work there and push it to a new public repo (CrossPad/crosspad-my-app for an org repo):

    Terminal window
    git -C components/crosspad-my-app add -A
    git -C components/crosspad-my-app commit -m "My App"
    gh repo create you/crosspad-my-app --public --source components/crosspad-my-app --remote origin --push

    gh ends with ✓ Pushed commits to …. The project keeps the app as a plain folder, which is fine for publishing.

    If not: publishing says gh is not authenticated. For any other Error: line from app-new: idf.py app-new stops with an error.

  2. Complete the manifest

    crosspad-app.json at the repo root describes the app to the catalog and to every client that installs it. The catalog needs five fields: id, name, a version like 1.2.3, description and platforms. Fill the rest too; each field is explained under Manifest fields.

    {
    "name": "My App",
    "id": "my-app",
    "version": "0.1.0",
    "description": "What it does, in one line",
    "category": "music",
    "icon": "my-app.png",
    "component_path": "components/crosspad-my-app",
    "platforms": ["esp-idf", "pc"],
    "requires": {
    "crosspad-core": ">=1.53.0",
    "crosspad-gui": ">=1.23.0"
    },
    "changelog": [
    "0.1.0: first version"
    ]
    }

    Set requires to at least core >=1.53.0 and gui >=1.23.0, the versions the template and the examples are written against; an app that uses CP_FONT_PIXEL (as the UI kit recipe does) needs gui >=1.23.1, which declares the pixel font with C linkage, as the simulator on Windows needs. (ui_modal_confirm_dismiss, which the UI kit also uses, is older: gui 1.8.0.) Check requires in your manifest: templates before SDK 1.53 wrote >=0.3.0 / >=0.2.0; raise them to these floors.

    The icon is a 48×48 px RGBA PNG in assets/, and its file name must be unique among all apps: the firmware build copies every app’s icons into one folder, and a second file of the same name overwrites the first (the build prints Asset collision: 'my-app.png' …). Name it after your id, as the template does (assets/my-app.png), and use the same name in icon and in the third argument of REGISTER_APP / REGISTER_APP_PL.

    The catalog reads the copy on GitHub, so commit and push every change:

    Terminal window
    git -C components/crosspad-my-app commit -am "Describe My App"
    git -C components/crosspad-my-app push

    You should see: git push end with a line such as 1a2b3c4..5d6e7f8 main -> main.

    If not: a push rejected for lack of access: publishing says gh is not authenticated. Manifest problems: CP Tools submit says Not yet lists each one and its fix.

  3. Keep library.json in step

    The template also writes library.json, the package description the Arduino build of the firmware reads ("appLibrary": true there keeps the app’s registration in the link, as WHOLE_ARCHIVE ON does in CMakeLists.txt). Its version must equal the manifest’s version, or the release workflow below refuses to tag. Bump both together. An app without library.json (the examples have none) skips this check.

    Terminal window
    grep '"version"' components/crosspad-my-app/crosspad-app.json components/crosspad-my-app/library.json

    You should see: the same version on both lines, such as "version": "0.1.0",.

  4. Get listed

    Add the topic crosspad-app, on the repo’s GitHub page (the gear next to About) or with the GitHub CLI. The catalog build picks it up within six hours, or at once when the repo publishes a release through the release workflow.

    Terminal window
    gh repo edit CrossPad/crosspad-my-app --add-topic crosspad-app

    You should see: ✓ Edited repository CrossPad/crosspad-my-app, and crosspad-app under About on the repo page.

    If not: publishing says gh is not authenticated. If gh refuses with a permission error, ask an org owner to add the topic.

Clients cache the catalog in app-registry.json in the project root for an hour, so delete the cache first:

Terminal window
rm -f app-registry.json
idf.py app-list

Your app appears under Official: (org repo) or Community: (any other repo), as [✓] my-app with its description, because this project has it installed.

If not: my app is not in the catalog

Every field of crosspad-app.json, as the template writes it. The catalog copies all of them except changelog into its entry for your app.

FieldNeeded byWhat it holds
idcatalogThe id people install by (idf.py app-install --app my-app), and the catalog’s key for the app. Lowercase letters, digits and dashes, starting with a letter; the app’s folder is crosspad-<id>.
namecatalogThe name shown in the catalog and in CP Tools. On the device the app’s label is the first argument of REGISTER_APP / REGISTER_APP_PL (MyApp), not this.
versioncatalogThree numbers, major.minor.patch. Each new version you push is a release.
descriptioncatalogOne line: what the app does.
platformscatalogWhere it builds: esp-idf (the firmware) and pc (the simulator). CP Tools warns before installing an app on a platform it does not list.
requiresinstallThe lowest crosspad-core and crosspad-gui the app builds against, as >=x.y.z. CP Tools warns, and asks, before installing an app into a project whose core or gui is older.
iconfirmwareThe icon’s file name in assets/: 48×48 px RGBA PNG, unique among all apps.
component_pathinstallWhere the app lands in the firmware project: components/crosspad-<id>.
categoryCP Toolsmusic, audio, tools or other.
changelogreleasesOne line per version, each starting with its version ("0.2.0: …"). The release workflow uses the lines for the version it releases as the release notes.
screenshotCP Tools (optional)A picture of the app, a path in the repo or a URL. CP Tools opens it from the app’s page.

Clients that follow Latest release (the default in CP Tools) install and update to the newest tag vX.Y.Z in your repo; an app without such tags is followed on its default branch instead. The reusable workflow in CrossPad/.github makes those tags for you: add this file to your repo as .github/workflows/app-release.yml.

name: App release
on:
push:
branches: [main]
paths: [crosspad-app.json]
workflow_dispatch:
permissions:
contents: write
concurrency:
group: app-release
cancel-in-progress: false
jobs:
release:
uses: CrossPad/.github/.github/workflows/app-release.yml@v1

From then on, a push to the default branch that changes version in crosspad-app.json tags the commit that introduced the version as vX.Y.Z and publishes it as a GitHub prerelease named after the app, with the matching changelog lines as its notes. It refuses, and the run fails, when:

  • version is not three plain numbers, or not above your newest vX.Y.Z tag;
  • library.json exists and carries another version;
  • id has characters other than letters, digits, ., _ and -;
  • changelog is not a list of strings.

The workflow runs only on the repo’s default branch: if yours is not main, change branches: [main] to its name.

Nothing is tagged backwards: in a repo with no vX.Y.Z tag yet, only a push that itself changes the version makes the first release. For a CrossPad org repo the release also rebuilds the catalog at once; for any other repo the catalog’s six-hourly build picks the new version up.

You should see: after the push, an App release run in your repo’s Actions tab, and the new version under Releases.

A profile is a named set of apps (plus build options) that a firmware project can apply in one go: the firmware project ships full (every official app) and lite (only the Sampler) in config/profiles/. A profile names its apps by catalog id, and applying it installs each one from the catalog, so an app can be part of a profile someone else applies only once it is published.

To make a profile with your app in it, install the apps you want and save the set; then copy config/profiles/my-set.json into the other checkout (or commit it to a repo both use) and apply it there:

Terminal window
# in the checkout that has the apps
idf.py app-install --app my-app
idf.py app-profile --profile-action save --name my-set --description "Sampler and My App"
# in the other checkout, after copying config/profiles/my-set.json into it
idf.py app-profile --profile-action apply --name my-set

You should see: Saved …/config/profiles/my-set.json after the save; the apply prints Profile 'my-set': … with one install line per app the checkout is missing, then installs them. --remove-extra also removes installed apps the profile leaves out. After an apply, rebuild from a clean build folder as in Start from the template, step 3.