Build your first plugin
Plugins run in a V8 isolate and can only access capabilities declared by the package, approved by an administrator and enforced by the CMS gateway.
Before you start
Section titled “Before you start”Install Node.js 22+, pnpm 10+, and the zcms CLI:
npm install -g @zcmsorg/cli@latestStep 1: Scaffold the project
Section titled “Step 1: Scaffold the project”zcms init ./hello --kind plugin --id com.acme.plugin.helloUse a reverse-DNS id under a domain you control. init asks for anything you leave out, and refuses to write into a directory that already holds something.
You get a project that builds, typechecks, tests, packs and signs with nothing changed:
hello/├── plugin.json # the manifest├── package.json├── build.mjs # esbuild -> ONE CommonJS file├── tsconfig.json├── src/index.ts├── test/plugin.test.ts└── .gitignore # ignores *.pem — your signing key lives here latercd hellopnpm installpnpm buildpnpm testThe rest of this page explains what the generated files contain, so you can change them with confidence.
Step 2: Complete the manifest
Section titled “Step 2: Complete the manifest”Create plugin.json at the package root. The required fields are id, name, version, author and engine. entry defaults to dist/index.js; permissions declares the scopes requested at activation time.
{ "id": "com.example.plugin.hello", "name": "Hello Plugin", "version": "0.1.0", "description": "Adds a read-only content helper.", "changelog": "- First public release.", "author": { "name": "Example Studio", "url": "https://example.com" }, "engine": ">=0.1.0", "entry": "dist/index.js", "scope": "site", "permissions": ["content:read"], "capabilities": ["hello.content-helper"], "media": { "screenshots": ["screenshots/admin.png"] }}Optional plugin fields include changelog, scope, capabilities, media, settingsSchema and database.tables. Prefer ctx.storage; declare relational tables only when necessary, and keep every table inside the plugin-specific prefix enforced by the platform.
The optional changelog field holds this version’s release notes — a short list of what changed since the previous version. Administrators see it as a “What’s new” note in the plugin manager and when they review an update, so they understand what a new version does before approving it. Write plain text with one change per line (blank lines and tabs are allowed); the limit is 2000 characters. Update changelog every time you bump version.
scope declares the plugin’s activation reach and is either "site" (the default) or "org":
"site"— installed and activated per website. This is the default and fits most plugins (SEO, commerce, an AI assistant, and so on)."org"— installed once for the whole organization (tenant) and runs on every website that organization owns, like a WordPress “network-activated” plugin. Administrators manage it from a separate Organization plugins screen.
scope is reach, not authority: an "org" plugin still has its permissions approved at the tier it installs at, and never becomes a core plugin. The platform reads scope from the signed manifest, so a package cannot widen its own privileges through this field. See Themes and plugins for how administrators enable plugins at each tier.
Step 3: Implement the entrypoint
Section titled “Step 3: Implement the entrypoint”Implement the generated entrypoint with @zcmsorg/plugin-sdk types and APIs.
- Use only APIs exposed by the SDK.
- Obtain content, media and mail access through the provided gateway.
- Handle the case where an optional permission was not approved.
- Keep secrets in the platform secret store; never bundle them in the package.
- Do not import database packages, filesystem APIs or Node.js built-in modules outside the runtime contract.
Step 4: Run locally
Section titled “Step 4: Run locally”Build the plugin and start the Z-CMS development environment. Install the local development build on a test site, then activate it for that site only.
Check the following before continuing:
- The plugin loads without runtime or schema errors.
- Each feature works with the minimum documented permissions.
- The plugin fails safely when a permission is denied.
- Deactivation stops hooks and background work cleanly.
- Reactivation does not duplicate jobs, webhooks or stored configuration.
Step 5: Test trust boundaries
Section titled “Step 5: Test trust boundaries”Add automated tests for the plugin lifecycle, permission checks and invalid input. Include negative tests that try to access another tenant, use an unapproved permission and call APIs outside the SDK contract.
Run the project’s typecheck, lint and test scripts. Fix every failure before packaging.
Step 6: Build the release package
Section titled “Step 6: Build the release package”-
Leave the version to
zcms pack(step 5) — it stamps the current version and advances it, keeping the manifest andpackage.jsonin sync; pass--set-versionto pin an exact one. -
Update the
changelognote in the manifest and the permission disclosure. -
Build from a clean checkout with the committed lockfile.
-
Generate the publisher key pair once if you do not already have one:
Terminal window zcms keygen --out ./keys -
Copy
plugin.json,distand required runtime assets into a clean release directory, then pack that directory:Terminal window zcms pack ./build/package --kind plugin \--key ./keys/publisher-private.pem \--pub ./keys/publisher-public.pem \--out ./release/hello-plugin-0.1.0.zcms -
Verify the publisher signature:
Terminal window zcms verify ./release/hello-plugin-0.1.0.zcms -
Record the checksum printed by
zcms pack.
zcms pack excludes src, node_modules, .git, .env, source maps and build-tool configuration. It sorts entries and zeroes archive timestamps so packing the same built directory produces identical bytes — add --no-bump when you re-pack to check this, so the version is not advanced between runs.
The first zcms verify checks the publisher signature only. Marketplace adds its own co-signature during intake; a runtime still refuses the publisher-only package.
Step 7: Submit the package
Section titled “Step 7: Submit the package”Continue with Publish a package for publisher verification, Marketplace submission, review, signing and release.