Contributor Guide
Publish a plugin or theme for EmDash CMS. From signup to first live version, here's everything you need.
Quick start
- 1 Sign in with GitHub at /dashboard. Your GitHub account is your marketplace identity.
- 2 Click "Register Plugin" (or "Register Theme"), fill out the metadata form, and upload a
.tgzbundle. - 3 Our static scanner reviews your bundle in seconds. If it passes, your plugin is published immediately with a "Scanned" trust tier. If the scanner flags soft findings, it's published as "Scanned — Caution". If blocking findings, it's rejected and you can see exactly why.
- 4 Iterate, upload new versions, and link a GitHub repo to auto-publish on tagged releases.
manifest.json schema
Every plugin bundle must contain a manifest.json at its root. The schema mirrors the EmDash CMS core contract so plugins work upstream too.
id — lowercase slug, alphanumeric + hyphens. Must match your registered plugin id.
version — strict semver (X.Y.Z). No pre-release suffixes in webhook submissions.
capabilities — from the fixed list of 11: network:fetch, network:fetch:any, read:content, write:content, read:media, write:media, read:users, email:send, email:provide, email:intercept, page:inject. Declare the minimum you need.
allowedHosts — every external domain your plugin calls. The scanner cross-checks this against every URL in your code.
hooks — only the 20 canonical EmDash hook names are accepted.
Bundle layout and limits
Your .tgz archive must contain:
manifest.jsonat the root.- All source files (JavaScript/TypeScript, HTML, JSON) that your plugin needs at runtime.
- No symlinks, hardlinks, or device files. These are hard-rejected by the bundle validator.
- No path traversal or absolute paths.
Hard limits:
- 10 MB compressed tarball size.
- 50 MB decompressed total.
- 5 MB per individual file.
- 200 files maximum.
How review works
Every version goes through three gates. All three are automated — you do not wait on a human unless the scanner flags something ambiguous.
- 1. Bundle validation.
Size limits, path safety, symlink rejection, manifest schema validation, checksum computation. Takes under a second.
- 2. Static scan.
Deterministic pattern matching for dangerous primitives and host declarations. Blocking findings (eval, new Function, child_process, process.binding, Worker-from-blob) reject the version. Soft findings (require, fs, undeclared hosts, browser-only APIs) publish with a "Caution" tier. Full ruleset at /docs/security.
- 3. AI review (when available).
Workers AI runs a second-pass review of clean bundles. Currently triggered by moderators on demand — as the marketplace scales this will move onto the upload hot path.
Trust tiers
Every published version carries a tier. Users see it on cards and detail pages.
Common rejection reasons
- Use of eval() or new Function(). Arbitrary code execution is a hard rejection. Refactor to use explicit imports.
- child_process or process.binding. Node-only APIs — not available in the EmDash runtime. Remove any bundled Node-targeted code.
- Undeclared hosts. If your code calls
fetch("https://x.com/..."),x.commust be inmanifest.allowedHosts. - Missing manifest.json. Every bundle needs one at the root, with a valid schema.
- Manifest id mismatch. The id in
manifest.jsonmust match your registered plugin id. - Broad capabilities.
network:fetch:any,read:users, andemail:interceptadd a caution signal — only declare them if your plugin genuinely needs them.
GitHub auto-submit
Instead of manually uploading bundles, install the emdashcms.org GitHub App on your repo and link it to your plugin from the dashboard. Every time you publish a GitHub Release whose tag matches your plugin's version, we'll download the tarball, scan it, and publish automatically. Webhook-sourced versions also capture a link back to the GitHub release page as build provenance.
- Tag pattern defaults to
*— every release. Customise from the dashboard. - Draft releases and pre-releases are ignored.
- The plugin id in the uploaded manifest must match your registered id.
Version management
- 5 versions/day upload rate limit per author.
- 10 audit runs/day for AI-bound retries.
- 3 retries maximum per rejected version. After that, upload a new version with the fix.
- Static-scanner rejections cannot be retried — the scan is deterministic, so the same bundle will always produce the same result. Upload a corrected bundle instead.
- Revocation by an admin is reserved for cases where a published version needs to be blocked after the fact. Revoked versions are not downloadable and carry a public notice on the detail page.
Community standards
By publishing here you agree to the Code of Conduct, the Terms of Use, and the security review policy. Be kind, write clear descriptions, respond to support requests, and fix security issues promptly when reported.