Referencia de configuración

En esta página

La configuración principal de EmDash se encuentra en astro.config.mjs, mientras que src/live.config.ts registra el cargador de contenido. Los valores específicos del despliegue también pueden provenir de variables de entorno. Un pequeño bloque de metadatos en package.json admite etiquetas de plantilla y flujos locales antiguos de la CLI.

Integración de Astro

Configura EmDash como una integración de Astro en 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: [],
		}),
	],
});

Opciones de la integración

database

Obligatoria. Configuración del adaptador de base de datos. Elige un adaptador:

// 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" });

Consulta Opciones de base de datos para más detalles.

migrations

Opcional. Controla cómo se gestionan en tiempo de ejecución las migraciones internas de base de datos de EmDash. Si se omite esta opción, el valor predeterminado es { runtime: "auto" }.

migrations: {
	runtime: "check", // "auto" | "check" | "manual"
	dev: "auto",     // optional development override
}

auto comprueba y aplica las migraciones pendientes, check devuelve 503 cuando hay migraciones pendientes conocidas por la compilación en ejecución (excepto en las solicitudes al endpoint de imágenes que instala EmDash) y manual no realiza ninguna consulta de migración en tiempo de ejecución. EMDASH_MIGRATIONS_MODE sobrescribe el modo de ejecución efectivo. Consulta Gestionar las migraciones de la base de datos del núcleo antes de adoptar check o manual.

storage

Opcional. Configuración del adaptador de almacenamiento de medios. Cuando se omite esta opción, EmDash guarda los archivos en ./.emdash/uploads y los sirve a través de /_emdash/api/media/file. Elige un adaptador cuando el directorio local predeterminado no sea adecuado:

// 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
});

Consulta Opciones de almacenamiento para más detalles.

images

Opcional. Controla si EmDash integra los medios almacenados con la optimización de imágenes de Astro. El valor predeterminado es true.

Cuando está habilitada, EmDash envuelve el endpoint de imágenes de Astro para que <Image> y getImage() puedan leer los bytes de origen directamente del adaptador de almacenamiento configurado. Esto también funciona cuando la URL original del medio está detrás de Cloudflare Access. Las solicitudes al endpoint envuelto omiten el arranque en tiempo de ejecución de EmDash, por lo que en ellas locals.emdash solo contiene storage, además de db para un usuario con sesión iniciada. locals.user se establece igual que en otras rutas públicas. Establece images: false cuando otro servicio de imágenes gestione los medios o cuando cada imagen deba renderizarse sin el envoltorio de endpoint de EmDash.

emdash({
	images: false,
});

mediaProviders

Opcional. Añade servicios de medios a la biblioteca de medios. El proveedor local respaldado por el almacenamiento sigue disponible automáticamente; cada descriptor de este array añade otro lugar donde los editores pueden explorar o subir medios.

El siguiente ejemplo añade Cloudflare Images y Cloudflare Stream:

import { cloudflareImages, cloudflareStream } from "@emdash-cms/cloudflare";

emdash({
	mediaProviders: [cloudflareImages({}), cloudflareStream({})],
});

Las credenciales de los proveedores se resuelven en tiempo de ejecución. Las configuraciones vacías anteriores usan las variables de entorno predeterminadas de Cloudflare descritas en las secciones de adaptadores cloudflareImages(config) y cloudflareStream(config). Consulta Biblioteca de medios: Proveedores de medios para la configuración de bindings y de renderizado.

objectCache

Opcional. Almacena en caché los resultados de las consultas de contenido y configuración en un almacén clave-valor, de modo que las lecturas se sirvan sin consultar la base de datos en cada solicitud. Está deshabilitada si se omite. Elige un adaptador:

// 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();

Consulta Caché de objetos para la configuración y las opciones.

middleware.outer

Opcional. Registra un módulo de middleware de Astro fuera de toda la pila de middleware de EmDash. Como la integración lo registra con order: "pre" de Astro, también se ejecuta antes del middleware definido en src/middleware.ts. Úsalo para filtros de solicitudes o cachés de respuesta completa que deban evitar la inicialización del entorno de ejecución y de la base de datos cuando hay un acierto, o para encabezados de respuesta que dependan del HTML final de EmDash.

emdash({
	middleware: {
		outer: "./src/outer-middleware.ts",
	},
});

El orden de ejecución es:

  1. El middleware exterior se ejecuta hasta await next().
  2. EmDash inicializa su entorno de ejecución y su base de datos, y luego ejecuta los middleware de configuración, autenticación y contexto de la solicitud.
  3. Se renderiza la ruta de Astro.
  4. EmDash aplica las modificaciones de la respuesta, incluidos el HTML de edición visual y los encabezados de seguridad y de temporización.
  5. next() se resuelve en el middleware exterior con esa respuesta final.

Antes de llamar a next(), el middleware dispone de la solicitud normal de Astro y del contexto de ejecución de la plataforma, pero locals.emdash, locals.user, la base de datos y el estado de EmDash ligado a la solicitud no están disponibles. Una Response anticipada omite EmDash por completo, así que debe incluir los encabezados de seguridad y de caché que necesite. Una vez resuelto next(), es seguro finalizar los nonces de CSP, almacenar en caché el cuerpo completo o establecer Content-Length. Si el middleware cambia el cuerpo, elimina o recalcula cualquier encabezado Content-Length existente.

El hook usa la API de middleware de Astro tanto en Node como en Cloudflare. Este ejemplo mínimo con la Cache API de Cloudflare solo almacena en caché respuestas HTML anónimas y devuelve los aciertos antes de la inicialización de EmDash:

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;
});

En Node, usa la misma forma de middleware con una caché compatible con Node, como Redis. Las claves de caché y las reglas de omisión deben incluir toda propiedad de la solicitud que cambie la respuesta renderizada.

playground

Opcional. Habilita el middleware que usan los playgrounds de EmDash desechables basados en el navegador. Crea una base de datos de Durable Object con permiso de escritura para cada sesión, aplica la semilla configurada e inicia sesión del visitante como administrador anónimo antes de que se ejecute el middleware normal de EmDash.

import { playgroundDatabase } from "@emdash-cms/cloudflare";

emdash({
	database: playgroundDatabase({ binding: "PLAYGROUND_DB" }),
	playground: {
		middlewareEntrypoint: "@emdash-cms/cloudflare/db/playground-middleware",
	},
});

Este modo requiere @emdash-cms/cloudflare y un binding de Durable Object. Omite el middleware normal de configuración y autenticación, así que úsalo solo para sitios de demostración efímeros y no para un CMS en producción.

plugins

Opcional. Array de plugins que se ejecutan en el mismo proceso que el sitio de Astro. Los plugins nativos van aquí. Un plugin compatible con el sandbox también puede ejecutarse aquí cuando confías en él con acceso completo al proceso y no necesitas aislamiento.

El siguiente ejemplo registra un plugin nativo:

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

plugins: [seoPlugin()];

Los plugins nativos pueden usar directamente las API del servidor y del framework, por lo que no se pueden mover a sandboxed a menos que el paquete también ofrezca un punto de entrada de plugin compatible con el sandbox. Consulta Elegir un formato de plugin para ver las diferencias de desarrollo y despliegue.

sandboxed

Opcional. Array de plugins compatibles con el sandbox que usan las API de plugin declaradas de EmDash y se ejecutan en entornos aislados. No coloques aquí un plugin nativo: el código nativo puede depender de un acceso al proceso y al framework que el sandbox no ofrece.

import thirdPartyPlugin from "third-party-emdash-plugin";
import { sandbox } from "@emdash-cms/cloudflare";

emdash({
	sandboxed: [thirdPartyPlugin()],
	sandboxRunner: sandbox(),
});

Los plugins en sandbox se omiten cuando no hay configurado un ejecutor de sandbox utilizable. Consulta Sandbox de plugins para la configuración del ejecutor en Cloudflare y Node.js.

sandboxRunner

Opcional. Especificador de módulo de la fábrica que inicia los entornos de ejecución aislados de los plugins. Es obligatorio para los plugins de sandboxed y para los plugins del marketplace o del registro.

En Cloudflare Workers, usa el adaptador sandbox():

import { sandbox } from "@emdash-cms/cloudflare";

emdash({
	sandboxRunner: sandbox(),
});

Los despliegues en Node.js usan el módulo ejecutor workerd documentado en Sandbox de plugins: Node.js.

sandbox

Opcional. Controla si un ejecutor de sandbox configurado aísla los plugins. El sandbox está habilitado cuando se configura sandboxRunner. Establece sandbox: false solo para diagnosticar si un problema proviene de un plugin o de su entorno de ejecución en sandbox:

emdash({
	sandboxRunner: sandbox(),
	sandbox: false,
});

Con false, los plugins declarados en sandboxed y los instalados desde el marketplace se ejecutan en el proceso principal del servidor sin aislamiento ni límites de recursos. Restablece el sandbox después del diagnóstico.

registry

Opcional. Configura el agregador y la política del registro de plugins. Sin un valor explícito, EmDash usa https://registry.emdashcms.com cuando sandboxRunner está configurado y sandbox no es false.

Establece registry: false para deshabilitar el descubrimiento en el registro y los plugins instalados desde el registro, manteniendo disponible el ejecutor de sandbox para los plugins declarados en sandboxed y los plugins antiguos del Marketplace:

emdash({
	sandboxRunner: sandbox(),
	registry: false,
});

Pasa la URL del servicio del registro como cadena, o usa un objeto cuando el sitio necesite fuentes de moderación o una política de antigüedad de versiones. El siguiente ejemplo usa la forma de objeto:

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"],
		},
	},
});
OpciónTipoDescripción
aggregatorUrlstringURL base del servicio del registro. Usa HTTPS en producción.
acceptLabelersstringIdentificadores descentralizados (DID) opcionales, separados por comas, de los servicios de moderación que acepta la solicitud. Un DID es un identificador de cuenta estable de Atmosphere. Este ajuste no puede anular la política del servicio del registro.
policy.minimumReleaseAgestring | numberRetiene las versiones más recientes que esta antigüedad. Cadena de duración ("48h", "7d") o segundos.
policy.minimumReleaseAgeExcludestring[]DID de editores, o pares <did>/<plugin-slug>, exentos de la retención.

La política de antigüedad de versiones solo exime la primera versión de un paquete cuando el registro informa de una versión conservada y confirma que observó el paquete de forma continua. Un paquete rellenado a posteriori, una versión anterior eliminada o la falta de pruebas del historial mantienen la retención en vigor. Las exenciones explícitas de editores y paquetes se aplican con independencia del historial.

Consulta El registro de plugins para el flujo de instalación y el modelo de confianza.

marketplace

Obsoleta. URL base que se usa para actualizar los plugins instalados desde el Marketplace antiguo. La exploración del Marketplace y las nuevas instalaciones no se muestran en la administración. Los plugins existentes del Marketplace siguen siendo actualizables y desinstalables mientras esta opción esté configurada.

emdash({
	marketplace: "https://marketplace.emdashcms.com",
	sandboxRunner: sandbox(),
});

Las URL de producción deben usar HTTPS; HTTP solo se acepta para localhost y 127.0.0.1 durante el desarrollo. Conserva esta opción hasta que cada plugin del Marketplace se haya sustituido o desinstalado y luego elimínala. Sigue Migrar desde Marketplace para el procedimiento completo.

fonts

Opcional. Configuración de la tipografía de la interfaz de administración.

De forma predeterminada, EmDash carga Noto Sans mediante la API de fuentes de Astro. Las fuentes se descargan de Google en el momento de la compilación y se alojan por cuenta propia, por lo que no hay solicitudes a un CDN en tiempo de ejecución. La fuente base cubre los sistemas de escritura latino, cirílico, griego, devanagari y vietnamita.

Para añadir compatibilidad con otros sistemas de escritura, pasa nombres de scripts. El siguiente ejemplo añade árabe y japonés:

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

Los scripts disponibles son 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 y tibetan.

Cada script se corresponde con la variante de Noto Sans respectiva en Google Fonts (p. ej., "arabic" carga Noto Sans Arabic). Todas las variantes de fuente comparten un único nombre de font-family y usan unicode-range, de modo que el navegador solo descarga los archivos que necesita para los caracteres de la página.

Establécela en false para deshabilitar por completo la inyección de fuentes y usar las fuentes del sistema:

emdash({
	fonts: false,
})

El CSS de la administración usa la variable CSS --font-emdash. Esta se establece automáticamente mediante la configuración de fuentes anterior.

auth

Opcional. Un adaptador de autenticación. El inicio de sesión integrado de EmDash usa passkeys; al establecer auth se sustituyen por un proveedor externo. El adaptador de Cloudflare Access, access(), lo proporciona @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,
		},
	}),
});

Opciones de access():

OpciónTipoValor predeterminadoDescripción
teamDomainstringobligatoriaEl dominio de equipo de Cloudflare Access
audiencestring—Etiqueta de audiencia (AUD) de la aplicación. En Workers, prefiere audienceEnvVar.
audienceEnvVarstring"CF_ACCESS_AUDIENCE"Variable de entorno de la que se lee la etiqueta de audiencia en tiempo de ejecución
autoProvisionbooleantrueCrea un usuario de EmDash en el primer inicio de sesión
defaultRolenumber30Nivel de rol para los usuarios que no coincidan con roleMapping (consulta Roles de usuario)
syncRolesbooleanfalseVuelve a aplicar roleMapping en cada inicio de sesión en lugar de solo al aprovisionar
roleMappingobject—Asigna nombres de grupos del IdP a niveles de rol de EmDash; gana la primera coincidencia

authProviders

Opcional. Un array de proveedores de inicio de sesión conectables (de nivel superior, junto a auth). Cada entrada es el resultado de llamar a una fábrica de proveedor, como se muestra a continuación:

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()],
});

Proveedores integrados:

  • github() — lee EMDASH_OAUTH_GITHUB_CLIENT_ID / EMDASH_OAUTH_GITHUB_CLIENT_SECRET (o alternativas sin prefijo).
  • google() — lee EMDASH_OAUTH_GOOGLE_CLIENT_ID / EMDASH_OAUTH_GOOGLE_CLIENT_SECRET.
  • microsoft() — lee EMDASH_OAUTH_MICROSOFT_CLIENT_ID / EMDASH_OAUTH_MICROSOFT_CLIENT_SECRET / EMDASH_OAUTH_MICROSOFT_TENANT_ID (o alternativas sin prefijo). Acepta { emailVerified }. Consulta Microsoft.
  • atproto() — inicio de sesión con una cuenta de Atmosphere (Bluesky y el resto de la red AT Protocol). No necesita variables de entorno. Acepta { allowedDIDs, allowedHandles, defaultRole }. Consulta la guía de inicio de sesión con Atmosphere.

Los paquetes de terceros pueden registrar sus propios proveedores con la misma forma AuthProviderDescriptor; consulta Proveedores de inicio de sesión.

mcp

Opcional. Habilita el endpoint del Model Context Protocol (MCP) en /_emdash/api/mcp. El endpoint está habilitado de forma predeterminada y requiere un token de portador, por lo que habilitarlo no concede acceso anónimo.

Establece la opción en false cuando el sitio no deba exponer un endpoint MCP:

emdash({
	mcp: false,
});

Consulta la Referencia del servidor MCP para la creación de tokens y la configuración de clientes.

siteUrl

El origen público del sitio, visible para el navegador (esquema + host + puerto opcional, sin ruta). Establécelo antes de ejecutar la configuración de producción, a menos que el sitio se despliegue en Cloudflare Workers. De lo contrario, solo los hosts de desarrollo de loopback pueden completar la configuración sin un origen configurado.

Detrás de un proxy inverso que termina TLS, Astro.url devuelve la dirección interna (http://localhost:4321) en lugar de la pública (https://cms.example.com). Esto rompe las passkeys, la comprobación de origen de CSRF, las redirecciones de OAuth, las redirecciones de inicio de sesión, el descubrimiento de MCP, las exportaciones de instantáneas, el sitemap, robots.txt y los datos estructurados JSON-LD. Establecer siteUrl soluciona todo esto de una vez.

La integración valida este valor al cargarse: debe ser una URL válida con protocolo http: o https: y se normaliza a origen (se elimina la ruta).

El siguiente ejemplo establece el origen público:

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á establecida en la configuración, EmDash comprueba las variables de entorno en este orden: EMDASH_SITE_URL y luego SITE_URL. Esto es útil en despliegues con contenedores, donde la URL pública se establece en tiempo de ejecución.

La configuración falla con SITE_URL_REQUIRED en un host que no sea de loopback cuando no se establece ninguna de las dos fuentes. Esto impide que la primera solicitud de configuración no autenticada elija el origen que se usará en los correos de autenticación posteriores. Una compilación de producción desplegada en Cloudflare Workers es la excepción: Cloudflare solo enruta las solicitudes de los nombres de host vinculados al Worker, por lo que la configuración registra https:// y el nombre de host en el que se ejecuta. La misma compilación servida con wrangler dev, astro preview o workerd autoalojado en una dirección accesible no ofrece esa garantía; establece allí primero siteUrl o EMDASH_SITE_URL.

En Cloudflare Workers, la alternativa mediante variables de entorno lee process.env. Con nodejs_compat, Cloudflare rellena process.env de forma predeterminada para fechas de compatibilidad a partir del 2025-04-01. Los proyectos fijados a una fecha anterior también deben añadir nodejs_compat_populate_process_env.

// wrangler.jsonc
{
	"compatibility_date": "2026-02-24",
	"compatibility_flags": ["nodejs_compat"],
	"vars": { "EMDASH_SITE_URL": "https://cms.example.com" },
}

allowedOrigins

Opcional. Orígenes adicionales del navegador que acepta la verificación de passkeys para un despliegue disponible en más de un nombre de host.

siteUrl define un único origen canónico. Cuando el mismo despliegue de EmDash es accesible bajo varios nombres de host que comparten un dominio padre registrable (p. ej., https://example.com y https://preview.example.com), la verificación de passkeys rechaza las aserciones cuyo origen no coincide exactamente con siteUrl, aunque WebAuthn permite que las passkeys sean válidas entre subdominios bajo el mismo rpId.

Declara los orígenes adicionales aceptados con allowedOrigins en astro.config.mjs o con la variable de entorno EMDASH_ALLOWED_ORIGINS. La siteUrl canónica sigue siendo la fuente del rpId; las entradas indicadas aquí se aceptan en el momento de la verificación. Las dos fuentes se combinan en tiempo de ejecución, de modo que la configuración puede declarar los orígenes estables (versionados y revisados en código) mientras que el entorno añade extras específicos del entorno (p. ej., vistas previas efímeras de PR).

El siguiente ejemplo declara un origen adicional en la configuración:

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

Los valores equivalentes también pueden provenir de variables de entorno:

EMDASH_SITE_URL=https://example.com
EMDASH_ALLOWED_ORIGINS=https://preview.example.com,https://staging.example.com
Validación

EmDash valida estos valores para evitar configuración inútil que el navegador nunca respetaría:

  • Cada entrada debe ser una URL http: o https: analizable, sin punto final y sin etiquetas vacías en el nombre de host.
  • Cuando allowedOrigins no está vacío, siteUrl debe estar establecida (en cualquiera de las dos fuentes) y no puede ser un literal de IP ni tener un nombre de host con punto final.
  • Cada origen debe ser el mismo nombre de host que siteUrl o un subdominio de este. (WebAuthn exige que rpId sea un sufijo registrable de cada origen.)

Cuando la validación falla, verás un error con la fuente indicada, como 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.

Dónde aparece el error depende de dónde se declaren los valores:

  • Al iniciar Astro, cuando tanto config.allowedOrigins como config.siteUrl provienen de astro.config.mjs: los errores tipográficos en el código hacen fallar la compilación.
  • En la primera verificación de passkey, cuando alguno de los valores proviene de EMDASH_ALLOWED_ORIGINS o EMDASH_SITE_URL: las discrepancias del entorno aparecen como errores 500 en el primer intento de verificación.

Configuración de un 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) a los que acceden tus usuarios. En astro dev, añade los vite.server.allowedHosts correspondientes para que Vite acepte el encabezado Host del proxy.

Prefiere corregir primero allowedDomains (y los encabezados reenviados); usa siteUrl cuando la URL reconstruida siga divergiendo del origen del navegador (típico cuando TLS se termina por delante y la solicitud upstream sigue siendo http://).

Con TLS por delante, enlazar el servidor de desarrollo a loopback (astro dev --host 127.0.0.1) suele bastar: el proxy se conecta localmente mientras siteUrl coincide con el origen HTTPS público.

Si tu proxy escribe un encabezado con la IP del cliente, establece trustedProxyHeaders para que los límites de tasa de EmDash puedan usar la IP real del cliente. Sin una IP de cliente de confianza, se omiten los límites de tasa de autenticación y todos los envíos públicos de comentarios comparten un único contenedor de límite de tasa (20 comentarios cada 10 minutos entre todos los visitantes).

La siguiente configuración establece juntos allowedDomains, vite.server.allowedHosts y siteUrl para un despliegue detrás de un proxy inverso:

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

Opcional. Encabezados en los que confiar para resolver la IP del cliente cuando se ejecuta detrás de un proxy inverso que controlas. Lo usan los límites de tasa de autenticación (enlace mágico, registro, passkey, flujo de dispositivo de OAuth, registro de clientes de OAuth) y el endpoint público de comentarios.

En Cloudflare se usa automáticamente el objeto cf adjunto a la solicitud, por lo que normalmente no necesitas establecer esto. En despliegues autoalojados detrás de nginx, Caddy, Traefik, Fly, Railway o similares, establece aquí el encabezado que escribe tu proxy para que los límites de tasa puedan contar las solicitudes por IP real del cliente.

El siguiente ejemplo confía en el encabezado x-real-ip que establecen nginx, Caddy o Traefik:

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

Los encabezados se prueban por orden. Los valores que coinciden con *-forwarded-for se analizan como listas separadas por comas y se usa la primera entrada. El siguiente ejemplo prefiere el encabezado de Fly.io y recurre a x-forwarded-for:

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

Cuando no se establece en la configuración, EmDash lee la variable de entorno EMDASH_TRUSTED_PROXY_HEADERS (separada por comas). Un array vacío explícito en la configuración anula la variable de entorno.

maxUploadSize

Opcional. Tamaño máximo permitido, en bytes, para la subida de archivos multimedia. Se aplica tanto a las subidas multipart directas como a las subidas mediante URL firmada. El valor predeterminado es 52_428_800 (50 MB). El siguiente ejemplo sube el límite a 100 MB:

emdash({
	database: sqlite({ url: "file:./data.db" }),
	storage: local({
		directory: "./uploads",
		baseUrl: "/_emdash/api/media/file",
	}),
	maxUploadSize: 100 * 1024 * 1024, // 100 MB
});
ValorDescripción
number (bytes)Debe ser un entero finito positivo
omitidoPor defecto, 50 MB

Las subidas que superan el límite configurado se rechazan con una respuesta 413 Payload Too Large en la ruta de subida directa, o con 400 Validation Error en la ruta de URL firmada.

admin

Opcional. Sustituye la marca de EmDash en la interfaz de administración. Estos valores no cambian el título, el logotipo ni el favicon del sitio público.

emdash({
	admin: {
		logo: "/images/agency-logo.webp",
		siteName: "Agency CMS",
		footerLabel: "AgencyX",
		favicon: "/favicon.ico",
	},
});
OpciónTipoDescripción
logostringURL o ruta del logotipo para la página de inicio de sesión y la barra lateral
siteNamestringNombre que se muestra en la cabecera de la barra lateral y en el título del navegador
footerLabelstring | falseEtiqueta junto a la versión de la barra lateral. Por defecto "EmDash"; false la oculta
faviconstringURL o ruta del favicon para las páginas de administración

updateCheck

Opcional. Controla el aviso de actualización del núcleo en el panel de administración. El valor predeterminado es true.

Cuando está habilitada, EmDash consulta el registro público de npm en busca de versiones publicadas de emdash (como máximo una vez al día, diferida tras la respuesta para que nunca ralentice una solicitud) y muestra un banner descartable en el panel de administración cuando hay una versión más reciente. Al descartar el banner se oculta para esa versión; reaparece cuando haya disponible una versión más nueva.

El banner nombra la versión estable más reciente que lleva al menos 24 horas pública, de modo que una versión se anuncia un día después de publicarse. Para ajustarte a una política de antigüedad de versiones de tu gestor de paquetes, como minimumReleaseAge de pnpm, establece minimumReleaseAge como una cadena de duración ("48h", "7d") o un número de segundos. Con "7d", el banner ofrece la versión más reciente que tenga al menos una semana:

emdash({
	updateCheck: { minimumReleaseAge: "7d" },
})

La comprobación es un simple GET a https://registry.npmjs.org/emdash y no envía datos del sitio. Los despliegues sin acceso saliente a Internet simplemente nunca muestran el banner. Establécela en false para deshabilitar la comprobación por completo:

emdash({
	updateCheck: false,
})

El banner es solo informativo: actualizar EmDash significa subir la versión del paquete emdash en tu proyecto y volver a desplegar, algo que la administración no puede (ni debería) desencadenar.

toolbar

Opcional. Controla cómo se entrega la barra de herramientas del editor (la píldora flotante en las páginas públicas). El valor predeterminado es "server".

ValorComportamiento
"server" (predeterminado)La barra de herramientas se inyecta en el servidor en cada respuesta HTML renderizada para un editor autenticado.
"client"El HTML público es idéntico para todos los visitantes. Un diminuto script de arranque muestra una píldora «Edit» en los navegadores que han iniciado sesión en la administración; al hacer clic en ella se verifica la sesión y se recarga la página con un parámetro de consulta _edit, que siempre se renderiza al momento (nunca desde la caché) con la barra de herramientas completa.
falseNunca renderiza la barra de herramientas ni el script de arranque.
emdash({
	toolbar: "client",
})

Usa "client" cuando tu HTML público se sirva a través de una caché compartida (Cloudflare Cache Everything / Workers Cache, Fastly, Varnish, …). Con la inyección en el servidor, un editor que navega por el sitio público recibe la variante anónima en caché —sin la barra de herramientas— siempre que un visitante anónimo haya llenado antes la caché, de modo que la barra de herramientas aparece y desaparece según el estado de la caché. En modo cliente no se inyecta nada específico de la sesión en el HTML compartible, por lo que la caché sigue siendo plenamente eficaz y la barra de herramientas es fiable.

Notas sobre el modo "client":

  • Los visitantes sin sesión que abren una URL ?_edit compartida son redirigidos a la URL canónica, de modo que el parámetro no puede filtrar borradores ni llenar entradas de caché adicionales con el contenido de la página.
  • La señal de «sesión iniciada» es una marca no secreta de localStorage que establece la administración; la píldora verifica la sesión real antes de entrar en la vista de edición.
  • El script de arranque es un pequeño <script> en línea. Si tu sitio envía un Content-Security-Policy estricto sin 'unsafe-inline', añade un hash para él; lo mismo se aplica a la barra de herramientas inyectada en el servidor.
  • EmDash no inyecta nada específico de la sesión, pero si tus propias plantillas se ramifican según Astro.locals.user (p. ej., un enlace de navegación «Admin» para usuarios con sesión iniciada), esa variación sigue estando en tu HTML y sigue fragmentando la caché.

En cualquier modo, la barra de herramientas puede descartarse en el navegador con su botón × (por navegador, hasta la próxima vez que un editor abra la administración). Las respuestas de vista previa y de modo de edición siempre se renderizan en el servidor con Cache-Control: private, no-store.

Adaptadores de base de datos

Importa los adaptadores desde emdash/db:

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

sqlite(config)

Base de datos SQLite que usa el controlador de base de datos integrado de Node.js. El siguiente ejemplo se conecta a un archivo local:

OpciónTipoDescripción
urlstringRuta del archivo con prefijo file:
sqlite({ url: "file:./data.db" });

libsql(config)

Base de datos libSQL. El siguiente ejemplo se conecta a una base de datos libSQL remota:

OpciónTipoDescripción
urlstringURL de la base de datos
authTokenstringToken de autenticación en tiempo de ejecución (opcional para archivos locales)
migrationAuthTokenEnvstringNombre de la variable del token de migración (por defecto TURSO_AUTH_TOKEN)
libsql({
	url: process.env.LIBSQL_DATABASE_URL,
	authToken: process.env.LIBSQL_AUTH_TOKEN,
});

postgres(config)

Base de datos PostgreSQL con pool de conexiones.

OpciónTipoDescripción
connectionStringstringURL de conexión de PostgreSQL
hoststringHost de la base de datos
portnumberPuerto de la base de datos
databasestringNombre de la base de datos
userstringUsuario de la base de datos
passwordstringContraseña de la base de datos
sslbooleanHabilita SSL
pool.minnumberTamaño mínimo del pool (por defecto: 0)
pool.maxnumberTamaño máximo del pool (por defecto: 10)
pool.connectionTimeoutMillisnumberEspera máxima de conexión (por defecto en pg: 0, sin tiempo de espera)
pool.idleTimeoutMillisnumberVida útil de un cliente inactivo (por defecto en pg: 10 000 ms)
migrationConnectionStringEnvstringNombre de la variable de la cadena de conexión de migración (por defecto DATABASE_URL)

El siguiente ejemplo se conecta con una cadena de conexión:

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

d1(config)

Base de datos Cloudflare D1. Se importa desde @emdash-cms/cloudflare.

OpciónTipoValor predeterminadoDescripción
bindingstring—Nombre del binding de D1 de wrangler.jsonc
sessionstring"disabled"Modo de replicación de lectura: "disabled", "auto" o "primary-first"
bookmarkCookiestring"__em_d1_bookmark"Nombre de la cookie para los marcadores de sesión
coalescebooleanfalseAgrupa las lecturas concurrentes en el mismo turno del bucle de eventos; requiere un modo de sesión distinto de "disabled"

El siguiente ejemplo muestra un binding básico y otro con réplicas de lectura habilitadas:

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

// With read replicas
d1({ binding: "DB", session: "auto" });

Cuando session es "auto" o "primary-first", EmDash usa la API de sesiones de D1 para dirigir las consultas de lectura a réplicas cercanas. Los usuarios autenticados obtienen consistencia de lectura de sus propias escrituras basada en marcadores. Consulta Opciones de base de datos — Réplicas de lectura para más detalles.

hyperdrive(config?)

PostgreSQL a través de un binding de Cloudflare Hyperdrive. Importa este adaptador desde @emdash-cms/cloudflare.

OpciónTipoValor predeterminadoDescripción
bindingstring"HYPERDRIVE"Binding principal de Hyperdrive con la caché de consultas deshabilitada
cachedBindingstring—Segundo binding opcional, con caché habilitada, para lecturas públicas anónimas
preferUncachedAfterWriteMsnumber60_000Durante cuánto tiempo las lecturas públicas usan el binding principal tras una escritura de contenido cuando se establece cachedBinding
migrationConnectionStringEnvstringDerivado del binding principalVariable de entorno que contiene la URL directa de PostgreSQL que usa emdash migrate
maxnumber5Máximo de conexiones de un aislado de Worker a Hyperdrive

El siguiente ejemplo dirige las solicitudes autenticadas y las escrituras por el binding sin caché, mientras que las lecturas públicas anónimas pueden usar el binding con caché:

hyperdrive({
	binding: "HYPERDRIVE",
	cachedBinding: "HYPERDRIVE_CACHED",
	preferUncachedAfterWriteMs: 60_000,
});

Ambos bindings deben apuntar a la misma base de datos. Instala pg versión 8.16.3 o posterior, habilita la bandera de compatibilidad nodejs_compat y configura una URL directa de base de datos para las migraciones del despliegue. Consulta Opciones de base de datos: Hyperdrive para la configuración completa del Worker y de las migraciones.

durableObjects(config)

Almacena el CMS en un único Durable Object respaldado por SQLite. Importa este adaptador desde @emdash-cms/cloudflare.

OpciónTipoValor predeterminadoDescripción
bindingstringobligatoriaBinding del espacio de nombres de Durable Object para la clase EmDashDB
namestring"emdash"Nombre del objeto singleton; cámbialo solo para aislar varias bases de datos detrás de un binding
sessionstring"disabled""auto" dirige las lecturas anónimas a réplicas y las escrituras al primario
bookmarkCookiestring"__em_do_bookmark"Cookie usada para la consistencia de lectura de las propias escrituras en el modo "auto"
durableObjects({ binding: "DB_DO", session: "auto" });

El enrutamiento a réplicas requiere las banderas de compatibilidad experimental y replica_routing, además de las entradas de la clase de Durable Object y de migración en wrangler.jsonc.

previewDatabase(config)

Crea una base de datos de instantánea aislada por cada sesión de vista previa en un Durable Object. La única opción es el nombre binding, obligatorio:

previewDatabase({ binding: "PREVIEW_DB" });

Este adaptador es para la infraestructura de vistas previas, no para la base de datos principal de un sitio en producción.

playgroundDatabase(config)

Crea una base de datos con permiso de escritura y rellenada con semilla por cada sesión de playground en un Durable Object. Combínalo con la opción de integración playground:

playgroundDatabase({ binding: "PLAYGROUND_DB" });

El binding obligatorio identifica el espacio de nombres de Durable Object del playground. Usa este adaptador solo para sitios de demostración desechables.

Adaptadores de almacenamiento

Importa local y s3 desde emdash/astro. El adaptador r2 se importa desde @emdash-cms/cloudflare:

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

local(config)

Almacenamiento en el sistema de archivos local. El siguiente ejemplo sirve las subidas desde un directorio local:

OpciónTipoDescripción
directorystringRuta del directorio
baseUrlstringURL base para servir los archivos
local({
	directory: "./uploads",
	baseUrl: "/_emdash/api/media/file",
});

r2(config)

Binding de Cloudflare R2. El siguiente ejemplo usa un binding de R2 con una URL pública:

OpciónTipoDescripción
bindingstringNombre del binding de R2
publicUrlstringURL 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 a partir de la variable de entorno S3_* correspondiente cuando se inicia el proceso de Node. Los valores explícitos siempre tienen prioridad.

Una vez combinados los valores de la configuración y del entorno, endpoint y bucket son obligatorios. Si se establece alguna de las credenciales, accessKeyId y secretAccessKey son ambas obligatorias. Los valores que faltan hacen fallar el arranque con el código de error MISSING_S3_CONFIG.

Requisito previo: instala @aws-sdk/client-s3 y @aws-sdk/s3-request-presigner en tu proyecto. El núcleo de EmDash no incluye el SDK de AWS. Consulta Opciones de almacenamiento: almacenamiento compatible con S3 para más detalles.

OpciónTipoDescripción
endpointstringURL del endpoint de S3 (S3_ENDPOINT)
bucketstringNombre del bucket (S3_BUCKET)
accessKeyIdstringClave de acceso (S3_ACCESS_KEY_ID)
secretAccessKeystringClave secreta (S3_SECRET_ACCESS_KEY)
regionstringRegión, por defecto "auto" (S3_REGION)
publicUrlstringURL de CDN opcional (S3_PUBLIC_URL)

Los siguientes ejemplos resuelven todos los campos desde el entorno, mezclan configuración y entorno, o pasan cada campo de forma explícita:

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

La resolución de variables de entorno en tiempo de ejecución es una función exclusiva de Node. En Cloudflare Workers, los secretos y las variables se exponen mediante el parámetro env del manejador fetch, no mediante process.env, por lo que las variables de entorno S3_* no se recogen. 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.

Adaptadores de caché de objetos

Pasa uno de estos a la opción objectCache.

kvCache(config)

Backend de Cloudflare KV, compartido entre todos los aislados. Se importa desde @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?)

Backend en proceso para Node.js y desarrollo. Se importa desde 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)
})

Consulta Caché de objetos para la configuración y el comportamiento.

Adaptadores de autenticación y sandbox

Estos adaptadores devuelven valores para las opciones de integración auth y sandboxRunner.

access(config)

Sustituye el inicio de sesión con passkeys integrado por la autenticación de Cloudflare Access. Impórtalo desde @emdash-cms/cloudflare y pasa el resultado a auth:

import { access } from "@emdash-cms/cloudflare";

emdash({
	auth: access({
		teamDomain: "myteam.cloudflareaccess.com",
		audienceEnvVar: "CF_ACCESS_AUDIENCE",
	}),
});

teamDomain es obligatorio. El adaptador puede leer la audiencia de la aplicación de audience o de la variable que indica audienceEnvVar; también acepta autoProvision, defaultRole, syncRoles y roleMapping. La opción auth documenta sus valores predeterminados y el comportamiento de los roles.

sandbox()

Selecciona Cloudflare Worker Loader como ejecutor del sandbox de plugins. Impórtalo desde @emdash-cms/cloudflare y pasa su valor de retorno a sandboxRunner:

import { sandbox } from "@emdash-cms/cloudflare";

emdash({
	sandboxRunner: sandbox(),
});

El sitio también necesita un binding de Worker Loader y el punto de entrada del puente de plugins. Consulta Sandbox de plugins: Cloudflare Workers para esos ajustes de despliegue.

Adaptadores de proveedores de medios

Pasa descriptores de proveedores de medios a mediaProviders. Ambos proveedores integrados de Cloudflare se importan desde @emdash-cms/cloudflare.

Cada opción *EnvVar siguiente nombra una variable de entorno. El proveedor la lee en este orden: un binding de Cloudflare Workers con ese nombre y, después, process.env en el adaptador de Node. La opción directa correspondiente (accountId, accountHash, apiToken) siempre tiene prioridad sobre ambos.

cloudflareImages(config)

Añade Cloudflare Images para explorar, subir, eliminar y entregar recursos de imagen.

OpciónTipoValor predeterminadoDescripción
accountIdstringDe CF_ACCOUNT_IDID de la cuenta de Cloudflare
accountIdEnvVarstring"CF_ACCOUNT_ID"Variable usada cuando se omite accountId
accountHashstringDe CF_IMAGES_ACCOUNT_HASHHash de la cuenta usado en las URL de entrega
accountHashEnvVarstring"CF_IMAGES_ACCOUNT_HASH"Variable usada cuando se omite accountHash
apiTokenstringDe CF_IMAGES_TOKENToken con permisos de lectura y edición de Cloudflare Images
apiTokenEnvVarstring"CF_IMAGES_TOKEN"Variable usada cuando se omite apiToken
deliveryDomainstringimagedelivery.netNombre de host personalizado para la entrega de imágenes
defaultVariantstring"public"Variante de imagen usada para mostrar
mediaProviders: [cloudflareImages({ defaultVariant: "public" })];

cloudflareStream(config)

Añade Cloudflare Stream para explorar, buscar, subir, eliminar y reproducir recursos de vídeo.

OpciónTipoValor predeterminadoDescripción
accountIdstringDe CF_ACCOUNT_IDID de la cuenta de Cloudflare
accountIdEnvVarstring"CF_ACCOUNT_ID"Variable usada cuando se omite accountId
apiTokenstringDe CF_STREAM_TOKENToken con permisos de lectura y edición de Cloudflare Stream
apiTokenEnvVarstring"CF_STREAM_TOKEN"Variable usada cuando se omite apiToken
customerSubdomainstringPredeterminado de CloudflareNombre de host personalizado para la entrega de Stream
controlsbooleantrueMuestra los controles del reproductor
autoplaybooleanfalseInicia la reproducción automáticamente
loopbooleanfalseRepite la reproducción
mutedbooleanfalse, o true con autoplaySilencia la reproducción
mediaProviders: [cloudflareStream({ controls: true })];

Consulta Biblioteca de medios: Proveedores de medios para los bindings y componentes de renderizado necesarios.

Colecciones en vivo

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() no recibe argumentos:

emdashLoader();

Variables de entorno

EmDash respeta estas variables de entorno:

VariableDescripción
EMDASH_SITE_URLOrigen público visible para el navegador (recurre a SITE_URL)
EMDASH_ALLOWED_ORIGINSLista separada por comas de orígenes adicionales que acepta la verificación de passkeys (despliegues con varios subdominios).
EMDASH_DATABASE_URLSobrescribe la URL de la base de datos
EMDASH_ENCRYPTION_KEYClave para cifrar en reposo los secretos de los plugins. La proporciona el operador y nunca se almacena en la base de datos.
EMDASH_PREVIEW_SECRETSobrescritura opcional del secreto HMAC de la vista previa. Si no se establece, se genera un valor estable por sitio y se almacena en la base de datos.
EMDASH_IP_SALTSobrescritura opcional de la sal del hash de IP de quienes comentan. Si no se establece, se genera un valor estable por sitio y se almacena en la base de datos.
EMDASH_AUTH_SECRETHeredada. Se usa como fuente de la sal de IP si está establecida; las instalaciones existentes deben conservarla para mantener hashes de IP de comentaristas estables al actualizar.
EMDASH_TURNSTILE_SECRET_KEYClave secreta de Cloudflare Turnstile (recurre a TURNSTILE_SECRET_KEY). Cuando se establece, los envíos de comentarios deben incluir un token de Turnstile válido; combínala con la propiedad turnstileSiteKey de <CommentForm>.
EMDASH_URLURL remota de EmDash para la sincronización del esquema

Genera una clave de cifrado con el siguiente comando:

npx emdash secrets generate

Configuración de package.json

Las plantillas y los sitios pueden declarar metadatos opcionales bajo la clave emdash de package.json:

{
	"emdash": {
		"label": "My Blog Template",
		"schema": ".emdash/schema.sql",
		"seed": ".emdash/seed.json"
	}
}
OpciónDescripción
labelNombre de la plantilla para mostrar
schemaEsquema SQL opcional que lee emdash init
seedRuta del archivo JSON de semilla

Configuración de TypeScript

Durante el desarrollo local, la integración de Astro genera emdash-env.d.ts en la raíz del proyecto y lo actualiza tras los cambios de esquema. El archivo amplía el módulo emdash, de modo que las importaciones estándar getEmDashCollection() y getEmDashEntry() infieren los campos de las colecciones locales sin un alias de ruta.

El comando independiente emdash types obtiene el esquema de una instancia local o remota en ejecución y escribe .emdash/types.ts de forma predeterminada. Añade un alias solo cuando el código de la aplicación importe directamente esa salida independiente:

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

Genera los tipos independientes del esquema remoto con el siguiente comando:

npx emdash types