Skip to content
ZyncDocsGitHub Download Zync

Sign and Publish a Plugin

Build, sign, package, and submit a Zync Manifest v2 plugin to the signed community marketplace.

Publishing has two separate approvals: you sign your plugin package with your publisher key, and registry operators approve and sign its marketplace entry with a registry root. A GitHub release or successful local signature check alone does not make your plugin available in the Marketplace.

This guide follows the current registry preparation code and SDK 2.1.0-beta.4. The official preparation tool currently accepts stable semantic versions without prerelease or build metadata. Read the beta section before planning a beta launch.

  1. Choose your publisher namespace and plugin ID. Keep them stable across releases.
  2. Set the plugin version and tested engines.zync / engines.pluginApi ranges.
  3. Build from a clean checkout with locked dependencies and run your tests.
  4. Validate the built folder and test it in an installed Zync build, including permission denial, multiple panes, reconnects, and uninstall.
  5. Review every file to be shipped. Exclude keys, .env files, credentials, node_modules, development mocks, and unrelated source/build output. Include your license and any third-party notices required by bundled dependencies.

For the starter from Build your first plugin:

Terminal window
npm ci
npm run validate
npx --no-install zync-sdk validate dist --zync-version 2.34.0

Stop if any command fails. Also validate against your declared minimum desktop version and test that version in the app. --zync-version checks a compatibility declaration, not the behavior of every API your code calls.

Run the installed SDK executable explicitly. Avoid npx zync-sdk without a local installation because it may resolve an unrelated unscoped npm package.

Terminal window
npx --no-install zync-sdk keygen --out /absolute/private-storage/publisher-v1

Replace the path with a new directory outside Git whose parent already exists. On Windows, for example, use a private folder such as C:\ZyncSigningKeys\my-publisher-v1 after creating its parent. The SDK invokes OpenSSL; install it or supply --openssl /path/to/openssl. Git for Windows’ bundled OpenSSL is also detected.

The command requires an interactive terminal. It prompts for an optional passphrase; Enter creates an unencrypted private key. It writes publisher-private.pem and publisher-public.pem. Keep the private file and any passphrase out of source, build artifacts, issues, and chat. Back up the key securely; only the public key and fingerprint go to the registry.

This key identifies your publisher. It is separate from Zync’s application-signing key and the marketplace registry root. Generating it does not register a publisher.

From your plugin project, replace the private-key path below. The signed output must not already exist and must be outside dist. Never place the key inside the source package.

Terminal window
npx --no-install zync-sdk sign --source dist --key /absolute/private-storage/publisher-v1/publisher-private.pem --out signed/hello-1.0.0
npx --no-install zync-sdk verify --source signed/hello-1.0.0

Encrypted PEM keys prompt for their passphrase. The sign command prints a sha256:... publisher key fingerprint. Record it and verify it independently with the public key during publisher approval. The signed folder includes integrity.json and signature.json. Do not modify or add files afterward.

The verifier checks all payload hashes and the package signature. Its embedded public key is not automatically an approved publisher key. Registry review separately checks the publisher, plugin, repository, and allowed key fingerprint.

The ZIP must contain the contents of the signed folder at its root:

hello-1.0.0-signed.zip
manifest.json
worker.js
ui/index.html
ui/pane.js
ui/pane.css
integrity.json
signature.json

Here is a PowerShell 7 example. Run it only after the sign and verify commands succeed. Use new output paths for every candidate.

Terminal window
$ErrorActionPreference = 'Stop'
$assetName = 'hello-1.0.0-signed.zip'
if (Test-Path -LiteralPath release-check) { throw 'Use a new extraction directory' }
New-Item -ItemType Directory -Path release -ErrorAction Stop | Out-Null
Compress-Archive -Path signed/hello-1.0.0/* -DestinationPath "release/$assetName"
$digest = (Get-FileHash -Algorithm SHA256 -LiteralPath "release/$assetName").Hash.ToLowerInvariant()
[IO.File]::WriteAllText("$($PWD.Path)/release/$assetName.sha256", "$digest $assetName`n", [Text.UTF8Encoding]::new($false))
Expand-Archive -LiteralPath "release/$assetName" -DestinationPath release-check
npx --no-install zync-sdk verify --source release-check
if ($LASTEXITCODE -ne 0) { throw 'Packaged signature verification failed' }

Ensure release-check does not exist before extraction. The checksum asset name is the ZIP name plus .sha256. Its contents are exactly lowercase hex SHA-256, two spaces, and the ZIP filename, with an optional trailing newline. The registry preparer checks this format, not an arbitrary checksum document. Hash the final ZIP, not manifest.json or the uncompressed folder. ZIP tools may omit hidden files; verifying the extracted archive catches an incomplete package.

On macOS/Linux, use an installed ZIP tool to archive from inside the signed folder, then create the same checksum format with sha256sum or shasum -a 256 as available. Extract to a fresh directory and verify that extracted copy as well.

The current official preparer retrieves assets from GitHub releases. Commit the tested source, create a matching version tag such as v1.0.0, and attach:

  • hello-1.0.0-signed.zip
  • hello-1.0.0-signed.zip.sha256

Use your repository’s release UI or its reviewed release workflow. The tag should identify the exact source used to build the signed package. A custom tag convention is supported through releaseTag in the registry input. Public metadata and assets must be downloadable by the preparer without your private credentials.

Download the released ZIP and checksum again, compare the digest, extract the archive, and run zync-sdk verify on that copy. Install and exercise that exact candidate locally. Do not replace a published version with different bytes.

Open an issue or pull request in zync-plugin-registry with:

  • Publisher namespace, plugin ID, source repository, and a maintainer contact.
  • Public Ed25519 PEM key and the sha256:... fingerprint printed by the signer.
  • Source tag/commit, signed ZIP URL, checksum URL, and release notes.
  • Supported Zync versions/platforms, test results, screenshots, and permission explanations, especially for remote commands, file writes, and network access.
  • License, bundled dependency notices, and a privacy explanation if data leaves the device or remote server.

Maintainers review ownership and record the allowed binding in approved-publishers.json. A proposed entry looks like this; it is not self-approval:

An entry inside approved-publishers.json publishers[]
{
"publisher": "dev.example",
"pluginId": "dev.example.hello",
"repository": "YOUR_ACCOUNT/hello-zync",
"keyIds": ["sha256:REPLACE_WITH_THE_SIGNER_FINGERPRINT"]
}

Submit the public key for independent comparison. Never submit a private key or request the registry root. Approval for one plugin/repository does not automatically approve every package using the same publisher name.

Add a release to registry-input.json in the same registry repository. Preserve earlier releases and cumulative revocations. This example is an entry inside releases[], not a replacement for the entire input file:

{
"pluginId": "dev.example.hello",
"publisher": "dev.example",
"repository": "YOUR_ACCOUNT/hello-zync",
"version": "1.0.0",
"assetName": "hello-1.0.0-signed.zip",
"channel": "stable",
"publisherVerified": false
}

The default tag is v<version>. Add "releaseTag": "hello-v1.0.0" only if your release actually uses that convention. Optional thumbnailUrl must be HTTPS. Use a reviewed, stable image URL. Leave publisherVerified false unless registry maintainers authorize that identity label; a valid package signature alone does not confer it.

For local preparation, install Rust, clone your registry fork, and run from that checkout with a fresh output directory:

Terminal window
cargo run --locked -- prepare --input registry-input.json --approvals approved-publishers.json --output prepared-registry

This downloads the input releases, checks ZIP checksums, verifies package contents and signatures against approved keys, and creates an unsigned preparation bundle. It never executes the plugins. It is not a publication command and needs no private signing key. A failed run can leave partial output; retry in a new directory.

After review and merge, the repository’s Publish signed registry workflow prepares and verifies packages, signs metadata using its protected root, verifies the result, and commits the generated registry.json. It also runs daily to refresh the seven-day expiry. Registry versions increase independently of plugin versions. The published metadata must retain previous releases and revocations.

Publishers do not manually edit the generated signature or receive the root. Operator setup, root recovery, and rotation belong in the registry operations runbook.

Finally, refresh Marketplace in an installed Zync build configured for the official registry. Confirm the correct version/channel, approve permissions, install, exercise the plugin, and inspect its details. Announce marketplace availability only after this succeeds. A green plugin release workflow is an earlier step.

For every update, increase the plugin version, rebuild, validate, test, sign, and publish new immutable assets; then append a registry release entry. Explain added or changed permissions. Zync reviews those changes before activation and checks compatibility with the host version.

Marketplace updates cannot silently downgrade an installed version or replace the same semantic version with different bytes. The user’s explicit rollback action uses a retained version when available and rechecks its eligibility. Keep private storage migrations backward compatible because rollback does not revert arbitrary data migrations or remote side effects.

For key rotation, request review of the new public fingerprint before releasing packages under it. Keep prior approvals needed by retained releases. For a compromised key or release, contact registry maintainers promptly; cumulative revocations, not overwriting ZIP files, provide the containment mechanism.

The desktop can select per-plugin stable/beta releases from compatible signed metadata. The current official Rust prepare command rejects prerelease and build-metadata versions. Do not submit 1.1.0-beta.1 to that workflow expecting it to pass. Coordinate a supported registry process with maintainers first, or distribute a clearly marked local-development test package to willing testers.

SDK npm beta versions are unrelated to plugin marketplace channels. On clients using compatible beta metadata, disabling beta does not automatically downgrade an installed beta; a newer stable release or explicit eligible rollback is needed.

Use separate jobs for untrusted PR checks, building the candidate, signing, and publishing. Protect signing secrets and release environments. Give the publishing job only the verified artifacts and narrowly scoped release access. Do not run unreviewed plugin code after exposing a signing key. The SDK interactive CLI cannot prompt in headless CI; encrypted-key automation needs a reviewed signing integration that handles its passphrase securely. Do not put it in command arguments.

Failure Resolution
Unknown publisher/key Obtain approval for the exact publisher, plugin, repository, and key fingerprint
Release checksum mismatch Hash the final ZIP; use lowercase hex, two spaces, and the exact asset filename
Package identity mismatch Align manifest ID, publisher, version, registry entry, and release assets
Signature/integrity failure Rebuild and sign a fresh candidate; do not edit signed output
Stable versions only Use a stable version or coordinate a separate beta preparation path
Output already exists Choose a fresh signing/preparation/extraction directory
Marketplace unavailable Check host registry configuration and metadata expiry with operators
Installed plugin will not update Check engine ranges, channel choice, version ordering, permissions, and revocations

See best practices and the release checklist before submitting.