Diese Anleitung erstellt ein minimales Sandboxed Plugin von Grund auf. Das Plugin protokolliert jedes Speichern von Inhalten und stellt eine einzige API-Route bereit. Es läuft in einer isolierten Laufzeitumgebung, die vom konfigurierten Sandbox-Runner bereitgestellt wird. Derselbe Code läuft auch im Prozess, wenn ein Site-Betreiber ihn von sandboxed: [] in plugins: [] verschiebt, beispielsweise auf einer Plattform ohne Sandbox-Runner.
Falls Sie sich noch nicht zwischen Sandboxed und Native entschieden haben, lesen Sie zuerst Ein Plugin-Format wählen.
Zwei Komponenten
Ein Sandboxed Plugin besteht aus:
emdash-plugin.jsonc— ein manuell bearbeitetes Manifest: Identität, der Vertrauensvertrag (Capabilities, Hosts, Storage) und Profilfelder. Kein Code.src/plugin.ts— die Laufzeitumgebung: Hooks und Routes. Nur Type-Importe ausemdash/plugin; kein Laufzeit-Import vonemdash.
emdash-plugin build liest beide und erzeugt die dist/-Artefakte, die eine Site konsumiert.
Das folgende Beispiel zeigt das Dateilayout eines vollständigen Plugins:
my-plugin/
├── emdash-plugin.jsonc # Identität + Vertrauensvertrag + Profil
├── src/
│ └── plugin.ts # Hooks, Routes — läuft in der Sandbox-Laufzeit
├── package.json
└── tsconfig.json
Das Paket einrichten
-
Erstellen Sie das Verzeichnis und eine
package.json. Der Build istemdash-plugin build; es gibt keinentsdown-Aufruf zu schreiben.{ "name": "@my-org/plugin-hello", "version": "0.1.0", "type": "module", "main": "dist/index.mjs", "exports": { ".": { "import": "./dist/index.mjs", "types": "./dist/index.d.mts" }, "./sandbox": "./dist/plugin.mjs" }, "files": ["dist", "emdash-plugin.jsonc"], "scripts": { "build": "emdash-plugin build", "dev": "emdash-plugin dev" }, "peerDependencies": { "emdash": ">=0.13.0" }, "devDependencies": { "@emdash-cms/plugin-cli": "0.2.0", "emdash": ">=0.13.0", "typescript": "^5.9.0" } }"."ist der generierte Deskriptor, den eine Site importiert;"./sandbox"ist die gebaute Laufzeitdatei.emdash-plugin buildgeneriert beide. -
Fügen Sie eine
tsconfig.jsonhinzu:{ "compilerOptions": { "target": "ES2022", "module": "preserve", "moduleResolution": "bundler", "strict": true, "esModuleInterop": true, "verbatimModuleSyntax": true, "skipLibCheck": true, "types": [] }, "include": ["src/**/*"], "exclude": ["node_modules"] }
Das Manifest schreiben
emdash-plugin.jsonc trägt die Identität des Plugins (slug), seinen Vertrauensvertrag (capabilities, allowedHosts, storage), Profilfelder und den Publisher Pin.
Das folgende Beispiel zeigt ein vollständiges Manifest für das Hello-Plugin:
{
"$schema": "./node_modules/@emdash-cms/plugin-cli/schemas/emdash-plugin.schema.json",
"slug": "plugin-hello",
"publisher": "did:plc:abc123def456", // your Atmosphere account DID
"license": "MIT",
"author": { "name": "Jane Doe", "url": "https://example.com" },
"security": { "email": "security@example.com" },
"capabilities": [],
"allowedHosts": [],
"storage": { "events": { "indexes": ["timestamp"] } }
}
Hinweise zu diesem Manifest:
slugist eine URL-sichere ID, nicht der npm-Paketname./^[a-z][a-z0-9_-]*$/, max 64 Zeichen. Es ist ein einzelnes Pfadsegment in Plugin-Route-URLs (/_emdash/api/plugins/<slug>/...) und Teil generierter SQL-Identifikatoren für Speicherindizes, daher schlagen@,/, führende Ziffern und Großbuchstaben fehl. Kombinieren Sie einen Unscoped-slug(plugin-hello) mit einem Scoped-npm-Paketnamen.storagedeklariert Collections im Voraus.ctx.storage.eventsfunktioniert zur Laufzeit nur, weileventshier deklariert ist. Der Zugriff auf eine nicht deklarierte Collection wirft einen Fehler.versionwird weggelassen. Der Build liest sie auspackage.json, sodass es eine einzige Quelle der Wahrheit gibt. Siehe die Manifest-Referenz.- Der Vertrauensvertrag ist Zustimmung. Die Änderung von
capabilities,allowedHostsoderstorageerfordert später einen Version-Bump — installierte Sites haben dem alten Vertrag zugestimmt.
Die Laufzeitumgebung schreiben
src/plugin.ts exportiert standardmäßig ein einfaches Objekt, das mit satisfies SandboxedPlugin annotiert ist. emdash/plugin bietet nur Typen, sodass ein Sandboxed Plugin keine Laufzeitabhängigkeit von emdash hat.
Das folgende Beispiel protokolliert jedes Speichern von Inhalten im Plugin-Speicher und stellt eine recent-Route bereit, die die letzten zehn Speichervorgänge zurückgibt:
import type { SandboxedPlugin } from "emdash/plugin";
export default {
hooks: {
"content:afterSave": {
handler: async (event, ctx) => {
ctx.log.info("Content saved", {
collection: event.collection,
id: event.content.id,
});
await ctx.storage.events.put(`save-${Date.now()}`, {
timestamp: new Date().toISOString(),
collection: event.collection,
contentId: event.content.id,
});
},
},
},
routes: {
recent: {
handler: async (_routeCtx, ctx) => {
const result = await ctx.storage.events.query({ limit: 10 });
return { events: result.items };
},
},
},
} satisfies SandboxedPlugin;
Hinweise zur Laufzeitdatei:
satisfies SandboxedPlugintypisiert alles. Es leiteteventvom Hook-Namen ab (mit dem vollständigen kanonischen Event-Typ) undctxalsPluginContext, sodass Handler keine Parameter-Annotationen benötigen. Ein vertippter Hook-Schlüssel wie"content:afterSav"ist ein Compiler-Fehler.- Hook-Handler nehmen
(event, ctx). Die Event-Form hängt vom Hook-Namen ab; siehe die Hooks-Anleitung. - Route-Handler nehmen
(routeCtx, ctx)— zwei Argumente.routeCtxist{ input, request, requestMeta? };ctxist derselbePluginContext. Routes sind erreichbar unter/_emdash/api/plugins/<slug>/<route-name>. ctx.storage.eventsfunktioniert, weileventsim Manifest deklariert ist.ctx.kvist immer verfügbar — ein plugin-spezifischer Key-Value-Store mitget,set,delete,list(prefix).
Das Plugin registrieren
In der astro.config.mjs der Site importieren Sie den Standard-Export des Plugins und übergeben ihn. Sandboxed Plugins kommen in sandboxed: []; In-Process-Plugins in plugins: []. Ein Sandboxed Plugin funktioniert in beiden. Das folgende Beispiel verwendet sandboxed::
import { defineConfig } from "astro/config";
import emdash from "emdash/astro";
import { sandbox } from "@emdash-cms/cloudflare";
import hello from "@my-org/plugin-hello";
export default defineConfig({
integrations: [
emdash({
sandboxed: [hello],
sandboxRunner: sandbox(),
}),
],
});
sandboxRunner ist das austauschbare Teil. Das Beispiel verwendet sandbox() aus @emdash-cms/cloudflare, den Runner, den die meisten Sites heute verwenden. Wenn kein Runner konfiguriert ist (oder der konfigurierte Runner auf der aktuellen Plattform nicht verfügbar ist), werden sandboxed: []-Plugins beim Start übersprungen — verschieben Sie das Plugin in plugins: [], um es im Prozess auszuführen.
Erstellen und ausführen
Vom Plugin-Verzeichnis aus:
emdash-plugin validate # Schema-Prüfung des Manifests zuerst
emdash-plugin build # dist/ erzeugen
Für eine Bearbeitungsschleife führen Sie emdash-plugin dev aus (baut bei Speicherung neu, behält das letzte gute dist/ bei einem fehlgeschlagenen Build). Installieren oder verknüpfen Sie in der Site das Plugin (pnpm add file:../plugin-hello oder einen Workspace-Link) und starten Sie den Dev-Server. Speichern Sie einen Inhalt im Admin und Sie sollten Content saved … in den Logs sehen; GET /_emdash/api/plugins/plugin-hello/recent gibt die letzten zehn Speicher-Events zurück.
Was Sie als Nächstes lesen sollten
- Das Manifest — jedes Feld, der Vertrauensvertrag, Publisher-Pinning
- Die
emdash-pluginCLI —build,dev,bundle - Hooks — der vollständige Satz von Events
- API-Routes — Input-Validierung, öffentliche Routes, Fehler
- Storage und KV — Query-Optionen, Indizes, Batch-Operationen
- Capabilities und Sicherheit — Inhaltszugriff, Netzwerk, Host-Allowlists
- Bundling und Publishing — Versand zum Marketplace