La configurazione principale di EmDash si trova in astro.config.mjs, mentre src/live.config.ts registra il content loader. Valori specifici del deployment possono provenire anche da variabili d’ambiente. Un piccolo blocco di metadati emdash in package.json supporta le etichette dei template e i flussi CLI locali legacy.
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’adapter del database. Scegli un adapter:
// 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" });
Vedi Opzioni database per i dettagli.
migrations
Opzionale. Controlla la gestione a runtime delle migrazioni interne del database di EmDash. Se omessa, l’opzione predefinita è { runtime: "auto" }.
migrations: {
runtime: "check", // "auto" | "check" | "manual"
dev: "auto", // override opzionale in sviluppo
}
auto verifica e applica le migrazioni in sospeso, check restituisce 503 quando ci sono migrazioni note al build in esecuzione ancora in sospeso e manual non esegue alcuna query di migrazione a runtime. EMDASH_MIGRATIONS_MODE sovrascrive la modalità runtime effettiva. Prima di adottare check o manual, vedi Gestire le migrazioni del database principale.
storage
Opzionale. Configurazione dell’adapter di storage media. Se omessa, EmDash salva i file in ./.emdash/uploads e li serve tramite /_emdash/api/media/file. Scegli un adapter quando la directory locale predefinita non è adatta:
// 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
});
Vedi Opzioni di storage per i dettagli.
images
Opzionale. Controlla se EmDash integra i media memorizzati con l’ottimizzazione immagini di Astro. Il valore predefinito è true.
Se abilitata, EmDash avvolge l’endpoint immagini di Astro così <Image> e getImage() possono leggere i byte sorgente direttamente dall’adapter di storage configurato. Funziona anche quando l’URL media originale è dietro Cloudflare Access. Imposta images: false quando un altro servizio immagini gestisce i media o quando ogni immagine deve essere renderizzata senza il wrapper dell’endpoint di EmDash.
emdash({
images: false,
});
mediaProviders
Opzionale. Aggiunge servizi media alla libreria media. Il provider locale basato su storage resta disponibile automaticamente; ogni descrittore in questo array aggiunge un altro punto in cui gli editor possono sfogliare o caricare media.
L’esempio seguente aggiunge Cloudflare Images e Cloudflare Stream:
import { cloudflareImages, cloudflareStream } from "@emdash-cms/cloudflare";
emdash({
mediaProviders: [cloudflareImages({}), cloudflareStream({})],
});
Le credenziali del provider vengono risolte a runtime. Le configurazioni vuote sopra usano le variabili d’ambiente Cloudflare predefinite descritte nelle sezioni adapter cloudflareImages(config) e cloudflareStream(config). Per binding e setup di rendering, vedi Libreria media: provider media.
objectCache
Opzionale. Memorizza in cache i risultati delle query su contenuto e configurazione in un archivio chiave/valore, così le letture non interrogano il database a ogni richiesta. Disabilitato se omesso. Scegli un adapter:
// 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();
Vedi Object Cache per configurazione e opzioni.
middleware.outer
Opzionale. Registra un modulo middleware Astro esterno allo stack middleware completo di EmDash. Poiché l’integrazione lo registra in Astro con order: "pre", viene eseguito anche prima del middleware definito in src/middleware.ts. Usalo per gate sulle richieste o cache dell’intera risposta che devono evitare l’inizializzazione di runtime e database in caso di hit, o per header di risposta che dipendono dall’HTML finale di EmDash.
emdash({
middleware: {
outer: "./src/outer-middleware.ts",
},
});
L’ordine di esecuzione è:
- Il middleware esterno viene eseguito fino a
await next(). - EmDash inizializza runtime e database, poi esegue setup, autenticazione e middleware del contesto richiesta.
- La route Astro viene renderizzata.
- EmDash applica mutazioni alla risposta, inclusi HTML per la modifica visuale e header di sicurezza/timing.
next()risolve verso il middleware esterno con quella risposta finale.
Prima di chiamare next(), il middleware ha il normale contesto di richiesta Astro e di esecuzione piattaforma, ma locals.emdash, locals.user, il database e lo stato EmDash scoped alla richiesta non sono disponibili. Una Response anticipata salta completamente EmDash, quindi deve includere gli header di sicurezza e cache necessari. Dopo che next() risolve, è sicuro finalizzare i nonce CSP, mettere in cache il body completo o impostare Content-Length. Se il middleware modifica il body, rimuovi o ricalcola qualsiasi header Content-Length esistente.
L’hook usa l’API middleware di Astro sia su Node che su Cloudflare. Questo esempio minimale con Cloudflare Cache API mette in cache solo risposte HTML anonime e restituisce i hit prima dell’inizializzazione di 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;
});
Su Node, usa la stessa forma di middleware con una cache compatibile con Node come Redis. Chiavi di cache e regole di bypass devono includere ogni proprietà della richiesta che cambia la risposta renderizzata.
playground
Opzionale. Abilita il middleware usato dai playground EmDash usa e getta basati sul browser. Crea un database Durable Object scrivibile per ogni sessione, applica il seed configurato e autentica il visitatore come amministratore anonimo prima che venga eseguito il middleware EmDash normale.
import { playgroundDatabase } from "@emdash-cms/cloudflare";
emdash({
database: playgroundDatabase({ binding: "PLAYGROUND_DB" }),
playground: {
middlewareEntrypoint: "@emdash-cms/cloudflare/db/playground-middleware",
},
});
Questa modalità richiede @emdash-cms/cloudflare e un binding Durable Object. Bypassa il middleware di setup e autenticazione normale, quindi usala solo per siti demo effimeri, non per un CMS di produzione.
plugins
Opzionale. Array di plugin che girano nello stesso processo del sito Astro. I plugin nativi vanno qui. Un plugin compatibile con la sandbox può girare qui se ti fidi del pieno accesso al processo e non ti serve l’isolamento.
L’esempio seguente registra un plugin nativo:
import seoPlugin from "@emdash-cms/plugin-seo";
plugins: [seoPlugin()];
I plugin nativi possono usare direttamente le API di server e framework, quindi non possono essere spostati in sandboxed a meno che il pacchetto non esponga anche un entry point plugin compatibile con la sandbox. Vedi Scegliere un formato plugin per le differenze di authoring e deployment.
sandboxed
Opzionale. Array di plugin compatibili con la sandbox che usano le API plugin dichiarate da EmDash e girano in runtime isolati. Non mettere qui un plugin nativo: il codice nativo può dipendere da accesso a processo e framework che la sandbox non fornisce.
import thirdPartyPlugin from "third-party-emdash-plugin";
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxed: [thirdPartyPlugin()],
sandboxRunner: sandbox(),
});
I plugin in sandbox vengono saltati se non è configurato un sandbox runner utilizzabile. Vedi Sandbox dei plugin per la configurazione del runner su Cloudflare e Node.js.
sandboxRunner
Opzionale. Specifier del modulo per la factory che avvia runtime plugin isolati. È obbligatorio per i plugin in sandboxed e per i plugin del marketplace o del registry.
Su Cloudflare Workers, usa l’adapter sandbox():
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxRunner: sandbox(),
});
I deployment Node.js usano il modulo runner workerd documentato in Sandbox dei plugin: Node.js.
sandbox
Opzionale. Controlla se un sandbox runner configurato isola i plugin. La sandbox è abilitata quando sandboxRunner è configurato. Imposta sandbox: false solo per capire se un problema proviene dal plugin o dal suo runtime sandbox:
emdash({
sandboxRunner: sandbox(),
sandbox: false,
});
Con false, i plugin dichiarati in sandboxed e installati dal marketplace girano nel processo server principale senza isolamento né limiti di risorse. Ripristina la sandbox dopo la diagnosi.
registry
Opzionale. Configura aggregatore e policy del registry plugin. Senza valore esplicito, EmDash usa https://registry.emdashcms.com quando sandboxRunner è configurato e sandbox non è false.
Imposta registry: false per disabilitare la scoperta del registry e i plugin installati dal registry, mantenendo il sandbox runner disponibile per i plugin in sandboxed e i plugin legacy del Marketplace:
emdash({
sandboxRunner: sandbox(),
registry: false,
});
Passa l’URL del servizio registry come stringa, oppure usa un oggetto quando il sito richiede fonti di moderazione o una policy sull’età delle release. L’esempio seguente usa la forma oggetto:
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"],
},
},
});
| Opzione | Tipo | Descrizione |
|---|---|---|
aggregatorUrl | string | URL base del servizio registry. Usa HTTPS in produzione. |
acceptLabelers | string | Opzionale: identificatori decentralizzati (DID) separati da virgola per i servizi di moderazione accettati dalla richiesta. Un DID è un identificatore stabile di account Atmosphere. Questa impostazione non può sovrascrivere la policy del servizio registry. |
policy.minimumReleaseAge | string | number | Trattiene le release più recenti di questa età. Stringa durata ("48h", "7d") o secondi. |
policy.minimumReleaseAgeExclude | string[] | DID publisher o coppie <did>/<plugin-slug> esenti dal trattenimento. |
La policy sull’età delle release esenta la prima release di un pacchetto solo quando il registry segnala una release conservata e conferma di aver osservato il pacchetto in modo continuo. Un pacchetto backfillato, una release precedente eliminata o prove di storico mancanti mantengono il trattenimento. Le esenzioni esplicite per publisher e pacchetto si applicano indipendentemente dallo storico.
Vedi Il registry plugin per il flusso di installazione e il modello di fiducia.
marketplace
Deprecato. URL base usato per aggiornare i plugin installati dal Marketplace legacy. La navigazione del Marketplace e le nuove installazioni non compaiono nell’admin. I plugin Marketplace esistenti restano aggiornabili e disinstallabili finché questa opzione è configurata.
emdash({
marketplace: "https://marketplace.emdashcms.com",
sandboxRunner: sandbox(),
});
Gli URL di produzione devono usare HTTPS; HTTP è accettato solo per localhost e 127.0.0.1 in sviluppo. Mantieni questa opzione finché ogni plugin Marketplace non è stato sostituito o disinstallato, poi rimuovila. Segui Migrare dal Marketplace per la procedura completa.
fonts
Opzionale. Configurazione dei font dell’interfaccia admin.
Per impostazione predefinita, EmDash carica Noto Sans tramite l’Astro Font API. I font vengono scaricati da Google al build time e self-hosted, quindi non ci sono richieste CDN a runtime. Il font base copre latino, cirillico, greco, devanagari e vietnamita.
Per aggiungere supporto ad altri sistemi di scrittura, 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 su Google Fonts (ad es. "arabic" carica Noto Sans Arabic). Tutti i font face condividono un unico nome font-family e usano unicode-range così il browser scarica solo i file necessari per i caratteri presenti nella pagina.
Imposta false per disabilitare del tutto l’iniezione dei font e usare i font di sistema:
emdash({
fonts: false,
})
Il CSS admin usa la variabile CSS --font-emdash, impostata automaticamente dalla configurazione font sopra.
auth
Opzionale. Un adapter di autenticazione. Il login integrato di EmDash usa le passkey; impostare auth le sostituisce con un provider esterno. L’adapter 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 | 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 ruolo per utenti non corrispondenti in roleMapping (vedi Ruoli utente) |
syncRoles | boolean | false | Riapplica roleMapping a ogni login invece che solo al provisioning |
roleMapping | object | — | Mappa nomi gruppo IdP a livelli ruolo EmDash; vince la prima corrispondenza |
authProviders
Opzionale. Array di provider di login collegabili (top-level, accanto a auth). Ogni voce è il risultato della chiamata a una factory del provider, come nell’esempio:
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 richiesta. Accetta{ allowedDIDs, allowedHandles, defaultRole }. Vedi la guida login Atmosphere.
I pacchetti di terze parti possono registrare provider propri con la stessa forma AuthProviderDescriptor — vedi Provider di login.
mcp
Opzionale. Abilita l’endpoint Model Context Protocol (MCP) su /_emdash/api/mcp. L’endpoint è abilitato per impostazione predefinita e richiede un bearer token, quindi abilitarlo non concede accesso anonimo.
Imposta l’opzione su false quando il sito non deve esporre un endpoint MCP:
emdash({
mcp: false,
});
Vedi Riferimento server MCP per la creazione del token e la configurazione del client.
siteUrl
Origin pubblico visibile al browser per il sito (schema + host + porta opzionale, senza path). Impostalo prima di eseguire il setup di produzione. Solo gli host di sviluppo loopback possono completare il setup senza un origin configurato.
Dietro un reverse proxy che termina TLS, Astro.url restituisce l’indirizzo interno (http://localhost:4321) invece di quello pubblico (https://cms.example.com). Questo rompe passkey, corrispondenza origin CSRF, redirect OAuth, redirect di login, discovery MCP, export snapshot, sitemap, robots.txt e dati strutturati JSON-LD. Imposta siteUrl per correggere tutto insieme.
L’integrazione valida questo valore al caricamento: deve essere un URL valido con protocollo http: o https: ed è normalizzato all’origin (il path viene rimosso).
L’esempio seguente imposta l’origin pubblico:
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 in config, EmDash controlla le variabili d’ambiente in ordine: EMDASH_SITE_URL, poi SITE_URL. Utile per deployment container dove l’URL pubblico è impostato a runtime.
Il setup fallisce con SITE_URL_REQUIRED su un host non loopback quando nessuna delle due fonti è impostata. Evita che la prima richiesta di setup non autenticata scelga l’origin usato nelle email di autenticazione successive.
Su Cloudflare Workers, il fallback da variabili d’ambiente legge process.env. Con nodejs_compat, Cloudflare popola process.env per impostazione predefinita per date di compatibilità dal 2025-04-01 in poi. I progetti fissati a una data precedente devono aggiungere anche 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
Opzionale. Origin browser aggiuntivi accettati dalla verifica passkey per un deployment raggiungibile su più hostname.
siteUrl definisce un unico origin canonico. Quando lo stesso deployment EmDash è raggiungibile sotto più hostname che condividono un dominio genitore registrabile (ad es. https://example.com e https://preview.example.com), la verifica passkey rifiuta asserzioni il cui origin non corrisponde esattamente a siteUrl — anche se WebAuthn consente passkey valide tra sottodomini sotto lo stesso rpId.
Dichiara origin aggiuntivi accettati tramite allowedOrigins in astro.config.mjs o la variabile d’ambiente EMDASH_ALLOWED_ORIGINS. L’origin canonico siteUrl resta la fonte di rpId; le voci elencate qui sono accettate al momento della verifica. Le due fonti vengono unite a runtime, così la config può dichiarare origin stabili (versionati, revisionati nel codice) mentre l’env aggiunge extra specifici dell’ambiente (ad es. anteprime PR effimere).
L’esempio seguente dichiara un origin extra in config:
emdash({
siteUrl: "https://example.com",
allowedOrigins: ["https://preview.example.com"],
})
I valori equivalenti possono provenire anche 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 valori per evitare config morta che il browser non onorerebbe mai:
- Ogni voce deve essere un URL
http:ohttps:analizzabile, senza punto finale e senza label vuote nell’hostname. - Quando
allowedOriginsnon è vuoto,siteUrldeve essere impostato (da una delle fonti) e non deve essere un letterale IP né avere un hostname con punto finale. - Ogni origin deve essere lo stesso hostname di
siteUrlo un suo sottodominio. (WebAuthn richiede cherpIdsia un suffisso registrabile di ogni origin.)
Se 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 compare l’errore dipende da dove sono dichiarati i valori:
- All’avvio di Astro, quando sia
config.allowedOriginssiaconfig.siteUrlprovengono daastro.config.mjs— errori di battitura nel codice fanno fallire il build. - Alla prima verifica passkey, quando uno dei valori proviene da
EMDASH_ALLOWED_ORIGINSoEMDASH_SITE_URL— mismatch env compaiono come 500 al primo tentativo di verifica.
Setup reverse proxy
Astro riflette X-Forwarded-* solo quando l’host pubblico è consentito. Configura security.allowedDomains per l’hostname (e gli schema) che i tuoi utenti usano. In astro dev, aggiungi vite.server.allowedHosts corrispondenti così Vite accetta l’header Host del proxy.
Preferisci correggere prima allowedDomains (e gli header inoltrati); usa siteUrl quando l’URL ricostruito continua a divergere dall’origin del browser (tipico quando TLS termina davanti e la richiesta upstream resta http://).
Con TLS davanti, legare il server dev al loopback (astro dev --host 127.0.0.1) spesso basta: il proxy si connette in locale mentre siteUrl corrisponde all’origin HTTPS pubblico.
Se il proxy scrive un header IP client, imposta trustedProxyHeaders così i rate limit di EmDash possono usare l’IP client reale invece di raggruppare ogni richiesta sotto una chiave condivisa “unknown”.
La configurazione seguente imposta allowedDomains, vite.server.allowedHosts e siteUrl insieme per un deployment dietro reverse proxy:
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 da considerare attendibili per la risoluzione dell’IP client quando gira dietro un reverse proxy sotto il tuo controllo. Usato dai rate limit di autenticazione (magic-link, signup, passkey, OAuth device flow) e dall’endpoint commenti pubblico.
Su Cloudflare l’oggetto cf allegato alla richiesta è usato automaticamente — di norma non serve impostarlo. Su deployment self-hosted dietro nginx, Caddy, Traefik, Fly, Railway o simili, impostalo sull’header che scrive il proxy così i rate limit raggruppano per IP client reale invece di trattare ogni richiesta come “unknown”.
L’esempio seguente considera attendibile l’header x-real-ip impostato da nginx, Caddy o Traefik:
emdash({
database: sqlite({ url: "file:./data.db" }),
trustedProxyHeaders: ["x-real-ip"],
});
Gli header sono provati in ordine. I valori che corrispondono a *-forwarded-for sono analizzati come liste separate da virgola e si usa la prima voce. L’esempio seguente preferisce l’header di Fly.io e fa fallback su x-forwarded-for:
emdash({
trustedProxyHeaders: ["fly-client-ip", "x-forwarded-for"],
});
Se non impostato in config, EmDash legge la variabile d’ambiente EMDASH_TRUSTED_PROXY_HEADERS (separata da virgola). Un array vuoto esplicito in config sovrascrive la variabile d’ambiente.
maxUploadSize
Opzionale. Dimensione massima consentita per l’upload di file media, in byte. Si applica sia agli upload multipart diretti sia agli upload con URL firmato. Predefinito 52_428_800 (50 MB). L’esempio seguente alza 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 50 MB |
Gli upload che superano il limite configurato vengono rifiutati con risposta 413 Payload Too Large sul percorso di upload diretto, o 400 Validation Error sul percorso URL firmato.
admin
Opzionale. Sostituisce il branding EmDash nell’interfaccia admin. Questi valori non cambiano titolo, logo o favicon del sito pubblico.
emdash({
admin: {
logo: "/images/agency-logo.webp",
siteName: "Agency CMS",
favicon: "/favicon.ico",
},
});
| Opzione | Tipo | Descrizione |
|---|---|---|
logo | string | URL o path del logo per pagina di login e sidebar |
siteName | string | Nome mostrato nella sidebar e nel titolo del browser |
favicon | string | URL o path del favicon per le pagine admin |
toolbar
Opzionale. Controlla come viene erogata la toolbar dell’editor (la pillola flottante sulle pagine pubbliche). Predefinito "server".
| Valore | Comportamento |
|---|---|
"server" (predefinito) | La toolbar è iniettata lato server in ogni risposta HTML renderizzata per un editor autenticato. |
"client" | L’HTML pubblico è identico per ogni visitatore. Un piccolo script bootstrap mostra una pillola “Edit” nei browser che hanno effettuato login nell’admin; cliccandola verifica la sessione e ricarica la pagina con il parametro query _edit, sempre renderizzato fresco (mai in cache) con la toolbar completa. |
false | Non renderizzare mai la toolbar né lo script bootstrap. |
emdash({
toolbar: "client",
})
Usa "client" quando l’HTML pubblico è servito tramite cache condivisa (Cloudflare Cache Everything / Workers Cache, Fastly, Varnish, …). Con iniezione lato server, un editor che naviga il sito pubblico riceve la variante anonima in cache — senza toolbar — ogni volta che un visitatore anonimo ha riempito la cache per primo, quindi la toolbar compare e scompare con lo stato della cache. In modalità client nulla di specifico della sessione è iniettato nell’HTML condivisibile, così la cache resta pienamente efficace e la toolbar è affidabile.
Note sulla modalità "client":
- I visitatori non autenticati che aprono un URL condiviso
?_editvengono reindirizzati all’URL canonico, così il parametro non può far trapelare bozze o riempire voci cache extra con il contenuto della pagina. - Il segnale “logged in” è un flag
localStoragenon segreto impostato dall’admin; la pillola verifica la sessione reale prima di entrare nella vista modifica. - Il bootstrap è un piccolo
<script>inline. Se il sito invia unaContent-Security-Policyrestrittiva senza'unsafe-inline', aggiungi un hash — lo stesso vale per la toolbar iniettata lato server. - EmDash non inietta nulla di specifico della sessione — ma se i tuoi template ramificano su
Astro.locals.user(ad es. un link nav “Admin” per utenti autenticati), quella varianza resta nel tuo HTML e frammenta ancora la cache.
In ogni modalità, la toolbar può essere chiusa nel browser con il pulsante × (per browser, fino alla prossima apertura dell’admin da un editor). Le risposte in anteprima e in modalità modifica sono sempre renderizzate lato server con Cache-Control: private, no-store.
experimental
Opzionale. Funzionalità opt-in il cui comportamento o formato wire può cambiare o essere rimosso in una release minor. Ogni campo si abilita in modo indipendente.
experimental.registry
Deprecato. Usa l’opzione top-level registry. La configurazione experimental.registry esistente continua a funzionare quando l’opzione top-level è omessa. Se entrambe sono presenti, prevale il valore top-level.
La modifica seguente sposta un URL registry esistente al livello top-level:
emdash({
experimental: {
registry: "https://registry.example.com",
},
registry: "https://registry.example.com",
});
Adapter database
Importa gli adapter da emdash/db:
import { sqlite, libsql, postgres } from "emdash/db";
sqlite(config)
Database SQLite tramite il driver database integrato di Node.js. L’esempio seguente si connette a un file locale:
| Opzione | Tipo | Descrizione |
|---|---|---|
url | string | Path del 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 auth a runtime (opzionale per file locali) |
migrationAuthTokenEnv | string | Nome variabile token migrazione (predefinito TURSO_AUTH_TOKEN) |
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) |
pool.connectionTimeoutMillis | number | Attesa massima connessione (pg predefinito: 0, nessun timeout) |
pool.idleTimeoutMillis | number | Durata client idle (pg predefinito: 10.000 ms) |
migrationConnectionStringEnv | string | Nome variabile connection string migrazione (predefinito DATABASE_URL) |
L’esempio seguente si connette con una connection string:
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 i bookmark di sessione |
coalesce | boolean | false | Raggruppa letture concorrenti nello stesso turno event-loop; richiede modalità sessione diversa da "disabled" |
L’esempio seguente mostra un binding base e uno con read replica abilitate:
// Basic
d1({ binding: "DB" });
// With read replicas
d1({ binding: "DB", session: "auto" });
Quando session è "auto" o "primary-first", EmDash usa la D1 Sessions API per instradare le query di lettura verso replica vicine. Gli utenti autenticati ottengono consistenza read-your-writes basata su bookmark. Vedi Opzioni database — Read replica per i dettagli.
hyperdrive(config?)
PostgreSQL tramite un binding Cloudflare Hyperdrive. Importa questo adapter da @emdash-cms/cloudflare.
| Opzione | Tipo | Predefinito | Descrizione |
|---|---|---|---|
binding | string | "HYPERDRIVE" | Binding Hyperdrive primario con caching query disabilitato |
cachedBinding | string | — | Secondo binding opzionale con cache abilitata per letture pubbliche anonime |
preferUncachedAfterWriteMs | number | 60_000 | Per quanto tempo le letture pubbliche usano il primario dopo una scrittura di contenuto quando cachedBinding è impostato |
migrationConnectionStringEnv | string | Derivato dal binding primario | Variabile d’ambiente con l’URL PostgreSQL diretto usato da emdash migrate |
max | number | 5 | Connessioni massime da un isolate Worker a Hyperdrive |
L’esempio seguente instrada richieste autenticate e scritture tramite il binding senza cache, mentre le letture pubbliche anonime possono usare il binding con cache:
hyperdrive({
binding: "HYPERDRIVE",
cachedBinding: "HYPERDRIVE_CACHED",
preferUncachedAfterWriteMs: 60_000,
});
Entrambi i binding devono puntare allo stesso database. Installa pg versione 8.16.3 o successiva, abilita il flag di compatibilità nodejs_compat e configura un URL database diretto per le migrazioni di deployment. Vedi Opzioni database: Hyperdrive per il setup completo Worker e migrazioni.
durableObjects(config)
Memorizza il CMS in un Durable Object con backend SQLite. Importa questo adapter da @emdash-cms/cloudflare.
| Opzione | Tipo | Predefinito | Descrizione |
|---|---|---|---|
binding | string | obbligatorio | Binding namespace Durable Object per la classe EmDashDB |
name | string | "emdash" | Nome oggetto singleton; cambialo solo per isolare più database dietro un binding |
session | string | "disabled" | "auto" instrada letture anonime alle replica e scritture al primario |
bookmarkCookie | string | "__em_do_bookmark" | Cookie per consistenza read-your-writes in modalità "auto" |
durableObjects({ binding: "DB_DO", session: "auto" });
Il routing verso replica richiede i flag di compatibilità experimental e replica_routing più la classe Durable Object e le voci di migrazione in wrangler.jsonc.
previewDatabase(config)
Crea un database snapshot isolato per sessione di anteprima in un Durable Object. L’unica opzione è il nome binding obbligatorio:
previewDatabase({ binding: "PREVIEW_DB" });
Questo adapter è per l’infrastruttura di anteprima, non per il database primario di un sito di produzione.
playgroundDatabase(config)
Crea un database seeded scrivibile per sessione playground in un Durable Object. Abbinalo all’opzione di integrazione playground:
playgroundDatabase({ binding: "PLAYGROUND_DB" });
Il binding obbligatorio identifica il namespace Durable Object del playground. Usa questo adapter solo per siti demo usa e getta.
Adapter di storage
Importa local e s3 da emdash/astro. L’adapter r2 si importa da @emdash-cms/cloudflare:
import emdash, { local, s3 } from "emdash/astro";
import { r2 } from "@emdash-cms/cloudflare";
local(config)
Storage su filesystem locale. L’esempio seguente serve gli upload da una directory locale:
| Opzione | Tipo | Descrizione |
|---|---|---|
directory | string | Path della 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 usa un binding R2 con 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?)
Storage compatibile S3. Tutti i campi di config sono opzionali: ogni campo omesso da
s3({...}) viene risolto dalla variabile d’ambiente S3_* corrispondente all’avvio del
processo Node. I valori espliciti hanno sempre precedenza.
Dopo l’unione di config e variabili d’ambiente, endpoint e bucket sono obbligatori. Se è impostata una credenziale, servono sia accessKeyId sia secretAccessKey. Valori mancanti fanno fallire l’avvio con codice errore MISSING_S3_CONFIG.
Prerequisito: installa @aws-sdk/client-s3 e @aws-sdk/s3-request-presigner
nel progetto. Il core EmDash non include l’AWS SDK. Vedi
Opzioni di storage: storage compatibile S3
per i 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 config e ambiente o passano ogni campo esplicitamente:
// 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 risoluzione delle variabili d’ambiente a runtime è una funzionalità solo Node. Su Cloudflare
Workers, secret e variabili sono esposti tramite il parametro env del
fetch handler, non tramite process.env, quindi le variabili d’ambiente S3_* non
vengono lette. I deployment Workers dovrebbero usare l’adapter r2(config)
o passare valori espliciti a s3({...}). Vedi
Opzioni di storage per i dettagli.
Adapter object cache
Passane uno all’opzione objectCache.
kvCache(config)
Backend Cloudflare KV, condiviso tra tutti gli isolate. Importa da @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 in-process per Node.js e sviluppo. Importa da 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)
})
Vedi Object Cache per configurazione e comportamento.
Adapter autenticazione e sandbox
Questi adapter restituiscono valori per le opzioni di integrazione auth e sandboxRunner.
access(config)
Sostituisce il login passkey integrato con autenticazione Cloudflare Access. Importalo da @emdash-cms/cloudflare e passa il risultato a auth:
import { access } from "@emdash-cms/cloudflare";
emdash({
auth: access({
teamDomain: "myteam.cloudflareaccess.com",
audienceEnvVar: "CF_ACCESS_AUDIENCE",
}),
});
teamDomain è obbligatorio. L’adapter può leggere l’audience dell’applicazione da audience o dalla variabile indicata da audienceEnvVar; accetta anche autoProvision, defaultRole, syncRoles e roleMapping. L’opzione auth documenta i predefiniti e il comportamento dei ruoli.
sandbox()
Seleziona Cloudflare Worker Loader come sandbox runner dei plugin. Importalo da @emdash-cms/cloudflare e passa il valore restituito a sandboxRunner:
import { sandbox } from "@emdash-cms/cloudflare";
emdash({
sandboxRunner: sandbox(),
});
Il sito richiede anche un binding Worker Loader e l’entry point plugin bridge. Vedi Sandbox dei plugin: Cloudflare Workers per quelle impostazioni di deployment.
Adapter provider media
Passa descrittori provider media a mediaProviders. Entrambi i provider Cloudflare integrati si importano da @emdash-cms/cloudflare.
Ogni opzione *EnvVar sotto indica una variabile d’ambiente. Il provider la legge in questo ordine: un binding Cloudflare Workers con quel nome, poi process.env sull’adapter Node. L’opzione diretta corrispondente (accountId, accountHash, apiToken) ha sempre precedenza su entrambe.
cloudflareImages(config)
Aggiunge Cloudflare Images per sfogliare, caricare, eliminare e distribuire asset immagine.
| Opzione | Tipo | Predefinito | Descrizione |
|---|---|---|---|
accountId | string | Da CF_ACCOUNT_ID | ID account Cloudflare |
accountIdEnvVar | string | "CF_ACCOUNT_ID" | Variabile usata quando accountId è omesso |
accountHash | string | Da CF_IMAGES_ACCOUNT_HASH | Hash account usato negli URL di delivery |
accountHashEnvVar | string | "CF_IMAGES_ACCOUNT_HASH" | Variabile usata quando accountHash è omesso |
apiToken | string | Da CF_IMAGES_TOKEN | Token con permessi lettura e modifica Cloudflare Images |
apiTokenEnvVar | string | "CF_IMAGES_TOKEN" | Variabile usata quando apiToken è omesso |
deliveryDomain | string | imagedelivery.net | Hostname delivery immagini personalizzato |
defaultVariant | string | "public" | Variante immagine usata per la visualizzazione |
mediaProviders: [cloudflareImages({ defaultVariant: "public" })];
cloudflareStream(config)
Aggiunge Cloudflare Stream per sfogliare, cercare, caricare, eliminare e riprodurre asset video.
| Opzione | Tipo | Predefinito | Descrizione |
|---|---|---|---|
accountId | string | Da CF_ACCOUNT_ID | ID account Cloudflare |
accountIdEnvVar | string | "CF_ACCOUNT_ID" | Variabile usata quando accountId è omesso |
apiToken | string | Da CF_STREAM_TOKEN | Token con permessi lettura e modifica Cloudflare Stream |
apiTokenEnvVar | string | "CF_STREAM_TOKEN" | Variabile usata quando apiToken è omesso |
customerSubdomain | string | Predefinito Cloudflare | Hostname delivery Stream personalizzato |
controls | boolean | true | Mostra controlli del player |
autoplay | boolean | false | Avvia riproduzione automaticamente |
loop | boolean | false | Ripeti riproduzione |
muted | boolean | false, o true con autoplay | Audio disattivato |
mediaProviders: [cloudflareStream({ controls: true })];
Vedi Libreria media: provider media per i binding richiesti e i componenti di rendering.
Adapter cache Astro
cloudflareCache(config?)
L’adapter legacy restituisce un cache.provider Astro che memorizza le risposte nella Workers Cache API e purga i tag cache tramite Cloudflare REST API:
import { cloudflareCache } from "@emdash-cms/cloudflare";
export default defineConfig({
cache: {
provider: cloudflareCache(),
},
});
Accetta cacheName (predefinito "emdash") e bookmarkCookie (predefinito "__em_d1_bookmark"), più zoneId o zoneIdEnvVar e apiToken o apiTokenEnvVar per richieste purge-by-tag. I nomi variabile predefiniti sono CF_ZONE_ID e CF_CACHE_PURGE_TOKEN.
Collezioni live
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 loader
La funzione emdashLoader() non accetta argomenti:
emdashLoader();
Variabili d’ambiente
EmDash rispetta queste variabili d’ambiente:
| Variabile | Descrizione |
|---|---|
EMDASH_SITE_URL | Origin pubblico visibile al browser (fallback su SITE_URL) |
EMDASH_ALLOWED_ORIGINS | Elenco separato da virgola di origin aggiuntivi accettati dalla verifica passkey (deployment multi-sottodominio). |
EMDASH_DATABASE_URL | Override URL database |
EMDASH_ENCRYPTION_KEY | Chiave per cifrare i secret dei plugin at rest. Fornita dall’operatore — mai memorizzata nel database. |
EMDASH_PREVIEW_SECRET | Override opzionale del secret HMAC anteprima. Se non impostato, viene generato e memorizzato nel database un valore stabile per sito. |
EMDASH_IP_SALT | Override opzionale del salt per l’hash IP commentatori. Se non impostato, viene generato e memorizzato nel database un valore stabile per sito. |
EMDASH_AUTH_SECRET | Legacy. Usato come fonte salt IP se impostato; le installazioni esistenti dovrebbero mantenerlo per preservare hash IP commentatori stabili dopo l’upgrade. |
EMDASH_TURNSTILE_SECRET_KEY | Chiave segreta Cloudflare Turnstile (fallback su TURNSTILE_SECRET_KEY). Se impostata, gli invii commento devono includere un token Turnstile valido — abbinala alla prop turnstileSiteKey su <CommentForm>. |
EMDASH_URL | URL EmDash remoto per sync schema |
Genera una chiave di cifratura con il comando seguente:
npx emdash secrets generate
Configurazione package.json
Template e siti possono dichiarare metadati opzionali sotto una chiave emdash in package.json:
{
"emdash": {
"label": "My Blog Template",
"schema": ".emdash/schema.sql",
"seed": ".emdash/seed.json",
"url": "https://my-site.pages.dev"
}
}
| Opzione | Descrizione |
|---|---|
label | Nome template per la visualizzazione |
schema | Schema SQL opzionale letto da emdash init |
seed | Path al file seed JSON |
url | URL remoto usato dal flusso deprecato emdash dev --types |
Configurazione TypeScript
Durante lo sviluppo locale, l’integrazione Astro genera emdash-env.d.ts nella root del progetto e lo aggiorna dopo modifiche allo schema. Il file estende il modulo emdash, così gli import standard getEmDashCollection() e getEmDashEntry() inferiscono i campi delle collection locali senza alias di path.
Il comando separato emdash types recupera lo schema da un’istanza locale o remota in esecuzione e scrive .emdash/types.ts per impostazione predefinita. Aggiungi un alias solo quando il codice applicativo importa direttamente quell’output standalone:
{
"compilerOptions": {
"paths": {
"@emdash-cms/types": ["./.emdash/types.ts"]
}
}
}
Genera i tipi schema remoto standalone con il comando seguente:
npx emdash types