---
title: Publishing Plugins
description: Bundle and publish your EmDash plugin to the marketplace.
---

import { Aside, Steps, Tabs, TabItem } from "@astrojs/starlight/components";

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

## Prerequisites

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)

## Bundle Format

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

| File             | Required | Description                            |
| ---------------- | -------- | -------------------------------------- |
| `manifest.json`  | Yes      | Plugin metadata extracted from `definePlugin()` |
| `backend.js`     | No       | Bundled sandbox code (self-contained ES module) |
| `admin.js`       | No       | Bundled admin UI code                  |
| `README.md`      | No       | Plugin documentation                   |
| `icon.png`       | No       | Plugin icon (256x256 PNG)              |
| `screenshots/`   | No       | Up 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.

## Building a Bundle

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

```bash
cd packages/plugins/my-plugin
emdash plugin bundle
```

This will:

<Steps>
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/`
</Steps>

### Entrypoint Resolution

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

```json title="package.json"
{
  "exports": {
    ".": { "import": "./dist/index.mjs" },
    "./sandbox": { "import": "./dist/sandbox-entry.mjs" },
    "./admin": { "import": "./dist/admin.mjs" }
  }
}
```

| Export       | Purpose | Built as |
| ------------ | ------- | -------- |
| `"."`        | Main entry — used to extract the manifest | Externals: `emdash`, `@emdash-cms/*` |
| `"./sandbox"` | Backend code that runs in the sandbox | Fully self-contained (no externals) |
| `"./admin"`  | Admin UI components | Fully self-contained |

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

<Aside type="note">
  The bundle command maps dist paths back to source automatically. If your `"."` export points to `./dist/index.mjs`, it will find and build `src/index.ts`.
</Aside>

### Options

```bash
emdash plugin bundle [--dir <path>] [--outDir <path>]
```

| Flag | Default | Description |
| ---- | ------- | ----------- |
| `--dir` | Current directory | Plugin source directory |
| `--outDir`, `-o` | `dist` | Output directory for the tarball |

### Validation

The bundle command checks:

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

<Aside type="tip">
  If your backend code imports a Node.js built-in, the bundle will fail validation. Replace Node.js APIs with Web APIs or move the logic to a trusted plugin instead.
</Aside>

## Publishing

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

```bash
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:

```bash
# Explicit tarball path
emdash plugin publish --tarball dist/my-plugin-1.0.0.tar.gz

# Build first, then publish
emdash plugin publish --build
```

### Authentication

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

<Steps>
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`)
</Steps>

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:

```bash
# Log in without publishing
emdash plugin login

# Log out (clear stored token)
emdash plugin logout
```

### First-Time Registration

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

### Version Requirements

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

### Security Audit

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.

### Options

```bash
emdash plugin publish [--tarball <path>] [--build] [--dir <path>] [--registry <url>]
```

| Flag | Default | Description |
| ---- | ------- | ----------- |
| `--tarball` | Latest `.tar.gz` in `dist/` | Explicit tarball path |
| `--build` | `false` | Run `emdash plugin bundle` before publishing |
| `--dir` | Current directory | Plugin directory (used with `--build`) |
| `--registry` | `https://marketplace.emdashcms.com` | Marketplace URL |

## Complete Workflow

Here's the typical publish cycle:

```bash
# 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:

```bash
# Build the tarball
emdash plugin bundle

# Check the output
tar tzf dist/my-plugin-1.1.0.tar.gz

# Publish
emdash plugin publish
```
