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 une 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 (import 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",
});
// Liaison R2 (Cloudflare Workers)
storage: r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev", // optionnel
});
// Compatible S3 (n'importe quelle plateforme) — tous les champs des 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, défaut : "auto"
publicUrl: "https://cdn.example.com", // optionnel
});
Voir Options de stockage pour plus de détails.
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 de polices Astro. Les polices sont téléchargées depuis Google au moment de la construction et auto-hébergées, il n’y a donc pas de requêtes CDN à l’exécution. La police de base couvre les écritures latine, cyrillique, grecque, devanagari et vietnamienne.
Pour ajouter la prise en charge 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 correspond à la variante Noto Sans correspondante sur Google Fonts (par exemple, "arabic" charge Noto Sans Arabic). Toutes les polices partagent un seul nom font-family et utilisent unicode-range afin que le navigateur ne télécharge que les fichiers dont il a besoin pour les caractères de la page.
Définissez sur false pour désactiver complètement l’injection de polices et utiliser les polices système :
emdash({
fonts: false,
})
Le CSS d’administration utilise la variable CSS --font-emdash. Ceci est défini automatiquement par la configuration de police ci-dessus.
auth
Optionnel. Un adaptateur d’authentification. La connexion intégrée d’EmDash utilise les passkeys ; définir 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 | Défaut | Description |
|---|---|---|---|
teamDomain | string | requis | Votre domaine d’équipe Cloudflare Access |
audience | string | — | Tag d’audience (AUD) de l’application. Sur Workers, préférez audienceEnvVar. |
audienceEnvVar | string | "CF_ACCESS_AUDIENCE" | Variable d’environnement à partir de laquelle lire le tag d’audience à l’exécution |
autoProvision | boolean | true | Créer un utilisateur EmDash lors de la première connexion |
defaultRole | number | 30 | Niveau de rôle pour les utilisateurs non correspondants à roleMapping (voir Rôles utilisateur) |
syncRoles | boolean | false | Réappliquer roleMapping à chaque connexion au lieu de seulement lors du provisionnement |
roleMapping | object | — | Mapper les noms de groupes IdP aux niveaux de rôle EmDash ; la première correspondance gagne |
authProviders
Optionnel. Un tableau de fournisseurs de connexion enfichables (niveau supérieur, aux côtés de auth). Chaque entrée est le résultat de l’appel d’une fabrique de fournisseur, comme indiqué ci-dessous :
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 replis non préfixés).google()— litEMDASH_OAUTH_GOOGLE_CLIENT_ID/EMDASH_OAUTH_GOOGLE_CLIENT_SECRET.atproto()— Connexion au compte Atmosphere (Bluesky et le réseau AT Protocol plus large). 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 orientée navigateur pour le site (schéma + hôte + port optionnel, pas de chemin).
Derrière un proxy inverse de terminaison TLS, Astro.url renvoie l’adresse interne (http://localhost:4321) au lieu de l’adresse 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 snapshot, le sitemap, robots.txt et les données structurées JSON-LD. Définissez siteUrl pour tout corriger d’un coup.
L’intégration valide cette valeur au moment du chargement : elle doit être une URL valide avec protocole http: ou https: et est normalisée en origine (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",
});
Lorsque 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. Ceci est utile pour les déploiements de conteneurs où l’URL publique est définie à l’exécution.
Vérification de passkey multi-origine
siteUrl définit une origine canonique unique. Lorsque le même déploiement EmDash est accessible sous plusieurs noms d’hôtes qui partagent un domaine parent enregistrable (par exemple, 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 sur les sous-domaines sous le même rpId.
Déclarez des origines acceptées supplémentaires 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 au moment de la vérification. Les deux sources sont fusionnées à l’exécution, de sorte que la configuration peut déclarer les origines stables (versionnées, revues de code) tandis que l’environnement ajoute des extras spécifiques à l’environnement (par exemple, aperçus PR éphémères).
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 également provenir de variables d’environnement :
EMDASH_SITE_URL=https://example.com
EMDASH_ALLOWED_ORIGINS=https://preview.example.com,https://staging.example.com
Validation
EmDash valide ceux-ci pour éviter une configuration morte que le navigateur n’honorerait jamais :
- Chaque entrée doit être une URL analysable
http:ouhttps:sans point de fin et sans étiquettes vides dans le nom d’hôte. - Lorsque
allowedOriginsn’est pas vide,siteUrldoit être défini (n’importe quelle source) et ne doit pas être un littéral IP ni avoir un nom d’hôte avec point de fin. - Chaque origine doit être le même nom d’hôte que
siteUrlou un sous-domaine de celui-ci. (WebAuthn exige querpIdsoit un suffixe enregistrable de chaque origine.)
Lorsque la validation échoue, vous verrez une erreur attribuée à la source comme 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.
L’endroit où l’erreur apparaît dépend de l’endroit où les valeurs sont déclarées :
- Au démarrage d’Astro, lorsque
config.allowedOriginsetconfig.siteUrlproviennent tous deux deastro.config.mjs— les fautes de frappe dans le code font échouer la construction. - À la première vérification de passkey, lorsque l’une ou l’autre valeur provient de
EMDASH_ALLOWED_ORIGINSouEMDASH_SITE_URL— les désaccords d’environnement apparaissent comme des 500 à la première tentative de vérification.
Configuration du proxy inverse
Astro ne reflète X-Forwarded-* que lorsque l’hôte public est autorisé. Configurez security.allowedDomains pour le nom d’hôte (et les schémas) que vos utilisateurs atteignent. Dans astro dev, ajoutez des vite.server.allowedHosts correspondants pour que Vite accepte l’en-tête Host du proxy.
Préférez d’abord corriger allowedDomains (et les en-têtes transférés) ; utilisez siteUrl lorsque l’URL reconstruite diverge toujours de l’origine du navigateur (typique lorsque TLS est terminé à l’avant et la requête en amont reste http://).
Avec TLS à l’avant, lier le serveur de développement à loopback (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 IP client, définissez trustedProxyHeaders afin que les limites de débit d’EmDash puissent utiliser l’IP client réelle au lieu de regrouper chaque requête sous une clé “unknown” partagée.
La configuration suivante définit allowedDomains, vite.server.allowedHosts et siteUrl ensemble pour un déploiement de 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’authentification (magic-link, inscription, passkey, flux de dispositif OAuth) et le point de terminaison de commentaire public.
Sur Cloudflare, l’objet cf attaché à la requête est utilisé automatiquement — vous n’avez normalement pas besoin de définir ceci. Sur les déploiements auto-hébergés derrière nginx, Caddy, Traefik, Fly, Railway ou similaires, définissez ceci sur l’en-tête que votre proxy écrit afin que les limites de débit puissent regrouper par IP client réelle au lieu de traiter chaque requête comme “unknown”.
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. L’exemple suivant préfère l’en-tête de Fly.io et se rabat sur x-forwarded-for :
emdash({
trustedProxyHeaders: ["fly-client-ip", "x-forwarded-for"],
});
Lorsqu’il n’est pas 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 de téléchargement de fichier média en octets. S’applique aux téléchargements multipart directs et aux téléchargements d’URL signée. Par défaut 52_428_800 (50 Mo). L’exemple suivant augmente 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 MB
});
| Valeur | Description |
|---|---|
number (octets) | Doit être un entier fini positif |
| omis | Par défaut 50 Mo |
Les téléchargements qui dépassent la limite configurée sont rejetés avec une réponse 413 Payload Too Large sur le chemin de téléchargement direct, ou une 400 Validation Error sur le chemin d’URL signée.
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. L’exemple suivant se connecte à un fichier local :
| Option | Type | Description |
|---|---|---|
url | string | Chemin de fichier avec préfixe file: |
sqlite({ url: "file:./data.db" });
libsql(config)
Base de données libSQL. L’exemple suivant se connecte à une base de données libSQL distante :
| Option | Type | Description |
|---|---|---|
url | string | URL de la base de données |
authToken | string | Jeton d’authentification (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 mise en commun 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 minimale du pool (défaut : 0) |
pool.max | number | Taille maximale du pool (défaut : 10) |
L’exemple suivant se connecte avec une chaîne de connexion :
postgres({ connectionString: process.env.DATABASE_URL });
d1(config)
Base de données Cloudflare D1. Importer depuis @emdash-cms/cloudflare.
| Option | Type | Défaut | Description |
|---|---|---|---|
binding | string | — | Nom de liaison D1 depuis wrangler.jsonc |
session | string | "disabled" | Mode de réplication en lecture : "disabled", "auto" ou "primary-first" |
bookmarkCookie | string | "__em_d1_bookmark" | Nom de cookie pour les signets de session |
L’exemple suivant montre une liaison de base et une avec des réplicas de lecture activés :
// Basique
d1({ binding: "DB" });
// Avec réplicas de lecture
d1({ binding: "DB", session: "auto" });
Lorsque session est "auto" ou "primary-first", EmDash utilise l’API Sessions D1 pour acheminer les requêtes de lecture vers des réplicas proches. Les utilisateurs authentifiés obtiennent une cohérence lecture-après-écriture basée sur des signets. Voir Options de base de données — Réplicas de lecture pour plus de détails.
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. L’exemple suivant sert les téléchargements depuis un répertoire 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)
Liaison Cloudflare R2. L’exemple suivant utilise une liaison R2 avec une URL publique :
| Option | Type | Description |
|---|---|---|
binding | string | Nom de liaison 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 lorsque le
processus Node démarre. Les valeurs explicites ont toujours la priorité.
Prérequis : installez @aws-sdk/client-s3 et @aws-sdk/s3-request-presigner
dans votre projet. EmDash core n’inclut pas le SDK AWS. Voir
Options de stockage : Stockage compatible S3
pour plus de détails.
| Option | Type | Description |
|---|---|---|
endpoint | string | URL de point de terminaison 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) |
Les exemples suivants résolvent tous les champs depuis l’environnement, mélangent configuration et environnement, ou passent chaque champ explicitement :
// Tous les champs depuis les variables d'environnement S3_* (déploiements de conteneurs Node)
s3()
// Mixte : CDN depuis la configuration, le reste depuis l'environnement
s3({ publicUrl: "https://cdn.example.com" })
// Tout explicite
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",
})
La résolution de variables d’environnement à l’exécution est une fonctionnalité Node uniquement. Sur Cloudflare
Workers, les secrets et variables sont exposés via le paramètre env du
gestionnaire fetch, pas via process.env, donc les variables d’environnement S3_*
ne sont pas récupérées. Les déploiements Workers doivent utiliser l’adaptateur r2(config)
ou passer des valeurs explicites à s3({...}). Voir
Options de stockage pour plus de détails.
Collections en direct
Configurez le chargeur 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 chargeur
La fonction emdashLoader() ne prend aucun argument :
emdashLoader();
Variables d’environnement
EmDash respecte ces variables d’environnement :
| Variable | Description |
|---|---|
EMDASH_SITE_URL | Origine publique orientée navigateur (se rabat sur 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-domaines). |
EMDASH_DATABASE_URL | Remplacer l’URL de la base de données |
EMDASH_ENCRYPTION_KEY | Clé pour chiffrer les secrets de 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 d’aperçu. Lorsqu’il n’est pas défini, une valeur stable par site est générée et stockée dans la base de données. |
EMDASH_IP_SALT | Remplacement optionnel pour le sel de hachage IP du commentateur. Lorsqu’il n’est pas défini, une valeur stable par site est générée et stockée dans la base de données. |
EMDASH_AUTH_SECRET | Hérité. Utilisé comme source de sel IP s’il est défini ; les installations existantes doivent conserver ceci pour préserver les hachages IP de commentateur stables lors des mises à niveau. |
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 de package.json
Les modèles 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 modèle pour l’affichage |
seed | Chemin vers le fichier JSON de semence |
url | URL distante pour la synchronisation de schéma |
Configuration TypeScript
EmDash génère des types dans .emdash/types.ts. Ajoutez un alias de chemin à votre tsconfig.json :
{
"compilerOptions": {
"paths": {
"@emdash-cms/types": ["./.emdash/types.ts"]
}
}
}
Générez des types avec la commande suivante :
npx emdash types