Why Build an EmDash Plugin?
EmDash plugins let you extend your CMS with custom functionality without modifying core code. Whether you need custom SEO meta fields, AI-generated content summaries, automated image optimization, or a completely new content type — plugins give you a sandboxed environment to build it in.
Unlike traditional CMS plugin systems (WordPress, Drupal) that run PHP on the server, EmDash plugins run on Cloudflare Workers. This means they:
- Scale to zero when not in use (no infrastructure cost)
- Can access Cloudflare's edge network (KV, D1, R2, Queues)
- Are sandboxed from each other (no plugin can crash your site)
- Deploy independently (update a plugin without touching your CMS)
Prerequisites
Before starting, make sure you have:
```bash
An EmDash site (v0.8+) running on Cloudflare Pages
npx emdash --version
wrangler CLI with D1 access
npx wrangler --version
Cloudflare account with Workers and D1 enabled
```
Step 1: Scaffold Your Plugin
Every plugin lives in `plugins/<name>/` at your project root. EmDash provides a scaffold command:
```bash
npx emdash create-plugin my-custom-seo
```
This generates:
```
plugins/my-custom-seo/
src/
index.ts # Main plugin entry
plugin.json # Plugin manifest
package.json
tsconfig.json
```
Step 2: Define Your Plugin Manifest
The `plugin.json` file tells EmDash about your plugin:
```json
{
"name": "my-custom-seo",
"version": "1.0.0",
"displayName": "My Custom SEO",
"description": "Adds advanced SEO fields to your posts",
"hooks": ["onAdminRoute", "onContentRender"],
"permissions": ["storage:read", "storage:write"]
}
```
Key fields:
- **hooks**: Which lifecycle events your plugin listens to
- **permissions**: What resources your plugin can access (storage, KV, D1)
Step 3: Add Admin UI Fields
Plugins can add custom fields to the admin editor. Here's how to add SEO meta fields to the post editor:
```typescript
import { definePlugin } from "emdash/plugin";
export default definePlugin({
name: "my-custom-seo",
hooks: {
onAdminRoute: {
handler: async ({ route, form }) => {
if (route.collection === "posts" && route.action === "edit") {
form.addField({
name: "seoTitle",
label: "Custom SEO Title",
type: "text",
placeholder: "Override the default title tag"
});
form.addField({
name: "seoDescription",
label: "Custom SEO Description",
type: "textarea",
placeholder: "Override the default meta description"
});
}
}
}
}
});
```
Step 4: Store Plugin Data
Plugins get isolated storage via KV. No need to worry about key collisions with other plugins:
```typescript
async onContentSave({ entry, storage }) {
// Save SEO data when the user publishes
await storage.set(`seo:${entry.slug}`, JSON.stringify({
title: entry.data.seoTitle,
description: entry.data.seoDescription
}));
}
```
Step 5: Render Custom Content
The `onContentRender` hook lets you transform how content appears on the frontend:
```typescript
onContentRender: {
handler: async ({ content, storage, entry }) => {
const seoData = await storage.get(`seo:${entry.slug}`);
if (seoData) {
const parsed = JSON.parse(seoData);
// Inject meta tags into the page head
content.head.push({
tag: "meta",
attrs: {
name: "description",
content: parsed.description
}
});
}
return content;
}
}
```
Step 6: Deploy Your Plugin
Deploying is a single command:
```bash
npx emdash deploy-plugin my-custom-seo
```
This bundles your plugin, uploads it to Cloudflare Workers, and registers it with your EmDash site. The admin UI picks it up automatically — no restart needed.
Plugin Architecture Tips
Here are patterns we've found valuable from building plugins for our own EmDash sites:
Use KV for Plugin Settings
Every plugin gets its own KV namespace. Store user-facing settings (API keys, toggles, thresholds) in KV, not in the code:
```typescript
const apiKey = await storage.get("settings:apiKey");
const enabled = await storage.get("settings:enabled");
```
Cache Expensive Operations
Workers have a 10ms CPU time budget per request. Cache database queries and API calls:
```typescript
const cached = await caches.default.match(request);
if (cached) return cached;
```
Handle Errors Gracefully
A crashing plugin should never take down the page. Wrap your handlers:
```typescript
try {
// Your plugin logic
} catch (error) {
console.error("Plugin error:", error);
// Return unmodified content
return content;
}
```
Going Further
Once you've built your first plugin, try these next steps:
1. **Add plugin settings UI** — Let users configure your plugin from the admin panel
2. **Use D1 for relational data** — Create custom tables for your plugin's data model
3. **Register API routes** — Expose custom endpoints under `/_emdash/api/plugins/your-plugin/`
4. **Publish to the marketplace** — Share your plugin with other EmDash users
Conclusion
EmDash's plugin system gives you WordPress-level extensibility with serverless-scale infrastructure. Your first plugin takes about 30 minutes to build and deploy. The sandboxed architecture means you can experiment freely without risking your production site.
Start with something small — a custom field, a content filter, or an SEO helper — and iterate from there. Every plugin you build expands what your CMS can do.