Hook into events
Run code before or after content saves, media uploads, and plugin lifecycle events.
EmDash’s plugin system lets you extend the CMS without modifying core code. Plugins can hook into content lifecycle events, store their own data, expose settings to administrators, and add custom UI to the admin panel.
EmDash plugins are configuration transformers, not separate applications. They run in the same process as your Astro site and interact through well-defined interfaces.
Key principles:
Hook into events
Run code before or after content saves, media uploads, and plugin lifecycle events.
Store data
Persist plugin-specific data in indexed collections without writing database migrations.
Expose settings
Declare a settings schema and get an auto-generated admin UI for configuration.
Add admin pages
Create custom admin pages and dashboard widgets with React components.
Create API routes
Expose endpoints for your plugin’s admin UI or external integrations.
Make HTTP requests
Call external APIs with declared host restrictions for security.
Every plugin is created with definePlugin():
import { definePlugin } from "emdash";
export default definePlugin({ id: "my-plugin", version: "1.0.0",
// What APIs the plugin needs access to capabilities: ["read:content", "network:fetch"],
// Hosts the plugin can make HTTP requests to allowedHosts: ["api.example.com"],
// Persistent storage collections storage: { entries: { indexes: ["userId", "createdAt"], }, },
// Event handlers hooks: { "content:afterSave": async (event, ctx) => { ctx.log.info("Content saved", { id: event.content.id }); }, },
// REST API endpoints routes: { status: { handler: async (ctx) => ({ ok: true }), }, },
// Admin UI configuration admin: { settingsSchema: { apiKey: { type: "secret", label: "API Key" }, }, pages: [{ path: "/dashboard", label: "Dashboard" }], widgets: [{ id: "status", size: "half" }], },});Every hook and route handler receives a PluginContext object with access to:
| Property | Description | Availability |
|---|---|---|
ctx.storage | Plugin’s document collections | Always (if declared) |
ctx.kv | Key-value store for settings and state | Always |
ctx.content | Read/write site content | With read:content or write:content |
ctx.media | Read/write media files | With read:media or write:media |
ctx.http | HTTP client for external requests | With network:fetch |
ctx.log | Structured logger | Always |
ctx.plugin | Plugin metadata (id, version) | Always |
The context shape is identical across all hooks and routes. Capability-gated properties are only present when the plugin declares the required capability.
Capabilities determine what APIs are available in the plugin context:
| Capability | Grants Access To |
|---|---|
read:content | ctx.content.get(), ctx.content.list() |
write:content | ctx.content.create(), ctx.content.update(), ctx.content.delete() |
read:media | ctx.media.get(), ctx.media.list() |
write:media | ctx.media.getUploadUrl(), ctx.media.delete() |
network:fetch | ctx.http.fetch() |
Register plugins in your Astro configuration:
import { defineConfig } from "astro/config";import { emdash } from "emdash/astro";import seoPlugin from "@emdash-cms/plugin-seo";import auditLogPlugin from "@emdash-cms/plugin-audit-log";
export default defineConfig({ integrations: [ emdash({ plugins: [seoPlugin({ generateSitemap: true }), auditLogPlugin({ retentionDays: 90 })], }), ],});Plugins are resolved at build time. Order matters for hooks with the same priority—earlier plugins in the array run first.
EmDash supports two plugin execution modes:
| Mode | Description | Platform |
|---|---|---|
| Trusted | Plugins run in-process with full access | Any |
| Sandboxed | Plugins run in isolated V8 workers | Cloudflare only |
In trusted mode (the default), capabilities are documentation—plugins can access anything. In sandboxed mode, capabilities are enforced at the runtime level.
Create a Plugin
Build your first plugin with storage, hooks, and admin UI.
Available Hooks
Browse all hooks for content, media, and plugin lifecycle.
Plugin Storage
Learn about storage and how to query plugin data.
Admin UI
Add admin pages and dashboard widgets.
Sandbox Security
Understand sandbox isolation across Cloudflare and Node.js deployments.