EmDash est configuré via deux fichiers : astro.config.mjs pour l’intégration et src/live.config.ts pour les collections de contenu.
Intégration Astro
Configurez EmDash comme intégration Astro dans 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: [],
}),
],
});
Options d’intégration
database
Requis. Configuration de l’adaptateur de base de données. Choisissez un adaptateur :
// 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 (importer depuis @emdash-cms/cloudflare)
database: d1({ binding: "DB" });
Voir Options de base de données pour plus de détails.
storage
Requis. Configuration de l’adaptateur de stockage de médias. Choisissez un adaptateur :
// Système de fichiers local (développement)
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
// Binding R2 (Cloudflare Workers)
storage: r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev", // optionnel
});
// Compatible S3 (toute plateforme) — tous les champs depuis les variables d'environnement S3_*
storage: s3()
// Ou avec des valeurs explicites
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", // optionnel, par défaut : "auto"
publicUrl: "https://cdn.example.com", // optionnel
});
Voir Options de stockage pour plus de détails.
objectCache
Optionnel. Met en cache les résultats des requêtes de contenu et de configuration dans un magasin clé/valeur afin que les lectures soient servies sans interroger la base de données à chaque requête. Désactivé lorsqu’omis. Choisissez un adaptateur :
// Cloudflare KV (partagé entre tous les isolates)
import { kvCache } from "@emdash-cms/cloudflare";
objectCache: kvCache({ binding: "CACHE" });
// En mémoire (Node.js / développement)
import { memoryCache } from "emdash/astro";
objectCache: memoryCache();
Voir Object Cache pour la configuration et les options.
plugins
Optionnel. Tableau de plugins EmDash. L’exemple suivant enregistre un plugin :
import seoPlugin from "@emdash-cms/plugin-seo";
plugins: [seoPlugin()];
fonts
Optionnel. Configuration des polices de l’interface d’administration.
Par défaut, EmDash charge Noto Sans via l’API Astro Font. Les polices sont téléchargées depuis Google au moment de la compilation et auto-hébergées, il n’y a donc pas de requêtes CDN en temps d’exécution. La police de base couvre les scripts latin, cyrillique, grec, devanagari et vietnamien.
Pour ajouter le support de systèmes d’écriture supplémentaires, passez des noms de scripts. L’exemple suivant ajoute l’arabe et le japonais :
emdash({
fonts: {
scripts: ["arabic", "japanese"],
},
})
Les scripts disponibles sont 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 et tibetan.
Chaque script est associé à la variante Noto Sans correspondante sur Google Fonts (ex. "arabic" charge Noto Sans Arabic). Toutes les faces de police partagent un seul nom font-family et utilisent unicode-range pour que le navigateur ne télécharge que les fichiers nécessaires pour les caractères de la page.
Définissez à false pour désactiver entièrement l’injection de polices et utiliser les polices système :
emdash({
fonts: false,
})
Le CSS admin utilise la variable CSS --font-emdash. Elle est définie automatiquement par la configuration des polices ci-dessus.
auth
Optionnel. Un adaptateur d’authentification. La connexion intégrée d’EmDash utilise les passkeys ; configurer auth les remplace par un fournisseur externe. L’adaptateur Cloudflare Access, access(), est fourni par @emdash-cms/cloudflare :
import { access } from "@emdash-cms/cloudflare";
emdash({
auth: access({
teamDomain: "myteam.cloudflareaccess.com",
audience: "your-app-audience-tag",
roleMapping: {
Admins: 50,
Editors: 40,
},
}),
});
Options pour access() :
| Option | Type | Par défaut | Description |
|---|---|---|---|
teamDomain | string | requis | Votre domaine d’équipe Cloudflare Access |
audience | string | — | Tag Application Audience (AUD). Sur Workers, préférez audienceEnvVar. |
audienceEnvVar | string | "CF_ACCESS_AUDIENCE" | Variable d’environnement pour lire le tag d’audience au moment de l’exécution |
autoProvision | boolean | true | Créer un utilisateur EmDash à la première connexion |
defaultRole | number | 30 | Niveau de rôle pour les utilisateurs non mappés par roleMapping (voir Rôles utilisateur) |
syncRoles | boolean | false | Réappliquer roleMapping à chaque connexion au lieu du seul provisionnement |
roleMapping | object | — | Mapper les noms de groupes IdP aux niveaux de rôle EmDash ; première correspondance gagne |
authProviders
Optionnel. Un tableau de fournisseurs de connexion enfichables (niveau supérieur, à côté de auth). Chaque entrée est le résultat de l’appel d’une factory de fournisseur :
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()],
});
Fournisseurs intégrés :
github()— litEMDASH_OAUTH_GITHUB_CLIENT_ID/EMDASH_OAUTH_GITHUB_CLIENT_SECRET(ou les fallbacks sans préfixe).google()— litEMDASH_OAUTH_GOOGLE_CLIENT_ID/EMDASH_OAUTH_GOOGLE_CLIENT_SECRET.atproto()— Connexion de compte Atmosphere (Bluesky et le réseau AT Protocol élargi). Aucune variable d’environnement nécessaire. Accepte{ allowedDIDs, allowedHandles, defaultRole }. Voir le guide de connexion Atmosphere.
Les packages tiers peuvent enregistrer leurs propres fournisseurs en utilisant la même forme AuthProviderDescriptor — voir Fournisseurs de connexion.
siteUrl
Optionnel. L’origine publique côté navigateur pour le site (schéma + hôte + port optionnel, pas de chemin).
Derrière un proxy inverse terminant TLS, Astro.url retourne l’adresse interne (http://localhost:4321) au lieu de la publique (https://cms.example.com). Cela casse les passkeys, la correspondance d’origine CSRF, les redirections OAuth, les redirections de connexion, la découverte MCP, les exports de snapshots, le sitemap, robots.txt et les données structurées JSON-LD. Définissez siteUrl pour corriger tout cela d’un coup.
L’intégration valide cette valeur au chargement : elle doit être une URL valide avec le protocole http: ou https: et est normalisée en origin (le chemin est supprimé).
L’exemple suivant définit l’origine publique :
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
});
Quand siteUrl n’est pas défini dans la configuration, EmDash vérifie les variables d’environnement dans l’ordre : EMDASH_SITE_URL, puis SITE_URL. Utile pour les déploiements en conteneurs où l’URL publique est définie au moment de l’exécution.
Sur Cloudflare Workers, le fallback de variable d’environnement lit process.env, qui est vide sauf si le flag de compatibilité nodejs_compat_populate_process_env est activé. Pour utiliser la variable d’environnement au lieu de l’option de configuration, définissez les deux :
// wrangler.jsonc
{
"compatibility_flags": ["nodejs_compat", "nodejs_compat_populate_process_env"],
"vars": { "EMDASH_SITE_URL": "https://cms.example.com" },
}
Vérification de passkey multi-origine
siteUrl définit une seule origine canonique. Quand le même déploiement EmDash est accessible sous plusieurs noms d’hôte partageant un domaine parent enregistrable (ex. https://example.com et https://preview.example.com), la vérification de passkey rejette les assertions dont l’origine ne correspond pas exactement à siteUrl — même si WebAuthn permet aux passkeys d’être valides entre les sous-domaines sous le même rpId.
Déclarez des origines supplémentaires acceptées via allowedOrigins dans astro.config.mjs ou la variable d’environnement EMDASH_ALLOWED_ORIGINS. Le siteUrl canonique reste la source du rpId ; les entrées listées ici sont acceptées lors de la vérification. Les deux sources sont fusionnées au moment de l’exécution.
L’exemple suivant déclare une origine supplémentaire dans la configuration :
emdash({
siteUrl: "https://example.com",
allowedOrigins: ["https://preview.example.com"],
})
Les valeurs équivalentes peuvent aussi provenir des variables d’environnement :
EMDASH_SITE_URL=https://example.com
EMDASH_ALLOWED_ORIGINS=https://preview.example.com,https://staging.example.com
Validation
EmDash valide ces valeurs pour prévenir les configurations mortes que le navigateur ne respecterait jamais :
- Chaque entrée doit être une URL
http:ouhttps:analysable sans point final et sans étiquettes vides dans le nom d’hôte. - Quand
allowedOriginsn’est pas vide,siteUrldoit être défini (depuis n’importe quelle source) et ne doit pas être un littéral IP ou avoir un nom d’hôte avec point final. - Chaque origine doit être le même nom d’hôte que
siteUrlou un sous-domaine de celui-ci.
Quand la validation échoue, vous verrez une erreur attribuée à la source.
Où l’erreur apparaît dépend d’où les valeurs sont déclarées :
- Au démarrage d’Astro, quand
config.allowedOriginsetconfig.siteUrlviennent deastro.config.mjs— les coquilles dans le code font échouer la compilation. - À la première vérification de passkey, quand l’une des valeurs vient de
EMDASH_ALLOWED_ORIGINSouEMDASH_SITE_URL— les discordances d’environnement apparaissent comme des 500s lors de la première tentative de vérification.
Configuration de proxy inverse
Astro ne reflète X-Forwarded-* que quand l’hôte public est autorisé. Configurez security.allowedDomains pour le nom d’hôte (et schémas) que vos utilisateurs utilisent. Dans astro dev, ajoutez le vite.server.allowedHosts correspondant pour que Vite accepte l’en-tête Host du proxy.
Préférez corriger allowedDomains (et les en-têtes transmis) d’abord ; utilisez siteUrl quand l’URL reconstruite diverge encore de l’origine du navigateur (typique quand TLS est terminé devant et la requête en amont reste en http://).
Avec TLS devant, lier le serveur de développement à la boucle locale (astro dev --host 127.0.0.1) suffit souvent : le proxy se connecte localement tandis que siteUrl correspond à l’origine HTTPS publique.
Si votre proxy écrit un en-tête d’IP client, définissez trustedProxyHeaders pour que les limites de débit d’EmDash puissent utiliser l’IP réelle du client.
La configuration suivante définit allowedDomains, vite.server.allowedHosts et siteUrl ensemble pour un déploiement avec proxy inverse :
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
Optionnel. En-têtes auxquels faire confiance pour la résolution d’IP client lors de l’exécution derrière un proxy inverse que vous contrôlez. Utilisé par les limites de débit d’auth (magic-link, inscription, passkey, OAuth device flow) et le point de terminaison public des commentaires.
Sur Cloudflare, l’objet cf attaché à la requête est utilisé automatiquement — vous n’avez normalement pas besoin de le définir. Sur les déploiements auto-hébergés derrière nginx, Caddy, Traefik, Fly, Railway ou similaires, définissez ceci à l’en-tête que votre proxy écrit.
L’exemple suivant fait confiance à l’en-tête x-real-ip défini par nginx, Caddy ou Traefik :
emdash({
database: sqlite({ url: "file:./data.db" }),
trustedProxyHeaders: ["x-real-ip"],
});
Les en-têtes sont essayés dans l’ordre. Les valeurs correspondant à *-forwarded-for sont analysées comme des listes séparées par des virgules et la première entrée est utilisée :
emdash({
trustedProxyHeaders: ["fly-client-ip", "x-forwarded-for"],
});
Quand non défini dans la configuration, EmDash lit la variable d’environnement EMDASH_TRUSTED_PROXY_HEADERS (séparée par des virgules). Un tableau vide explicite dans la configuration remplace la variable d’environnement.
maxUploadSize
Optionnel. Taille maximale autorisée pour le téléchargement de fichiers médias en octets. S’applique aux téléchargements multipart directs et aux téléchargements avec URL signée. Par défaut 52_428_800 (50 Mo). L’exemple suivant élève la limite à 100 Mo :
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
maxUploadSize: 100 * 1024 * 1024, // 100 Mo
});
toolbar
Optionnel. Contrôle comment la barre d’outils de l’éditeur (la pastille flottante sur les pages publiques) est livrée. Par défaut "server".
| Valeur | Comportement |
|---|---|
"server" (par défaut) | La barre d’outils est injectée côté serveur dans chaque réponse HTML rendue pour un éditeur authentifié. |
"client" | Le HTML public est identique pour chaque visiteur. Un petit script bootstrap affiche une pastille “Edit” dans les navigateurs connectés à l’admin ; cliquer vérifie la session et recharge la page avec un paramètre de requête _edit, qui est toujours rendu frais (jamais mis en cache) avec la barre d’outils complète. |
false | Ne jamais rendre la barre d’outils ni le script bootstrap. |
emdash({
toolbar: "client",
})
Utilisez "client" quand votre HTML public est servi via un cache partagé (Cloudflare Cache Everything / Workers Cache, Fastly, Varnish, …).
experimental
Optionnel. Fonctionnalités opt-in dont le comportement ou le format peut changer, ou être supprimé, dans une version mineure. Chaque champ est activé indépendamment.
experimental.registry
Optionnel. Pointe les flux de navigation et d’installation de plugins du tableau de bord admin vers un registre de plugins fédéré au lieu du marketplace central. Nécessite sandboxRunner, car les plugins du registre s’exécutent en sandbox.
emdash({
sandboxRunner: "@emdash-cms/sandbox-cloudflare",
experimental: {
registry: {
aggregatorUrl: "https://registry.emdashcms.com",
acceptLabelers: "did:plc:emdashverification",
policy: {
minimumReleaseAge: "48h",
minimumReleaseAgeExclude: ["did:plc:yourfirstpartydid"],
},
},
},
});
| Option | Type | Description |
|---|---|---|
aggregatorUrl | string | Origine de l’agrégateur où les endpoints XRPC du registre sont montés. HTTPS en production. |
acceptLabelers | string | DIDs d’étiqueteurs séparés par des virgules transmis à l’agrégateur pour les étiquettes de retrait et de vérification. |
policy.minimumReleaseAge | string | number | Retenir les releases plus récents que cet âge. Chaîne de durée ("48h", "7d") ou secondes. |
policy.minimumReleaseAgeExclude | string[] | DIDs (ou paires <did>/<slug>) exemptés de la rétention. |
Voir Le registre de plugins pour le workflow complet.
Adaptateurs de base de données
Importez les adaptateurs depuis emdash/db :
import { sqlite, libsql, postgres } from "emdash/db";
sqlite(config)
Base de données SQLite utilisant better-sqlite3.
| Option | Type | Description |
|---|---|---|
url | string | Chemin de fichier avec préfixe file: |
sqlite({ url: "file:./data.db" });
libsql(config)
Base de données libSQL.
| Option | Type | Description |
|---|---|---|
url | string | URL de la base de données |
authToken | string | Token d’auth (optionnel pour les fichiers locaux) |
libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
postgres(config)
Base de données PostgreSQL avec pooling de connexions.
| Option | Type | Description |
|---|---|---|
connectionString | string | URL de connexion PostgreSQL |
host | string | Hôte de la base de données |
port | number | Port de la base de données |
database | string | Nom de la base de données |
user | string | Utilisateur de la base de données |
password | string | Mot de passe de la base de données |
ssl | boolean | Activer SSL |
pool.min | number | Taille min. du pool (défaut : 0) |
pool.max | number | Taille max. du pool (défaut : 10) |
postgres({ connectionString: process.env.DATABASE_URL });
d1(config)
Base de données Cloudflare D1. Importer depuis @emdash-cms/cloudflare.
| Option | Type | Par défaut | Description |
|---|---|---|---|
binding | string | — | Nom du binding D1 de wrangler.jsonc |
session | string | "disabled" | Mode de réplication de lecture : "disabled", "auto" ou "primary-first" |
bookmarkCookie | string | "__em_d1_bookmark" | Nom du cookie pour les signets de session |
d1({ binding: "DB" });
d1({ binding: "DB", session: "auto" });
Adaptateurs de stockage
Importez local et s3 depuis emdash/astro. L’adaptateur r2 est importé depuis @emdash-cms/cloudflare :
import emdash, { local, s3 } from "emdash/astro";
import { r2 } from "@emdash-cms/cloudflare";
local(config)
Stockage sur système de fichiers local.
| Option | Type | Description |
|---|---|---|
directory | string | Chemin du répertoire |
baseUrl | string | URL de base pour servir les fichiers |
local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
r2(config)
Binding Cloudflare R2.
| Option | Type | Description |
|---|---|---|
binding | string | Nom du binding R2 |
publicUrl | string | URL publique optionnelle |
r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev",
});
s3(config?)
Stockage compatible S3. Tous les champs de configuration sont optionnels : tout champ omis de s3({...}) est résolu depuis la variable d’environnement S3_* correspondante au démarrage du processus Node. Les valeurs explicites ont toujours la priorité.
Prérequis : installez @aws-sdk/client-s3 et @aws-sdk/s3-request-presigner dans votre projet. Le core EmDash ne bundle pas le SDK AWS. Voir Options de stockage : Stockage compatible S3 pour les détails.
| Option | Type | Description |
|---|---|---|
endpoint | string | URL de l’endpoint S3 (S3_ENDPOINT) |
bucket | string | Nom du bucket (S3_BUCKET) |
accessKeyId | string | Clé d’accès (S3_ACCESS_KEY_ID) |
secretAccessKey | string | Clé secrète (S3_SECRET_ACCESS_KEY) |
region | string | Région, défaut "auto" (S3_REGION) |
publicUrl | string | URL CDN optionnelle (S3_PUBLIC_URL) |
s3()
s3({ publicUrl: "https://cdn.example.com" })
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",
})
Adaptateurs d’object cache
Passez l’un de ceux-ci à l’option objectCache.
kvCache(config)
Backend Cloudflare KV, partagé entre tous les isolates. Importer depuis @emdash-cms/cloudflare.
kvCache({
binding: "CACHE",
defaultTtl: 3600,
revalidate: 1000,
timeout: 2000,
keyPrefix: "em",
})
memoryCache(config?)
Backend en processus pour Node.js et développement. Importer depuis emdash/astro.
memoryCache({
defaultTtl: 3600,
revalidate: 1000,
maxEntries: 1000,
keyPrefix: "em",
})
Voir Object Cache pour la configuration et le comportement.
Collections en direct
Configurez le loader EmDash dans src/live.config.ts :
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
Options du loader
La fonction emdashLoader() ne prend aucun argument :
emdashLoader();
Variables d’environnement
EmDash respecte ces variables d’environnement :
| Variable | Description |
|---|---|
EMDASH_SITE_URL | Origine publique côté navigateur (recourt à SITE_URL) |
EMDASH_ALLOWED_ORIGINS | Liste séparée par des virgules d’origines supplémentaires acceptées par la vérification de passkey (déploiements multi-sous-domaine). |
EMDASH_DATABASE_URL | Remplacer l’URL de la base de données |
EMDASH_ENCRYPTION_KEY | Clé pour chiffrer les secrets des plugins au repos. Fournie par l’opérateur — jamais stockée dans la base de données. |
EMDASH_PREVIEW_SECRET | Remplacement optionnel pour le secret HMAC de prévisualisation. |
EMDASH_IP_SALT | Remplacement optionnel pour le sel du hash IP du commentateur. |
EMDASH_AUTH_SECRET | Hérité. Utilisé comme source de sel IP si défini. Les nouvelles installations ne devraient pas le définir. |
EMDASH_TURNSTILE_SECRET_KEY | Clé secrète Cloudflare Turnstile (recourt à TURNSTILE_SECRET_KEY). |
EMDASH_URL | URL EmDash distante pour la synchronisation de schéma |
Générez une clé de chiffrement avec la commande suivante :
npx emdash secrets generate
Configuration package.json
Les templates et sites peuvent déclarer des métadonnées optionnelles sous une clé emdash dans package.json :
{
"emdash": {
"label": "My Blog Template",
"seed": ".emdash/seed.json",
"url": "https://my-site.pages.dev"
}
}
| Option | Description |
|---|---|
label | Nom du template pour l’affichage |
seed | Chemin vers le fichier seed JSON |
url | URL distante pour la synchronisation de schéma |
Configuration TypeScript
EmDash génère les types dans .emdash/types.ts. Ajoutez un alias de chemin à votre tsconfig.json :
{
"compilerOptions": {
"paths": {
"@emdash-cms/types": ["./.emdash/types.ts"]
}
}
}
Générez les types avec la commande suivante :
npx emdash types