Plugin Marketplace
Sign in

Documentation

All articles

Publishing a plugin

Publishing from GitHub Actions

Publish a GitHub release and let a workflow sign and upload the plugin.

You can publish new versions from GitHub Actions. You publish a GitHub release, and a workflow signs the plugin and uploads it to the marketplace. There is no password or API key to store: GitHub gives every workflow run a token that says which repository it comes from, and the marketplace checks it. Every release still goes through the checks and review.

Set it up

  1. A signing key. Create a key pair and register the public key under Signing keys, as in Signing keys. Skip this if you already have one.

  2. A namespace. The plugin id must be in one of your namespaces: a verified domain, or io.github.<your account>.*, which the first release from a repository of that account proves by itself.

  3. A trusted publisher. Under Trusted publishing in the author console, add the plugin id, the repository (owner/name), the workflow file name (release.yml) and, optionally, a GitHub environment.

  4. The secret. Store private_key.pem as the secret PRUSA_SLICER_PLUGIN_MARKETPLACE_SIGNING_KEY, best in a GitHub environment that only release tags may use (see Security):

    gh secret set PRUSA_SLICER_PLUGIN_MARKETPLACE_SIGNING_KEY --env release < private_key.pem

    Whoever has the private key can sign plugins as you. Never commit it, keep a backup in a safe place such as a password manager (neither the marketplace nor GitHub can give it back), and if it leaks, revoke it and register a new one.

  5. The workflow. Add this file to the repository:

    name: Publish to Plugin Marketplace
    on:
      release:
        types: [published]
    permissions:
      contents: read
      id-token: write # lets the run prove which repository and workflow it is
    jobs:
      publish:
        runs-on: ubuntu-latest
        environment: release # optional, see Security
        steps:
          - uses: actions/checkout@v7
          - uses: prusa3d/plugin-marketplace-publish@v1
            with:
              path: . # the folder with manifest.json
              private-key: ${{ secrets.PRUSA_SLICER_PLUGIN_MARKETPLACE_SIGNING_KEY }}
  6. A release. Bump version in manifest.json, commit, and publish a GitHub release tagged v<version>. A pre-release goes to the beta channel, and the release notes become the changelog.

    gh release create v1.2.0 --title "1.2.0" --notes "What changed"

The release then shows on your plugin’s page in the author console.

Several plugins in one repository

Keep each plugin in its own folder and add one trusted publisher per plugin, all with the same repository and workflow. Each release publishes one plugin: its tag names the folder and the version, for example flow-tower/v1.2.0.

your-account/slicer-plugins
├── .github/workflows/release.yml
└── plugins/
    ├── flow-tower/
    │   ├── manifest.json   "id": "io.github.your-account.flow-tower"
    │   └── main.lua
    └── gridfinity/
        ├── manifest.json   "id": "io.github.your-account.gridfinity"
        ├── main.lua
        └── assets/bin.svg

The workflow picks the folder from the tag:

    steps:
      - uses: actions/checkout@v7
      - name: Pick the plugin from the tag
        id: plugin
        run: |
          name="${GITHUB_REF_NAME%/v*}"   # flow-tower/v1.2.0 -> flow-tower
          dir="plugins/$name"
          if [ ! -f "$dir/manifest.json" ]; then
            echo "::error::tag $GITHUB_REF_NAME names no plugin folder ($dir)"
            exit 1
          fi
          echo "dir=$dir" >> "$GITHUB_OUTPUT"
      - uses: prusa3d/plugin-marketplace-publish@v1
        with:
          path: ${{ steps.plugin.outputs.dir }}
          private-key: ${{ secrets.PRUSA_SLICER_PLUGIN_MARKETPLACE_SIGNING_KEY }}

The marketplace accepts a bundle only for a plugin id this repository and workflow are bound to, so one plugin’s release can never publish another. Each folder is a complete bundle: code shared between plugins has to be copied into every folder that uses it.

Inputs

Input Default What it does
private-key required RSA private key, 2048 bits or more. Pass a secret.
path . Folder with manifest.json and the files the plugin ships.
marketplace-url production The marketplace to publish to.
channel auto stable or beta. Empty: beta for a pre-release, stable otherwise.
changelog release notes Release notes, Markdown. Empty: the GitHub release body.
check-version true For a tag, the tag must end with the version in manifest.json.
dry-run false Build and sign only, do not upload.

Security

  • Anyone who can change the workflow can sign with the key in the secrets. Create a GitHub environment with required reviewers that only v* tags may deploy to, keep the secret in it, add environment: <name> to the job and the same environment to the trusted publisher. A workflow pushed on some branch then cannot read the key, every publish waits for an approval, and runs outside the environment are refused.
  • The first release remembers the repository’s numeric GitHub id, so a repository deleted and re-created under the same name by someone else cannot publish.
  • The action ships plain files only: it refuses symbolic links and private keys in the plugin folder, and lists every file it signs in the run’s log.

When it fails

The run says What to do
no OIDC token available Add permissions: id-token: write to the workflow.
tag '...' does not match manifest.json version Bump version in manifest.json, or tag the release v<version> (or <folder>/v<version>).
... is not a trusted publisher of ... The repository, workflow file or environment is not the one under Trusted publishing, or the plugin id is not added there.
... is outside your namespaces Verify the domain, or use io.github.<your account>.*.
signature did not verify against any registered key Register the public key of the key in the secret under Signing keys.
bundle is signed with an expired key Put an active key in the secret, and its public key under Signing keys.
version ... already exists This version is already uploaded. Bump the version for a new release.
the answer did not come from the marketplace Something in front of the marketplace (a proxy or firewall on the way) refused the upload. The message names it, and a Cloudflare Ray ID if there is one; report it with that.