EmDash se configura a través de dos archivos: astro.config.mjs para la integración y src/live.config.ts para las colecciones de contenido.
Integración de Astro
Configura EmDash como una integración de Astro:
import { defineConfig } from "astro/config";
import emdash, { local, r2, s3 } from "emdash/astro";
import { sqlite, libsql, d1 } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
plugins: [],
}),
],
});
Opciones de integración
database
Obligatorio. Configuración del adaptador de base de datos.
// 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 (importar desde @emdash-cms/cloudflare)
database: d1({ binding: "DB" });
Consulta Opciones de base de datos para más detalles.
storage
Obligatorio. Configuración del adaptador de almacenamiento multimedia.
// Sistema de archivos local (desarrollo)
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
// Binding R2 (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
});
Consulta Opciones de almacenamiento para más detalles.
plugins
Opcional. Array de plugins de EmDash.
import seoPlugin from "@emdash-cms/plugin-seo";
plugins: [seoPlugin()];
auth
Opcional. Configuración de autenticación.
auth: {
// Configuración de auto-registro
selfSignup: {
domains: ["example.com"],
defaultRole: 20, // Contributor
},
// Proveedores OAuth
oauth: {
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
},
// Configuración de sesión
session: {
maxAge: 30 * 24 * 60 * 60, // 30 días
sliding: true, // Renovar caducidad con actividad
},
// O usar Cloudflare Access (modo exclusivo)
cloudflareAccess: {
teamDomain: "myteam.cloudflareaccess.com",
audience: "your-app-audience-tag",
autoProvision: true,
defaultRole: 30,
syncRoles: false,
roleMapping: {
"Admins": 50,
"Editors": 40,
},
},
}
auth.selfSignup
Permite que los usuarios se auto-registren si su dominio de correo está permitido.
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
domains | string[] | [] | Dominios de correo permitidos |
defaultRole | number | 20 | Rol para auto-registros |
selfSignup: {
domains: ["example.com", "acme.org"],
defaultRole: 20, // Contributor
}
auth.oauth
Configura proveedores de inicio de sesión OAuth.
oauth: {
github: {
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
},
}
auth.session
Configuración de sesión.
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
maxAge | number | 2592000 (30d) | Duración de la sesión en segundos |
sliding | boolean | true | Renovar caducidad con actividad |
auth.cloudflareAccess
Usa Cloudflare Access como proveedor de autenticación en lugar de passkeys.
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
teamDomain | string | obligatorio | Tu dominio de equipo de Access |
audience | string | obligatorio | Etiqueta Application Audience (AUD) |
autoProvision | boolean | true | Crear usuarios en el primer inicio de sesión |
defaultRole | number | 30 | Rol por defecto para nuevos usuarios |
syncRoles | boolean | false | Actualizar rol en cada inicio de sesión |
roleMapping | object | — | Mapear grupos del IdP a roles |
siteUrl
Opcional. El origen público orientado al navegador del sitio (esquema + host + puerto opcional, sin ruta).
Detrás de un proxy inverso con terminación TLS, Astro.url devuelve la dirección interna (http://localhost:4321) en lugar de la pública (https://cms.example.com). Esto rompe los passkeys, la coincidencia de origen CSRF, las redirecciones OAuth, las redirecciones de inicio de sesión, el descubrimiento MCP, las exportaciones de snapshots, el sitemap, robots.txt y los datos estructurados JSON-LD. Establece siteUrl para corregir todo esto de una vez.
La integración valida este valor al cargar: debe ser una URL válida con protocolo http: o https: y se normaliza a origin (la ruta se elimina).
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
});
Cuando siteUrl no está configurado, EmDash comprueba las variables de entorno en orden: EMDASH_SITE_URL, luego SITE_URL. Esto es útil para despliegues en contenedor donde la URL pública se define en tiempo de ejecución.
Configuración del proxy inverso
Astro solo refleja X-Forwarded-* cuando el host público está permitido. Configura security.allowedDomains para el nombre de host (y los esquemas) que usan tus usuarios. En astro dev, añade vite.server.allowedHosts correspondiente para que Vite acepte la cabecera Host del proxy.
Es preferible corregir allowedDomains (y las cabeceras reenviadas) primero; usa siteUrl cuando la URL reconstruida aún diverja del origen del navegador (habitual cuando TLS termina delante y la petición upstream sigue siendo http://).
Con TLS delante, vincular el servidor de desarrollo al loopback (astro dev --host 127.0.0.1) suele ser suficiente: el proxy se conecta localmente mientras siteUrl coincide con el origen HTTPS público.
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",
}),
],
});
Adaptadores de base de datos
Importar desde emdash/db:
import { sqlite, libsql, postgres, d1 } from "emdash/db";
sqlite(config)
Base de datos SQLite usando better-sqlite3.
| Opción | Tipo | Descripción |
|---|---|---|
url | string | Ruta del archivo con prefijo file: |
sqlite({ url: "file:./data.db" });
libsql(config)
Base de datos libSQL.
| Opción | Tipo | Descripción |
|---|---|---|
url | string | URL de la base de datos |
authToken | string | Token de autenticación (opcional para archivos locales) |
libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
postgres(config)
Base de datos PostgreSQL con pool de conexiones.
| Opción | Tipo | Descripción |
|---|---|---|
connectionString | string | URL de conexión PostgreSQL |
host | string | Host de la base de datos |
port | number | Puerto de la base de datos |
database | string | Nombre de la base de datos |
user | string | Usuario de la base de datos |
password | string | Contraseña de la base de datos |
ssl | boolean | Habilitar SSL |
pool.min | number | Tamaño mínimo del pool (por defecto: 0) |
pool.max | number | Tamaño máximo del pool (por defecto: 10) |
postgres({ connectionString: process.env.DATABASE_URL });
d1(config)
Base de datos Cloudflare D1. Importar desde @emdash-cms/cloudflare.
| Opción | Tipo | Por defecto | Descripción |
|---|---|---|---|
binding | string | — | Nombre del binding D1 en wrangler.jsonc |
session | string | "disabled" | Modo de replicación de lectura: "disabled", "auto" o "primary-first" |
bookmarkCookie | string | "__ec_d1_bookmark" | Nombre de la cookie para bookmarks de sesión |
// Básico
d1({ binding: "DB" });
// Con réplicas de lectura
d1({ binding: "DB", session: "auto" });
Cuando session es "auto" o "primary-first", EmDash usa la API de sesiones D1 para enrutar consultas de lectura a réplicas cercanas. Los usuarios autenticados obtienen consistencia de lectura-tras-escritura basada en bookmarks. Consulta Opciones de base de datos — Réplicas de lectura para más detalles.
Adaptadores de almacenamiento
Importar desde emdash/astro:
import emdash, { local, r2, s3 } from "emdash/astro";
local(config)
Almacenamiento en sistema de archivos local.
| Opción | Tipo | Descripción |
|---|---|---|
directory | string | Ruta del directorio |
baseUrl | string | URL base para servir archivos |
local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
r2(config)
Binding de Cloudflare R2.
| Opción | Tipo | Descripción |
|---|---|---|
binding | string | Nombre del binding R2 |
publicUrl | string | URL pública opcional |
r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev",
});
s3(config?)
Almacenamiento compatible con S3. Todos los campos de configuración son opcionales: cualquier campo omitido en s3({...}) se resuelve desde la variable de entorno S3_* correspondiente cuando el proceso Node se inicia. Los valores explícitos siempre tienen prioridad.
Prerequisito: instala @aws-sdk/client-s3 y @aws-sdk/s3-request-presigner en tu proyecto. El núcleo de EmDash no incluye el AWS SDK. Consulta Opciones de almacenamiento → Almacenamiento compatible con S3 para más detalles.
| Opción | Tipo | Descripción |
|---|---|---|
endpoint | string | URL del endpoint S3 (S3_ENDPOINT) |
bucket | string | Nombre del bucket (S3_BUCKET) |
accessKeyId | string | Clave de acceso (S3_ACCESS_KEY_ID) |
secretAccessKey | string | Clave secreta (S3_SECRET_ACCESS_KEY) |
region | string | Región, por defecto "auto" (S3_REGION) |
publicUrl | string | URL CDN opcional (S3_PUBLIC_URL) |
// 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 (unchanged from before)
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 resolución de variables de entorno en tiempo de ejecución es una funcionalidad exclusiva de Node. En Cloudflare Workers, los secretos y variables se exponen a través del parámetro env del manejador fetch, no a través de process.env, por lo que las variables de entorno S3_* no se leen. Los despliegues en Workers deben usar el adaptador r2(config) o pasar valores explícitos a s3({...}). Consulta Opciones de almacenamiento para más detalles.
Live Collections
Configura el cargador de EmDash en src/live.config.ts:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
Opciones del cargador
La función emdashLoader() acepta configuración opcional:
emdashLoader({
// Actualmente sin opciones - reservado para uso futuro
});
Variables de entorno
EmDash respeta estas variables de entorno:
| Variable | Descripción |
|---|---|
EMDASH_SITE_URL | Origen público orientado al navegador (recurre a SITE_URL) |
EMDASH_DATABASE_URL | Sobrescribir la URL de la base de datos |
EMDASH_AUTH_SECRET | Secreto para autenticación con passkey |
EMDASH_PREVIEW_SECRET | Secreto para generación de tokens de vista previa |
EMDASH_URL | URL remota de EmDash para sincronización del esquema |
Genera un secreto de autenticación con:
npx emdash auth secret
Configuración en package.json
Configuración opcional en package.json:
{
"emdash": {
"label": "My Blog Template",
"description": "A clean, minimal blog template",
"seed": ".emdash/seed.json",
"url": "https://my-site.pages.dev",
"preview": "https://emdash-blog.pages.dev"
}
}
| Opción | Descripción |
|---|---|
label | Nombre de la plantilla para mostrar |
description | Descripción de la plantilla |
seed | Ruta al archivo JSON seed |
url | URL remota para sincronización del esquema |
preview | URL del sitio de demo para vista previa |
Configuración de TypeScript
EmDash genera tipos en .emdash/types.ts. Añade a tu tsconfig.json:
{
"compilerOptions": {
"paths": {
"@emdash-cms/types": ["./.emdash/types.ts"]
}
}
}
Genera tipos con:
npx emdash types