Skip to content

Publishing Plugins

Once you’ve built a plugin, you can publish it to the EmDash Marketplace so other sites can install it from the admin dashboard.

Before publishing, make sure your plugin:

  • Has a valid package.json with the "." export pointing to your plugin entry
  • Uses definePlugin() with a unique id and valid semver version
  • Declares its capabilities (what APIs it needs access to)

Published plugins are distributed as .tar.gz tarballs containing:

FileRequiredDescription
manifest.jsonYesPlugin metadata extracted from definePlugin()
backend.jsNoBundled sandbox code (self-contained ES module)
admin.jsNoBundled admin UI code
README.mdNoPlugin documentation
icon.pngNoPlugin icon (256x256 PNG)
screenshots/NoUp to 5 screenshots (PNG/JPEG, max 1920x1080)

The manifest.json is generated automatically from your definePlugin() call. It contains the plugin ID, version, capabilities, hook names, route names, and admin configuration — but no executable code.

The emdash plugin bundle command produces a tarball from your plugin source:

Terminal window
cd packages/plugins/my-plugin
emdash plugin bundle

This will:

  1. Read your package.json to find entrypoints
  2. Build the main entry with tsdown to extract the manifest
  3. Bundle backend.js (minified, tree-shaken, self-contained)
  4. Bundle admin.js if an "./admin" export exists
  5. Collect assets (README, icon, screenshots)
  6. Validate the bundle (size limits, no Node.js built-ins in backend)
  7. Write {id}-{version}.tar.gz to dist/

The bundle command finds your code through package.json exports:

package.json
{
"exports": {
".": { "import": "./dist/index.mjs" },
"./sandbox": { "import": "./dist/sandbox-entry.mjs" },
"./admin": { "import": "./dist/admin.mjs" }
}
}
ExportPurposeBuilt as
"."Main entry — used to extract the manifestExternals: emdash, @emdash-cms/*
"./sandbox"Backend code that runs in the sandboxFully self-contained (no externals)
"./admin"Admin UI componentsFully self-contained

If "./sandbox" is missing, the command looks for src/sandbox-entry.ts as a fallback.

Terminal window
emdash plugin bundle [--dir <path>] [--outDir <path>]
FlagDefaultDescription
--dirCurrent directoryPlugin source directory
--outDir, -odistOutput directory for the tarball

The bundle command checks:

  • Size limit — Total bundle must be under 5MB
  • No Node.js built-insbackend.js cannot import fs, path, child_process, etc. (sandbox code runs in a V8 isolate, not Node.js)
  • Icon dimensionsicon.png should be 256x256 (warns if wrong, still includes it)
  • Screenshot limits — Max 5 screenshots, max 1920x1080

The emdash plugin publish command uploads your tarball to the marketplace:

Terminal window
emdash plugin publish

This will find the most recent .tar.gz in your dist/ directory and upload it. You can also specify the tarball explicitly or build before publishing:

Terminal window
# Explicit tarball path
emdash plugin publish --tarball dist/my-plugin-1.0.0.tar.gz
# Build first, then publish
emdash plugin publish --build

The first time you publish, the CLI authenticates you via GitHub:

  1. The CLI opens your browser to GitHub’s device authorization page
  2. You enter the code displayed in your terminal
  3. GitHub issues an access token
  4. The CLI exchanges it for a marketplace JWT (stored in ~/.config/emdash/auth.json)

The token lasts 30 days. After it expires, you’ll be prompted to re-authenticate on the next publish.

You can also manage authentication separately:

Terminal window
# Log in without publishing
emdash plugin login
# Log out (clear stored token)
emdash plugin logout

If your plugin ID doesn’t exist in the marketplace yet, emdash plugin publish registers it automatically before uploading the first version.

Each published version must have a higher semver than the last. You cannot overwrite or republish an existing version.

Every published version goes through an automated security audit. The marketplace scans your backend.js and admin.js for:

  • Data exfiltration patterns
  • Credential harvesting via settings
  • Obfuscated code
  • Resource abuse (crypto mining, etc.)
  • Suspicious network activity

The audit produces a verdict of pass, warn, or fail, which is displayed on the plugin’s marketplace listing. Depending on the marketplace’s enforcement level, a fail verdict may block publication entirely.

Terminal window
emdash plugin publish [--tarball <path>] [--build] [--dir <path>] [--registry <url>]
FlagDefaultDescription
--tarballLatest .tar.gz in dist/Explicit tarball path
--buildfalseRun emdash plugin bundle before publishing
--dirCurrent directoryPlugin directory (used with --build)
--registryhttps://marketplace.emdashcms.comMarketplace URL

Here’s the typical publish cycle:

Terminal window
# 1. Make your changes
# 2. Bump the version in definePlugin() and package.json
# 3. Bundle and publish in one step
emdash plugin publish --build

Or if you prefer to inspect the bundle first:

Terminal window
# Build the tarball
emdash plugin bundle
# Check the output
tar tzf dist/my-plugin-1.1.0.tar.gz
# Publish
emdash plugin publish