Skip to content
On this pageHow EmDash plugins work
  1. How EmDash plugins work
  2. Plugin anatomy
  3. Building a simple plugin
  4. Custom Portable Text blocks
  5. Plugin distribution
  6. Plugin ideas with no competition
  7. Frequently asked questions

Build Your First EmDash Plugin: Developer Guide

Written by Ben 4 min read Updated last month

EmDash's plugin system is architecturally impressive but the ecosystem is empty. That's an opportunity. This guide walks through building a plugin from scratch — from understanding the sandboxed runtime to shipping a working plugin with hooks, storage, and admin UI.

How EmDash plugins work

EmDash plugins run in sandboxed Worker isolates, separate from your main application. Each plugin declares capabilities in a manifest — what data it can access, what events it listens to, what APIs it needs. The runtime enforces these declarations at the platform level, not by convention.

This means a plugin that declares "read content" literally cannot access user data, media storage, or anything outside its declared scope. Compare this to WordPress where every plugin has full access to your database, filesystem, and server environment.

Plugin anatomy

A plugin is a function that returns a configuration object via definePlugin(). It declares:

  • Capabilities — what the plugin needs access to (content, media, storage, email)
  • Hooks — functions that fire on content lifecycle events (beforePublish, afterCreate, etc.)
  • Storage — key-value store for plugin data, scoped to the plugin
  • Settings — configuration fields that appear in the admin panel
  • Admin pages — custom pages in the admin panel for plugin-specific UI
  • API routes — custom endpoints for webhooks, external integrations, etc.
  • Block types — custom Portable Text blocks for the content editor

You register your plugin in astro.config.mjs alongside the emdash() integration.

HooksReact to content lifecycle eventsafterPublish, beforeCreate, onDelete
StorageScoped key-value store for plugin datastore.get('analytics'), store.set('count', n)
SettingsAdmin-editable config fields for your pluginAPI key field, toggle, select option
Admin pagesCustom pages injected into the admin navAnalytics dashboard, form submission log
API routesCustom HTTP endpoints for external callsWebhook receiver, form POST handler
Block typesCustom Portable Text blocks in the editorCTA, comparison table, embedded map

Building a simple plugin

Let's build a plugin that logs content publish events and provides a dashboard showing recent publications. This demonstrates hooks, storage, and admin UI — the three most common plugin patterns.

Start by creating a plugin file. Export a function that calls definePlugin() with your plugin name, version, capabilities, and hooks. The afterPublish hook receives the published entry — store the entry title, collection, and timestamp in the plugin's key-value storage.

For the admin page, define a React component that reads from storage and renders a list of recent publications. EmDash injects your admin pages into the admin panel navigation automatically.

Custom Portable Text blocks

Plugins can add custom block types to the Portable Text editor. This lets editors insert structured content — CTAs, feature grids, comparison tables, embedded maps — that your templates render with custom components.

Define a block type with a schema (the fields editors fill in) and a renderer (the Astro component that renders it on the frontend). EmDash adds the block to the editor's insert menu and handles serialization automatically.

Plugin distribution

EmDash will have a plugin marketplace, but it's not live yet. For now, plugins are distributed as npm packages or Git repositories. Users install them as dependencies and register them in astro.config.mjs.

When the marketplace launches, early plugins with established users will have a significant head start. If you build something useful now, you're positioning it for discovery when the ecosystem grows.

Plugin ideas with no competition

Every category is empty. Here are the highest-value plugins that don't exist yet:

Every plugin category is empty. The first quality plugin for analytics, contact forms, or SEO auditing will own that category for years.

Content & growth

  • Analytics — privacy-first page view tracking using D1 storage. No third-party scripts, no GDPR headaches.
  • Newsletter integration — connect to Buttondown, ConvertKit, or Mailchimp from publish hooks.
  • Social sharing — Open Graph image generation at the edge and share buttons as a custom block type.

Utility & operations

  • Contact forms — form builder with email notifications, spam protection, and a submissions log in the admin.
  • SEO auditing — content analysis and optimization suggestions surfaced inline in the editor.
  • Backup/export — scheduled content exports to R2 or external storage via a Workers cron trigger.

Build any of these well and you'll own the category. That's the advantage of building in a new ecosystem.

Frequently asked questions

Frequently asked questions

How do I publish my plugin for others to install?

Publish it as an npm package or link it as a Git repository. Users install it as a dependency and register it in astro.config.mjs. EmDash's marketplace isn't live yet — plugins are distributed manually in v0.1.

Can my plugin access other plugins' data?

No. Each plugin is isolated in its own Worker isolate with its own storage. There's no cross-plugin API. Plugins that need to coordinate do so through the content or event system.

Can plugins run without sandboxing?

Yes — plugins registered directly in astro.config.mjs run unsandboxed, which is useful for projects where you control all the code. Marketplace plugins must be sandboxed and require the paid worker_loaders binding on Cloudflare.

What's the best plugin to build right now?

Anything in a category that doesn't exist. Analytics, contact forms, SEO auditing, backups, and newsletter integrations are all empty. The first quality implementation of each owns the category.

Learn about the MCP server →