Konfigurationsreferenz

Auf dieser Seite

EmDash wird über zwei Dateien konfiguriert: astro.config.mjs für die Integration und src/live.config.ts für Content Collections.

Astro-Integration

Konfigurieren Sie EmDash als Astro-Integration in astro.config.mjs:

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. Datenbank-Adapter-Konfiguration. Wählen Sie 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 aus @emdash-cms/cloudflare)
database: d1({ binding: "DB" });

Siehe Datenbankoptionen für Details.

storage

Erforderlich. Medienspeicher-Adapter-Konfiguration. Wählen Sie einen Adapter:

// Lokales Dateisystem (Entwicklung)
storage: local({
	directory: "./uploads",
	baseUrl: "/_emdash/api/media/file",
});

// R2-Bindung (Cloudflare Workers)
storage: r2({
	binding: "MEDIA",
	publicUrl: "https://pub-xxxx.r2.dev", // optional
});

// S3-kompatibel (beliebige Plattform) — alle Felder aus S3_*-Umgebungsvariablen
storage: s3()

// Oder mit expliziten Werten
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, Standard: "auto"
	publicUrl: "https://cdn.example.com", // optional
});

Siehe Speicheroptionen für Details.

plugins

Optional. Array von EmDash-Plugins. Das folgende Beispiel registriert ein Plugin:

import seoPlugin from "@emdash-cms/plugin-seo";

plugins: [seoPlugin()];

fonts

Optional. Admin-UI-Schriftart-Konfiguration.

Standardmäßig lädt EmDash Noto Sans über die Astro Font API. Schriftarten werden zur Build-Zeit von Google heruntergeladen und selbst gehostet, sodass keine Laufzeit-CDN-Anfragen erfolgen. Die Basisschrift deckt lateinische, kyrillische, griechische, Devanagari- und vietnamesische Schriften ab.

Um Unterstützung für zusätzliche Schriftsysteme hinzuzufügen, übergeben Sie Schriftnamen. Das folgende Beispiel fügt Arabisch und Japanisch hinzu:

emdash({
  fonts: {
    scripts: ["arabic", "japanese"],
  },
})

Die verfügbaren Schriften 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.

Jede Schrift wird auf die entsprechende Noto Sans-Variante in Google Fonts abgebildet (z. B. lädt "arabic" Noto Sans Arabic). Alle Schriftarten teilen einen einzigen font-family-Namen und verwenden unicode-range, sodass der Browser nur die Dateien herunterlädt, die für die Zeichen auf der Seite benötigt werden.

Setzen Sie 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. Diese wird automatisch durch die obige Schriftkonfiguration gesetzt.

auth

Optional. Ein Authentifizierungsadapter. EmDashs integrierter Login verwendet Passkeys; das Setzen von auth ersetzt sie durch einen externen Anbieter. 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():

OptionTypStandardBeschreibung
teamDomainstringerforderlichIhre Cloudflare Access Team-Domain
audiencestringApplication Audience (AUD)-Tag. Bei Workers bevorzugen Sie audienceEnvVar.
audienceEnvVarstring"CF_ACCESS_AUDIENCE"Umgebungsvariable, aus der das Audience-Tag zur Laufzeit gelesen wird
autoProvisionbooleantrueErstellt einen EmDash-Benutzer beim ersten Login
defaultRolenumber30Rollenstufe für Benutzer, die nicht durch roleMapping abgeglichen werden (siehe Benutzerrollen)
syncRolesbooleanfalseWendet roleMapping bei jedem Login erneut an, anstatt nur bei der Bereitstellung
roleMappingobjectOrdnet IdP-Gruppennamen EmDash-Rollenstufen zu; erste Übereinstimmung gewinnt

authProviders

Optional. Ein Array von pluggbaren Login-Providern (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() — liest EMDASH_OAUTH_GITHUB_CLIENT_ID / EMDASH_OAUTH_GITHUB_CLIENT_SECRET (oder unpräfixierte Fallbacks).
  • google() — liest EMDASH_OAUTH_GOOGLE_CLIENT_ID / EMDASH_OAUTH_GOOGLE_CLIENT_SECRET.
  • atproto() — Atmosphere-Konto-Login (Bluesky und das breitere AT-Protocol-Netzwerk). Keine Umgebungsvariablen erforderlich. Akzeptiert { allowedDIDs, allowedHandles, defaultRole }. Siehe den Atmosphere-Login-Leitfaden.

Drittanbieter-Pakete können ihre eigenen Provider mit derselben AuthProviderDescriptor-Form registrieren — siehe Login-Provider.

siteUrl

Optional. Der öffentliche, dem Browser zugewandte Origin für die Website (Schema + Host + optionaler Port, kein Pfad).

Hinter einem TLS-terminierenden Reverse-Proxy gibt Astro.url die interne Adresse (http://localhost:4321) anstelle der öffentlichen (https://cms.example.com) zurück. Dies bricht Passkeys, CSRF-Origin-Abgleich, OAuth-Weiterleitungen, Login-Weiterleitungen, MCP-Discovery, Snapshot-Exporte, Sitemap, robots.txt und JSON-LD-strukturierte Daten. Setzen Sie siteUrl, um all dies auf einmal zu beheben.

Die Integration validiert diesen Wert zur Ladezeit: Er muss eine gültige URL mit http:- oder https:-Protokoll sein und wird auf Origin normalisiert (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 Umgebungsvariablen in dieser Reihenfolge: EMDASH_SITE_URL, dann SITE_URL. Dies ist nützlich für Container-Deployments, bei denen die öffentliche URL zur Laufzeit gesetzt wird.

Multi-Origin-Passkey-Verifizierung

siteUrl definiert einen einzigen kanonischen Origin. Wenn dieselbe EmDash-Bereitstellung unter mehreren Hostnamen erreichbar ist, die eine registrierbare Elterndomäne 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 Passkeys über Subdomänen unter derselben rpId als gültig erlaubt.

Deklarieren Sie zusätzliche akzeptierte Origins entweder über allowedOrigins in astro.config.mjs oder die EMDASH_ALLOWED_ORIGINS-Umgebungsvariable. Der kanonische siteUrl bleibt die Quelle der rpId; hier aufgeführte Einträge werden zur Verifizierungszeit akzeptiert. Die beiden Quellen werden zur Laufzeit zusammengeführt, sodass die Konfiguration stabile Origins deklarieren kann (versioniert, code-reviewed), während die Umgebung umgebungsspezifische Extras hinzufügt (z. B. ephemere PR-Vorschauen).

Das folgende Beispiel deklariert einen zusätzlichen Origin in der Konfiguration:

emdash({
	siteUrl: "https://example.com",
	allowedOrigins: ["https://preview.example.com"],
})

Die äquivalenten 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, um tote Konfiguration zu verhindern, die der Browser niemals akzeptieren würde:

  • Jeder Eintrag muss eine parsbare http:- oder https:-URL ohne abschließenden Punkt und ohne leere Labels im Hostnamen sein.
  • Wenn allowedOrigins nicht leer ist, muss siteUrl gesetzt sein (aus einer der beiden Quellen) und darf kein IP-Literal sein oder einen Hostnamen mit abschließendem Punkt haben.
  • Jeder Origin muss derselbe Hostname wie siteUrl oder eine Subdomain davon sein. (WebAuthn erfordert, dass rpId ein registrierbares Suffix jedes Origins ist.)

Wenn die Validierung fehlschlägt, sehen Sie einen quellenattribuierten Fehler 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 Astro-Start, wenn sowohl config.allowedOrigins als auch config.siteUrl aus astro.config.mjs stammen — Tippfehler im Code lassen den Build fehlschlagen.
  • Bei der ersten Passkey-Verifizierung, wenn einer der Werte aus EMDASH_ALLOWED_ORIGINS oder EMDASH_SITE_URL stammt — Umgebungsmismatches treten als 500er beim ersten Verifizierungsversuch auf.

Reverse-Proxy-Einrichtung

Astro reflektiert X-Forwarded-* nur, wenn der öffentliche Host erlaubt ist. Konfigurieren Sie security.allowedDomains für den Hostnamen (und Schemata), den Ihre Benutzer treffen. In astro dev fügen Sie passende vite.server.allowedHosts hinzu, damit Vite den Proxy-Host-Header akzeptiert.

Bevorzugen Sie zuerst die Behebung von allowedDomains (und weitergeleiteten Headern); verwenden Sie siteUrl, wenn die rekonstruierte URL immer noch vom Browser-Origin abweicht (typisch, wenn TLS vorne terminiert wird und die Upstream-Anfrage http:// bleibt).

Mit TLS vorne reicht oft die Bindung des Dev-Servers an Loopback (astro dev --host 127.0.0.1) aus: Der Proxy verbindet sich lokal, während siteUrl mit dem öffentlichen HTTPS-Origin übereinstimmt.

Wenn Ihr Proxy einen Client-IP-Header schreibt, setzen Sie trustedProxyHeaders, damit EmDashs Ratenbegrenzungen die echte Client-IP verwenden können, anstatt jede Anfrage unter einem gemeinsamen “unknown”-Schlüssel zu gruppieren.

Die folgende Konfiguration setzt allowedDomains, vite.server.allowedHosts und siteUrl zusammen für eine Reverse-Proxy-Bereitstellung:

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 bei der Client-IP-Auflösung vertraut werden soll, wenn hinter einem von Ihnen kontrollierten Reverse-Proxy läuft. Wird von Auth-Ratenbegrenzungen (Magic-Link, Registrierung, Passkey, OAuth-Device-Flow) und dem öffentlichen Kommentar-Endpunkt verwendet.

Bei Cloudflare wird das an die Anfrage angehängte cf-Objekt automatisch verwendet — Sie müssen dies normalerweise nicht setzen. Bei selbst gehosteten Bereitstellungen hinter nginx, Caddy, Traefik, Fly, Railway oder ähnlichen setzen Sie dies auf den Header, den Ihr Proxy schreibt, damit Ratenbegrenzungen nach echter Client-IP gruppieren können, anstatt jede Anfrage als “unknown” zu behandeln.

Das folgende Beispiel vertraut dem x-real-ip-Header, der von nginx, Caddy oder Traefik gesetzt wird:

emdash({
	database: sqlite({ url: "file:./data.db" }),
	trustedProxyHeaders: ["x-real-ip"],
});

Header werden in Reihenfolge versucht. Werte, die auf *-forwarded-for passen, werden als kommagetrennte Listen geparst und der erste Eintrag wird verwendet. Das folgende Beispiel bevorzugt Fly.ios Header und fällt auf x-forwarded-for zurück:

emdash({
	trustedProxyHeaders: ["fly-client-ip", "x-forwarded-for"],
});

Wenn nicht in der Konfiguration gesetzt, liest EmDash die EMDASH_TRUSTED_PROXY_HEADERS-Umgebungsvariable (kommagetrennt). Ein explizites leeres Array in der Konfiguration überschreibt die Umgebungsvariable.

maxUploadSize

Optional. Maximal zulässige Mediendatei-Upload-Größe in Bytes. Gilt sowohl für direkte Multipart-Uploads als auch für signierte URL-Uploads. 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
});
WertBeschreibung
number (Bytes)Muss eine positive endliche Ganzzahl sein
weggelassenStandardwert ist 50 MB

Uploads, die das konfigurierte Limit überschreiten, werden mit einer 413 Payload Too Large-Antwort auf dem direkten Upload-Pfad oder einem 400 Validation Error auf dem signierten URL-Pfad abgelehnt.

Datenbank-Adapter

Importieren Sie die Adapter aus emdash/db:

import { sqlite, libsql, postgres } from "emdash/db";

sqlite(config)

SQLite-Datenbank mit better-sqlite3. Das folgende Beispiel verbindet sich mit einer lokalen Datei:

OptionTypBeschreibung
urlstringDateipfad mit file:-Präfix
sqlite({ url: "file:./data.db" });

libsql(config)

libSQL-Datenbank. Das folgende Beispiel verbindet sich mit einer entfernten libSQL-Datenbank:

OptionTypBeschreibung
urlstringDatenbank-URL
authTokenstringAuth-Token (optional für lokale Dateien)
libsql({
	url: process.env.LIBSQL_DATABASE_URL,
	authToken: process.env.LIBSQL_AUTH_TOKEN,
});

postgres(config)

PostgreSQL-Datenbank mit Connection-Pooling.

OptionTypBeschreibung
connectionStringstringPostgreSQL-Verbindungs-URL
hoststringDatenbank-Host
portnumberDatenbank-Port
databasestringDatenbankname
userstringDatenbankbenutzer
passwordstringDatenbankpasswort
sslbooleanSSL aktivieren
pool.minnumberMinimale Pool-Größe (Standard: 0)
pool.maxnumberMaximale Pool-Größe (Standard: 10)

Das folgende Beispiel verbindet sich mit einem Connection-String:

postgres({ connectionString: process.env.DATABASE_URL });

d1(config)

Cloudflare D1-Datenbank. Import aus @emdash-cms/cloudflare.

OptionTypStandardBeschreibung
bindingstringD1-Bindungsname aus wrangler.jsonc
sessionstring"disabled"Lesereplikationsmodus: "disabled", "auto" oder "primary-first"
bookmarkCookiestring"__em_d1_bookmark"Cookie-Name für Session-Bookmarks

Das folgende Beispiel zeigt eine Basisbindung und eine mit aktivierten Lesereplikaten:

// Basis
d1({ binding: "DB" });

// Mit Lesereplikaten
d1({ binding: "DB", session: "auto" });

Wenn session "auto" oder "primary-first" ist, verwendet EmDash die D1 Sessions API, um Leseabfragen an nahe Replikate zu routen. Authentifizierte Benutzer erhalten Bookmark-basierte Read-your-writes-Konsistenz. Siehe Datenbankoptionen — Lesereplikate für Details.

Speicher-Adapter

Importieren Sie local und s3 aus emdash/astro. Der r2-Adapter wird aus @emdash-cms/cloudflare importiert:

import emdash, { local, s3 } from "emdash/astro";
import { r2 } from "@emdash-cms/cloudflare";

local(config)

Lokaler Dateisystem-Speicher. Das folgende Beispiel serviert Uploads aus einem lokalen Verzeichnis:

OptionTypBeschreibung
directorystringVerzeichnispfad
baseUrlstringBasis-URL zum Servieren von Dateien
local({
	directory: "./uploads",
	baseUrl: "/_emdash/api/media/file",
});

r2(config)

Cloudflare R2-Bindung. Das folgende Beispiel verwendet eine R2-Bindung mit einer öffentlichen URL:

OptionTypBeschreibung
bindingstringR2-Bindungsname
publicUrlstringOptionale ö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({...}) weggelassen wird, wird aus der entsprechenden S3_*-Umgebungsvariable aufgelöst, wenn der Node-Prozess startet. Explizite Werte haben immer Vorrang.

Voraussetzung: Installieren Sie @aws-sdk/client-s3 und @aws-sdk/s3-request-presigner in Ihrem Projekt. EmDash Core bündelt das AWS SDK nicht. Siehe Speicheroptionen: S3-kompatibler Speicher für Details.

OptionTypBeschreibung
endpointstringS3-Endpunkt-URL (S3_ENDPOINT)
bucketstringBucket-Name (S3_BUCKET)
accessKeyIdstringZugriffsschlüssel (S3_ACCESS_KEY_ID)
secretAccessKeystringGeheimer Schlüssel (S3_SECRET_ACCESS_KEY)
regionstringRegion, Standard "auto" (S3_REGION)
publicUrlstringOptionale 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:

// Alle Felder aus S3_*-Umgebungsvariablen (Node-Container-Bereitstellungen)
s3()

// Mix: CDN aus Konfiguration, Rest aus Umgebung
s3({ publicUrl: "https://cdn.example.com" })

// Alles explizit
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",
})

Laufzeit-Umgebungsvariablen-Auflösung ist ein Node-only-Feature. Bei Cloudflare Workers werden Secrets und Variablen über den env-Parameter des Fetch-Handlers bereitgestellt, nicht über process.env, sodass S3_*-Umgebungsvariablen nicht erfasst werden. Workers-Bereitstellungen sollten entweder den r2(config)-Adapter verwenden oder explizite Werte an s3({...}) übergeben. Siehe Speicheroptionen für Details.

Live-Collections

Konfigurieren Sie 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() nimmt keine Argumente:

emdashLoader();

Umgebungsvariablen

EmDash respektiert diese Umgebungsvariablen:

VariableBeschreibung
EMDASH_SITE_URLÖffentlicher Browser-zugewandter Origin (fällt auf SITE_URL zurück)
EMDASH_ALLOWED_ORIGINSKommagetrennte Liste zusätzlicher Origins, die von der Passkey-Verifizierung akzeptiert werden (Multi-Subdomain-Bereitstellungen).
EMDASH_DATABASE_URLDatenbank-URL überschreiben
EMDASH_ENCRYPTION_KEYSchlüssel zum Verschlüsseln von Plugin-Secrets im Ruhezustand. Vom Betreiber bereitgestellt — niemals in der Datenbank gespeichert.
EMDASH_PREVIEW_SECRETOptionale Überschreibung für Preview-HMAC-Secret. Wenn nicht gesetzt, wird ein stabiler pro-Site-Wert generiert und in der Datenbank gespeichert.
EMDASH_IP_SALTOptionale Überschreibung für das Commenter-IP-Hash-Salt. Wenn nicht gesetzt, wird ein stabiler pro-Site-Wert generiert und in der Datenbank gespeichert.
EMDASH_AUTH_SECRETLegacy. Wird als IP-Salt-Quelle verwendet, wenn gesetzt; bestehende Installationen sollten dies beibehalten, um stabile Commenter-IP-Hashes über Upgrades hinweg zu bewahren.
EMDASH_URLEntfernte EmDash-URL für Schema-Sync

Generieren Sie einen Verschlüsselungsschlüssel mit dem folgenden Befehl:

npx emdash secrets generate

package.json-Konfiguration

Templates und Sites können optionale Metadaten unter einem emdash-Schlüssel in package.json deklarieren:

{
	"emdash": {
		"label": "My Blog Template",
		"seed": ".emdash/seed.json",
		"url": "https://my-site.pages.dev"
	}
}
OptionBeschreibung
labelVorlagenname zur Anzeige
seedPfad zur Seed-JSON-Datei
urlEntfernte URL für Schema-Sync

TypeScript-Konfiguration

EmDash generiert Typen in .emdash/types.ts. Fügen Sie einen Pfad-Alias zu Ihrer tsconfig.json hinzu:

{
	"compilerOptions": {
		"paths": {
			"@emdash-cms/types": ["./.emdash/types.ts"]
		}
	}
}

Generieren Sie Typen mit dem folgenden Befehl:

npx emdash types