Referencia de Configuración

En esta página

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

Configure 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 integración

database

Requerido. Configuración del adaptador de base de datos. Elija 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 (importar desde @emdash-cms/cloudflare)
database: d1({ binding: "DB" });

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

storage

Requerido. Configuración del adaptador de almacenamiento de medios. Elija un adaptador:

// Sistema de archivos local (desarrollo)
storage: local({
	directory: "./uploads",
	baseUrl: "/_emdash/api/media/file",
});

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

// Compatible con S3 (cualquier plataforma) — todos los campos de variables de entorno S3_*
storage: s3()

// O con valores explícitos
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", // opcional, predeterminado: "auto"
	publicUrl: "https://cdn.example.com", // opcional
});

Ver Opciones de almacenamiento para más detalles.

plugins

Opcional. Array de plugins de EmDash. El siguiente ejemplo registra un plugin:

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

plugins: [seoPlugin()];

fonts

Opcional. Configuración de fuentes de la interfaz de administración.

Por defecto, EmDash carga Noto Sans a través de la API de fuentes de Astro. Las fuentes se descargan de Google en tiempo de compilación y se alojan localmente, por lo que no hay solicitudes CDN en tiempo de ejecución. La fuente base cubre escrituras latina, cirílica, griega, devanagari y vietnamita.

Para agregar soporte para sistemas de escritura adicionales, pase nombres de escritura. El siguiente ejemplo agrega árabe y japonés:

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

Las escrituras 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 escritura se asigna a la variante correspondiente de Noto Sans en Google Fonts (por ejemplo, "arabic" carga Noto Sans Arabic). Todas las fuentes comparten un único nombre de font-family y usan unicode-range para que el navegador solo descargue los archivos que necesita para los caracteres en la página.

Establezca en false para deshabilitar completamente la inyección de fuentes y usar fuentes del sistema:

emdash({
	fonts: false,
})

El CSS de administración usa la variable CSS --font-emdash. Esto 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 utiliza passkeys; configurar auth los reemplaza con un proveedor externo. El adaptador de Cloudflare Access, access(), es proporcionado por @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 para access():

OpciónTipoPredeterminadoDescripción
teamDomainstringrequeridoSu dominio de equipo de Cloudflare Access
audiencestringEtiqueta de Audience (AUD) de la aplicación. En Workers, prefiera audienceEnvVar.
audienceEnvVarstring"CF_ACCESS_AUDIENCE"Variable de entorno desde la cual leer la etiqueta de audiencia en tiempo de ejecución
autoProvisionbooleantrueCrear un usuario de EmDash en el primer inicio de sesión
defaultRolenumber30Nivel de rol para usuarios no coincidentes con roleMapping (ver Roles de usuario)
syncRolesbooleanfalseVolver a aplicar roleMapping en cada inicio de sesión en lugar de solo en el aprovisionamiento
roleMappingobjectMapear nombres de grupos IdP a niveles de rol de EmDash; la primera coincidencia gana

authProviders

Opcional. Un array de proveedores de inicio de sesión conectables (nivel superior, junto a auth). Cada entrada es el resultado de llamar a una fábrica de proveedores, 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 respaldos sin prefijo).
  • google() — lee EMDASH_OAUTH_GOOGLE_CLIENT_ID / EMDASH_OAUTH_GOOGLE_CLIENT_SECRET.
  • atproto() — Inicio de sesión de cuenta Atmosphere (Bluesky y la red AT Protocol más amplia). No se necesitan variables de entorno. Acepta { allowedDIDs, allowedHandles, defaultRole }. Ver la guía de inicio de sesión de Atmosphere.

Los paquetes de terceros pueden registrar sus propios proveedores usando la misma forma AuthProviderDescriptor — ver Proveedores de inicio de sesión.

siteUrl

Opcional. El origen público orientado al navegador para el sitio (esquema + host + puerto opcional, sin ruta).

Detrás de un proxy inverso de 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 passkeys, coincidencia de origen CSRF, redirecciones OAuth, redirecciones de inicio de sesión, descubrimiento MCP, exportaciones de instantáneas, sitemap, robots.txt y datos estructurados JSON-LD. Establezca siteUrl para arreglar todo esto a la vez.

La integración valida este valor en tiempo de carga: 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á configurado en config, EmDash verifica las variables de entorno en orden: EMDASH_SITE_URL, luego SITE_URL. Esto es útil para implementaciones de contenedores donde la URL pública se establece en tiempo de ejecución.

Verificación de passkey de múltiples orígenes

siteUrl define un único origen canónico. Cuando la misma implementación de EmDash es accesible bajo varios nombres de host que comparten un dominio padre registrable (por ejemplo, https://example.com y https://preview.example.com), la verificación de passkey rechaza aserciones cuyo origen no coincide exactamente con siteUrl — aunque WebAuthn permite que los passkeys sean válidos en subdominios bajo el mismo rpId.

Declare orígenes aceptados adicionales a través de allowedOrigins en astro.config.mjs o la variable de entorno EMDASH_ALLOWED_ORIGINS. El siteUrl canónico permanece como la fuente de rpId; las entradas listadas aquí se aceptan en tiempo de verificación. Las dos fuentes se fusionan en tiempo de ejecución, por lo que la configuración puede declarar los orígenes estables (versionados, revisados por código) mientras que el entorno agrega extras específicos del entorno (por ejemplo, vistas previas de PR efímeras).

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 para prevenir configuración muerta que el navegador nunca honraría:

  • Cada entrada debe ser una URL analizable http: o https: sin punto final y sin etiquetas vacías en el nombre de host.
  • Cuando allowedOrigins no está vacío, siteUrl debe estar configurado (cualquier fuente) y no debe 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 él. (WebAuthn requiere que rpId sea un sufijo registrable de cada origen.)

Cuando la validación falla, verá un error atribuido a la fuente 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 declaran los valores:

  • En el inicio de Astro, cuando tanto config.allowedOrigins como config.siteUrl provienen de astro.config.mjs — los errores tipográficos en el código fallan la compilación.
  • En la primera verificación de passkey, cuando cualquier valor proviene de EMDASH_ALLOWED_ORIGINS o EMDASH_SITE_URL — los desajustes de entorno aparecen como 500 en el primer intento de verificación.

Configuración de proxy inverso

Astro solo refleja X-Forwarded-* cuando el host público está permitido. Configure security.allowedDomains para el nombre de host (y esquemas) que alcanzan sus usuarios. En astro dev, agregue vite.server.allowedHosts coincidentes para que Vite acepte el encabezado Host del proxy.

Prefiera arreglar allowedDomains (y encabezados reenviados) primero; use siteUrl cuando la URL reconstruida todavía diverge del origen del navegador (típico cuando TLS se termina al frente y la solicitud upstream permanece http://).

Con TLS al frente, vincular el servidor de desarrollo a 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.

Si su proxy escribe un encabezado de IP de cliente, establezca trustedProxyHeaders para que los límites de tasa de EmDash puedan usar la IP real del cliente en lugar de agrupar cada solicitud bajo una clave “unknown” compartida.

La siguiente configuración establece allowedDomains, vite.server.allowedHosts y siteUrl juntos para una implementación de 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 la resolución de IP del cliente cuando se ejecuta detrás de un proxy inverso que usted controla. Utilizado por límites de tasa de autenticación (magic-link, registro, passkey, flujo de dispositivo OAuth) y el endpoint de comentarios públicos.

En Cloudflare, el objeto cf adjunto a la solicitud se usa automáticamente — normalmente no necesita configurar esto. En implementaciones autoalojadas detrás de nginx, Caddy, Traefik, Fly, Railway o similares, establezca esto en el encabezado que su proxy escribe para que los límites de tasa puedan agrupar por IP de cliente real en lugar de tratar cada solicitud como “unknown”.

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

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

Los encabezados se prueban en 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 está configurado en config, EmDash lee la variable de entorno EMDASH_TRUSTED_PROXY_HEADERS (separada por comas). Un array vacío explícito en config anula la variable de entorno.

maxUploadSize

Opcional. Tamaño máximo permitido de carga de archivo multimedia en bytes. Se aplica tanto a cargas multiparte directas como a cargas de URL firmadas. El valor predeterminado es 52_428_800 (50 MB). El siguiente ejemplo aumenta 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
omitidoPredeterminado es 50 MB

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

Adaptadores de base de datos

Importe los adaptadores desde emdash/db:

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

sqlite(config)

Base de datos SQLite usando better-sqlite3. El siguiente ejemplo se conecta a un archivo local:

OpciónTipoDescripción
urlstringRuta de 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 (opcional para archivos locales)
libsql({
	url: process.env.LIBSQL_DATABASE_URL,
	authToken: process.env.LIBSQL_AUTH_TOKEN,
});

postgres(config)

Base de datos PostgreSQL con agrupación de conexiones.

OpciónTipoDescripción
connectionStringstringURL de conexión 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
sslbooleanHabilitar SSL
pool.minnumberTamaño mínimo del pool (predeterminado: 0)
pool.maxnumberTamaño máximo del pool (predeterminado: 10)

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

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

d1(config)

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

OpciónTipoPredeterminadoDescripción
bindingstringNombre de enlace D1 de wrangler.jsonc
sessionstring"disabled"Modo de replicación de lectura: "disabled", "auto" o "primary-first"
bookmarkCookiestring"__em_d1_bookmark"Nombre de cookie para marcadores de sesión

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

// 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-después-de-escritura basada en marcadores. Ver Opciones de base de datos — Réplicas de lectura para detalles.

Adaptadores de almacenamiento

Importe 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 sistema de archivos local. El siguiente ejemplo sirve cargas desde un directorio local:

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

r2(config)

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

OpciónTipoDescripción
bindingstringNombre de enlace 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 de s3({...}) se resuelve desde la variable de entorno S3_* coincidente cuando el proceso de Node se inicia. Los valores explícitos siempre tienen precedencia.

Requisito previo: instale @aws-sdk/client-s3 y @aws-sdk/s3-request-presigner en su proyecto. EmDash core no incluye el AWS SDK. Ver Opciones de almacenamiento: Almacenamiento compatible con S3 para detalles.

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

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

// Todos los campos de variables de entorno S3_* (implementaciones de contenedores Node)
s3()

// Mezcla: CDN de configuración, resto del entorno
s3({ publicUrl: "https://cdn.example.com" })

// Todo explícito
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 característica solo 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 capturan. Las implementaciones de Workers deben usar el adaptador r2(config) o pasar valores explícitos a s3({...}). Ver Opciones de almacenamiento para detalles.

Colecciones en vivo

Configure 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 toma argumentos:

emdashLoader();

Variables de entorno

EmDash respeta estas variables de entorno:

VariableDescripción
EMDASH_SITE_URLOrigen público orientado al navegador (recurre a SITE_URL)
EMDASH_ALLOWED_ORIGINSLista separada por comas de orígenes adicionales aceptados por la verificación de passkey (implementaciones multi-subdominio).
EMDASH_DATABASE_URLAnular URL de base de datos
EMDASH_ENCRYPTION_KEYClave para cifrar secretos de plugins en reposo. Proporcionada por el operador — nunca almacenada en la base de datos.
EMDASH_PREVIEW_SECRETAnulación opcional para el secreto HMAC de vista previa. Cuando no está configurado, se genera un valor estable por sitio y se almacena en la base de datos.
EMDASH_IP_SALTAnulación opcional para el salt de hash de IP del comentarista. Cuando no está configurado, se genera un valor estable por sitio y se almacena en la base de datos.
EMDASH_AUTH_SECRETHeredado. Se usa como fuente de salt de IP si está configurado; las instalaciones existentes deben mantener esto para preservar hashes de IP de comentarista estables a través de actualizaciones.
EMDASH_URLURL remota de EmDash para sincronización de esquema

Genere una clave de cifrado con el siguiente comando:

npx emdash secrets generate

Configuración de package.json

Las plantillas y sitios pueden declarar metadatos opcionales bajo una clave emdash en package.json:

{
	"emdash": {
		"label": "My Blog Template",
		"seed": ".emdash/seed.json",
		"url": "https://my-site.pages.dev"
	}
}
OpciónDescripción
labelNombre de plantilla para mostrar
seedRuta al archivo JSON de semilla
urlURL remota para sincronización de esquema

Configuración de TypeScript

EmDash genera tipos en .emdash/types.ts. Agregue un alias de ruta a su tsconfig.json:

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

Genere tipos con el siguiente comando:

npx emdash types