Seed-Dateien sind JSON-Dokumente, die EmDash-Sites bootstrappen. Sie definieren Collections, Felder, Taxonomien, Menüs, Redirects, Widget-Bereiche, Site-Einstellungen und optionale Beispielinhalte.
Grundstruktur
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"meta": {},
"settings": {},
"collections": [],
"taxonomies": [],
"bylines": [],
"menus": [],
"redirects": [],
"widgetAreas": [],
"sections": [],
"content": {}
}
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
$schema | string | Nein | JSON-Schema-URL für Editor-Validierung |
version | "1" | Ja | Version des Seed-Formats |
meta | object | Nein | Metadaten über die Seed-Datei |
settings | object | Nein | Site-Einstellungen |
collections | array | Nein | Collection-Definitionen |
taxonomies | array | Nein | Taxonomie-Definitionen |
bylines | array | Nein | Byline-Profildefinitionen |
menus | array | Nein | Navigationsmenüs |
redirects | array | Nein | Weiterleitungsregeln |
widgetAreas | array | Nein | Widget-Bereich-Definitionen |
sections | array | Nein | Wiederverwendbare Inhaltsblöcke |
content | object | Nein | Beispiel-Inhaltseinträge |
Meta
Optionale Metadaten über die Seed-Datei:
{
"meta": {
"name": "Blog Starter",
"description": "A simple blog with posts, pages, and categories",
"author": "EmDash"
}
}
Einstellungen
Seitenweite Konfigurationswerte:
{
"settings": {
"title": "My Site",
"tagline": "A modern CMS",
"postsPerPage": 10,
"dateFormat": "MMMM d, yyyy"
}
}
Einstellungen werden in die options-Tabelle mit dem Präfix site: geschrieben. Der Setup-Assistent ermöglicht es Benutzern, title und tagline zu überschreiben.
Collections
Collection-Definitionen erstellen Inhaltstypen in der Datenbank:
{
"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"
}
]
}
]
}
Collection-Eigenschaften
| Eigenschaft | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
slug | string | Ja | URL-sicherer Bezeichner (Kleinbuchstaben, Unterstriche) |
label | string | Ja | Anzeigename (Plural) |
labelSingular | string | Nein | Anzeigename (Singular) |
description | string | Nein | Beschreibung in der Admin-UI |
icon | string | Nein | Lucide-Icon-Name |
supports | array | Nein | Features: "drafts", "revisions" |
fields | array | Ja | Felddefinitionen |
Feldeigenschaften
| Eigenschaft | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
slug | string | Ja | Spaltenname (Kleinbuchstaben, Unterstriche) |
label | string | Ja | Anzeigename |
type | string | Ja | Feldtyp |
required | boolean | Nein | Validierung: Feld muss einen Wert haben |
unique | boolean | Nein | Validierung: Wert muss eindeutig sein |
defaultValue | any | Nein | Standardwert für neue Einträge |
validation | object | Nein | Zusätzliche Validierungsregeln |
widget | string | Nein | Widget-Überschreibung in der Admin-UI |
options | object | Nein | Widget-spezifische Konfiguration |
Feldtypen
| Typ | Beschreibung | Gespeichert als |
|---|---|---|
string | Kurzer Text | TEXT |
text | Langer Text (Textarea) | TEXT |
number | Numerischer Wert | REAL |
integer | Ganze Zahl | INTEGER |
boolean | Wahr/Falsch | INTEGER |
date | Datumswert | TEXT (ISO 8601) |
datetime | Datum und Uhrzeit | TEXT (ISO 8601) |
email | E-Mail-Adresse | TEXT |
url | URL | TEXT |
slug | URL-sicherer String | TEXT |
portableText | Rich-Text-Inhalt | JSON |
image | Bildreferenz | JSON |
file | Dateireferenz | JSON |
json | Beliebiges JSON | JSON |
reference | Verweis auf anderen Eintrag | TEXT |
Taxonomien
Klassifikationssysteme für Inhalte:
{
"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"]
}
]
}
Taxonomie-Eigenschaften
| Eigenschaft | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
name | string | Ja | Eindeutiger Bezeichner |
label | string | Ja | Anzeigename (Plural) |
labelSingular | string | Nein | Anzeigename (Singular) |
hierarchical | boolean | Ja | Verschachtelte Begriffe erlauben (Kategorien) oder flach (Tags) |
collections | array | Ja | Collections, auf die diese Taxonomie angewendet wird |
terms | array | Nein | Vordefinierte Begriffe |
Begriff-Eigenschaften
| Eigenschaft | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
slug | string | Ja | URL-sicherer Bezeichner |
label | string | Ja | Anzeigename |
description | string | Nein | Beschreibung des Begriffs |
parent | string | Nein | Slug des Elternbegriffs (nur hierarchisch) |
Menüs
Navigationsmenüs, die im Admin bearbeitet werden können:
{
"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"
}
]
}
]
}
Menüeintrag-Typen
| Typ | Beschreibung | Erforderliche Felder |
|---|---|---|
custom | Benutzerdefinierte URL | url |
page | Link zu einem Seiteneintrag | ref |
post | Link zu einem Beitragseintrag | ref |
taxonomy | Link zu einem Taxonomie-Archiv | ref, collection |
collection | Link zu einem Collection-Archiv | collection |
Menüeintrag-Eigenschaften
| Eigenschaft | Typ | Beschreibung |
|---|---|---|
type | string | Eintragstyp (siehe oben) |
label | string | Anzeigetext (automatisch generiert für page/post-Refs) |
url | string | Benutzerdefinierte URL (für Typ custom) |
ref | string | Inhalts-ID in der Seed-Datei (für page/post-Typen) |
collection | string | Collection-Slug |
target | string | "_blank" für neues Fenster |
titleAttr | string | HTML-title-Attribut |
cssClasses | string | Eigene CSS-Klassen |
children | array | Verschachtelte Menüeinträge |
Bylines
Byline-Profile sind von der Eigentümerschaft (author_id) getrennt. Definieren Sie wiederverwendbare Byline-Identitäten einmal und referenzieren Sie sie aus Inhaltseinträgen.
{
"bylines": [
{
"id": "editorial",
"slug": "emdash-editorial",
"displayName": "EmDash Editorial"
},
{
"id": "guest",
"slug": "guest-contributor",
"displayName": "Guest Contributor",
"isGuest": true
}
]
}
| Eigenschaft | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Seed-lokale ID, verwendet von content[].bylines |
slug | string | Ja | URL-sicherer Byline-Slug |
displayName | string | Ja | In Templates und APIs angezeigter Name |
bio | string | Nein | Optionale Profilbeschreibung |
websiteUrl | string | Nein | Optionale Website-URL |
isGuest | boolean | Nein | Markiert Byline als Gastprofil |
Redirects
Weiterleitungsregeln zur Beibehaltung alter URLs nach einer Migration:
{
"redirects": [
{ "source": "/old-about", "destination": "/about" },
{ "source": "/legacy-feed", "destination": "/rss.xml", "type": 308 },
{
"source": "/category/news",
"destination": "/categories/news",
"groupName": "migration"
}
]
}
Redirect-Eigenschaften
| Eigenschaft | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
source | string | Ja | Quellpfad (muss mit / beginnen) |
destination | string | Ja | Zielpfad (muss mit / beginnen) |
type | number | Nein | HTTP-Status: 301, 302, 307 oder 308 |
enabled | boolean | Nein | Ob die Weiterleitung aktiv ist (Standard: true) |
groupName | string | Nein | Optionales Gruppierungslabel für Admin-Filter/Suche |
Widget-Bereiche
Konfigurierbare Inhaltsbereiche:
{
"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!" }]
}
]
}
]
}
]
}
Widget-Typen
| Typ | Beschreibung | Erforderliche Felder |
|---|---|---|
content | Rich-Text-Inhalt | content (Portable Text) |
menu | Rendert ein Menü | menuName |
component | Registrierte Komponente | componentId |
Eingebaute Komponenten
| Komponenten-ID | Beschreibung |
|---|---|
core:recent-posts | Liste aktueller Beiträge |
core:categories | Kategorieliste |
core:tags | Tag-Cloud |
core:search | Suchformular |
core:archives | Monatsarchive |
Sections
Wiederverwendbare Inhaltsblöcke, die Redakteure per Slash-Befehl /section in Portable-Text-Felder einfügen können:
{
"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." }
]
}
]
}
]
}
Section-Eigenschaften
| Eigenschaft | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
slug | string | Ja | URL-sicherer Bezeichner |
title | string | Ja | Anzeigename in der Section-Auswahl |
description | string | Nein | Erklärt, wann diese Section verwendet werden soll |
keywords | array | Nein | Suchbegriffe zum Finden der Section |
content | array | Ja | Portable-Text-Blöcke |
source | string | Nein | "theme" (Standard für Seeds) oder "import" |
Sections aus Seed-Dateien werden mit source: "theme" markiert und können nicht aus der Admin-UI gelöscht werden. Redakteure können eigene Sections erstellen (source: "user") und beliebige Section-Typen beim Bearbeiten von Inhalten einfügen.
Inhalte
Beispielinhalte nach Collection geordnet:
{
"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." }]
}
]
}
}
]
}
}
Inhaltseintrag-Eigenschaften
| Eigenschaft | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
id | string | Ja | Seed-lokale ID für Referenzen |
slug | string | Ja | URL-Slug |
status | string | Nein | "published" oder "draft" (Standard: "published") |
data | object | Ja | Feldwerte |
bylines | array | Nein | Geordnete Byline-Credits (byline, optionales roleLabel) |
taxonomies | object | Nein | Begriff-Zuordnungen nach Taxonomie-Name |
Inhaltsreferenzen
Referenzieren Sie andere Inhaltseinträge mit dem Präfix $ref::
{
"data": {
"related_posts": ["$ref:another-post", "$ref:third-post"]
}
}
Das Präfix $ref: löst Seed-IDs beim Seeding in Datenbank-IDs auf.
Medienreferenzen
Bilder von URLs einbinden:
{
"data": {
"featured_image": {
"$media": {
"url": "https://images.unsplash.com/photo-xxx",
"alt": "Description of the image",
"filename": "hero.jpg",
"caption": "Photo by Someone"
}
}
}
}
Lokale Bilder aus .emdash/media/ einbinden:
{
"data": {
"featured_image": {
"$media": {
"file": "hero.jpg",
"alt": "Description of the image"
}
}
}
}
Medien-Eigenschaften
| Eigenschaft | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
url | string | Ja* | Remote-URL zum Herunterladen |
file | string | Ja* | Lokaler Dateiname in .emdash/media/ |
alt | string | Nein | Alt-Text für Barrierefreiheit |
filename | string | Nein | Dateinamen überschreiben |
caption | string | Nein | Medien-Beschriftung |
*Entweder url oder file ist erforderlich, nicht beides.
Seeds programmatisch anwenden
Verwenden Sie die Seed-API für CLI-Tools oder Skripte:
import { applySeed, validateSeed } from "emdash/seed";
import seedData from "./.emdash/seed.json";
// Zuerst validieren
const validation = validateSeed(seedData);
if (!validation.valid) {
console.error(validation.errors);
process.exit(1);
}
// Seed anwenden
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 }
// }
Anwenden-Optionen
| Option | Typ | Standard | Beschreibung |
|---|---|---|---|
includeContent | boolean | false | Beispiel-Inhaltseinträge erstellen |
onConflict | string | "skip" | "skip", "update" oder "error" |
mediaBasePath | string | — | Basispfad für lokale Mediendateien |
storage | Storage | — | Storage-Adapter für Medien-Uploads |
baseUrl | string | — | Basis-URL für Medien-URLs |
Idempotenz
Seeding kann sicher mehrmals ausgeführt werden. Konfliktverhalten nach Entitätstyp:
| Entität | Verhalten |
|---|---|
| Collection | Überspringen, wenn Slug existiert |
| Feld | Überspringen, wenn Collection + Slug existiert |
| Taxonomie-Definition | Überspringen, wenn Name existiert |
| Taxonomie-Begriff | Überspringen, wenn Name + Slug existiert |
| Byline-Profil | Überspringen, wenn Slug existiert |
| Menü | Überspringen, wenn Name existiert |
| Menüeinträge | Ersetzen (Menü wird neu erstellt) |
| Redirect | Überspringen, wenn Source existiert |
| Widget-Bereich | Überspringen, wenn Name existiert |
| Widgets | Ersetzen (Bereich wird neu erstellt) |
| Section | Überspringen, wenn Slug existiert |
| Einstellungen | Aktualisieren (Einstellungen sollen sich ändern) |
| Inhalt | Überspringen, wenn Slug in Collection existiert |
Validierung
Seed-Dateien werden vor der Anwendung validiert:
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));
Validierungsprüfungen:
- Erforderliche Felder sind vorhanden
- Slugs folgen Namenskonventionen (Kleinbuchstaben, Unterstriche)
- Feldtypen sind gültig
- Referenzen zeigen auf vorhandene Inhalte
- Elternbegriffe hierarchischer Begriffe existieren
- Redirect-Pfade sind sichere lokale URLs
- Redirect-Sources sind eindeutig
- Keine doppelten Slugs innerhalb von Collections
CLI-Befehle
# Seed-Datei anwenden
npx emdash seed .emdash/seed.json
# Ohne Beispielinhalte anwenden
npx emdash seed .emdash/seed.json --no-content
# Nur validieren
npx emdash seed .emdash/seed.json --validate
# Aktuelles Schema als Seed exportieren
npx emdash export-seed > seed.json
# Mit Inhalten exportieren
npx emdash export-seed --with-content > seed.json
Nächste Schritte
- Themes erstellen — Ein vollständiges Theme bauen
- Themes-Überblick — Wie Themes funktionieren