Les fichiers seed sont des documents JSON qui initialisent les sites EmDash. Ils définissent les collections, les champs, les taxonomies, les menus, les redirections, les zones de widgets, les paramètres du site et le contenu d’exemple optionnel.
Structure Racine
Un fichier seed a la structure de niveau supérieur suivante :
{
"$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 de l’é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
L’objet meta contient des métadonnées descriptives optionnelles sur le seed :
{
"meta": {
"name": "Blog Starter",
"description": "A simple blog with posts, pages, and categories",
"author": "EmDash"
}
}
Settings
L’objet settings contient les 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 pré-remplira title et tagline à partir du fichier seed (s’il est fourni), permettant aux utilisateurs de les remplacer lors de la configuration initiale.
Collections
Chaque définition de collection crée un type 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 sécurisé pour URL (minuscules, underscores) |
label | string | Oui | Nom d’affichage au pluriel |
labelSingular | string | Non | Nom d’affichage au singulier |
description | string | Non | Description de l’interface d’administration |
icon | string | Non | Nom de l’icône Lucide |
supports | array | Non | Fonctionnalités : "drafts", "revisions" |
fields | array | Oui | Définitions de champs |
Propriétés de Field
| Propriété | Type | Requis | Description |
|---|---|---|---|
slug | string | Oui | Nom de colonne (minuscules, underscores) |
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 | Remplacement du widget de l’interface d’administration |
options | object | Non | Configuration spécifique au widget |
Types de Field
| Type | Description | Stocké comme |
|---|---|---|
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 e-mail | TEXT |
url | URL | TEXT |
slug | Chaîne sécurisée pour URL | TEXT |
portableText | Contenu de texte enrichi | JSON |
image | Référence d’image | JSON |
file | Référence de fichier | JSON |
json | JSON arbitraire | JSON |
reference | Référence à une autre entrée | TEXT |
Taxonomies
Les taxonomies sont des 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 Taxonomy
| 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 Term
| Propriété | Typ | Requis | Description |
|---|---|---|---|
slug | string | Oui | Identifiant sécurisé pour URL |
label | string | Oui | Nom d’affichage |
description | string | Non | Description du terme |
parent | string | Non | Slug du terme parent (hiérarchique uniquement) |
Menus
Le tableau menus définit les menus de navigation modifiables depuis l’administration :
{
"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 de Menu Item
| 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 de Menu Item
| 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/post) |
url | string | URL personnalisée (pour le type custom) |
ref | string | ID de contenu dans le seed (pour les types page/post) |
collection | string | Slug de collection |
target | string | "_blank" pour une nouvelle fenêtre |
titleAttr | string | Attribut title HTML |
cssClasses | string | Classes CSS personnalisées |
children | array | Éléments de menu imbriqués |
Bylines
Les profils de byline sont séparés de la propriété (author_id). Définissez des identités de byline réutilisables une fois, puis référencez-les à partir d’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 du seed utilisé par content[].bylines |
slug | string | Oui | Slug de byline sécurisé pour URL |
displayName | string | Oui | Nom affiché dans les templates et les API |
bio | string | Non | Biographie de profil optionnelle |
websiteUrl | string | Non | URL de site web optionnelle |
isGuest | boolean | Non | Marque la byline comme profil invité |
Redirects
Le tableau redirects définit les règles de redirection qui préservent les URL héritées après la 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 Redirect
| 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 (par défaut : true) |
groupName | string | Non | Étiquette de regroupement optionnelle pour le filtrage/recherche admin |
Widget Areas
Le tableau widgetAreas définit les 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 Widget
| Type | Description | Champs requis |
|---|---|---|
content | Contenu de texte enrichi | content (Portable Text) |
menu | Affiche un menu | menuName |
component | Composant enregistré | componentId |
Composants Intégrés
| ID de composant | Description |
|---|---|
core:recent-posts | Liste des articles récents |
core:categories | Liste de catégories |
core:tags | Nuage de tags |
core:search | Formulaire de recherche |
core:archives | Archives mensuelles |
Sections
Les sections sont des blocs de contenu réutilisables que les éditeurs insèrent 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 sécurisé pour URL |
title | string | Oui | Nom d’affichage affiché 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" (par défaut pour les seeds) ou "import" |
Les sections des fichiers seed sont marquées source: "theme" et ne peuvent pas être supprimées de 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 de contenu.
Content
L’objet content contient des entrées de contenu d’exemple organisées 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 de Content Entry
| Propriété | Type | Requis | Description |
|---|---|---|---|
id | string | Oui | ID local du seed pour les références |
slug | string | Oui | Slug d’URL |
status | string | Non | "published" ou "draft" (par défaut : "published") |
data | object | Oui | Valeurs de champs |
bylines | array | Non | Crédits de byline ordonnés (byline, roleLabel optionnel) |
taxonomies | object | Non | Affectations de termes par nom de taxonomie |
Content References
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 pendant le seeding.
Media References
Inclure 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"
}
}
}
}
Inclure des images locales depuis .emdash/media/ :
{
"data": {
"featured_image": {
"$media": {
"file": "hero.jpg",
"alt": "Description of the image"
}
}
}
}
Propriétés de Media
| 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 | Remplacer le nom de fichier |
caption | string | Non | Légende du média |
*Soit url soit file est requis, pas les deux.
Applying Seeds Programmatically
Utilisez l’API seed pour les outils CLI ou les scripts :
import { applySeed, validateSeed } from "emdash/seed";
import seedData from "./.emdash/seed.json";
// Valider d'abord
const validation = validateSeed(seedData);
if (!validation.valid) {
console.error(validation.errors);
process.exit(1);
}
// Appliquer le 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 }
// }
Options Apply
| Option | Type | Par défaut | Description |
|---|---|---|---|
includeContent | boolean | false | Créer des entrées de contenu d’exemple |
onConflict | string | "skip" | "skip", "update" ou "error" |
mediaBasePath | string | — | Chemin de base pour les fichiers multimédias locaux |
storage | Storage | — | Adaptateur de stockage pour les uploads multimédias |
baseUrl | string | — | URL de base pour les URL multimédias |
Idempotency
Le seeding peut être exécuté plusieurs fois en toute sécurité. Comportement de conflit par type d’entité :
| Entité | Comportement |
|---|---|
| Collection | Ignorer si le slug existe |
| Champ | Ignorer si collection + slug existe |
| Définition de taxonomie | Ignorer si le nom existe |
| Terme de taxonomie | Ignorer si nom + slug existe |
| Profil de byline | Ignorer si le slug existe |
| Menu | Ignorer si le nom existe |
| Éléments de menu | Remplacer tous (le menu est recréé) |
| Redirection | Ignorer si la source existe |
| Zone de widget | Ignorer si le nom existe |
| Widgets | Remplacer tous (la zone est recréée) |
| Section | Ignorer si le slug existe |
| Paramètres | Mettre à jour (les paramètres sont destinés à changer) |
| Contenu | Ignorer si le slug existe dans la collection |
Validation
Les fichiers seed sont validés avant l’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));
La validation vérifie que :
- Les champs requis sont présents
- Les slugs sont valides pour leur type (les slugs de collection et de champ permettent les lettres minuscules, les chiffres et les underscores ; les autres slugs permettent également les tirets)
- 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 en double dans les collections
CLI Commands
Le fichier seed à .emdash/seed.json, package.json#emdash.seed ou seed/seed.json est intégré dans le build et appliqué à la première requête lorsque la base de données est vide. Pour exporter le schéma d’un site existant (et éventuellement son contenu) sous forme de fichier seed :
# `mkdir -p` car .emdash/ peut ne pas encore exister sur un projet neuf
mkdir -p .emdash
# Exporter le schéma actuel sous forme de fichier seed
npx emdash export-seed > .emdash/seed.json
# Exporter avec le contenu
npx emdash export-seed --with-content > .emdash/seed.json
Next Steps
- Creating Themes — Créer un thème complet
- Themes Overview — Comment fonctionnent les thèmes