Die Hauptkonfiguration von EmDash befindet sich in astro.config.mjs, während src/live.config.ts den Content-Loader registriert. Deployment-spezifische Werte können auch aus Umgebungsvariablen stammen. Ein kleiner package.json-Metadatenblock unterstützt Template-Bezeichnungen und ältere lokale CLI-Abläufe.
Astro-Integration
Konfiguriere EmDash in astro.config.mjs als Astro-Integration:
import { defineConfig } from "astro/config";
import emdash, { local, s3 } from "emdash/astro";
import { sqlite, libsql } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
plugins: [],
}),
],
});
Integrationsoptionen
database
Erforderlich. Konfiguration des Datenbank-Adapters. Wähle einen Adapter:
// SQLite (Node.js)
database: sqlite({ url: "file:./data.db" });
// PostgreSQL
database: postgres({ connectionString: process.env.DATABASE_URL });
// libSQL
database: libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
// Cloudflare D1 (import from @emdash-cms/cloudflare)
database: d1({ binding: "DB" });
Details findest du unter Datenbankoptionen.
migrations
Optional. Steuert, wie die internen Datenbankmigrationen von EmDash zur Laufzeit behandelt werden. Wenn die Option weggelassen wird, gilt standardmäßig { runtime: "auto" }.
migrations: {
runtime: "check", // "auto" | "check" | "manual"
dev: "auto", // optional development override
}
auto prüft ausstehende Migrationen und wendet sie an, check gibt 503 zurück, wenn dem laufenden Build bekannte Migrationen ausstehen (außer bei Anfragen an den Bild-Endpunkt, den EmDash installiert), und manual führt zur Laufzeit keine Migrationsabfrage aus. EMDASH_MIGRATIONS_MODE überschreibt den wirksamen Laufzeitmodus. Lies Core-Datenbankmigrationen verwalten, bevor du check oder manual einführst.
storage
Optional. Konfiguration des Medienspeicher-Adapters. Wenn diese Option fehlt, speichert EmDash Dateien in ./.emdash/uploads und liefert sie über /_emdash/api/media/file aus. Wähle einen Adapter, wenn das lokale Standardverzeichnis nicht geeignet ist:
// Local filesystem (development)
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
// R2 binding (Cloudflare Workers)
storage: r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev", // optional
});
// S3-compatible (any platform) — all fields from S3_* environment variables
storage: s3()
// Or with explicit values
storage: s3({
endpoint: "https://s3.amazonaws.com",
bucket: "my-bucket",
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
region: "us-east-1", // optional, default: "auto"
publicUrl: "https://cdn.example.com", // optional
});
Details findest du unter Speicheroptionen.
images
Optional. Steuert, ob EmDash gespeicherte Medien in die Bildoptimierung von Astro einbindet. Der Standardwert ist true.
Wenn die Option aktiviert ist, umschließt EmDash den Bild-Endpunkt von Astro, sodass <Image> und getImage() die Quellbytes direkt aus dem konfigurierten Speicher-Adapter lesen können. Das funktioniert auch, wenn die ursprüngliche Medien-URL hinter Cloudflare Access liegt. Anfragen an den umschlossenen Endpunkt überspringen den Laufzeitstart von EmDash; daher enthält locals.emdash dort nur storage sowie db für einen angemeldeten Benutzer. locals.user wird wie bei anderen öffentlichen Routen gesetzt. Setze images: false, wenn ein anderer Bilddienst die Medien übernimmt oder wenn jedes Bild ohne den Endpunkt-Wrapper von EmDash gerendert werden soll.
emdash({
images: false,
});
mediaProviders
Optional. Fügt der Medienbibliothek weitere Mediendienste hinzu. Der speichergestützte lokale Provider bleibt automatisch verfügbar; jeder Descriptor in diesem Array fügt einen weiteren Ort hinzu, an dem Redakteure Medien durchsuchen oder hochladen können.
Das folgende Beispiel fügt Cloudflare Images und Cloudflare Stream hinzu:
import { cloudflareImages, cloudflareStream } from "@emdash-cms/cloudflare";
emdash({
mediaProviders: [cloudflareImages({}), cloudflareStream({})],
});
Die Zugangsdaten der Provider werden zur Laufzeit aufgelöst. Die leeren Konfigurationen oben verwenden die Standard-Umgebungsvariablen von Cloudflare, die in den Adapter-Abschnitten cloudflareImages(config) und cloudflareStream(config) beschrieben sind. Die Einrichtung von Bindings und Rendering findest du unter Medienbibliothek: Medien-Provider.
objectCache
Optional. Speichert Ergebnisse von Inhalts- und Konfigurationsabfragen in einem Key-Value-Speicher, sodass Lesezugriffe ohne Datenbankabfrage bei jeder Anfrage bedient werden. Ohne Angabe deaktiviert. Wähle einen Adapter:
// Cloudflare KV (shared across all isolates)
import { kvCache } from "@emdash-cms/cloudflare";
objectCache: kvCache({ binding: "CACHE" });
// In-memory (Node.js / development)
import { memoryCache } from "emdash/astro";
objectCache: memoryCache();
Einrichtung und Optionen findest du unter Objekt-Cache.
middleware.outer
Optional. Registriert ein Astro-Middleware-Modul außerhalb des gesamten EmDash-Middleware-Stacks. Weil die Integration es mit Astro order: "pre" registriert, läuft es auch vor der Middleware, die in src/middleware.ts definiert ist. Verwende es für Request-Gates oder Full-Response-Caches, die bei einem Treffer die Initialisierung von Laufzeit und Datenbank vermeiden müssen, oder für Response-Header, die vom finalen HTML von EmDash abhängen.
emdash({
middleware: {
outer: "./src/outer-middleware.ts",
},
});
Die Ausführungsreihenfolge ist:
- Die äußere Middleware läuft bis
await next(). - EmDash initialisiert seine Laufzeit und Datenbank und führt dann die Middleware für Einrichtung, Authentifizierung und Request-Kontext aus.
- Die Astro-Route wird gerendert.
- EmDash wendet Response-Änderungen an, darunter HTML für die visuelle Bearbeitung sowie Sicherheits- und Timing-Header.
next()wird mit dieser finalen Response an die äußere Middleware aufgelöst.
Vor dem Aufruf von next() hat die Middleware den normalen Astro-Request und den Ausführungskontext der Plattform, aber locals.emdash, locals.user, die Datenbank und der Request-bezogene EmDash-Zustand sind nicht verfügbar. Eine frühe Response überspringt EmDash vollständig und muss daher alle benötigten Sicherheits- und Cache-Header selbst enthalten. Nachdem next() aufgelöst wurde, kannst du gefahrlos CSP-Nonces finalisieren, den vollständigen Body cachen oder Content-Length setzen. Wenn die Middleware den Body ändert, entferne oder berechne einen vorhandenen Content-Length-Header neu.
Der Hook verwendet die Middleware-API von Astro sowohl unter Node als auch unter Cloudflare. Dieses minimale Beispiel mit der Cloudflare Cache API cacht nur anonyme HTML-Responses und liefert Treffer vor der Initialisierung von EmDash zurück:
import { waitUntil } from "cloudflare:workers";
import { defineMiddleware } from "astro:middleware";
export const onRequest = defineMiddleware(async ({ request }, next) => {
if (request.method !== "GET" || request.headers.has("cookie")) {
return next();
}
const cacheKey = new Request(request.url, { method: "GET" });
const cached = await caches.default.match(cacheKey);
if (cached) return cached;
const response = await next();
const isHtml = response.headers.get("content-type")?.includes("text/html");
const isPrivate = response.headers.get("cache-control")?.includes("no-store");
if (response.ok && isHtml && !isPrivate) {
waitUntil(caches.default.put(cacheKey, response.clone()));
}
return response;
});
Unter Node verwendest du dieselbe Middleware-Form mit einem Node-kompatiblen Cache wie Redis. Cache-Schlüssel und Umgehungsregeln müssen jede Eigenschaft der Anfrage berücksichtigen, die die gerenderte Response verändert.
playground
Optional. Aktiviert die Middleware, die von Wegwerf-Playgrounds für EmDash im Browser verwendet wird. Sie erstellt für jede Sitzung eine beschreibbare Durable-Object-Datenbank, wendet den konfigurierten Seed an und meldet den Besucher als anonymen Administrator an, bevor die normale EmDash-Middleware läuft.
import { playgroundDatabase } from "@emdash-cms/cloudflare";
emdash({
database: playgroundDatabase({ binding: "PLAYGROUND_DB" }),
playground: {
middlewareEntrypoint: "@emdash-cms/cloudflare/db/playground-middleware",
},
});
Dieser Modus erfordert @emdash-cms/cloudflare und ein Durable-Object-Binding. Er umgeht die normale Middleware für Einrichtung und Authentifizierung; verwende ihn daher nur für kurzlebige Demo-Websites und nicht für ein Produktions-CMS.
plugins
Optional. Array von Plugins, die im selben Prozess wie die Astro-Website laufen. Native Plugins gehören hierher. Ein sandbox-kompatibles Plugin kann hier ebenfalls laufen, wenn du ihm vollen Prozesszugriff anvertraust und keine Isolation brauchst.
Das folgende Beispiel registriert ein natives Plugin:
import seoPlugin from "@emdash-cms/plugin-seo";
plugins: [seoPlugin()];
Native Plugins können Server- und Framework-APIs direkt nutzen und lassen sich daher nicht nach sandboxed verschieben, sofern das Paket nicht zusätzlich einen sandbox-kompatiblen Plugin-Einstiegspunkt bereitstellt. Die Unterschiede bei Entwicklung und Deployment findest du unter Plugin-Format wählen.
sandboxed
Optional. Array sandbox-kompatibler Plugins, die die deklarierten Plugin-APIs von EmDash verwenden und in isolierten Laufzeiten laufen. Platziere hier kein natives Plugin: Nativer Code kann von Prozess- und Framework-Zugriff abhängen, den die Sandbox nicht bietet.
import thirdPartyPlugin from "third-party-emdash-plugin";
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxed: [thirdPartyPlugin()],
sandboxRunner: sandbox(),
});
Sandboxed-Plugins werden übersprungen, wenn kein nutzbarer Sandbox-Runner konfiguriert ist. Die Einrichtung des Runners für Cloudflare und Node.js findest du unter Plugin-Sandbox.
sandboxRunner
Optional. Modulspezifizierer der Factory, die isolierte Plugin-Laufzeiten startet. Sie ist erforderlich für Plugins in sandboxed sowie für Marketplace- oder Registry-Plugins.
Verwende auf Cloudflare Workers den Adapter sandbox():
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxRunner: sandbox(),
});
Node.js-Deployments verwenden das workerd-Runner-Modul, das unter Plugin-Sandbox: Node.js dokumentiert ist.
sandbox
Optional. Steuert, ob ein konfigurierter Sandbox-Runner Plugins isoliert. Die Sandbox ist aktiviert, wenn sandboxRunner konfiguriert ist. Setze sandbox: false nur, um zu diagnostizieren, ob ein Problem von einem Plugin oder von dessen Sandbox-Laufzeit stammt:
emdash({
sandboxRunner: sandbox(),
sandbox: false,
});
Mit false laufen Plugins, die in sandboxed deklariert oder aus dem Marketplace installiert wurden, im Hauptserverprozess ohne Isolation und ohne Ressourcenlimits. Stelle die Sandbox nach der Diagnose wieder her.
registry
Optional. Konfiguriert den Aggregator und die Richtlinie der Plugin-Registry. Ohne expliziten Wert verwendet EmDash https://registry.emdashcms.com, wenn sandboxRunner konfiguriert und sandbox nicht false ist.
Setze registry: false, um die Registry-Suche und über die Registry installierte Plugins zu deaktivieren, während der Sandbox-Runner für in sandboxed deklarierte Plugins und ältere Marketplace-Plugins verfügbar bleibt:
emdash({
sandboxRunner: sandbox(),
registry: false,
});
Übergib die URL des Registry-Dienstes als String oder verwende ein Objekt, wenn die Website Moderationsquellen oder eine Richtlinie zum Release-Alter braucht. Das folgende Beispiel verwendet die Objektform:
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxRunner: sandbox(),
registry: {
aggregatorUrl: "https://registry.emdashcms.com",
acceptLabelers: "did:web:labels.emdashcms.com",
policy: {
minimumReleaseAge: "48h",
minimumReleaseAgeExclude: ["did:plc:yourfirstpartydid"],
},
},
});
| Option | Typ | Beschreibung |
|---|---|---|
aggregatorUrl | string | Basis-URL des Registry-Dienstes. Verwende in Produktion HTTPS. |
acceptLabelers | string | Optionale, durch Kommas getrennte dezentrale Bezeichner (DIDs) von Moderationsdiensten, die die Anfrage akzeptiert. Eine DID ist ein stabiler Konto-Bezeichner der Atmosphere. Diese Einstellung kann die Richtlinie des Registry-Dienstes nicht überschreiben. |
policy.minimumReleaseAge | string | number | Hält Releases zurück, die jünger als dieses Alter sind. Dauer-String ("48h", "7d") oder Sekunden. |
policy.minimumReleaseAgeExclude | string[] | DIDs von Publishern oder Paare <did>/<plugin-slug>, die von der Zurückhaltung ausgenommen sind. |
Die Richtlinie zum Release-Alter nimmt das erste Release eines Pakets nur dann aus, wenn die Registry ein aufbewahrtes Release meldet und bestätigt, dass sie das Paket lückenlos beobachtet hat. Ein nachträglich befülltes Paket, ein gelöschtes früheres Release oder fehlende Belege zur Historie lassen die Zurückhaltung in Kraft. Ausdrückliche Ausnahmen für Publisher und Pakete gelten unabhängig von der Historie.
Den Installationsablauf und das Vertrauensmodell findest du unter Die Plugin-Registry.
marketplace
Veraltet. Basis-URL, die zum Aktualisieren von Plugins verwendet wird, die aus dem älteren Marketplace installiert wurden. Das Durchsuchen des Marketplace und neue Installationen werden im Admin nicht angezeigt. Vorhandene Marketplace-Plugins bleiben aktualisierbar und deinstallierbar, solange diese Option konfiguriert ist.
emdash({
marketplace: "https://marketplace.emdashcms.com",
sandboxRunner: sandbox(),
});
Produktions-URLs müssen HTTPS verwenden; HTTP wird nur für localhost und 127.0.0.1 während der Entwicklung akzeptiert. Behalte diese Option bei, bis jedes Marketplace-Plugin ersetzt oder deinstalliert wurde, und entferne sie dann. Das vollständige Vorgehen findest du unter Vom Marketplace migrieren.
fonts
Optional. Schriftkonfiguration der Admin-Oberfläche.
Standardmäßig lädt EmDash Noto Sans über die Astro Font API. Die Schriften werden zur Build-Zeit von Google heruntergeladen und selbst gehostet, sodass es zur Laufzeit keine CDN-Anfragen gibt. Die Basisschrift deckt die Schriftsysteme Lateinisch, Kyrillisch, Griechisch, Devanagari und Vietnamesisch ab.
Um weitere Schriftsysteme zu unterstützen, übergib Skriptnamen. Das folgende Beispiel fügt Arabisch und Japanisch hinzu:
emdash({
fonts: {
scripts: ["arabic", "japanese"],
},
})
Die verfügbaren Skripte sind arabic, armenian, bengali, chinese-simplified, chinese-traditional, chinese-hongkong, devanagari, ethiopic, farsi, georgian, gujarati, gurmukhi, hebrew, japanese, kannada, khmer, korean, lao, malayalam, myanmar, oriya, sinhala, tamil, telugu, thai und tibetan.
Jedes Skript entspricht der passenden Noto-Sans-Variante auf Google Fonts (z. B. lädt "arabic" Noto Sans Arabic). Alle Schriftschnitte teilen sich einen einzigen font-family-Namen und verwenden unicode-range, sodass der Browser nur die Dateien herunterlädt, die er für die Zeichen auf der Seite braucht.
Setze die Option auf false, um die Schrifteinbindung vollständig zu deaktivieren und Systemschriften zu verwenden:
emdash({
fonts: false,
})
Das Admin-CSS verwendet die CSS-Variable --font-emdash. Sie wird von der obigen Schriftkonfiguration automatisch gesetzt.
auth
Optional. Ein Authentifizierungs-Adapter. Die integrierte Anmeldung von EmDash erfolgt über Passkeys; wenn du auth setzt, werden sie durch einen externen Provider ersetzt. Der Cloudflare-Access-Adapter access() wird von @emdash-cms/cloudflare bereitgestellt:
import { access } from "@emdash-cms/cloudflare";
emdash({
auth: access({
teamDomain: "myteam.cloudflareaccess.com",
audience: "your-app-audience-tag",
roleMapping: {
Admins: 50,
Editors: 40,
},
}),
});
Optionen für access():
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
teamDomain | string | erforderlich | Deine Cloudflare-Access-Team-Domain |
audience | string | — | Application-Audience-Tag (AUD). Bevorzuge auf Workers audienceEnvVar. |
audienceEnvVar | string | "CF_ACCESS_AUDIENCE" | Umgebungsvariable, aus der der Audience-Tag zur Laufzeit gelesen wird |
autoProvision | boolean | true | Beim ersten Login einen EmDash-Benutzer anlegen |
defaultRole | number | 30 | Rollenstufe für Benutzer, die nicht von roleMapping erfasst werden (siehe Benutzerrollen) |
syncRoles | boolean | false | roleMapping bei jedem Login erneut anwenden, statt nur bei der Bereitstellung |
syncName | boolean | true | Den Namen des Benutzers bei jeder authentifizierten Anfrage durch den IdP-Namen ersetzen. Auf false setzen, damit Namen im Admin bearbeitbar sind |
roleMapping | object | — | Ordnet Gruppennamen des Identity Providers EmDash-Rollenstufen zu; der erste Treffer gewinnt |
authProviders
Optional. Ein Array austauschbarer Login-Provider (auf oberster Ebene, neben auth). Jeder Eintrag ist das Ergebnis des Aufrufs einer Provider-Factory, wie unten gezeigt:
import { github } from "emdash/auth/providers/github";
import { google } from "emdash/auth/providers/google";
import { atproto } from "@emdash-cms/auth-atproto";
emdash({
authProviders: [github(), google(), atproto()],
});
Integrierte Provider:
github()— liestEMDASH_OAUTH_GITHUB_CLIENT_ID/EMDASH_OAUTH_GITHUB_CLIENT_SECRET(oder Fallbacks ohne Präfix).google()— liestEMDASH_OAUTH_GOOGLE_CLIENT_ID/EMDASH_OAUTH_GOOGLE_CLIENT_SECRET.microsoft()— liestEMDASH_OAUTH_MICROSOFT_CLIENT_ID/EMDASH_OAUTH_MICROSOFT_CLIENT_SECRET/EMDASH_OAUTH_MICROSOFT_TENANT_ID(oder Fallbacks ohne Präfix). Akzeptiert{ emailVerified }. Siehe Microsoft.atproto()— Anmeldung mit einem Atmosphere-Konto (Bluesky und das gesamte AT-Protocol-Netzwerk). Keine Umgebungsvariablen nötig. Akzeptiert{ allowedDIDs, allowedHandles, defaultRole }. Siehe den Leitfaden zur Atmosphere-Anmeldung.
Pakete von Drittanbietern können eigene Provider mit derselben Form AuthProviderDescriptor registrieren — siehe Login-Provider.
mcp
Optional. Aktiviert den Endpunkt des Model Context Protocol (MCP) unter /_emdash/api/mcp. Der Endpunkt ist standardmäßig aktiviert und erfordert ein Bearer-Token; seine Aktivierung gewährt also keinen anonymen Zugriff.
Setze die Option auf false, wenn die Website keinen MCP-Endpunkt bereitstellen darf:
emdash({
mcp: false,
});
Die Erstellung von Tokens und die Konfiguration von Clients findest du in der MCP-Server-Referenz.
siteUrl
Der öffentliche, für den Browser sichtbare Origin der Website (Schema + Host + optionaler Port, kein Pfad). Setze ihn, bevor du die Produktionseinrichtung ausführst, es sei denn, die Website wird auf Cloudflare Workers bereitgestellt. Nur Loopback-Entwicklungshosts können die Einrichtung andernfalls ohne konfigurierten Origin abschließen.
Hinter einem TLS-terminierenden Reverse-Proxy gibt Astro.url die interne Adresse (http://localhost:4321) statt der öffentlichen (https://cms.example.com) zurück. Das stört Passkeys, den Origin-Abgleich für CSRF, OAuth-Weiterleitungen, Login-Weiterleitungen, die MCP-Discovery, Snapshot-Exporte, die Sitemap, robots.txt und strukturierte JSON-LD-Daten. Mit siteUrl behebst du all das auf einmal.
Die Integration validiert diesen Wert beim Laden: Er muss eine gültige URL mit dem Protokoll http: oder https: sein und wird auf den Origin normalisiert (der Pfad wird entfernt).
Das folgende Beispiel setzt den öffentlichen Origin:
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
});
Wenn siteUrl nicht in der Konfiguration gesetzt ist, prüft EmDash die Umgebungsvariablen in dieser Reihenfolge: EMDASH_SITE_URL, dann SITE_URL. Das ist nützlich für Container-Deployments, bei denen die öffentliche URL zur Laufzeit gesetzt wird.
Die Bildoptimierung liest diese Umgebungsvariablen zur Build-Zeit, weil Astro die erlaubten Bild-Origins in image.remotePatterns festhält, wenn astro build läuft. Ist EMDASH_SITE_URL oder SITE_URL nur in der Laufzeitumgebung gesetzt, etwa bei Hosts, die Variablen erst beim Start der App einspeisen, werden lokal gespeicherte Medien nicht optimiert ausgeliefert. Setzen Sie die Variable auch in der Build-Umgebung oder legen Sie siteUrl in der Konfiguration fest.
Die Einrichtung scheitert auf einem Nicht-Loopback-Host mit SITE_URL_REQUIRED, wenn keine der beiden Quellen gesetzt ist. Das verhindert, dass die erste nicht authentifizierte Einrichtungsanfrage den Origin wählt, der später in Authentifizierungs-E-Mails verwendet wird. Ein Produktions-Build, der auf Cloudflare Workers bereitgestellt wird, ist die Ausnahme: Cloudflare leitet nur Anfragen für Hostnamen weiter, die an den Worker gebunden sind, daher speichert die Einrichtung https:// und den Hostnamen, unter dem sie läuft. Derselbe Build, der über wrangler dev, astro preview oder ein selbst gehostetes workerd auf einer erreichbaren Adresse bereitgestellt wird, bietet diese Garantie nicht; setze dort zuerst siteUrl oder EMDASH_SITE_URL.
Links in E-Mails zur Anmeldung, Einladung, Selbstregistrierung, Wiederherstellung und Kommentarbenachrichtigung verwenden den ersten verfügbaren Origin aus: siteUrl oder deren Umgebungsvariablen, der Site URL unter Settings > General, danach dem bei der Einrichtung festgehaltenen Origin. Verwendet wird nur der Origin der Site URL, und er muss https:// nutzen, sofern der Host nicht localhost, 127.0.0.1 oder [::1] ist. ctx.site.url verwendet denselben Origin wie Links in E-Mails und wird beim Start der Laufzeit gelesen. Wenn Sie eine Site ohne konfiguriertes siteUrl auf eine neue Domain verschoben haben, ändern Sie die Site URL mit Change domain, damit E-Mail-Links darauf zeigen; siehe Zu einer neuen Domain wechseln. Sitemaps und robots.txt verwenden eine andere Reihenfolge, bei der die Site URL vor siteUrl kommt; siehe robots.txt.
Auf Cloudflare Workers liest der Umgebungsvariablen-Fallback process.env. Mit nodejs_compat befüllt Cloudflare process.env für Kompatibilitätsdaten ab 2025-04-01 standardmäßig. Projekte, die auf ein früheres Datum festgelegt sind, müssen zusätzlich nodejs_compat_populate_process_env hinzufügen.
// wrangler.jsonc
{
"compatibility_date": "2026-02-24",
"compatibility_flags": ["nodejs_compat"],
"vars": { "EMDASH_SITE_URL": "https://cms.example.com" },
}
allowedOrigins
Optional. Zusätzliche Browser-Origins, die die Passkey-Verifizierung für ein Deployment akzeptiert, das unter mehr als einem Hostnamen erreichbar ist.
siteUrl definiert einen einzigen kanonischen Origin. Wenn dasselbe EmDash-Deployment unter mehreren Hostnamen erreichbar ist, die eine registrierbare übergeordnete Domain teilen (z. B. https://example.com und https://preview.example.com), lehnt die Passkey-Verifizierung Assertions ab, deren Origin nicht exakt mit siteUrl übereinstimmt — obwohl WebAuthn erlaubt, dass Passkeys über Subdomains unter derselben rpId hinweg gültig sind.
Deklariere zusätzliche akzeptierte Origins entweder über allowedOrigins in astro.config.mjs oder über die Umgebungsvariable EMDASH_ALLOWED_ORIGINS. Die kanonische siteUrl bleibt die Quelle der rpId; die hier aufgeführten Einträge werden bei der Verifizierung akzeptiert. Beide Quellen werden zur Laufzeit zusammengeführt, sodass die Konfiguration die stabilen Origins (versioniert, im Code-Review geprüft) deklarieren kann, während die Umgebung umgebungsspezifische Extras ergänzt (z. B. kurzlebige PR-Vorschauen).
Das folgende Beispiel deklariert in der Konfiguration einen zusätzlichen Origin:
emdash({
siteUrl: "https://example.com",
allowedOrigins: ["https://preview.example.com"],
})
Die entsprechenden Werte können auch aus Umgebungsvariablen stammen:
EMDASH_SITE_URL=https://example.com
EMDASH_ALLOWED_ORIGINS=https://preview.example.com,https://staging.example.com
Validierung
EmDash validiert diese Werte, um tote Konfiguration zu vermeiden, die der Browser nie berücksichtigen würde:
- Jeder Eintrag muss eine parsbare
http:- oderhttps:-URL ohne abschließenden Punkt und ohne leere Labels im Hostnamen sein. - Wenn
allowedOriginsnicht leer ist, musssiteUrlgesetzt sein (über eine der beiden Quellen) und darf weder ein IP-Literal sein noch einen Hostnamen mit abschließendem Punkt haben. - Jeder Origin muss derselbe Hostname wie
siteUrloder eine Subdomain davon sein. (WebAuthn verlangt, dassrpIdein registrierbares Suffix jedes Origins ist.)
Wenn die Validierung fehlschlägt, siehst du einen Fehler mit Quellenangabe wie EmDash config error in EMDASH_ALLOWED_ORIGINS: "https://other-site.com" is not a subdomain of siteUrl "https://example.com". Allowed origins must be the same hostname as siteUrl or a subdomain of it.
Wo der Fehler auftritt, hängt davon ab, wo die Werte deklariert sind:
- Beim Start von Astro, wenn
config.allowedOriginsundconfig.siteUrlbeide ausastro.config.mjsstammen — Tippfehler im Code lassen den Build scheitern. - Bei der ersten Passkey-Verifizierung, wenn einer der Werte aus
EMDASH_ALLOWED_ORIGINSoderEMDASH_SITE_URLstammt — Abweichungen in der Umgebung zeigen sich als 500er beim ersten Verifizierungsversuch.
Reverse-Proxy-Einrichtung
Astro übernimmt X-Forwarded-* nur, wenn der öffentliche Host erlaubt ist. Konfiguriere security.allowedDomains für den Hostnamen (und die Schemata), die deine Benutzer aufrufen. Füge in astro dev passende Einträge in vite.server.allowedHosts hinzu, damit Vite den Host-Header des Proxys akzeptiert.
Korrigiere zuerst allowedDomains (und die Forwarded-Header); verwende siteUrl, wenn die rekonstruierte URL dennoch vom Browser-Origin abweicht (typisch, wenn TLS davor terminiert wird und die Upstream-Anfrage http:// bleibt).
Wenn TLS davor liegt, genügt es oft, den Dev-Server an Loopback zu binden (astro dev --host 127.0.0.1): Der Proxy verbindet sich lokal, während siteUrl dem öffentlichen HTTPS-Origin entspricht.
Wenn dein Proxy einen Client-IP-Header schreibt, setze trustedProxyHeaders, damit die Rate Limits von EmDash die echte Client-IP verwenden können. Ohne vertrauenswürdige Client-IP werden die Rate Limits der Authentifizierung übersprungen, und alle öffentlichen Kommentareinreichungen teilen sich einen Rate-Limit-Bucket (20 Kommentare pro 10 Minuten für alle Besucher zusammen).
Die folgende Konfiguration setzt allowedDomains, vite.server.allowedHosts und siteUrl gemeinsam für ein Deployment hinter einem Reverse-Proxy:
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
security: {
allowedDomains: [
{ hostname: "cms.example.com", protocol: "https" },
{ hostname: "cms.example.com", protocol: "http" },
],
},
vite: {
server: {
allowedHosts: ["cms.example.com"],
},
},
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
}),
],
});
trustedProxyHeaders
Optional. Header, denen für die Ermittlung der Client-IP vertraut wird, wenn die Website hinter einem Reverse-Proxy läuft, den du kontrollierst. Wird von den Rate Limits der Authentifizierung (Magic-Link, Registrierung, Passkey, OAuth Device Flow, OAuth-Client-Registrierung) und vom öffentlichen Kommentar-Endpunkt verwendet.
Auf Cloudflare wird das an die Anfrage angehängte cf-Objekt automatisch verwendet — du musst dies normalerweise nicht setzen. Bei selbst gehosteten Deployments hinter nginx, Caddy, Traefik, Fly, Railway oder Ähnlichem setzt du hier den Header, den dein Proxy schreibt, damit die Rate Limits Anfragen pro echter Client-IP zählen können.
Das folgende Beispiel vertraut dem Header x-real-ip, den nginx, Caddy oder Traefik setzen:
emdash({
database: sqlite({ url: "file:./data.db" }),
trustedProxyHeaders: ["x-real-ip"],
});
Die Header werden der Reihe nach probiert. Werte, die auf *-forwarded-for passen, werden als durch Kommas getrennte Listen geparst, und der erste Eintrag wird verwendet. Das folgende Beispiel bevorzugt den Header von Fly.io und fällt auf x-forwarded-for zurück:
emdash({
trustedProxyHeaders: ["fly-client-ip", "x-forwarded-for"],
});
Wenn die Option nicht in der Konfiguration gesetzt ist, liest EmDash die Umgebungsvariable EMDASH_TRUSTED_PROXY_HEADERS (durch Kommas getrennt). Ein explizit leeres Array in der Konfiguration überschreibt die Umgebungsvariable.
maxUploadSize
Optional. Maximal erlaubte Größe für den Upload von Mediendateien in Bytes. Gilt sowohl für direkte Multipart-Uploads als auch für Uploads über signierte URLs. Standardwert ist 52_428_800 (50 MB). Das folgende Beispiel erhöht das Limit auf 100 MB:
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
maxUploadSize: 100 * 1024 * 1024, // 100 MB
});
| Wert | Beschreibung |
|---|---|
number (Bytes) | Muss eine positive endliche Ganzzahl sein |
| weggelassen | Standardmäßig 50 MB |
Uploads, die das konfigurierte Limit überschreiten, werden auf dem Pfad für direkte Uploads mit einer Response 413 Payload Too Large abgelehnt, auf dem Pfad mit signierter URL mit 400 Validation Error.
admin
Optional. Ersetzt das EmDash-Branding in der Admin-Oberfläche und begrenzt, welche Admin-Sprachen der Build enthält. Die Branding-Werte ändern weder Titel noch Logo oder Favicon der öffentlichen Website.
emdash({
admin: {
logo: "/images/agency-logo.webp",
siteName: "Agency CMS",
footerLabel: "AgencyX",
favicon: "/favicon.ico",
},
});
| Option | Typ | Beschreibung |
|---|---|---|
logo | string | Logo-URL oder -Pfad für die Anmeldeseite und die Seitenleiste |
siteName | string | Name, der im Kopf der Seitenleiste und im Browser-Titel angezeigt wird |
footerLabel | string | false | Beschriftung neben der Version in der Seitenleiste. Standard ist "EmDash"; false blendet sie aus |
favicon | string | Favicon-URL oder -Pfad für Admin-Seiten |
locales | string[] | Einzubeziehende Admin-Sprachen, etwa ["en", "de"]. Kataloge für andere Sprachen werden aus dem Admin-Bundle ausgelassen, und nur die aufgeführten Sprachen erscheinen im Sprachumschalter. Benutzer, deren bevorzugte Sprachen nicht aufgeführt sind, sehen Englisch. en ist als Fallback immer enthalten, auch wenn Sie es weglassen, sodass admin: { locales: ["en-GB"] } sowohl Englisch als auch Britisches Englisch ausliefert. Ein unbekannter Code lässt den Build fehlschlagen, und der Fehler listet die verfügbaren Codes auf. |
updateCheck
Optional. Steuert den Hinweis auf Core-Updates im Admin-Dashboard. Standardwert ist true.
Wenn die Option aktiviert ist, prüft EmDash die öffentliche npm-Registry auf veröffentlichte emdash-Releases — höchstens einmal pro Tag, nach der Response zurückgestellt, sodass sie nie eine Anfrage verlangsamt — und zeigt im Admin-Dashboard ein schließbares Banner an, wenn eine neuere Version verfügbar ist. Wenn du das Banner schließt, wird es für diese Version ausgeblendet; es erscheint wieder, sobald eine neuere Version verfügbar wird.
Das Banner nennt das neueste stabile Release, das seit mindestens 24 Stunden öffentlich ist, sodass ein Release einen Tag nach seiner Veröffentlichung angekündigt wird. Um einer Richtlinie zum Release-Alter in deinem Paketmanager zu entsprechen, etwa minimumReleaseAge von pnpm, setze minimumReleaseAge auf einen Dauer-String ("48h", "7d") oder eine Anzahl von Sekunden. Mit "7d" bietet das Banner das neueste Release an, das mindestens eine Woche alt ist:
emdash({
updateCheck: { minimumReleaseAge: "7d" },
})
Die Prüfung ist ein einfaches GET an https://registry.npmjs.org/emdash und überträgt keine Website-Daten. Deployments ohne ausgehenden Internetzugang zeigen das Banner schlicht nie an. Setze die Option auf false, um die Prüfung vollständig zu deaktivieren:
emdash({
updateCheck: false,
})
Das Banner ist nur informativ — EmDash zu aktualisieren bedeutet, das Paket emdash in deinem Projekt anzuheben und neu zu deployen, was das Admin nicht auslösen kann (und nicht auslösen sollte).
toolbar
Optional. Steuert, wie die Redaktions-Symbolleiste (die schwebende Pille auf öffentlichen Seiten) ausgeliefert wird. Standardwert ist "server".
| Wert | Verhalten |
|---|---|
"server" (Standard) | Die Symbolleiste wird serverseitig in jede HTML-Response eingefügt, die für einen authentifizierten Redakteur gerendert wird. |
"client" | Das öffentliche HTML ist für jeden Besucher identisch. Ein winziges Bootstrap-Skript zeigt in Browsern, die sich im Admin angemeldet haben, eine „Edit“-Pille an; ein Klick darauf verifiziert die Sitzung und lädt die Seite mit dem Query-Parameter _edit neu, die immer frisch (nie gecacht) mit der vollständigen Symbolleiste gerendert wird. |
false | Die Symbolleiste und das Bootstrap-Skript werden nie gerendert. |
emdash({
toolbar: "client",
})
Verwende "client", wenn dein öffentliches HTML über einen geteilten Cache ausgeliefert wird (Cloudflare Cache Everything / Workers Cache, Fastly, Varnish, …). Bei serverseitiger Einfügung erhält ein Redakteur, der die öffentliche Website durchstöbert, die gecachte anonyme Variante — ohne Symbolleiste —, sobald ein anonymer Besucher den Cache zuerst befüllt hat; die Symbolleiste erscheint und verschwindet also je nach Cache-Zustand. Im Client-Modus wird nichts Sitzungsspezifisches in teilbares HTML eingefügt, sodass der Cache voll wirksam bleibt und die Symbolleiste zuverlässig ist.
Hinweise zum Modus "client":
- Abgemeldete Besucher, die eine geteilte
?_edit-URL öffnen, werden auf die kanonische URL umgeleitet, sodass der Parameter weder Entwürfe preisgeben noch zusätzliche Cache-Einträge mit Seiteninhalt befüllen kann. - Das Signal „angemeldet“ ist ein nicht geheimes
localStorage-Flag, das das Admin setzt; die Pille verifiziert die echte Sitzung, bevor sie die Bearbeitungsansicht öffnet. - Das Bootstrap ist ein kleines Inline-
<script>. Wenn deine Website ein striktesContent-Security-Policyohne'unsafe-inline'sendet, füge einen Hash dafür hinzu — dasselbe gilt für die serverseitig eingefügte Symbolleiste. - EmDash fügt nichts Sitzungsspezifisches ein — wenn aber deine eigenen Templates anhand von
Astro.locals.userverzweigen (z. B. ein „Admin“-Navigationslink für angemeldete Benutzer), steckt diese Varianz weiterhin in deinem HTML und fragmentiert den Cache weiterhin.
In jedem Modus kann die Symbolleiste im Browser über ihre ×-Schaltfläche geschlossen werden (pro Browser, bis ein Redakteur das nächste Mal das Admin öffnet). Vorschau- und Bearbeitungsmodus-Responses werden immer serverseitig mit Cache-Control: private, no-store gerendert.
Datenbank-Adapter
Importiere die Adapter aus emdash/db:
import { sqlite, libsql, postgres } from "emdash/db";
sqlite(config)
SQLite-Datenbank mit dem integrierten Datenbanktreiber von Node.js. Das folgende Beispiel verbindet sich mit einer lokalen Datei:
| Option | Typ | Beschreibung |
|---|---|---|
url | string | Dateipfad mit Präfix file: |
sqlite({ url: "file:./data.db" });
libsql(config)
libSQL-Datenbank. Das folgende Beispiel verbindet sich mit einer entfernten libSQL-Datenbank:
| Option | Typ | Beschreibung |
|---|---|---|
url | string | Datenbank-URL |
authToken | string | Auth-Token zur Laufzeit (für lokale Dateien optional) |
migrationAuthTokenEnv | string | Name der Variable für das Migrations-Token (Standard TURSO_AUTH_TOKEN) |
libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
postgres(config)
PostgreSQL-Datenbank mit Connection-Pooling.
| Option | Typ | Beschreibung |
|---|---|---|
connectionString | string | PostgreSQL-Verbindungs-URL |
host | string | Datenbank-Host |
port | number | Datenbank-Port |
database | string | Datenbankname |
user | string | Datenbankbenutzer |
password | string | Datenbankpasswort |
ssl | boolean | SSL aktivieren |
pool.min | number | Minimale Pool-Größe (Standard: 0) |
pool.max | number | Maximale Pool-Größe (Standard: 10) |
pool.connectionTimeoutMillis | number | Maximale Wartezeit auf eine Verbindung (pg-Standard: 0, kein Timeout) |
pool.idleTimeoutMillis | number | Lebensdauer eines inaktiven Clients (pg-Standard: 10.000 ms) |
migrationConnectionStringEnv | string | Name der Variable für die Verbindungszeichenfolge der Migration (Standard DATABASE_URL) |
Das folgende Beispiel verbindet sich über eine Verbindungszeichenfolge:
postgres({ connectionString: process.env.DATABASE_URL });
d1(config)
Cloudflare-D1-Datenbank. Import aus @emdash-cms/cloudflare.
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
binding | string | — | Name des D1-Bindings aus wrangler.jsonc |
session | string | "disabled" | Modus der Lese-Replikation: "disabled", "auto" oder "primary-first" |
bookmarkCookie | string | "__em_d1_bookmark" | Cookie-Name für Sitzungs-Bookmarks |
coalesce | boolean | false | Bündelt gleichzeitige Lesezugriffe im selben Event-Loop-Durchlauf; erfordert einen anderen Sitzungsmodus als "disabled" |
Das folgende Beispiel zeigt ein einfaches Binding und eines mit aktivierten Lese-Replikaten:
// Basic
d1({ binding: "DB" });
// With read replicas
d1({ binding: "DB", session: "auto" });
Wenn session auf "auto" oder "primary-first" steht, verwendet EmDash die D1-Sessions-API, um Leseabfragen an nahe gelegene Replikate zu leiten. Authentifizierte Benutzer erhalten Read-your-writes-Konsistenz auf Basis von Bookmarks. Details findest du unter Datenbankoptionen — Lese-Replikate.
hyperdrive(config?)
PostgreSQL über ein Cloudflare-Hyperdrive-Binding. Importiere diesen Adapter aus @emdash-cms/cloudflare.
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
binding | string | "HYPERDRIVE" | Primäres Hyperdrive-Binding mit deaktiviertem Query-Caching |
cachedBinding | string | — | Optionales zweites Binding mit aktiviertem Caching für anonyme öffentliche Lesezugriffe |
preferUncachedAfterWriteMs | number | 60_000 | Wie lange öffentliche Lesezugriffe nach einem Inhaltsschreibzugriff das primäre Binding verwenden, wenn cachedBinding gesetzt ist |
migrationConnectionStringEnv | string | Aus dem primären Binding abgeleitet | Umgebungsvariable mit der direkten PostgreSQL-URL, die emdash migrate verwendet |
max | number | 5 | Maximale Anzahl an Verbindungen eines Worker-Isolates zu Hyperdrive |
Das folgende Beispiel leitet authentifizierte Anfragen und Schreibzugriffe über das ungecachte Binding, während anonyme öffentliche Lesezugriffe das gecachte Binding verwenden können:
hyperdrive({
binding: "HYPERDRIVE",
cachedBinding: "HYPERDRIVE_CACHED",
preferUncachedAfterWriteMs: 60_000,
});
Beide Bindings müssen auf dieselbe Datenbank zeigen. Installiere pg in Version 8.16.3 oder höher, aktiviere das Kompatibilitäts-Flag nodejs_compat und konfiguriere eine direkte Datenbank-URL für Deployment-Migrationen. Die vollständige Einrichtung von Worker und Migration findest du unter Datenbankoptionen: Hyperdrive.
durableObjects(config)
Speichert das CMS in einem einzigen SQLite-gestützten Durable Object. Importiere diesen Adapter aus @emdash-cms/cloudflare.
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
binding | string | erforderlich | Durable-Object-Namespace-Binding für die Klasse EmDashDB |
name | string | "emdash" | Name des Singleton-Objekts; ändere ihn nur, um mehrere Datenbanken hinter einem Binding zu isolieren |
session | string | "disabled" | "auto" leitet anonyme Lesezugriffe an Replikate und Schreibzugriffe an das primäre Objekt |
bookmarkCookie | string | "__em_do_bookmark" | Cookie für Read-your-writes-Konsistenz im Modus "auto" |
durableObjects({ binding: "DB_DO", session: "auto" });
Das Routing zu Replikaten erfordert die Kompatibilitäts-Flags experimental und replica_routing sowie die Einträge für Durable-Object-Klasse und Migration in wrangler.jsonc.
previewDatabase(config)
Erstellt pro Vorschau-Sitzung eine isolierte Snapshot-Datenbank in einem Durable Object. Die einzige Option ist der erforderliche Name binding:
previewDatabase({ binding: "PREVIEW_DB" });
Dieser Adapter ist für die Vorschau-Infrastruktur gedacht, nicht für die primäre Datenbank einer Produktions-Website.
playgroundDatabase(config)
Erstellt pro Playground-Sitzung eine beschreibbare, mit einem Seed befüllte Datenbank in einem Durable Object. Kombiniere ihn mit der Integrationsoption playground:
playgroundDatabase({ binding: "PLAYGROUND_DB" });
Das erforderliche binding bezeichnet den Durable-Object-Namespace des Playgrounds. Verwende diesen Adapter nur für Wegwerf-Demo-Websites.
Speicher-Adapter
Importiere local und s3 aus emdash/astro. Der Adapter r2 wird aus @emdash-cms/cloudflare importiert:
import emdash, { local, s3 } from "emdash/astro";
import { r2 } from "@emdash-cms/cloudflare";
local(config)
Speicherung im lokalen Dateisystem. Das folgende Beispiel liefert Uploads aus einem lokalen Verzeichnis aus:
| Option | Typ | Beschreibung |
|---|---|---|
directory | string | Verzeichnispfad |
baseUrl | string | Basis-URL zum Ausliefern von Dateien |
local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
r2(config)
Cloudflare-R2-Binding. Das folgende Beispiel verwendet ein R2-Binding mit einer öffentlichen URL:
| Option | Typ | Beschreibung |
|---|---|---|
binding | string | Name des R2-Bindings |
publicUrl | string | Optionale öffentliche URL |
r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev",
});
s3(config?)
S3-kompatibler Speicher. Alle Konfigurationsfelder sind optional: Jedes Feld, das in
s3({...}) fehlt, wird beim Start des
Node-Prozesses aus der passenden Umgebungsvariable S3_* aufgelöst. Explizite Werte haben immer Vorrang.
Nachdem Konfigurations- und Umgebungswerte zusammengeführt wurden, sind endpoint und bucket erforderlich. Wenn eine der beiden Zugangsdaten gesetzt ist, sind sowohl accessKeyId als auch secretAccessKey erforderlich. Fehlende Werte lassen den Start mit dem Fehlercode MISSING_S3_CONFIG scheitern.
Voraussetzung: Installiere @aws-sdk/client-s3 und @aws-sdk/s3-request-presigner
in deinem Projekt. EmDash Core bündelt das AWS SDK nicht. Details findest du unter
Speicheroptionen: S3-kompatibler Speicher.
| Option | Typ | Beschreibung |
|---|---|---|
endpoint | string | S3-Endpunkt-URL (S3_ENDPOINT) |
bucket | string | Bucket-Name (S3_BUCKET) |
accessKeyId | string | Zugriffsschlüssel (S3_ACCESS_KEY_ID) |
secretAccessKey | string | Geheimer Schlüssel (S3_SECRET_ACCESS_KEY) |
region | string | Region, Standard "auto" (S3_REGION) |
publicUrl | string | Optionale CDN-URL (S3_PUBLIC_URL) |
Die folgenden Beispiele lösen alle Felder aus der Umgebung auf, mischen Konfiguration und Umgebung oder übergeben jedes Feld explizit:
// All fields from S3_* environment variables (Node container deployments)
s3()
// Mix: CDN from config, rest from environment
s3({ publicUrl: "https://cdn.example.com" })
// All explicit
s3({
endpoint: "https://xxx.r2.cloudflarestorage.com",
bucket: "media",
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
publicUrl: "https://cdn.example.com",
})
Die Auflösung von Umgebungsvariablen zur Laufzeit ist ein reines Node-Feature. Auf Cloudflare
Workers werden Secrets und Variablen über den Parameter env des
Fetch-Handlers bereitgestellt, nicht über process.env; S3_*-Umgebungsvariablen
werden daher nicht übernommen. Workers-Deployments sollten entweder den Adapter r2(config)
verwenden oder explizite Werte an s3({...}) übergeben. Details findest du unter
Speicheroptionen.
Objekt-Cache-Adapter
Übergib einen davon an die Option objectCache.
kvCache(config)
Cloudflare-KV-Backend, über alle Isolates hinweg geteilt. Import aus @emdash-cms/cloudflare.
kvCache({
binding: "CACHE", // KV binding name (required)
defaultTtl: 3600, // entry TTL in seconds (optional, KV minimum 60)
revalidate: 1000, // cross-isolate staleness window in ms (optional)
timeout: 2000, // per-op timeout in ms before a miss (optional, 0 disables)
keyPrefix: "em", // cache key prefix (optional)
})
memoryCache(config?)
In-Process-Backend für Node.js und die Entwicklung. Import aus emdash/astro.
memoryCache({
defaultTtl: 3600, // entry TTL in seconds (optional)
revalidate: 1000, // staleness window in ms (optional)
maxEntries: 1000, // max cached keys before eviction (optional)
keyPrefix: "em", // cache key prefix (optional)
})
Einrichtung und Verhalten findest du unter Objekt-Cache.
Authentifizierungs- und Sandbox-Adapter
Diese Adapter liefern Werte für die Integrationsoptionen auth und sandboxRunner.
access(config)
Ersetzt die integrierte Passkey-Anmeldung durch die Authentifizierung über Cloudflare Access. Importiere sie aus @emdash-cms/cloudflare und übergib das Ergebnis an auth:
import { access } from "@emdash-cms/cloudflare";
emdash({
auth: access({
teamDomain: "myteam.cloudflareaccess.com",
audienceEnvVar: "CF_ACCESS_AUDIENCE",
}),
});
teamDomain ist erforderlich. Der Adapter kann die Application Audience aus audience oder aus der Variable lesen, die audienceEnvVar benennt; er akzeptiert außerdem autoProvision, defaultRole, syncRoles, syncName und roleMapping. Die Option auth dokumentiert deren Standardwerte, das Rollenverhalten und die Namenssynchronisierung.
sandbox()
Wählt den Cloudflare Worker Loader als Plugin-Sandbox-Runner. Importiere ihn aus @emdash-cms/cloudflare und übergib seinen Rückgabewert an sandboxRunner:
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxRunner: sandbox(),
});
Die Website braucht außerdem ein Worker-Loader-Binding und den Einstiegspunkt der Plugin-Bridge. Diese Deployment-Einstellungen findest du unter Plugin-Sandbox: Cloudflare Workers.
Medien-Provider-Adapter
Übergib Medien-Provider-Descriptors an mediaProviders. Beide integrierten Cloudflare-Provider werden aus @emdash-cms/cloudflare importiert.
Jede unten genannte *EnvVar-Option benennt eine Umgebungsvariable. Der Provider liest sie in dieser Reihenfolge: ein Cloudflare-Workers-Binding dieses Namens, dann process.env beim Node-Adapter. Die passende direkte Option (accountId, accountHash, apiToken) hat immer Vorrang vor beiden.
cloudflareImages(config)
Fügt Cloudflare Images zum Durchsuchen, Hochladen, Löschen und Ausliefern von Bild-Assets hinzu.
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
accountId | string | Aus CF_ACCOUNT_ID | Cloudflare-Konto-ID |
accountIdEnvVar | string | "CF_ACCOUNT_ID" | Variable, die verwendet wird, wenn accountId fehlt |
accountHash | string | Aus CF_IMAGES_ACCOUNT_HASH | In Auslieferungs-URLs verwendeter Konto-Hash |
accountHashEnvVar | string | "CF_IMAGES_ACCOUNT_HASH" | Variable, die verwendet wird, wenn accountHash fehlt |
apiToken | string | Aus CF_IMAGES_TOKEN | Token mit Lese- und Bearbeitungsrechten für Cloudflare Images |
apiTokenEnvVar | string | "CF_IMAGES_TOKEN" | Variable, die verwendet wird, wenn apiToken fehlt |
deliveryDomain | string | imagedelivery.net | Eigener Hostname für die Bildauslieferung |
defaultVariant | string | "public" | Für die Anzeige verwendete Bildvariante |
mediaProviders: [cloudflareImages({ defaultVariant: "public" })];
cloudflareStream(config)
Fügt Cloudflare Stream zum Durchsuchen, Suchen, Hochladen, Löschen und Abspielen von Video-Assets hinzu.
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
accountId | string | Aus CF_ACCOUNT_ID | Cloudflare-Konto-ID |
accountIdEnvVar | string | "CF_ACCOUNT_ID" | Variable, die verwendet wird, wenn accountId fehlt |
apiToken | string | Aus CF_STREAM_TOKEN | Token mit Lese- und Bearbeitungsrechten für Cloudflare Stream |
apiTokenEnvVar | string | "CF_STREAM_TOKEN" | Variable, die verwendet wird, wenn apiToken fehlt |
customerSubdomain | string | Cloudflare-Standard | Eigener Hostname für die Stream-Auslieferung |
controls | boolean | true | Player-Steuerelemente anzeigen |
autoplay | boolean | false | Wiedergabe automatisch starten |
loop | boolean | false | Wiedergabe wiederholen |
muted | boolean | false, oder true mit Autoplay | Wiedergabe stummschalten |
mediaProviders: [cloudflareStream({ controls: true })];
Die erforderlichen Bindings und Rendering-Komponenten findest du unter Medienbibliothek: Medien-Provider.
Live-Collections
Konfiguriere den EmDash-Loader in src/live.config.ts:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
Loader-Optionen
Die Funktion emdashLoader() akzeptiert keine Argumente:
emdashLoader();
Umgebungsvariablen
EmDash berücksichtigt diese Umgebungsvariablen:
| Variable | Beschreibung |
|---|---|
EMDASH_SITE_URL | Öffentlicher, für den Browser sichtbarer Origin (fällt auf SITE_URL zurück) |
EMDASH_ALLOWED_ORIGINS | Durch Kommas getrennte Liste zusätzlicher Origins, die die Passkey-Verifizierung akzeptiert (Deployments mit mehreren Subdomains). |
EMDASH_DATABASE_URL | Datenbank-URL überschreiben |
EMDASH_ENCRYPTION_KEY | Schlüssel zum Verschlüsseln von Plugin-Secrets im Ruhezustand. Vom Betreiber bereitgestellt — wird nie in der Datenbank gespeichert. |
EMDASH_PREVIEW_SECRET | Optionale Überschreibung des HMAC-Secrets für die Vorschau. Wenn nicht gesetzt, wird ein stabiler Wert pro Website erzeugt und in der Datenbank gespeichert. |
EMDASH_IP_SALT | Optionale Überschreibung des Salts für den IP-Hash von Kommentierenden. Wenn nicht gesetzt, wird ein stabiler Wert pro Website erzeugt und in der Datenbank gespeichert. |
EMDASH_AUTH_SECRET | Veraltet. Wird, falls gesetzt, als Quelle für den IP-Salt verwendet; bestehende Installationen sollten dies beibehalten, um über ein Upgrade hinweg stabile IP-Hashes von Kommentierenden zu bewahren. |
EMDASH_TURNSTILE_SECRET_KEY | Secret Key von Cloudflare Turnstile (fällt auf TURNSTILE_SECRET_KEY zurück). Wenn gesetzt, müssen Kommentareinreichungen ein gültiges Turnstile-Token enthalten — kombiniere dies mit der Prop turnstileSiteKey an <CommentForm>. |
EMDASH_URL | URL einer entfernten EmDash-Instanz für die Schema-Synchronisierung |
Erzeuge einen Verschlüsselungsschlüssel mit dem folgenden Befehl:
npx emdash secrets generate
package.json-Konfiguration
Templates und Websites können unter dem Schlüssel emdash in der package.json optionale Metadaten deklarieren:
{
"emdash": {
"label": "My Blog Template",
"schema": ".emdash/schema.sql",
"seed": ".emdash/seed.json"
}
}
| Option | Beschreibung |
|---|---|
label | Template-Name zur Anzeige |
schema | Optionales SQL-Schema, das emdash init liest |
seed | Pfad zur Seed-JSON-Datei |
TypeScript-Konfiguration
Während der lokalen Entwicklung erzeugt die Astro-Integration emdash-env.d.ts im Projektstamm und aktualisiert die Datei nach Schemaänderungen. Die Datei erweitert das Modul emdash, sodass die Standard-Imports getEmDashCollection() und getEmDashEntry() lokale Collection-Felder ohne Pfad-Alias ableiten.
Der separate Befehl emdash types ruft das Schema von einer laufenden lokalen oder entfernten Instanz ab und schreibt standardmäßig .emdash/types.ts. Füge nur dann einen Alias hinzu, wenn Anwendungscode diese eigenständige Ausgabe direkt importiert:
{
"compilerOptions": {
"paths": {
"@emdash-cms/types": ["./.emdash/types.ts"]
}
}
}
Erzeuge die eigenständigen Typen des entfernten Schemas mit dem folgenden Befehl:
npx emdash types