Los archivos seed son documentos JSON que inicializan sitios EmDash. Definen colecciones, campos, taxonomías, menús, redirecciones, áreas de widgets, configuraciones del sitio y contenido de muestra opcional.
Estructura Raíz
Un archivo seed tiene la siguiente estructura de nivel superior:
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"meta": {},
"settings": {},
"collections": [],
"taxonomies": [],
"bylines": [],
"menus": [],
"redirects": [],
"widgetAreas": [],
"sections": [],
"content": {}
}
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
$schema | string | No | URL del esquema JSON para validación del editor |
version | "1" | Sí | Versión del formato seed |
meta | object | No | Metadatos sobre el seed |
settings | object | No | Configuraciones del sitio |
collections | array | No | Definiciones de colecciones |
taxonomies | array | No | Definiciones de taxonomías |
bylines | array | No | Definiciones de perfiles de byline |
menus | array | No | Menús de navegación |
redirects | array | No | Reglas de redirección |
widgetAreas | array | No | Definiciones de áreas de widgets |
sections | array | No | Bloques de contenido reutilizables |
content | object | No | Entradas de contenido de muestra |
Meta
El objeto meta contiene metadatos descriptivos opcionales sobre el seed:
{
"meta": {
"name": "Blog Starter",
"description": "A simple blog with posts, pages, and categories",
"author": "EmDash"
}
}
Settings
El objeto settings contiene valores de configuración a nivel del sitio:
{
"settings": {
"title": "My Site",
"tagline": "A modern CMS",
"postsPerPage": 10,
"dateFormat": "MMMM d, yyyy"
}
}
Las configuraciones se aplican a la tabla options con el prefijo site:. El Asistente de Configuración prellenará title y tagline desde el archivo seed (si se proporciona), permitiendo a los usuarios sobrescribirlos durante la configuración inicial.
Collections
Cada definición de colección crea un tipo de contenido en la base de datos:
{
"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"
}
]
}
]
}
Propiedades de Collection
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
slug | string | Sí | Identificador seguro para URL (minúsculas, guiones bajos) |
label | string | Sí | Nombre de visualización plural |
labelSingular | string | No | Nombre de visualización singular |
description | string | No | Descripción de la UI de administración |
icon | string | No | Nombre del icono Lucide |
supports | array | No | Funciones: "drafts", "revisions" |
fields | array | Sí | Definiciones de campos |
Propiedades de Field
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
slug | string | Sí | Nombre de columna (minúsculas, guiones bajos) |
label | string | Sí | Nombre de visualización |
type | string | Sí | Tipo de campo |
required | boolean | No | Validación: el campo debe tener un valor |
unique | boolean | No | Validación: el valor debe ser único |
defaultValue | any | No | Valor predeterminado para nuevas entradas |
validation | object | No | Reglas de validación adicionales |
widget | string | No | Anulación de widget de la UI de administración |
options | object | No | Configuración específica del widget |
Tipos de Field
| Tipo | Descripción | Almacenado como |
|---|---|---|
string | Texto corto | TEXT |
text | Texto largo (textarea) | TEXT |
number | Valor numérico | REAL |
integer | Número entero | INTEGER |
boolean | Verdadero/falso | INTEGER |
date | Valor de fecha | TEXT (ISO 8601) |
datetime | Fecha y hora | TEXT (ISO 8601) |
email | Dirección de correo electrónico | TEXT |
url | URL | TEXT |
slug | Cadena segura para URL | TEXT |
portableText | Contenido de texto enriquecido | JSON |
image | Referencia de imagen | JSON |
file | Referencia de archivo | JSON |
json | JSON arbitrario | JSON |
reference | Referencia a otra entrada | TEXT |
Taxonomies
Las taxonomías son sistemas de clasificación para el contenido:
{
"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"]
}
]
}
Propiedades de Taxonomy
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
name | string | Sí | Identificador único |
label | string | Sí | Nombre de visualización plural |
labelSingular | string | No | Nombre de visualización singular |
hierarchical | boolean | Sí | Permitir términos anidados (categorías) o planos (etiquetas) |
collections | array | Sí | Colecciones a las que se aplica esta taxonomía |
terms | array | No | Términos predefinidos |
Propiedades de Term
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
slug | string | Sí | Identificador seguro para URL |
label | string | Sí | Nombre de visualización |
description | string | No | Descripción del término |
parent | string | No | Slug del término padre (solo jerárquico) |
Menus
El array menus define menús de navegación editables desde el administrador:
{
"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"
}
]
}
]
}
Tipos de Menu Item
| Tipo | Descripción | Campos requeridos |
|---|---|---|
custom | URL personalizada | url |
page | Enlace a una entrada de página | ref |
post | Enlace a una entrada de post | ref |
taxonomy | Enlace a un archivo de taxonomía | ref, collection |
collection | Enlace a un archivo de colección | collection |
Propiedades de Menu Item
| Propiedad | Tipo | Descripción |
|---|---|---|
type | string | Tipo de elemento (ver arriba) |
label | string | Texto de visualización (autogenerado para refs de página/post) |
url | string | URL personalizada (para tipo custom) |
ref | string | ID de contenido en seed (para tipos page/post) |
collection | string | Slug de colección |
target | string | "_blank" para nueva ventana |
titleAttr | string | Atributo title HTML |
cssClasses | string | Clases CSS personalizadas |
children | array | Elementos de menú anidados |
Bylines
Los perfiles de byline están separados de la propiedad (author_id). Defina identidades de byline reutilizables una vez, luego haga referencia a ellas desde entradas de contenido.
{
"bylines": [
{
"id": "editorial",
"slug": "emdash-editorial",
"displayName": "EmDash Editorial"
},
{
"id": "guest",
"slug": "guest-contributor",
"displayName": "Guest Contributor",
"isGuest": true
}
]
}
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
id | string | Sí | ID local del seed usado por content[].bylines |
slug | string | Sí | Slug de byline seguro para URL |
displayName | string | Sí | Nombre mostrado en plantillas y APIs |
bio | string | No | Biografía de perfil opcional |
websiteUrl | string | No | URL de sitio web opcional |
isGuest | boolean | No | Marca byline como perfil de invitado |
Redirects
El array redirects define reglas de redirección que preservan URLs heredadas después de la migración:
{
"redirects": [
{ "source": "/old-about", "destination": "/about" },
{ "source": "/legacy-feed", "destination": "/rss.xml", "type": 308 },
{
"source": "/category/news",
"destination": "/categories/news",
"groupName": "migration"
}
]
}
Propiedades de Redirect
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
source | string | Sí | Ruta de origen (debe comenzar con /) |
destination | string | Sí | Ruta de destino (debe comenzar con /) |
type | number | No | Estado HTTP: 301, 302, 307 o 308 |
enabled | boolean | No | Si la redirección está activa (predeterminado: true) |
groupName | string | No | Etiqueta de agrupación opcional para filtrado/búsqueda del admin |
Widget Areas
El array widgetAreas define regiones de contenido 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!" }]
}
]
}
]
}
]
}
Tipos de Widget
| Tipo | Descripción | Campos requeridos |
|---|---|---|
content | Contenido de texto enriquecido | content (Portable Text) |
menu | Renderiza un menú | menuName |
component | Componente registrado | componentId |
Componentes Integrados
| ID de componente | Descripción |
|---|---|
core:recent-posts | Lista de posts recientes |
core:categories | Lista de categorías |
core:tags | Nube de etiquetas |
core:search | Formulario de búsqueda |
core:archives | Archivos mensuales |
Sections
Las sections son bloques de contenido reutilizables que los editores insertan en campos Portable Text mediante el comando de barra /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." }
]
}
]
}
]
}
Propiedades de Section
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
slug | string | Sí | Identificador seguro para URL |
title | string | Sí | Nombre de visualización mostrado en el selector de sections |
description | string | No | Explica cuándo usar esta section |
keywords | array | No | Términos de búsqueda para encontrar la section |
content | array | Sí | Bloques Portable Text |
source | string | No | "theme" (predeterminado para seeds) o "import" |
Las sections de archivos seed están marcadas con source: "theme" y no se pueden eliminar desde la UI de administración. Los editores pueden crear sus propias sections (source: "user") e insertar cualquier tipo de section al editar contenido.
Content
El objeto content contiene entradas de contenido de muestra organizadas por colección:
{
"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." }]
}
]
}
}
]
}
}
Propiedades de Content Entry
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
id | string | Sí | ID local del seed para referencias |
slug | string | Sí | Slug de URL |
status | string | No | "published" o "draft" (predeterminado: "published") |
data | object | Sí | Valores de campos |
bylines | array | No | Créditos de byline ordenados (byline, opcional roleLabel) |
taxonomies | object | No | Asignaciones de términos por nombre de taxonomía |
Content References
Haga referencia a otras entradas de contenido usando el prefijo $ref::
{
"data": {
"related_posts": ["$ref:another-post", "$ref:third-post"]
}
}
El prefijo $ref: resuelve IDs del seed a IDs de base de datos durante el seeding.
Media References
Incluir imágenes desde URLs:
{
"data": {
"featured_image": {
"$media": {
"url": "https://images.unsplash.com/photo-xxx",
"alt": "Description of the image",
"filename": "hero.jpg",
"caption": "Photo by Someone"
}
}
}
}
Incluir imágenes locales desde .emdash/media/:
{
"data": {
"featured_image": {
"$media": {
"file": "hero.jpg",
"alt": "Description of the image"
}
}
}
}
Propiedades de Media
| Propiedad | Tipo | Requerido | Descripción |
|---|---|---|---|
url | string | Sí* | URL remota para descargar |
file | string | Sí* | Nombre de archivo local en .emdash/media/ |
alt | string | No | Texto alternativo para accesibilidad |
filename | string | No | Sobrescribir nombre de archivo |
caption | string | No | Pie de foto del medio |
*Se requiere url o file, no ambos.
Applying Seeds Programmatically
Use la API de seed para herramientas CLI o scripts:
import { applySeed, validateSeed } from "emdash/seed";
import seedData from "./.emdash/seed.json";
// Validar primero
const validation = validateSeed(seedData);
if (!validation.valid) {
console.error(validation.errors);
process.exit(1);
}
// Aplicar 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 }
// }
Opciones de Apply
| Opción | Tipo | Predeterminado | Descripción |
|---|---|---|---|
includeContent | boolean | false | Crear entradas de contenido de muestra |
onConflict | string | "skip" | "skip", "update" o "error" |
mediaBasePath | string | — | Ruta base para archivos de medios locales |
storage | Storage | — | Adaptador de almacenamiento para cargas de medios |
baseUrl | string | — | URL base para URLs de medios |
Idempotency
El seeding es seguro para ejecutar múltiples veces. Comportamiento de conflicto por tipo de entidad:
| Entidad | Comportamiento |
|---|---|
| Collection | Omitir si existe el slug |
| Field | Omitir si existe collection + slug |
| Definición de taxonomía | Omitir si existe el nombre |
| Término de taxonomía | Omitir si existe nombre + slug |
| Perfil de byline | Omitir si existe el slug |
| Menú | Omitir si existe el nombre |
| Elementos de menú | Reemplazar todos (el menú se recrea) |
| Redirección | Omitir si existe el origen |
| Área de widget | Omitir si existe el nombre |
| Widgets | Reemplazar todos (el área se recrea) |
| Section | Omitir si existe el slug |
| Configuraciones | Actualizar (las configuraciones están destinadas a cambiar) |
| Contenido | Omitir si existe el slug en la colección |
Validation
Los archivos seed se validan antes de la aplicación:
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 validación verifica que:
- Los campos requeridos estén presentes
- Los slugs sean válidos para su tipo (los slugs de collection y field permiten letras minúsculas, dígitos y guiones bajos; otros slugs también permiten guiones)
- Los tipos de campo sean válidos
- Las referencias apunten a contenido existente
- Existan padres de términos jerárquicos
- Las rutas de redirección sean URLs locales seguras
- Los orígenes de redirección sean únicos
- No haya slugs duplicados dentro de las colecciones
CLI Commands
El archivo seed en .emdash/seed.json, package.json#emdash.seed o seed/seed.json se incluye en la compilación y se aplica en la primera solicitud cuando la base de datos está vacía. Para exportar el esquema de un sitio existente (y opcionalmente su contenido) como un archivo seed:
# `mkdir -p` porque .emdash/ puede no existir aún en un proyecto nuevo
mkdir -p .emdash
# Exportar el esquema actual como un archivo seed
npx emdash export-seed > .emdash/seed.json
# Exportar con contenido
npx emdash export-seed --with-content > .emdash/seed.json
Next Steps
- Creating Themes — Crear un tema completo
- Themes Overview — Cómo funcionan los temas