Referência de Configuração

Nesta página

O EmDash é configurado através de dois arquivos: astro.config.mjs para a integração e src/live.config.ts para coleções de conteúdo.

Integração Astro

Configure o EmDash como uma integração Astro em 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: [],
		}),
	],
});

Opções de integração

database

Obrigatório. Configuração do adaptador de banco de dados. Escolha um 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 de @emdash-cms/cloudflare)
database: d1({ binding: "DB" });

Consulte Opções de Banco de Dados para detalhes.

storage

Obrigatório. Configuração do adaptador de armazenamento de mídia. Escolha um adaptador:

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

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

// Compatível com S3 (qualquer plataforma) — todos os campos de variáveis de ambiente S3_*
storage: s3()

// Ou com 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, padrão: "auto"
	publicUrl: "https://cdn.example.com", // opcional
});

Consulte Opções de Armazenamento para detalhes.

plugins

Opcional. Array de plugins EmDash. O exemplo a seguir registra um plugin:

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

plugins: [seoPlugin()];

fonts

Opcional. Configuração de fonte da interface administrativa.

Por padrão, o EmDash carrega Noto Sans através da API de Fontes do Astro. As fontes são baixadas do Google no momento da compilação e auto-hospedadas, portanto não há requisições CDN em tempo de execução. A fonte base cobre escritas latina, cirílica, grega, devanágari e vietnamita.

Para adicionar suporte a sistemas de escrita adicionais, passe nomes de scripts. O exemplo a seguir adiciona árabe e japonês:

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

Os scripts disponíveis são 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 e tibetan.

Cada script é mapeado para a variante correspondente do Noto Sans no Google Fonts (por exemplo, "arabic" carrega Noto Sans Arabic). Todas as faces de fonte compartilham um único nome font-family e usam unicode-range para que o navegador baixe apenas os arquivos necessários para os caracteres na página.

Defina como false para desabilitar completamente a injeção de fontes e usar fontes do sistema:

emdash({
	fonts: false,
})

O CSS administrativo usa a variável CSS --font-emdash. Isto é definido automaticamente pela configuração de fonte acima.

auth

Opcional. Um adaptador de autenticação. O login integrado do EmDash usa passkeys; definir auth as substitui por um provedor externo. O adaptador Cloudflare Access, access(), é fornecido 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,
		},
	}),
});

Opções para access():

OpçãoTipoPadrãoDescrição
teamDomainstringobrigatórioSeu domínio de equipe do Cloudflare Access
audiencestringTag de Audience (AUD) da aplicação. No Workers, prefira audienceEnvVar.
audienceEnvVarstring"CF_ACCESS_AUDIENCE"Variável de ambiente para ler a tag de audience em tempo de execução
autoProvisionbooleantrueCriar um usuário EmDash no primeiro login
defaultRolenumber30Nível de papel para usuários não correspondidos por roleMapping (veja Papéis de usuário)
syncRolesbooleanfalseReaplicar roleMapping em cada login em vez de apenas no provisionamento
roleMappingobjectMapear nomes de grupos IdP para níveis de papel EmDash; primeira correspondência ganha

authProviders

Opcional. Um array de provedores de login plugáveis (nível superior, ao lado de auth). Cada entrada é o resultado da chamada de uma fábrica de provedor, como mostrado abaixo:

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

Provedores integrados:

  • github() — lê EMDASH_OAUTH_GITHUB_CLIENT_ID / EMDASH_OAUTH_GITHUB_CLIENT_SECRET (ou fallbacks sem prefixo).
  • google() — lê EMDASH_OAUTH_GOOGLE_CLIENT_ID / EMDASH_OAUTH_GOOGLE_CLIENT_SECRET.
  • atproto() — Login de conta Atmosphere (Bluesky e a rede AT Protocol mais ampla). Nenhuma variável de ambiente necessária. Aceita { allowedDIDs, allowedHandles, defaultRole }. Veja o guia de login Atmosphere.

Pacotes de terceiros podem registrar seus próprios provedores usando a mesma forma AuthProviderDescriptor — veja Provedores de Login.

siteUrl

Opcional. A origem pública voltada para o navegador do site (esquema + host + porta opcional, sem caminho).

Atrás de um proxy reverso de terminação TLS, Astro.url retorna o endereço interno (http://localhost:4321) em vez do público (https://cms.example.com). Isso quebra passkeys, correspondência de origem CSRF, redirecionamentos OAuth, redirecionamentos de login, descoberta MCP, exportações de snapshot, sitemap, robots.txt e dados estruturados JSON-LD. Defina siteUrl para corrigir tudo de uma vez.

A integração valida este valor no momento do carregamento: deve ser uma URL válida com protocolo http: ou https: e é normalizada para origem (caminho é removido).

O exemplo a seguir define a origem pública:

emdash({
	database: sqlite({ url: "file:./data.db" }),
	storage: local({
		directory: "./uploads",
		baseUrl: "/_emdash/api/media/file",
	}),
	siteUrl: "https://cms.example.com",
});

Quando siteUrl não está definido na configuração, o EmDash verifica variáveis de ambiente em ordem: EMDASH_SITE_URL, depois SITE_URL. Isto é útil para implantações de contêiner onde a URL pública é definida em tempo de execução.

Verificação de passkey multi-origem

siteUrl define uma única origem canônica. Quando a mesma implantação EmDash é acessível sob vários nomes de host que compartilham um domínio pai registrável (por exemplo, https://example.com e https://preview.example.com), a verificação de passkey rejeita asserções cuja origem não corresponde exatamente a siteUrl — embora WebAuthn permita que passkeys sejam válidas em subdomínios sob o mesmo rpId.

Declare origens aceitas adicionais via allowedOrigins em astro.config.mjs ou a variável de ambiente EMDASH_ALLOWED_ORIGINS. O siteUrl canônico permanece a fonte de rpId; entradas listadas aqui são aceitas no momento da verificação. As duas fontes são mescladas em tempo de execução, então a configuração pode declarar as origens estáveis (versionadas, revisadas por código) enquanto o ambiente adiciona extras específicos do ambiente (por exemplo, visualizações de PR efêmeras).

O exemplo a seguir declara uma origem adicional na configuração:

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

Os valores equivalentes também podem vir de variáveis de ambiente:

EMDASH_SITE_URL=https://example.com
EMDASH_ALLOWED_ORIGINS=https://preview.example.com,https://staging.example.com
Validação

O EmDash valida estes para prevenir configuração morta que o navegador nunca honraria:

  • Cada entrada deve ser uma URL http: ou https: analisável sem ponto final e sem rótulos vazios no nome do host.
  • Quando allowedOrigins não está vazio, siteUrl deve ser definido (qualquer fonte) e não deve ser um literal IP ou ter um nome de host com ponto final.
  • Cada origem deve ser o mesmo nome de host que siteUrl ou um subdomínio dele. (WebAuthn requer que rpId seja um sufixo registrável de cada origem.)

Quando a validação falha, você verá um erro atribuído à fonte 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.

Onde o erro aparece depende de onde os valores são declarados:

  • Na inicialização do Astro, quando ambos config.allowedOrigins e config.siteUrl vêm de astro.config.mjs — erros de digitação no código falham a compilação.
  • Na primeira verificação de passkey, quando qualquer valor vem de EMDASH_ALLOWED_ORIGINS ou EMDASH_SITE_URL — incompatibilidades de ambiente surgem como 500s na primeira tentativa de verificação.

Configuração de proxy reverso

O Astro apenas reflete X-Forwarded-* quando o host público é permitido. Configure security.allowedDomains para o nome do host (e esquemas) que seus usuários atingem. Em astro dev, adicione vite.server.allowedHosts correspondentes para que o Vite aceite o cabeçalho Host do proxy.

Prefira corrigir allowedDomains (e cabeçalhos encaminhados) primeiro; use siteUrl quando a URL reconstruída ainda diverge da origem do navegador (típico quando TLS é terminado na frente e a requisição upstream permanece http://).

Com TLS na frente, vincular o servidor de desenvolvimento ao loopback (astro dev --host 127.0.0.1) geralmente é suficiente: o proxy se conecta localmente enquanto siteUrl corresponde à origem HTTPS pública.

Se seu proxy escreve um cabeçalho de IP do cliente, defina trustedProxyHeaders para que os limites de taxa do EmDash possam usar o IP real do cliente em vez de agrupar cada requisição sob uma chave “unknown” compartilhada.

A configuração a seguir define allowedDomains, vite.server.allowedHosts e siteUrl juntos para uma implantação de proxy reverso:

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. Cabeçalhos para confiar na resolução de IP do cliente ao executar atrás de um proxy reverso que você controla. Usado por limites de taxa de autenticação (magic-link, inscrição, passkey, fluxo de dispositivo OAuth) e o endpoint de comentário público.

No Cloudflare, o objeto cf anexado à requisição é usado automaticamente — você normalmente não precisa definir isto. Em implantações auto-hospedadas atrás de nginx, Caddy, Traefik, Fly, Railway ou similares, defina isto para o cabeçalho que seu proxy escreve para que os limites de taxa possam agrupar por IP real do cliente em vez de tratar cada requisição como “unknown”.

O exemplo a seguir confia no cabeçalho x-real-ip definido por nginx, Caddy ou Traefik:

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

Os cabeçalhos são tentados em ordem. Valores que correspondem a *-forwarded-for são analisados como listas separadas por vírgula e a primeira entrada é usada. O exemplo a seguir prefere o cabeçalho do Fly.io e volta para x-forwarded-for:

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

Quando não definido na configuração, o EmDash lê a variável de ambiente EMDASH_TRUSTED_PROXY_HEADERS (separada por vírgula). Um array vazio explícito na configuração sobrescreve a variável de ambiente.

maxUploadSize

Opcional. Tamanho máximo permitido de upload de arquivo de mídia em bytes. Aplica-se a uploads multipart diretos e uploads de URL assinada. Padrão é 52_428_800 (50 MB). O exemplo a seguir aumenta o limite para 100 MB:

emdash({
	database: sqlite({ url: "file:./data.db" }),
	storage: local({
		directory: "./uploads",
		baseUrl: "/_emdash/api/media/file",
	}),
	maxUploadSize: 100 * 1024 * 1024, // 100 MB
});
ValorDescrição
number (bytes)Deve ser um inteiro finito positivo
omitidoPadrão é 50 MB

Uploads que excedem o limite configurado são rejeitados com uma resposta 413 Payload Too Large no caminho de upload direto, ou um 400 Validation Error no caminho de URL assinada.

Adaptadores de banco de dados

Importe os adaptadores de emdash/db:

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

sqlite(config)

Banco de dados SQLite usando better-sqlite3. O exemplo a seguir se conecta a um arquivo local:

OpçãoTipoDescrição
urlstringCaminho do arquivo com prefixo file:
sqlite({ url: "file:./data.db" });

libsql(config)

Banco de dados libSQL. O exemplo a seguir se conecta a um banco de dados libSQL remoto:

OpçãoTipoDescrição
urlstringURL do banco de dados
authTokenstringToken de autenticação (opcional para arquivos locais)
libsql({
	url: process.env.LIBSQL_DATABASE_URL,
	authToken: process.env.LIBSQL_AUTH_TOKEN,
});

postgres(config)

Banco de dados PostgreSQL com pooling de conexões.

OpçãoTipoDescrição
connectionStringstringURL de conexão PostgreSQL
hoststringHost do banco de dados
portnumberPorta do banco de dados
databasestringNome do banco de dados
userstringUsuário do banco de dados
passwordstringSenha do banco de dados
sslbooleanHabilitar SSL
pool.minnumberTamanho mínimo do pool (padrão: 0)
pool.maxnumberTamanho máximo do pool (padrão: 10)

O exemplo a seguir se conecta com uma string de conexão:

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

d1(config)

Banco de dados Cloudflare D1. Importe de @emdash-cms/cloudflare.

OpçãoTipoPadrãoDescrição
bindingstringNome do binding D1 de wrangler.jsonc
sessionstring"disabled"Modo de replicação de leitura: "disabled", "auto" ou "primary-first"
bookmarkCookiestring"__em_d1_bookmark"Nome do cookie para bookmarks de sessão

O exemplo a seguir mostra um binding básico e um com réplicas de leitura habilitadas:

// Básico
d1({ binding: "DB" });

// Com réplicas de leitura
d1({ binding: "DB", session: "auto" });

Quando session é "auto" ou "primary-first", o EmDash usa a API Sessions do D1 para rotear consultas de leitura para réplicas próximas. Usuários autenticados obtêm consistência de leitura após escrita baseada em bookmarks. Veja Opções de Banco de Dados — Réplicas de Leitura para detalhes.

Adaptadores de armazenamento

Importe local e s3 de emdash/astro. O adaptador r2 é importado de @emdash-cms/cloudflare:

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

local(config)

Armazenamento em sistema de arquivos local. O exemplo a seguir serve uploads de um diretório local:

OpçãoTipoDescrição
directorystringCaminho do diretório
baseUrlstringURL base para servir arquivos
local({
	directory: "./uploads",
	baseUrl: "/_emdash/api/media/file",
});

r2(config)

Binding Cloudflare R2. O exemplo a seguir usa um binding R2 com uma URL pública:

OpçãoTipoDescrição
bindingstringNome do binding R2
publicUrlstringURL pública opcional
r2({
	binding: "MEDIA",
	publicUrl: "https://pub-xxxx.r2.dev",
});

s3(config?)

Armazenamento compatível com S3. Todos os campos de configuração são opcionais: qualquer campo omitido de s3({...}) é resolvido da variável de ambiente S3_* correspondente quando o processo Node inicia. Valores explícitos sempre têm precedência.

Pré-requisito: instale @aws-sdk/client-s3 e @aws-sdk/s3-request-presigner em seu projeto. O núcleo do EmDash não inclui o AWS SDK. Veja Opções de Armazenamento: Armazenamento Compatível com S3 para detalhes.

OpçãoTipoDescrição
endpointstringURL do endpoint S3 (S3_ENDPOINT)
bucketstringNome do bucket (S3_BUCKET)
accessKeyIdstringChave de acesso (S3_ACCESS_KEY_ID)
secretAccessKeystringChave secreta (S3_SECRET_ACCESS_KEY)
regionstringRegião, padrão "auto" (S3_REGION)
publicUrlstringURL CDN opcional (S3_PUBLIC_URL)

Os exemplos a seguir resolvem todos os campos do ambiente, misturam configuração e ambiente, ou passam cada campo explicitamente:

// Todos os campos de variáveis de ambiente S3_* (implantações de contêiner Node)
s3()

// Misto: CDN da configuração, resto do ambiente
s3({ publicUrl: "https://cdn.example.com" })

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

Resolução de variável de ambiente em tempo de execução é um recurso exclusivo do Node. No Cloudflare Workers, segredos e variáveis são expostos através do parâmetro env do manipulador fetch, não através de process.env, então variáveis de ambiente S3_* não são capturadas. Implantações Workers devem usar o adaptador r2(config) ou passar valores explícitos para s3({...}). Veja Opções de Armazenamento para detalhes.

Coleções ao vivo

Configure o carregador EmDash em src/live.config.ts:

import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";

export const collections = {
	_emdash: defineLiveCollection({
		loader: emdashLoader(),
	}),
};

Opções do carregador

A função emdashLoader() não aceita argumentos:

emdashLoader();

Variáveis de ambiente

O EmDash respeita estas variáveis de ambiente:

VariávelDescrição
EMDASH_SITE_URLOrigem pública voltada para o navegador (volta para SITE_URL)
EMDASH_ALLOWED_ORIGINSLista separada por vírgulas de origens adicionais aceitas pela verificação de passkey (implantações multi-subdomínio).
EMDASH_DATABASE_URLSobrescrever URL do banco de dados
EMDASH_ENCRYPTION_KEYChave para criptografar segredos de plugins em repouso. Fornecida pelo operador — nunca armazenada no banco de dados.
EMDASH_PREVIEW_SECRETSobrescrita opcional para o segredo HMAC de visualização. Quando não definido, um valor estável por site é gerado e armazenado no banco de dados.
EMDASH_IP_SALTSobrescrita opcional para o salt de hash de IP do comentarista. Quando não definido, um valor estável por site é gerado e armazenado no banco de dados.
EMDASH_AUTH_SECRETLegado. Usado como fonte de salt de IP se definido; instalações existentes devem manter isto para preservar hashes de IP de comentarista estáveis através de atualizações.
EMDASH_URLURL EmDash remota para sincronização de esquema

Gere uma chave de criptografia com o seguinte comando:

npx emdash secrets generate

Configuração package.json

Templates e sites podem declarar metadados opcionais sob uma chave emdash em package.json:

{
	"emdash": {
		"label": "My Blog Template",
		"seed": ".emdash/seed.json",
		"url": "https://my-site.pages.dev"
	}
}
OpçãoDescrição
labelNome do template para exibição
seedCaminho para arquivo JSON de seed
urlURL remota para sincronização de esquema

Configuração TypeScript

O EmDash gera tipos em .emdash/types.ts. Adicione um alias de caminho ao seu tsconfig.json:

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

Gere tipos com o seguinte comando:

npx emdash types