I file seed sono documenti JSON che inizializzano i siti EmDash. Definiscono collezioni, campi, tassonomie, menu, redirect, aree widget, impostazioni del sito e contenuto di esempio opzionale.
Struttura Root
Un file seed ha la seguente struttura di livello superiore:
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"meta": {},
"settings": {},
"collections": [],
"taxonomies": [],
"bylines": [],
"menus": [],
"redirects": [],
"widgetAreas": [],
"sections": [],
"content": {}
}
| Campo | Tipo | Richiesto | Descrizione |
|---|---|---|---|
$schema | string | No | URL dello schema JSON per la validazione dell’editor |
version | "1" | Sì | Versione del formato seed |
meta | object | No | Metadati sul seed |
settings | object | No | Impostazioni del sito |
collections | array | No | Definizioni di collezioni |
taxonomies | array | No | Definizioni di tassonomie |
bylines | array | No | Definizioni di profili byline |
menus | array | No | Menu di navigazione |
redirects | array | No | Regole di redirect |
widgetAreas | array | No | Definizioni di aree widget |
sections | array | No | Blocchi di contenuto riutilizzabili |
content | object | No | Voci di contenuto di esempio |
Meta
L’oggetto meta contiene metadati descrittivi opzionali sul seed:
{
"meta": {
"name": "Blog Starter",
"description": "A simple blog with posts, pages, and categories",
"author": "EmDash"
}
}
Settings
L’oggetto settings contiene valori di configurazione a livello di sito:
{
"settings": {
"title": "My Site",
"tagline": "A modern CMS",
"postsPerPage": 10,
"dateFormat": "MMMM d, yyyy"
}
}
Le impostazioni vengono applicate alla tabella options con il prefisso site:. L’Assistente di Configurazione precompilerà title e tagline dal file seed (se fornito), consentendo agli utenti di sovrascriverli durante la configurazione iniziale.
Collections
Ogni definizione di collezione crea un tipo di contenuto nel database:
{
"collections": [
{
"slug": "posts",
"label": "Posts",
"labelSingular": "Post",
"description": "Blog posts",
"icon": "file-text",
"supports": ["drafts", "revisions"],
"fields": [
{
"slug": "title",
"label": "Title",
"type": "string",
"required": true
},
{
"slug": "content",
"label": "Content",
"type": "portableText"
},
{
"slug": "featured_image",
"label": "Featured Image",
"type": "image"
}
]
}
]
}
Proprietà di Collection
| Proprietà | Tipo | Richiesto | Descrizione |
|---|---|---|---|
slug | string | Sì | Identificatore sicuro per URL (minuscole, underscore) |
label | string | Sì | Nome visualizzato al plurale |
labelSingular | string | No | Nome visualizzato al singolare |
description | string | No | Descrizione dell’interfaccia amministrativa |
icon | string | No | Nome dell’icona Lucide |
supports | array | No | Funzionalità: "drafts", "revisions" |
fields | array | Sì | Definizioni dei campi |
Proprietà di Field
| Proprietà | Tipo | Richiesto | Descrizione |
|---|---|---|---|
slug | string | Sì | Nome della colonna (minuscole, underscore) |
label | string | Sì | Nome visualizzato |
type | string | Sì | Tipo di campo |
required | boolean | No | Validazione: il campo deve avere un valore |
unique | boolean | No | Validazione: il valore deve essere univoco |
defaultValue | any | No | Valore predefinito per nuove voci |
validation | object | No | Regole di validazione aggiuntive |
widget | string | No | Override del widget dell’interfaccia amministrativa |
options | object | No | Configurazione specifica del widget |
Tipi di Field
| Tipo | Descrizione | Memorizzato come |
|---|---|---|
string | Testo breve | TEXT |
text | Testo lungo (textarea) | TEXT |
number | Valore numerico | REAL |
integer | Numero intero | INTEGER |
boolean | Vero/falso | INTEGER |
date | Valore di data | TEXT (ISO 8601) |
datetime | Data e ora | TEXT (ISO 8601) |
email | Indirizzo email | TEXT |
url | URL | TEXT |
slug | Stringa sicura per URL | TEXT |
portableText | Contenuto rich text | JSON |
image | Riferimento immagine | JSON |
file | Riferimento file | JSON |
json | JSON arbitrario | JSON |
reference | Riferimento ad un’altra voce | TEXT |
Taxonomies
Le tassonomie sono sistemi di classificazione per il contenuto:
{
"taxonomies": [
{
"name": "category",
"label": "Categories",
"labelSingular": "Category",
"hierarchical": true,
"collections": ["posts"],
"terms": [
{ "slug": "news", "label": "News" },
{ "slug": "tutorials", "label": "Tutorials" },
{
"slug": "advanced",
"label": "Advanced Tutorials",
"parent": "tutorials"
}
]
},
{
"name": "tag",
"label": "Tags",
"labelSingular": "Tag",
"hierarchical": false,
"collections": ["posts"]
}
]
}
Proprietà di Taxonomy
| Proprietà | Tipo | Richiesto | Descrizione |
|---|---|---|---|
name | string | Sì | Identificatore univoco |
label | string | Sì | Nome visualizzato al plurale |
labelSingular | string | No | Nome visualizzato al singolare |
hierarchical | boolean | Sì | Consenti termini nidificati (categorie) o piatti (tag) |
collections | array | Sì | Collezioni a cui si applica questa tassonomia |
terms | array | No | Termini predefiniti |
Proprietà di Term
| Proprietà | Tipo | Richiesto | Descrizione |
|---|---|---|---|
slug | string | Sì | Identificatore sicuro per URL |
label | string | Sì | Nome visualizzato |
description | string | No | Descrizione del termine |
parent | string | No | Slug del termine padre (solo gerarchico) |
Menus
L’array menus definisce i menu di navigazione modificabili dall’amministratore:
{
"menus": [
{
"name": "primary",
"label": "Primary Navigation",
"items": [
{ "type": "custom", "label": "Home", "url": "/" },
{ "type": "page", "ref": "about" },
{ "type": "custom", "label": "Blog", "url": "/posts" },
{
"type": "custom",
"label": "External",
"url": "https://example.com",
"target": "_blank"
}
]
}
]
}
Tipi di Menu Item
| Tipo | Descrizione | Campi richiesti |
|---|---|---|
custom | URL personalizzato | url |
page | Link a una voce di pagina | ref |
post | Link a una voce di post | ref |
taxonomy | Link a un archivio di tassonomia | ref, collection |
collection | Link a un archivio di collezione | collection |
Proprietà di Menu Item
| Proprietà | Tipo | Descrizione |
|---|---|---|
type | string | Tipo di elemento (vedi sopra) |
label | string | Testo visualizzato (auto-generato per ref pagina/post) |
url | string | URL personalizzato (per tipo custom) |
ref | string | ID contenuto nel seed (per tipi page/post) |
collection | string | Slug di collezione |
target | string | "_blank" per nuova finestra |
titleAttr | string | Attributo title HTML |
cssClasses | string | Classi CSS personalizzate |
children | array | Elementi di menu nidificati |
Bylines
I profili byline sono separati dalla proprietà (author_id). Definisci identità byline riutilizzabili una volta, quindi fai riferimento ad esse dalle voci di contenuto.
{
"bylines": [
{
"id": "editorial",
"slug": "emdash-editorial",
"displayName": "EmDash Editorial"
},
{
"id": "guest",
"slug": "guest-contributor",
"displayName": "Guest Contributor",
"isGuest": true
}
]
}
| Proprietà | Tipo | Richiesto | Descrizione |
|---|---|---|---|
id | string | Sì | ID locale del seed usato da content[].bylines |
slug | string | Sì | Slug byline sicuro per URL |
displayName | string | Sì | Nome mostrato in template e API |
bio | string | No | Biografia del profilo opzionale |
websiteUrl | string | No | URL del sito web opzionale |
isGuest | boolean | No | Contrassegna byline come profilo ospite |
Redirects
L’array redirects definisce le regole di redirect che preservano gli URL legacy dopo la migrazione:
{
"redirects": [
{ "source": "/old-about", "destination": "/about" },
{ "source": "/legacy-feed", "destination": "/rss.xml", "type": 308 },
{
"source": "/category/news",
"destination": "/categories/news",
"groupName": "migration"
}
]
}
Proprietà di Redirect
| Proprietà | Tipo | Richiesto | Descrizione |
|---|---|---|---|
source | string | Sì | Percorso sorgente (deve iniziare con /) |
destination | string | Sì | Percorso di destinazione (deve iniziare con /) |
type | number | No | Stato HTTP: 301, 302, 307 o 308 |
enabled | boolean | No | Se il redirect è attivo (predefinito: true) |
groupName | string | No | Etichetta di raggruppamento opzionale per filtro/ricerca admin |
Widget Areas
L’array widgetAreas definisce regioni di contenuto configurabili:
{
"widgetAreas": [
{
"name": "sidebar",
"label": "Main Sidebar",
"description": "Appears on blog posts and pages",
"widgets": [
{
"type": "component",
"title": "Recent Posts",
"componentId": "core:recent-posts",
"props": { "count": 5 }
},
{
"type": "menu",
"title": "Quick Links",
"menuName": "footer"
},
{
"type": "content",
"title": "About",
"content": [
{
"_type": "block",
"style": "normal",
"children": [{ "_type": "span", "text": "Welcome to our site!" }]
}
]
}
]
}
]
}
Tipi di Widget
| Tipo | Descrizione | Campi richiesti |
|---|---|---|
content | Contenuto rich text | content (Portable Text) |
menu | Renderizza un menu | menuName |
component | Componente registrato | componentId |
Componenti Integrati
| ID componente | Descrizione |
|---|---|
core:recent-posts | Elenco di post recenti |
core:categories | Elenco di categorie |
core:tags | Tag cloud |
core:search | Form di ricerca |
core:archives | Archivi mensili |
Sections
Le sezioni sono blocchi di contenuto riutilizzabili che gli editor inseriscono nei campi Portable Text tramite il comando slash /section:
{
"sections": [
{
"slug": "hero-centered",
"title": "Centered Hero",
"description": "Full-width hero with centered heading and CTA button",
"keywords": ["hero", "banner", "header", "landing"],
"content": [
{
"_type": "block",
"style": "h1",
"children": [{ "_type": "span", "text": "Welcome to Our Site" }]
},
{
"_type": "block",
"children": [
{ "_type": "span", "text": "Your compelling tagline goes here." }
]
}
]
}
]
}
Proprietà di Section
| Proprietà | Tipo | Richiesto | Descrizione |
|---|---|---|---|
slug | string | Sì | Identificatore sicuro per URL |
title | string | Sì | Nome visualizzato mostrato nel selettore di sezioni |
description | string | No | Spiega quando usare questa sezione |
keywords | array | No | Termini di ricerca per trovare la sezione |
content | array | Sì | Blocchi Portable Text |
source | string | No | "theme" (predefinito per i seed) o "import" |
Le sezioni dai file seed sono contrassegnate con source: "theme" e non possono essere eliminate dall’interfaccia amministrativa. Gli editor possono creare le proprie sezioni (source: "user") e inserire qualsiasi tipo di sezione durante la modifica del contenuto.
Content
L’oggetto content contiene voci di contenuto di esempio organizzate per collezione:
{
"content": {
"posts": [
{
"id": "hello-world",
"slug": "hello-world",
"status": "published",
"bylines": [
{ "byline": "editorial" },
{ "byline": "guest", "roleLabel": "Guest essay" }
],
"data": {
"title": "Hello World",
"content": [
{
"_type": "block",
"style": "normal",
"children": [{ "_type": "span", "text": "Welcome!" }]
}
],
"excerpt": "Your first post."
},
"taxonomies": {
"category": ["news"],
"tag": ["welcome", "first-post"]
}
}
],
"pages": [
{
"id": "about",
"slug": "about",
"status": "published",
"data": {
"title": "About Us",
"content": [
{
"_type": "block",
"style": "normal",
"children": [{ "_type": "span", "text": "About page content." }]
}
]
}
}
]
}
}
Proprietà di Content Entry
| Proprietà | Tipo | Richiesto | Descrizione |
|---|---|---|---|
id | string | Sì | ID locale del seed per riferimenti |
slug | string | Sì | Slug URL |
status | string | No | "published" o "draft" (predefinito: "published") |
data | object | Sì | Valori dei campi |
bylines | array | No | Crediti byline ordinati (byline, opzionale roleLabel) |
taxonomies | object | No | Assegnazioni di termini per nome di tassonomia |
Content References
Fai riferimento ad altre voci di contenuto usando il prefisso $ref::
{
"data": {
"related_posts": ["$ref:another-post", "$ref:third-post"]
}
}
Il prefisso $ref: risolve gli ID del seed in ID del database durante il seeding.
Media References
Includere immagini da URL:
{
"data": {
"featured_image": {
"$media": {
"url": "https://images.unsplash.com/photo-xxx",
"alt": "Description of the image",
"filename": "hero.jpg",
"caption": "Photo by Someone"
}
}
}
}
Includere immagini locali da .emdash/media/:
{
"data": {
"featured_image": {
"$media": {
"file": "hero.jpg",
"alt": "Description of the image"
}
}
}
}
Proprietà di Media
| Proprietà | Tipo | Richiesto | Descrizione |
|---|---|---|---|
url | string | Sì* | URL remoto da scaricare |
file | string | Sì* | Nome file locale in .emdash/media/ |
alt | string | No | Testo alternativo per l’accessibilità |
filename | string | No | Sovrascrivere il nome file |
caption | string | No | Didascalia del media |
*È richiesto url o file, non entrambi.
Applying Seeds Programmatically
Usa l’API seed per strumenti CLI o script:
import { applySeed, validateSeed } from "emdash/seed";
import seedData from "./.emdash/seed.json";
// Prima valida
const validation = validateSeed(seedData);
if (!validation.valid) {
console.error(validation.errors);
process.exit(1);
}
// Applica il seed
const result = await applySeed(db, seedData, {
includeContent: true,
onConflict: "skip",
storage: myStorage,
baseUrl: "http://localhost:4321",
});
console.log(result);
// {
// collections: { created: 2, skipped: 0 },
// fields: { created: 8, skipped: 0 },
// taxonomies: { created: 2, terms: 5 },
// bylines: { created: 2, skipped: 0 },
// menus: { created: 1, items: 4 },
// redirects: { created: 3, skipped: 0 },
// widgetAreas: { created: 1, widgets: 3 },
// settings: { applied: 3 },
// content: { created: 3, skipped: 0 },
// media: { created: 2, skipped: 0 }
// }
Opzioni Apply
| Opzione | Tipo | Predefinito | Descrizione |
|---|---|---|---|
includeContent | boolean | false | Creare voci di contenuto di esempio |
onConflict | string | "skip" | "skip", "update" o "error" |
mediaBasePath | string | — | Percorso base per i file media locali |
storage | Storage | — | Adapter di storage per caricamenti media |
baseUrl | string | — | URL base per URL media |
Idempotency
Il seeding è sicuro da eseguire più volte. Comportamento di conflitto per tipo di entità:
| Entità | Comportamento |
|---|---|
| Collection | Salta se esiste lo slug |
| Field | Salta se esiste collection + slug |
| Definizione tassonomia | Salta se esiste il nome |
| Termine tassonomia | Salta se esiste nome + slug |
| Profilo byline | Salta se esiste lo slug |
| Menu | Salta se esiste il nome |
| Elementi menu | Sostituisci tutti (il menu viene ricreato) |
| Redirect | Salta se esiste la sorgente |
| Area widget | Salta se esiste il nome |
| Widgets | Sostituisci tutti (l’area viene ricreata) |
| Sezione | Salta se esiste lo slug |
| Impostazioni | Aggiorna (le impostazioni sono destinate a cambiare) |
| Contenuto | Salta se esiste lo slug nella collezione |
Validation
I file seed sono validati prima dell’applicazione:
import { validateSeed } from "emdash/seed";
const { valid, errors, warnings } = validateSeed(seedData);
if (!valid) {
errors.forEach((e) => console.error(e));
}
warnings.forEach((w) => console.warn(w));
La validazione verifica che:
- I campi richiesti siano presenti
- Gli slug siano validi per il loro tipo (gli slug di collection e field consentono lettere minuscole, cifre e underscore; altri slug consentono anche trattini)
- I tipi di campo siano validi
- I riferimenti puntino a contenuto esistente
- I genitori di termini gerarchici esistano
- I percorsi di redirect siano URL locali sicuri
- Le sorgenti di redirect siano univoche
- Non ci siano slug duplicati all’interno delle collezioni
CLI Commands
Il file seed a .emdash/seed.json, package.json#emdash.seed o seed/seed.json viene integrato nella build e applicato alla prima richiesta quando il database è vuoto. Per esportare lo schema di un sito esistente (e opzionalmente il suo contenuto) come file seed:
# `mkdir -p` perché .emdash/ potrebbe non esistere ancora su un progetto nuovo
mkdir -p .emdash
# Esporta lo schema corrente come file seed
npx emdash export-seed > .emdash/seed.json
# Esporta con il contenuto
npx emdash export-seed --with-content > .emdash/seed.json
Next Steps
- Creating Themes — Creare un tema completo
- Themes Overview — Come funzionano i temi