EmDash è configurato tramite due file: astro.config.mjs per l’integrazione e src/live.config.ts per le raccolte di contenuti.
Integrazione Astro
Configura EmDash come integrazione Astro in 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: [],
}),
],
});
Opzioni di integrazione
database
Obbligatorio. Configurazione dell’adattatore del database. Scegli un adattatore:
// 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 (importa da @emdash-cms/cloudflare)
database: d1({ binding: "DB" });
Vedi Opzioni Database per dettagli.
storage
Obbligatorio. Configurazione dell’adattatore di archiviazione media. Scegli un adattatore:
// File system locale (sviluppo)
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
// Binding R2 (Cloudflare Workers)
storage: r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev", // opzionale
});
// Compatibile S3 (qualsiasi piattaforma) — tutti i campi da variabili d'ambiente S3_*
storage: s3()
// Oppure con valori espliciti
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", // opzionale, predefinito: "auto"
publicUrl: "https://cdn.example.com", // opzionale
});
Vedi Opzioni di Archiviazione per dettagli.
plugins
Opzionale. Array di plugin EmDash. L’esempio seguente registra un plugin:
import seoPlugin from "@emdash-cms/plugin-seo";
plugins: [seoPlugin()];
fonts
Opzionale. Configurazione dei font dell’interfaccia amministrativa.
Per impostazione predefinita, EmDash carica Noto Sans tramite l’API Font di Astro. I font vengono scaricati da Google al momento della build e auto-ospitati, quindi non ci sono richieste CDN a runtime. Il font base copre scritture latina, cirillica, greca, devanagari e vietnamita.
Per aggiungere supporto per sistemi di scrittura aggiuntivi, passa i nomi degli script. L’esempio seguente aggiunge arabo e giapponese:
emdash({
fonts: {
scripts: ["arabic", "japanese"],
},
})
Gli script disponibili sono 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.
Ogni script corrisponde alla variante Noto Sans corrispondente su Google Fonts (ad es. "arabic" carica Noto Sans Arabic). Tutte le facce del font condividono un singolo nome font-family e usano unicode-range in modo che il browser scarichi solo i file necessari per i caratteri sulla pagina.
Imposta su false per disabilitare completamente l’iniezione di font e utilizzare i font di sistema:
emdash({
fonts: false,
})
Il CSS amministrativo utilizza la variabile CSS --font-emdash. Questa viene impostata automaticamente dalla configurazione del font sopra.
auth
Opzionale. Un adattatore di autenticazione. Il login integrato di EmDash utilizza le passkey; impostare auth le sostituisce con un provider esterno. L’adattatore Cloudflare Access, access(), è fornito da @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,
},
}),
});
Opzioni per access():
| Opzione | Tipo | Predefinito | Descrizione |
|---|---|---|---|
teamDomain | string | obbligatorio | Il tuo dominio team Cloudflare Access |
audience | string | — | Tag Application Audience (AUD). Su Workers, preferisci audienceEnvVar. |
audienceEnvVar | string | "CF_ACCESS_AUDIENCE" | Variabile d’ambiente da cui leggere il tag audience a runtime |
autoProvision | boolean | true | Crea un utente EmDash al primo login |
defaultRole | number | 30 | Livello di ruolo per utenti non corrispondenti a roleMapping (vedi Ruoli utente) |
syncRoles | boolean | false | Riapplica roleMapping ad ogni login invece che solo al provisioning |
roleMapping | object | — | Mappa nomi gruppo IdP a livelli di ruolo EmDash; vince la prima corrispondenza |
authProviders
Opzionale. Un array di provider di login collegabili (livello superiore, accanto a auth). Ogni voce è il risultato della chiamata di una factory di provider, come mostrato di seguito:
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()],
});
Provider integrati:
github()— leggeEMDASH_OAUTH_GITHUB_CLIENT_ID/EMDASH_OAUTH_GITHUB_CLIENT_SECRET(o fallback senza prefisso).google()— leggeEMDASH_OAUTH_GOOGLE_CLIENT_ID/EMDASH_OAUTH_GOOGLE_CLIENT_SECRET.atproto()— Login account Atmosphere (Bluesky e la rete AT Protocol più ampia). Nessuna variabile d’ambiente necessaria. Accetta{ allowedDIDs, allowedHandles, defaultRole }. Vedi la guida al login Atmosphere.
I pacchetti di terze parti possono registrare i propri provider utilizzando la stessa forma AuthProviderDescriptor — vedi Provider di Login.
siteUrl
Opzionale. L’origine pubblica rivolta al browser per il sito (schema + host + porta opzionale, nessun percorso).
Dietro un proxy inverso di terminazione TLS, Astro.url restituisce l’indirizzo interno (http://localhost:4321) invece di quello pubblico (https://cms.example.com). Questo rompe passkey, corrispondenza origine CSRF, reindirizzamenti OAuth, reindirizzamenti login, scoperta MCP, esportazioni snapshot, sitemap, robots.txt e dati strutturati JSON-LD. Imposta siteUrl per risolvere tutto in una volta.
L’integrazione valida questo valore al momento del caricamento: deve essere un URL valido con protocollo http: o https: ed è normalizzato a origine (il percorso viene rimosso).
L’esempio seguente imposta l’origine pubblica:
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
siteUrl: "https://cms.example.com",
});
Quando siteUrl non è impostato nella configurazione, EmDash controlla le variabili d’ambiente in ordine: EMDASH_SITE_URL, poi SITE_URL. Questo è utile per deployment di container in cui l’URL pubblico è impostato a runtime.
Verifica passkey multi-origine
siteUrl definisce un’unica origine canonica. Quando lo stesso deployment EmDash è raggiungibile sotto diversi hostname che condividono un dominio genitore registrabile (ad es. https://example.com e https://preview.example.com), la verifica passkey rifiuta asserzioni la cui origine non corrisponde esattamente a siteUrl — anche se WebAuthn permette alle passkey di essere valide tra sottodomini sotto lo stesso rpId.
Dichiara origini accettate aggiuntive tramite allowedOrigins in astro.config.mjs o la variabile d’ambiente EMDASH_ALLOWED_ORIGINS. Il siteUrl canonico rimane la fonte di rpId; le voci elencate qui sono accettate al momento della verifica. Le due fonti vengono unite a runtime, quindi la configurazione può dichiarare le origini stabili (versionizzate, revisionate nel codice) mentre l’ambiente aggiunge extra specifici dell’ambiente (ad es. anteprime PR effimere).
L’esempio seguente dichiara un’origine aggiuntiva nella configurazione:
emdash({
siteUrl: "https://example.com",
allowedOrigins: ["https://preview.example.com"],
})
I valori equivalenti possono anche provenire da variabili d’ambiente:
EMDASH_SITE_URL=https://example.com
EMDASH_ALLOWED_ORIGINS=https://preview.example.com,https://staging.example.com
Validazione
EmDash valida questi per prevenire configurazioni morte che il browser non onorerebbe mai:
- Ogni voce deve essere un URL analizzabile
http:ohttps:senza punto finale e senza etichette vuote nell’hostname. - Quando
allowedOriginsnon è vuoto,siteUrldeve essere impostato (qualsiasi fonte) e non deve essere un letterale IP o avere un hostname con punto finale. - Ogni origine deve essere lo stesso hostname di
siteUrlo un sottodominio di esso. (WebAuthn richiede cherpIdsia un suffisso registrabile di ogni origine.)
Quando la validazione fallisce, vedrai un errore attribuito alla fonte come 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.
Dove appare l’errore dipende da dove sono dichiarati i valori:
- All’avvio di Astro, quando sia
config.allowedOriginscheconfig.siteUrlprovengono daastro.config.mjs— errori di battitura nel codice fanno fallire la build. - Alla prima verifica passkey, quando uno dei valori proviene da
EMDASH_ALLOWED_ORIGINSoEMDASH_SITE_URL— le discrepanze ambientali emergono come 500 al primo tentativo di verifica.
Configurazione proxy inverso
Astro riflette X-Forwarded-* solo quando l’host pubblico è consentito. Configura security.allowedDomains per l’hostname (e gli schemi) che i tuoi utenti raggiungono. In astro dev, aggiungi vite.server.allowedHosts corrispondenti affinché Vite accetti l’header Host del proxy.
Preferisci correggere prima allowedDomains (e gli header inoltrati); usa siteUrl quando l’URL ricostruito diverge ancora dall’origine del browser (tipico quando TLS è terminato davanti e la richiesta upstream rimane http://).
Con TLS davanti, vincolare il server di sviluppo a loopback (astro dev --host 127.0.0.1) è spesso sufficiente: il proxy si connette localmente mentre siteUrl corrisponde all’origine HTTPS pubblica.
Se il tuo proxy scrive un header IP client, imposta trustedProxyHeaders affinché i limiti di rate di EmDash possano utilizzare l’IP client reale invece di raggruppare ogni richiesta sotto una chiave “unknown” condivisa.
La seguente configurazione imposta allowedDomains, vite.server.allowedHosts e siteUrl insieme per un deployment con 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
Opzionale. Header di cui fidarsi per la risoluzione IP client quando si esegue dietro un proxy inverso che controlli. Utilizzato dai limiti di rate di autenticazione (magic-link, registrazione, passkey, flusso dispositivo OAuth) e dall’endpoint commenti pubblici.
Su Cloudflare l’oggetto cf allegato alla richiesta viene utilizzato automaticamente — normalmente non è necessario impostare questo. Su deployment auto-ospitati dietro nginx, Caddy, Traefik, Fly, Railway o simili, impostalo sull’header che il tuo proxy scrive affinché i limiti di rate possano raggruppare per IP client reale invece di trattare ogni richiesta come “unknown”.
L’esempio seguente si fida dell’header x-real-ip impostato da nginx, Caddy o Traefik:
emdash({
database: sqlite({ url: "file:./data.db" }),
trustedProxyHeaders: ["x-real-ip"],
});
Gli header vengono provati in ordine. I valori che corrispondono a *-forwarded-for vengono analizzati come liste separate da virgole e viene utilizzata la prima voce. L’esempio seguente preferisce l’header di Fly.io e ricade su x-forwarded-for:
emdash({
trustedProxyHeaders: ["fly-client-ip", "x-forwarded-for"],
});
Quando non è impostato nella configurazione, EmDash legge la variabile d’ambiente EMDASH_TRUSTED_PROXY_HEADERS (separata da virgole). Un array vuoto esplicito nella configurazione sovrascrive la variabile d’ambiente.
maxUploadSize
Opzionale. Dimensione massima consentita per il caricamento di file multimediali in byte. Si applica sia ai caricamenti multipart diretti che ai caricamenti URL firmati. Predefinito a 52_428_800 (50 MB). L’esempio seguente aumenta il limite 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
});
| Valore | Descrizione |
|---|---|
number (byte) | Deve essere un intero finito positivo |
| omesso | Predefinito a 50 MB |
I caricamenti che superano il limite configurato vengono rifiutati con una risposta 413 Payload Too Large sul percorso di caricamento diretto, o un 400 Validation Error sul percorso URL firmato.
Adattatori database
Importa gli adattatori da emdash/db:
import { sqlite, libsql, postgres } from "emdash/db";
sqlite(config)
Database SQLite usando better-sqlite3. L’esempio seguente si connette a un file locale:
| Opzione | Tipo | Descrizione |
|---|---|---|
url | string | Percorso file con prefisso file: |
sqlite({ url: "file:./data.db" });
libsql(config)
Database libSQL. L’esempio seguente si connette a un database libSQL remoto:
| Opzione | Tipo | Descrizione |
|---|---|---|
url | string | URL del database |
authToken | string | Token di autenticazione (opzionale per file locali) |
libsql({
url: process.env.LIBSQL_DATABASE_URL,
authToken: process.env.LIBSQL_AUTH_TOKEN,
});
postgres(config)
Database PostgreSQL con connection pooling.
| Opzione | Tipo | Descrizione |
|---|---|---|
connectionString | string | URL di connessione PostgreSQL |
host | string | Host del database |
port | number | Porta del database |
database | string | Nome del database |
user | string | Utente del database |
password | string | Password del database |
ssl | boolean | Abilita SSL |
pool.min | number | Dimensione minima pool (predefinito: 0) |
pool.max | number | Dimensione massima pool (predefinito: 10) |
L’esempio seguente si connette con una stringa di connessione:
postgres({ connectionString: process.env.DATABASE_URL });
d1(config)
Database Cloudflare D1. Importa da @emdash-cms/cloudflare.
| Opzione | Tipo | Predefinito | Descrizione |
|---|---|---|---|
binding | string | — | Nome binding D1 da wrangler.jsonc |
session | string | "disabled" | Modalità replica lettura: "disabled", "auto" o "primary-first" |
bookmarkCookie | string | "__em_d1_bookmark" | Nome cookie per segnalibri di sessione |
L’esempio seguente mostra un binding di base e uno con repliche di lettura abilitate:
// Base
d1({ binding: "DB" });
// Con repliche di lettura
d1({ binding: "DB", session: "auto" });
Quando session è "auto" o "primary-first", EmDash utilizza l’API Sessions D1 per instradare le query di lettura a repliche vicine. Gli utenti autenticati ottengono coerenza read-your-writes basata su segnalibri. Vedi Opzioni Database — Repliche di Lettura per dettagli.
Adattatori di archiviazione
Importa local e s3 da emdash/astro. L’adattatore r2 è importato da @emdash-cms/cloudflare:
import emdash, { local, s3 } from "emdash/astro";
import { r2 } from "@emdash-cms/cloudflare";
local(config)
Archiviazione su file system locale. L’esempio seguente serve i caricamenti da una directory locale:
| Opzione | Tipo | Descrizione |
|---|---|---|
directory | string | Percorso directory |
baseUrl | string | URL base per servire i file |
local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
});
r2(config)
Binding Cloudflare R2. L’esempio seguente utilizza un binding R2 con un URL pubblico:
| Opzione | Tipo | Descrizione |
|---|---|---|
binding | string | Nome binding R2 |
publicUrl | string | URL pubblico opzionale |
r2({
binding: "MEDIA",
publicUrl: "https://pub-xxxx.r2.dev",
});
s3(config?)
Archiviazione compatibile S3. Tutti i campi di configurazione sono opzionali: qualsiasi campo omesso da
s3({...}) viene risolto dalla variabile d’ambiente S3_* corrispondente quando il
processo Node si avvia. I valori espliciti hanno sempre precedenza.
Prerequisito: installa @aws-sdk/client-s3 e @aws-sdk/s3-request-presigner
nel tuo progetto. EmDash core non include l’AWS SDK. Vedi
Opzioni di Archiviazione: Archiviazione Compatibile S3
per dettagli.
| Opzione | Tipo | Descrizione |
|---|---|---|
endpoint | string | URL endpoint S3 (S3_ENDPOINT) |
bucket | string | Nome bucket (S3_BUCKET) |
accessKeyId | string | Chiave di accesso (S3_ACCESS_KEY_ID) |
secretAccessKey | string | Chiave segreta (S3_SECRET_ACCESS_KEY) |
region | string | Regione, predefinito "auto" (S3_REGION) |
publicUrl | string | URL CDN opzionale (S3_PUBLIC_URL) |
Gli esempi seguenti risolvono tutti i campi dall’ambiente, mescolano configurazione e ambiente, o passano ogni campo esplicitamente:
// Tutti i campi da variabili d'ambiente S3_* (deployment container Node)
s3()
// Misto: CDN dalla configurazione, resto dall'ambiente
s3({ publicUrl: "https://cdn.example.com" })
// Tutto esplicito
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 risoluzione di variabili d’ambiente a runtime è una funzionalità solo Node. Su Cloudflare
Workers, i segreti e le variabili sono esposti tramite il parametro env del
gestore fetch, non tramite process.env, quindi le variabili d’ambiente S3_*
non vengono raccolte. I deployment Workers dovrebbero utilizzare l’adattatore r2(config)
o passare valori espliciti a s3({...}). Vedi
Opzioni di Archiviazione per dettagli.
Raccolte in tempo reale
Configura il loader EmDash in src/live.config.ts:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({
loader: emdashLoader(),
}),
};
Opzioni del loader
La funzione emdashLoader() non accetta argomenti:
emdashLoader();
Variabili d’ambiente
EmDash rispetta queste variabili d’ambiente:
| Variabile | Descrizione |
|---|---|
EMDASH_SITE_URL | Origine pubblica rivolta al browser (ricade su SITE_URL) |
EMDASH_ALLOWED_ORIGINS | Lista separata da virgole di origini aggiuntive accettate dalla verifica passkey (deployment multi-sottodominio). |
EMDASH_DATABASE_URL | Sovrascrivi URL database |
EMDASH_ENCRYPTION_KEY | Chiave per crittografare i segreti dei plugin a riposo. Fornita dall’operatore — mai memorizzata nel database. |
EMDASH_PREVIEW_SECRET | Sovrascrittura opzionale per il segreto HMAC di anteprima. Quando non impostato, viene generato un valore stabile per sito e memorizzato nel database. |
EMDASH_IP_SALT | Sovrascrittura opzionale per il salt hash IP del commentatore. Quando non impostato, viene generato un valore stabile per sito e memorizzato nel database. |
EMDASH_AUTH_SECRET | Legacy. Usato come fonte salt IP se impostato; le installazioni esistenti dovrebbero mantenerlo per preservare hash IP commentatore stabili attraverso gli aggiornamenti. |
EMDASH_URL | URL EmDash remoto per sincronizzazione schema |
Genera una chiave di crittografia con il seguente comando:
npx emdash secrets generate
Configurazione package.json
I template e i siti possono dichiarare metadati opzionali sotto una chiave emdash in package.json:
{
"emdash": {
"label": "My Blog Template",
"seed": ".emdash/seed.json",
"url": "https://my-site.pages.dev"
}
}
| Opzione | Descrizione |
|---|---|
label | Nome template per visualizzazione |
seed | Percorso al file JSON seed |
url | URL remoto per sincronizzazione schema |
Configurazione TypeScript
EmDash genera tipi in .emdash/types.ts. Aggiungi un alias di percorso al tuo tsconfig.json:
{
"compilerOptions": {
"paths": {
"@emdash-cms/types": ["./.emdash/types.ts"]
}
}
}
Genera tipi con il seguente comando:
npx emdash types