Les fichiers seed sont des documents JSON qui initialisent les sites EmDash. Ils définissent les collections, champs, taxonomies, menus, redirections, zones de widgets, paramètres du site et du contenu d’exemple optionnel.
Structure racine
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"meta": {},
"settings": {},
"collections": [],
"taxonomies": [],
"bylines": [],
"menus": [],
"redirects": [],
"widgetAreas": [],
"sections": [],
"content": {}
}
| Champ | Type | Requis | Description |
|---|---|---|---|
$schema | string | Non | URL du schéma JSON pour la validation éditeur |
version | "1" | Oui | Version du format seed |
meta | object | Non | Métadonnées sur le seed |
settings | object | Non | Paramètres du site |
collections | array | Non | Définitions de collections |
taxonomies | array | Non | Définitions de taxonomies |
bylines | array | Non | Définitions de profils de byline |
menus | array | Non | Menus de navigation |
redirects | array | Non | Règles de redirection |
widgetAreas | array | Non | Définitions de zones de widgets |
sections | array | Non | Blocs de contenu réutilisables |
content | object | Non | Entrées de contenu d’exemple |
Meta
Métadonnées optionnelles sur le seed :
{
"meta": {
"name": "Blog Starter",
"description": "A simple blog with posts, pages, and categories",
"author": "EmDash"
}
}
Settings
Valeurs de configuration à l’échelle du site :
{
"settings": {
"title": "My Site",
"tagline": "A modern CMS",
"postsPerPage": 10,
"dateFormat": "MMMM d, yyyy"
}
}
Les paramètres sont appliqués à la table options avec le préfixe site:. L’assistant de configuration permet aux utilisateurs de modifier title et tagline.
Collections
Les définitions de collections créent des types de contenu dans la base de données :
{
"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"
}
]
}
]
}
Propriétés de collection
| Propriété | Type | Requis | Description |
|---|---|---|---|
slug | string | Oui | Identifiant URL-safe (minuscules, tirets bas) |
label | string | Oui | Nom d’affichage au pluriel |
labelSingular | string | Non | Nom d’affichage au singulier |
description | string | Non | Description dans l’interface d’administration |
icon | string | Non | Nom d’icône Lucide |
supports | array | Non | Fonctionnalités : "drafts", "revisions" |
fields | array | Oui | Définitions de champs |
Propriétés de champ
| Propriété | Type | Requis | Description |
|---|---|---|---|
slug | string | Oui | Nom de colonne (minuscules, tirets bas) |
label | string | Oui | Nom d’affichage |
type | string | Oui | Type de champ |
required | boolean | Non | Validation : le champ doit avoir une valeur |
unique | boolean | Non | Validation : la valeur doit être unique |
defaultValue | any | Non | Valeur par défaut pour les nouvelles entrées |
validation | object | Non | Règles de validation supplémentaires |
widget | string | Non | Widget d’interface d’administration personnalisé |
options | object | Non | Configuration spécifique au widget |
Types de champs
| Type | Description | Stocké en |
|---|---|---|
string | Texte court | TEXT |
text | Texte long (textarea) | TEXT |
number | Valeur numérique | REAL |
integer | Nombre entier | INTEGER |
boolean | Vrai/faux | INTEGER |
date | Valeur de date | TEXT (ISO 8601) |
datetime | Date et heure | TEXT (ISO 8601) |
email | Adresse email | TEXT |
url | URL | TEXT |
slug | Chaîne URL-safe | TEXT |
portableText | Contenu texte riche | JSON |
image | Référence d’image | JSON |
file | Référence de fichier | JSON |
json | JSON arbitraire | JSON |
reference | Référence vers une autre entrée | TEXT |
Taxonomies
Systèmes de classification pour le contenu :
{
"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"]
}
]
}
Propriétés de taxonomie
| Propriété | Type | Requis | Description |
|---|---|---|---|
name | string | Oui | Identifiant unique |
label | string | Oui | Nom d’affichage au pluriel |
labelSingular | string | Non | Nom d’affichage au singulier |
hierarchical | boolean | Oui | Autoriser les termes imbriqués (catégories) ou plats (tags) |
collections | array | Oui | Collections auxquelles cette taxonomie s’applique |
terms | array | Non | Termes prédéfinis |
Propriétés de terme
| Propriété | Type | Requis | Description |
|---|---|---|---|
slug | string | Oui | Identifiant URL-safe |
label | string | Oui | Nom d’affichage |
description | string | Non | Description du terme |
parent | string | Non | Slug du terme parent (hiérarchique uniquement) |
Menus
Menus de navigation modifiables depuis l’admin :
{
"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"
}
]
}
]
}
Types d’éléments de menu
| Type | Description | Champs requis |
|---|---|---|
custom | URL personnalisée | url |
page | Lien vers une entrée de page | ref |
post | Lien vers une entrée d’article | ref |
taxonomy | Lien vers une archive de taxonomie | ref, collection |
collection | Lien vers une archive de collection | collection |
Propriétés d’élément de menu
| Propriété | Type | Description |
|---|---|---|
type | string | Type d’élément (voir ci-dessus) |
label | string | Texte d’affichage (auto-généré pour les refs page/article) |
url | string | URL personnalisée (pour le type custom) |
ref | string | ID du contenu dans le seed (pour les types page/post) |
collection | string | Slug de la collection |
target | string | "_blank" pour une nouvelle fenêtre |
titleAttr | string | Attribut HTML title |
cssClasses | string | Classes CSS personnalisées |
children | array | Éléments de menu imbriqués |
Bylines
Les profils de byline sont distincts de la propriété (author_id). Définissez des identités de byline réutilisables une fois, puis référencez-les depuis les entrées de contenu.
{
"bylines": [
{
"id": "editorial",
"slug": "emdash-editorial",
"displayName": "EmDash Editorial"
},
{
"id": "guest",
"slug": "guest-contributor",
"displayName": "Guest Contributor",
"isGuest": true
}
]
}
| Propriété | Type | Requis | Description |
|---|---|---|---|
id | string | Oui | ID local au seed utilisé par content[].bylines |
slug | string | Oui | Slug URL-safe de la byline |
displayName | string | Oui | Nom affiché dans les templates et les API |
bio | string | Non | Bio de profil optionnelle |
websiteUrl | string | Non | URL de site web optionnelle |
isGuest | boolean | Non | Marque la byline comme profil invité |
Redirections
Règles de redirection pour préserver les URL anciennes après migration :
{
"redirects": [
{ "source": "/old-about", "destination": "/about" },
{ "source": "/legacy-feed", "destination": "/rss.xml", "type": 308 },
{
"source": "/category/news",
"destination": "/categories/news",
"groupName": "migration"
}
]
}
Propriétés de redirection
| Propriété | Type | Requis | Description |
|---|---|---|---|
source | string | Oui | Chemin source (doit commencer par /) |
destination | string | Oui | Chemin de destination (doit commencer par /) |
type | number | Non | Statut HTTP : 301, 302, 307 ou 308 |
enabled | boolean | Non | Si la redirection est active (défaut : true) |
groupName | string | Non | Libellé de regroupement optionnel pour le filtrage admin |
Zones de widgets
Régions de contenu configurables :
{
"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!" }]
}
]
}
]
}
]
}
Types de widgets
| Type | Description | Champs requis |
|---|---|---|
content | Contenu texte riche | content (Portable Text) |
menu | Affiche un menu | menuName |
component | Composant enregistré | componentId |
Composants intégrés
| ID du composant | Description |
|---|---|
core:recent-posts | Liste des articles récents |
core:categories | Liste des catégories |
core:tags | Nuage de tags |
core:search | Formulaire de recherche |
core:archives | Archives mensuelles |
Sections
Blocs de contenu réutilisables que les éditeurs peuvent insérer dans les champs Portable Text via la commande 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." }
]
}
]
}
]
}
Propriétés de section
| Propriété | Type | Requis | Description |
|---|---|---|---|
slug | string | Oui | Identifiant URL-safe |
title | string | Oui | Nom d’affichage dans le sélecteur de sections |
description | string | Non | Explique quand utiliser cette section |
keywords | array | Non | Termes de recherche pour trouver la section |
content | array | Oui | Blocs Portable Text |
source | string | Non | "theme" (défaut pour les seeds) ou "import" |
Les sections issues des fichiers seed sont marquées source: "theme" et ne peuvent pas être supprimées depuis l’interface d’administration. Les éditeurs peuvent créer leurs propres sections (source: "user") et insérer n’importe quel type de section lors de l’édition du contenu.
Contenu
Contenu d’exemple organisé par collection :
{
"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." }]
}
]
}
}
]
}
}
Propriétés d’entrée de contenu
| Propriété | Type | Requis | Description |
|---|---|---|---|
id | string | Oui | ID local au seed pour les références |
slug | string | Oui | Slug d’URL |
status | string | Non | "published" ou "draft" (défaut : "published") |
data | object | Oui | Valeurs des champs |
bylines | array | Non | Crédits de byline ordonnés (byline, roleLabel optionnel) |
taxonomies | object | Non | Assignations de termes par nom de taxonomie |
Références de contenu
Référencez d’autres entrées de contenu en utilisant le préfixe $ref: :
{
"data": {
"related_posts": ["$ref:another-post", "$ref:third-post"]
}
}
Le préfixe $ref: résout les ID du seed en ID de base de données lors du seeding.
Références média
Incluez des images depuis des URL :
{
"data": {
"featured_image": {
"$media": {
"url": "https://images.unsplash.com/photo-xxx",
"alt": "Description of the image",
"filename": "hero.jpg",
"caption": "Photo by Someone"
}
}
}
}
Incluez des images locales depuis .emdash/media/ :
{
"data": {
"featured_image": {
"$media": {
"file": "hero.jpg",
"alt": "Description of the image"
}
}
}
}
Propriétés média
| Propriété | Type | Requis | Description |
|---|---|---|---|
url | string | Oui* | URL distante à télécharger |
file | string | Oui* | Nom de fichier local dans .emdash/media/ |
alt | string | Non | Texte alternatif pour l’accessibilité |
filename | string | Non | Nom de fichier de remplacement |
caption | string | Non | Légende du média |
*Soit url soit file est requis, pas les deux.
Appliquer les seeds par programmation
Utilisez l’API de seed pour les outils CLI ou les scripts :
import { applySeed, validateSeed } from "emdash/seed";
import seedData from "./.emdash/seed.json";
const validation = validateSeed(seedData);
if (!validation.valid) {
console.error(validation.errors);
process.exit(1);
}
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 }
// }
Options d’application
| Option | Type | Défaut | Description |
|---|---|---|---|
includeContent | boolean | false | Créer les entrées de contenu d’exemple |
onConflict | string | "skip" | "skip", "update" ou "error" |
mediaBasePath | string | — | Chemin de base pour les fichiers médias locaux |
storage | Storage | — | Adaptateur de stockage pour les uploads médias |
baseUrl | string | — | URL de base pour les URL des médias |
Idempotence
Le seeding peut être exécuté plusieurs fois en toute sécurité. Comportement en cas de conflit par type d’entité :
| Entité | Comportement |
|---|---|
| Collection | Ignoré si le slug existe |
| Champ | Ignoré si collection + slug existe |
| Définition de taxonomie | Ignoré si le nom existe |
| Terme de taxonomie | Ignoré si nom + slug existe |
| Profil de byline | Ignoré si le slug existe |
| Menu | Ignoré si le nom existe |
| Éléments de menu | Remplacés (le menu est recréé) |
| Redirection | Ignoré si la source existe |
| Zone de widgets | Ignoré si le nom existe |
| Widgets | Remplacés (la zone est recréée) |
| Section | Ignoré si le slug existe |
| Paramètres | Mis à jour (les paramètres sont faits pour changer) |
| Contenu | Ignoré si le slug existe dans la collection |
Validation
Les fichiers seed sont validés avant application :
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));
Vérifications de validation :
- Les champs requis sont présents
- Les slugs suivent les conventions de nommage (minuscules, tirets bas)
- Les types de champs sont valides
- Les références pointent vers du contenu existant
- Les parents de termes hiérarchiques existent
- Les chemins de redirection sont des URL locales sûres
- Les sources de redirection sont uniques
- Pas de slugs dupliqués au sein des collections
Commandes CLI
# Appliquer un fichier seed
npx emdash seed .emdash/seed.json
# Appliquer sans contenu d'exemple
npx emdash seed .emdash/seed.json --no-content
# Valider uniquement
npx emdash seed .emdash/seed.json --validate
# Exporter le schéma actuel comme seed
npx emdash export-seed > seed.json
# Exporter avec le contenu
npx emdash export-seed --with-content > seed.json
Prochaines étapes
- Créer des thèmes — Construire un thème complet
- Vue d’ensemble des thèmes — Comment fonctionnent les thèmes