Skip to main content
Version: v0.7.6

Publish a plugin to the marketplace

This guide takes a finished plugin folder and turns it into a catalog entry users can find and install. You will package the plugin into a distributable .zip, register it in the marketplace catalog, and cut a tagged GitHub Release that serves the download.

The marketplace is the openwa-plugins repository. Publishing means opening a pull request that adds your plugin folder; the catalog (plugins.json) and the release .zip are generated from your folder's manifest.json and CHANGELOG.md, not written by hand.

Prerequisites
  • A working plugin folder that builds and installs locally. See Building a plugin.
  • Node.js and the zip CLI on your PATH (the package script shells out to zip).
  • A fork of the openwa-plugins repository.

How publishing fits together

Three artifacts come out of one plugin folder. Each is derived from the manifest and changelog, so those two files are the single source of truth.

The catalog entry's download field points at a GitHub Release asset whose URL is fully predictable from the plugin id and version. Get those two right and everything else lines up.

1. Meet the marketplace standard

Every plugin in the catalog follows the same layout so tooling stays consistent. Before you package, confirm your folder has these files:

FilePurpose
manifest.jsonMachine-readable metadata. The single source of truth for id, name, version, and type.
README.mdHuman docs, with a generated Details block between <!-- BEGIN DETAILS --> and <!-- END DETAILS --> markers.
CHANGELOG.mdDated version history. The top released heading sets the release date and must match the manifest version.
index.tsEntry source that default-exports an IPlugin class.
dist/index.jsThe built artifact referenced by manifest.main (produced by the package step).

The full standard — manifest fields, the configSchema vocabulary, and README section order — is documented in PLUGIN-STANDARD.md. This guide covers only the publishing path.

Required manifest fields

OpenWA rejects an install without these five fields, and the package script checks them before building:

FieldConstraint
idMatches /^[a-z0-9][a-z0-9._-]*$/i, unique, and not a reserved id (whatsapp-web.js, baileys, auto-reply, translation).
nameDisplay name shown in the dashboard.
versionSemVer. Must equal the top released CHANGELOG.md heading.
typeMust be extension — the only user-installable type.
mainA require()-able file present in the package, e.g. dist/index.js.

The catalog also surfaces description, author, license, keywords, status, minOpenWAVersion, and testedOpenWAVersion. Set them in the manifest; the catalog generator reads them from there.

2. Version and set compatibility

OpenWA does not yet negotiate host-version compatibility, so the version fields are a convention you maintain, not a gate the host enforces. Set them honestly.

FieldMeaning
versionThe plugin's own SemVer version. MAJOR = breaking change for operators, MINOR = new capability, PATCH = fix.
minOpenWAVersionThe lowest OpenWA version the plugin is known to work on.
testedOpenWAVersionThe OpenWA version you actually verified against.

The version must agree with your changelog. The package step fails with a version drift error if manifest.json says one version and the top CHANGELOG.md heading says another:

✗ my-plugin: version drift: manifest is 1.0.0 but CHANGELOG top is 0.9.0

Keep CHANGELOG.md in Keep a Changelog form: an ## [Unreleased] section at the top, then ## [MAJOR.MINOR.PATCH] — YYYY-MM-DD headings in descending order. To release 1.0.0, move the unreleased notes under a dated ## [1.0.0] — 2026-06-26 heading and set the manifest version to 1.0.0.

3. Build the distributable .zip

Package the plugin from the repository root. Pass the plugin folder name:

node package.mjs my-plugin

The script validates the manifest, bundles index.ts into a single CommonJS dist/index.js with esbuild, then zips manifest.json plus the built dist/ into my-plugin.zip. It finishes by printing the size and SHA-256 of the package:

✓ Packaged my-plugin v1.0.0 → my-plugin.zip (12.4 KB)
sha256: 9f2c0a7e8b5d4c3a1f6e0b9d8c7a6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f

The build npm script is a shortcut that runs the same packager against one specific plugin:

# package.json: "build": "node package.mjs gsheets-logger"
npm run build

To build your own plugin, call node package.mjs <your-id> directly rather than npm run build.

Install limits are enforced

OpenWA refuses a package over 5 MB compressed, 200 files, or 20 MB uncompressed. The packager also fails locally if the .zip exceeds the 5 MB compressed limit. There is no npm install at install time, so bundle every dependency into dist/index.js.

4. Add the catalog entry

The catalog (plugins.json) and the README tables are generated — never edit them by hand. After your manifest.json and CHANGELOG.md are final, regenerate them:

npm run catalog

This rewrites plugins.json, the catalog table in the root README.md, and each plugin README's Details block, all from the manifests and changelogs it discovers:

Catalog written (6 plugin(s)); updated 3 file(s).

Your plugin's catalog entry is built from the manifest, with the release date pulled from the top CHANGELOG.md heading:

{
"id": "my-plugin",
"name": "My Plugin",
"version": "1.0.0",
"type": "extension",
"status": "stable",
"description": "What the plugin does, one sentence.",
"author": "Your Name <you@example.com>",
"license": "MIT",
"keywords": ["whatsapp", "openwa"],
"minOpenWAVersion": "0.7.0",
"testedOpenWAVersion": "0.7.6",
"releasedAt": "2026-06-26",
"repoPath": "my-plugin",
"repoUrl": "https://github.com/rmyndharis/OpenWA-plugins",
"homepage": "https://github.com/rmyndharis/OpenWA-plugins/tree/main/my-plugin",
"download": "https://github.com/rmyndharis/OpenWA-plugins/releases/download/my-plugin-v1.0.0/my-plugin.zip"
}

Two fields are deliberately absent from the catalog: package size and SHA-256. Those are per-release artifacts published on the GitHub Release, so keeping them out of plugins.json keeps the catalog deterministic and the CI drift check stable.

The download URL is predictable

The generator computes download from the repository, plugin id, and version:

<repoUrl>/releases/download/<id>-v<version>/<id>.zip

So my-plugin at version 1.0.0 resolves to the asset at the release tagged my-plugin-v1.0.0. The catalog points at that URL before the release exists — you create the matching release in the next step.

Verify the catalog is current

CI runs a drift check that fails if the committed catalog is stale or a manifest version disagrees with its changelog. Run it yourself before opening the pull request:

npm run catalog:check

A clean tree reports:

Catalog up to date (6 plugin(s)).

If it fails, it lists the stale files. Run npm run catalog to regenerate them and commit the result:

Catalog is out of date. Run `npm run catalog` and commit:
- plugins.json
- my-plugin/README.md

5. Open the pull request and release

  1. Commit your plugin folder, the regenerated plugins.json, and the updated README files to a branch on your fork.
  2. Open a pull request against openwa-plugins describing what the plugin does and the OpenWA version you tested against.
  3. Once merged, tag the release as <plugin-id>-vX.Y.Z — the version must match the manifest and changelog (for example my-plugin-v1.0.0).

The release GitHub Action takes over from the tag. It builds the plugin, attaches <id>.zip and <id>.zip.sha256 to a GitHub Release, and uses the matching CHANGELOG.md section as the release notes. The asset lands at exactly the download URL already recorded in the catalog.

Match the tag to the version

The tag, the manifest version, and the top CHANGELOG.md heading must all be the same value. A mismatch points the catalog's download URL at a release asset that does not exist.

How users install your published plugin

Until the in-dashboard marketplace ships, users download the release .zip and upload it. An admin-scoped API key manages every plugin route. The install path is the same one covered in the overview:

# 1. Install the uploaded package
curl -X POST "http://localhost:2785/api/plugins/install" \
-H "X-API-Key: YOUR_API_KEY" \
-F "file=@my-plugin.zip"

# 2. Configure it (secrets are masked on read, preserved on write)
curl -X PUT "http://localhost:2785/api/plugins/my-plugin/config" \
-H "X-API-Key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "config": { "apiKey": "..." } }'

# 3. Enable it — plugins install disabled and never auto-enable
curl -X POST "http://localhost:2785/api/plugins/my-plugin/enable" \
-H "X-API-Key: YOUR_API_KEY"

The planned in-dashboard marketplace reads this same plugins.json and installs the release asset by URL, so a correct catalog entry is what makes one-click install work later. The design is described in OPENWA-MARKETPLACE.md.

Troubleshooting

SymptomCauseFix
manifest.json missing required field(s)One of id, name, version, type, main is absent.Add the missing field, then re-run node package.mjs <id>.
type must be "extension"The manifest declares a non-installable type.Set "type": "extension". Engine, storage, queue, and auth plugins are first-party built-ins.
version drift: manifest is X but CHANGELOG top is YThe manifest version and the top dated changelog heading disagree.Make both the same SemVer value.
CHANGELOG.md has no released "## [x.y.z] — YYYY-MM-DD" headingThe changelog has only an ## [Unreleased] section.Add a dated ## [X.Y.Z] — YYYY-MM-DD heading for this release.
package is N KB, over the 5 MB install limitThe bundle is too large.Trim dependencies; the runtime needs only Node built-ins plus your bundled code.
catalog:check fails in CIplugins.json or a README was edited by hand or left stale.Run npm run catalog and commit the regenerated files.
zip failed (is the \zip` CLI installed?)`The zip binary is missing.Install zip and re-run the package step.

Next steps