Bundling and publishing
Once your sandboxed plugin works, publish it so other sites can install it. Publishing is sandboxed-only — native plugins distribute via npm.
When you publish, the CLI records the release to your own Atmosphere account. You host the tarball yourself — a GitHub release asset, R2, S3, or any public URL — and the registry stores a link to it.
Prerequisites
Section titled “Prerequisites”- A valid
emdash-plugin.jsoncwithslug,publisher,license, an author (authororauthors), and a security contact (securityorsecurityContacts). Runemdash-plugin validateto confirm. - A
version(inpackage.json, or the manifest for registry-only plugins). - An Atmosphere account to publish under.
Your Atmosphere account
Section titled “Your Atmosphere account”You publish under an Atmosphere account: a portable, user-owned identity used across Bluesky and other apps in the AT Protocol network. One account is your single login across the network, with the same @handle everywhere, and your identity and data are not tied to any one app. EmDash uses this account as your publisher identity: every release you publish is a record in your own account, signed in as you.
EmDash uses the same Atmosphere accounts as its Atmosphere login for sites.
Use an existing account
Section titled “Use an existing account”If you already have a Bluesky account or any other Atmosphere account, sign in with its handle:
emdash-plugin login alice.bsky.socialThis opens your account provider’s sign-in page in the browser. EmDash never sees your password. emdash-plugin whoami lists your stored sessions; emdash-plugin switch <did> changes the active one.
Sign up for an account
Section titled “Sign up for an account”If you do not have an Atmosphere account yet, create one through any provider, then run emdash-plugin login <your-handle>. Your options:
- An app, such as Bluesky. Signing up for Bluesky creates an Atmosphere account hosted by Bluesky. This is the quickest route.
- An independent provider. Community-run or privacy-focused account hosts. Browse options at atmosphereaccount.com.
- Self-hosted. Run your own provider for full control over your identity and data.
Whichever you choose, the @handle from that account is what you pass to emdash-plugin login, and the account’s DID is what you pin as the publisher in your manifest.
Three steps
Section titled “Three steps”The following commands log in, build a tarball, and publish a release that points at the hosted tarball:
emdash-plugin login # if not already logged inemdash-plugin bundle # produces dist/<slug>-<version>.tar.gz# upload that tarball to a public URL, then:emdash-plugin publish --url https://your-host/<slug>-<version>.tar.gzbundle prints the next two steps when it finishes, including the --url invocation, so you don’t have to remember the shape.
Bundle
Section titled “Bundle”bundle runs build, validates, collects assets, and creates a tarball. Inside the tarball, plugin.mjs is packed as backend.js (the filename the registry expects).
The command accepts the following flags:
emdash-plugin bundle [--dir <path>] [--out-dir|-o <path>] [--validate-only]| Flag | Default | Description |
|---|---|---|
--dir | Current directory | Plugin source directory. |
--out-dir, -o | dist | Output directory for the tarball. |
--validate-only | false | Skip the tarball, but still produce dist/ artifacts. |
Tarball contents
Section titled “Tarball contents”| File | Required | Description |
|---|---|---|
manifest.json | Yes | Generated manifest: id, version, capabilities, hosts, and the hooks and routes read from your source. You do not maintain this by hand. |
backend.js | Yes | The built, self-contained runtime file (dist/plugin.mjs). |
README.md | No | Plugin documentation. |
icon.png | No | 256×256 PNG. |
screenshots/ | No | Up to 5, max 1920×1080. |
Validation
Section titled “Validation”bundle (and --validate-only) check:
- Size caps (RFC 0001, decompressed): total ≤ 256 KB, per-file ≤ 128 KB, ≤ 20 files. The gzipped tarball is a fraction of that.
- No Node built-ins in
backend.js— sandbox code can’t importfs,path,child_process, etc. Use Web APIs, or move that logic to a native plugin. - Capability sanity — names must be in the recognised set.
- Trust-contract coherence — the
network:request/allowedHostscross-rules from the manifest. - Asset limits — icon 256×256, ≤ 5 screenshots at ≤ 1920×1080.
To inspect the tarball before publishing, list its contents:
emdash-plugin bundletar tzf dist/my-plugin-1.1.0.tar.gzPublish
Section titled “Publish”Publishing writes the release record. The tarball must already be hosted at a public URL:
emdash-plugin publish --url <hosted-tarball-url>--url is required: it is where the plugin’s bytes live, and the registry record points at it. To verify the hosted URL serves the exact bytes you built before writing the record, pass --local:
emdash-plugin publish --url https://your-host/foo-1.0.0.tar.gz --local dist/foo-1.0.0.tar.gzWhat publish does:
- Fetches the tarball at
--url(with URL and size guards) and extracts the manifest from those bytes. - Resumes your Atmosphere account session and checks publisher pinning — the active session must match the manifest’s pinned
publisher, or it refuses withMANIFEST_PUBLISHER_MISMATCH. - Creates the package profile from the manifest on first publish (
license, author, security contact). On later publishes, the existing profile wins. - Publishes the profile and release records to your account.
On first publish you can supply profile fields by flag (--license, --security-email, …) instead of the manifest; explicit flags override manifest values, which is handy in CI. --no-manifest opts out of the manifest entirely.
Versions are immutable
Section titled “Versions are immutable”A published version cannot be overwritten or republished. Bump version before publishing again. The build reads version from package.json (see the manifest reference). Bump major for a broadened trust contract, minor for new hooks or routes, and patch for fixes.
Publisher mismatch
Section titled “Publisher mismatch”If publish fails with MANIFEST_PUBLISHER_MISMATCH, the active session is a different Atmosphere account than the manifest’s pinned publisher. Switch to the pinned account with emdash-plugin switch <did>, or update publisher in the manifest if you are genuinely transferring the plugin to a new account. See Use an existing account for managing sessions.
What to read next
Section titled “What to read next”- The
emdash-pluginCLI — every command - The manifest — fields, trust contract, publisher pinning
- Capabilities and security