Formato de archivo seed

En esta página

Los archivos seed son documentos JSON que inicializan sitios de EmDash. Definen colecciones, campos, taxonomías, menús, redirecciones, áreas de widgets, ajustes del sitio y contenido de ejemplo opcional.

Estructura raíz

{
	"$schema": "https://emdashcms.com/seed.schema.json",
	"version": "1",
	"meta": {},
	"settings": {},
	"collections": [],
	"taxonomies": [],
	"bylines": [],
	"menus": [],
	"redirects": [],
	"widgetAreas": [],
	"sections": [],
	"content": {}
}
CampoTipoObligatorioDescripción
$schemastringNoURL del esquema JSON para validación en el editor
version"1"Versión del formato seed
metaobjectNoMetadatos sobre el seed
settingsobjectNoAjustes del sitio
collectionsarrayNoDefiniciones de colecciones
taxonomiesarrayNoDefiniciones de taxonomías
bylinesarrayNoDefiniciones de perfiles de autoría
menusarrayNoMenús de navegación
redirectsarrayNoReglas de redirección
widgetAreasarrayNoDefiniciones de áreas de widgets
sectionsarrayNoBloques de contenido reutilizables
contentobjectNoEntradas de contenido de ejemplo

Meta

Metadatos opcionales sobre el seed:

{
	"meta": {
		"name": "Blog Starter",
		"description": "A simple blog with posts, pages, and categories",
		"author": "EmDash"
	}
}

Settings

Valores de configuración de todo el sitio:

{
	"settings": {
		"title": "My Site",
		"tagline": "A modern CMS",
		"postsPerPage": 10,
		"dateFormat": "MMMM d, yyyy"
	}
}

Los ajustes se aplican a la tabla options con el prefijo site:. El Asistente de Configuración permite a los usuarios sobreescribir title y tagline.

Collections

Las definiciones de colecciones crean tipos 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 colección

PropiedadTipoObligatorioDescripción
slugstringIdentificador seguro para URL (minúsculas, guiones bajos)
labelstringNombre para mostrar en plural
labelSingularstringNoNombre para mostrar en singular
descriptionstringNoDescripción en la UI de admin
iconstringNoNombre de icono Lucide
supportsarrayNoFunciones: "drafts", "revisions"
fieldsarrayDefiniciones de campos

Propiedades de campo

PropiedadTipoObligatorioDescripción
slugstringNombre de columna (minúsculas, guiones bajos)
labelstringNombre para mostrar
typestringTipo de campo
requiredbooleanNoValidación: el campo debe tener un valor
uniquebooleanNoValidación: el valor debe ser único
defaultValueanyNoValor predeterminado para nuevas entradas
validationobjectNoReglas de validación adicionales
widgetstringNoSobrescritura de widget de la UI de admin
optionsobjectNoConfiguración específica del widget

Tipos de campo

TipoDescripciónAlmacenado como
stringTexto cortoTEXT
textTexto largo (textarea)TEXT
numberValor numéricoREAL
integerNúmero enteroINTEGER
booleanVerdadero/falsoINTEGER
dateValor de fechaTEXT (ISO 8601)
datetimeFecha y horaTEXT (ISO 8601)
emailDirección de emailTEXT
urlURLTEXT
slugString seguro para URLTEXT
portableTextContenido de texto enriquecidoJSON
imageReferencia a imagenJSON
fileReferencia a archivoJSON
jsonJSON arbitrarioJSON
referenceReferencia a otra entradaTEXT

Taxonomies

Sistemas de clasificación para 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 taxonomía

PropiedadTipoObligatorioDescripción
namestringIdentificador único
labelstringNombre para mostrar en plural
labelSingularstringNoNombre para mostrar en singular
hierarchicalbooleanPermitir términos anidados (categorías) o planos (etiquetas)
collectionsarrayColecciones a las que aplica esta taxonomía
termsarrayNoTérminos predefinidos

Propiedades de término

PropiedadTipoObligatorioDescripción
slugstringIdentificador seguro para URL
labelstringNombre para mostrar
descriptionstringNoDescripción del término
parentstringNoSlug del término padre (solo para jerárquicas)

Menús de navegación editables desde el 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"
				}
			]
		}
	]
}

Tipos de elementos de menú

TipoDescripciónCampos obligatorios
customURL personalizadaurl
pageEnlace a una entrada de páginaref
postEnlace a una entrada de postref
taxonomyEnlace a un archivo de taxonomíaref, collection
collectionEnlace a un archivo de coleccióncollection

Propiedades de elementos de menú

PropiedadTipoDescripción
typestringTipo de elemento (ver arriba)
labelstringTexto para mostrar (autogenerado para refs de page/post)
urlstringURL personalizada (para tipo custom)
refstringID del contenido en el seed (para tipos page/post)
collectionstringSlug de la colección
targetstring"_blank" para nueva ventana
titleAttrstringAtributo title de HTML
cssClassesstringClases CSS personalizadas
childrenarrayElementos de menú anidados

Bylines

Los perfiles de autoría son independientes de la propiedad (author_id). Define identidades de autoría reutilizables una vez, luego referéncialas desde las entradas de contenido.

{
	"bylines": [
		{
			"id": "editorial",
			"slug": "emdash-editorial",
			"displayName": "EmDash Editorial"
		},
		{
			"id": "guest",
			"slug": "guest-contributor",
			"displayName": "Guest Contributor",
			"isGuest": true
		}
	]
}
PropiedadTipoObligatorioDescripción
idstringID local del seed usado por content[].bylines
slugstringSlug de autoría seguro para URL
displayNamestringNombre mostrado en plantillas y APIs
biostringNoBiografía de perfil opcional
websiteUrlstringNoURL de sitio web opcional
isGuestbooleanNoMarca el perfil como invitado

Redirects

Reglas de redirección para preservar URLs antiguas 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 redirección

PropiedadTipoObligatorioDescripción
sourcestringRuta de origen (debe empezar con /)
destinationstringRuta de destino (debe empezar con /)
typenumberNoEstado HTTP: 301, 302, 307 o 308
enabledbooleanNoSi la redirección está activa (predeterminado: true)
groupNamestringNoEtiqueta de agrupación opcional para filtrado/búsqueda en admin

Widget Areas

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

TipoDescripciónCampos obligatorios
contentContenido de texto enriquecidocontent (Portable Text)
menuRenderiza un menúmenuName
componentComponente registradocomponentId

Componentes integrados

ID del componenteDescripción
core:recent-postsLista de posts recientes
core:categoriesLista de categorías
core:tagsNube de etiquetas
core:searchFormulario de búsqueda
core:archivesArchivos mensuales

Sections

Bloques de contenido reutilizables que los editores pueden insertar 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 sección

PropiedadTipoObligatorioDescripción
slugstringIdentificador seguro para URL
titlestringNombre para mostrar en el selector de secciones
descriptionstringNoExplica cuándo usar esta sección
keywordsarrayNoTérminos de búsqueda para encontrar la sección
contentarrayBloques Portable Text
sourcestringNo"theme" (predeterminado para seeds) o "import"

Las secciones de archivos seed se marcan como source: "theme" y no se pueden eliminar desde la UI de administración. Los editores pueden crear sus propias secciones (source: "user") e insertar cualquier tipo de sección al editar contenido.

Content

Contenido de ejemplo organizado 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 entrada de contenido

PropiedadTipoObligatorioDescripción
idstringID local del seed para referencias
slugstringSlug de URL
statusstringNo"published" o "draft" (predeterminado: "published")
dataobjectValores de campo
bylinesarrayNoCréditos de autoría ordenados (byline, roleLabel opcional)
taxonomiesobjectNoAsignaciones de términos por nombre de taxonomía

Referencias de contenido

Referencia 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.

Referencias de medios

Incluye 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"
			}
		}
	}
}

Incluye imágenes locales desde .emdash/media/:

{
	"data": {
		"featured_image": {
			"$media": {
				"file": "hero.jpg",
				"alt": "Description of the image"
			}
		}
	}
}

Propiedades de medios

PropiedadTipoObligatorioDescripción
urlstringSí*URL remota para descargar
filestringSí*Nombre de archivo local en .emdash/media/
altstringNoTexto alternativo para accesibilidad
filenamestringNoSobrescribir nombre de archivo
captionstringNoLeyenda del medio

*Se requiere url o file, no ambos.

Aplicar seeds programáticamente

Usa la API de seed para herramientas CLI o 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 }
// }

Opciones de aplicación

OpciónTipoPredeterminadoDescripción
includeContentbooleanfalseCrear entradas de contenido de ejemplo
onConflictstring"skip""skip", "update" o "error"
mediaBasePathstringRuta base para archivos de medios locales
storageStorageAdaptador de almacenamiento para subida de medios
baseUrlstringURL base para URLs de medios

Idempotencia

El seeding es seguro de ejecutar múltiples veces. Comportamiento de conflictos por tipo de entidad:

EntidadComportamiento
ColecciónOmitir si el slug existe
CampoOmitir si colección + slug existe
Definición de taxonomíaOmitir si el nombre existe
Término de taxonomíaOmitir si nombre + slug existe
Perfil de autoríaOmitir si el slug existe
MenúOmitir si el nombre existe
Elementos de menúReemplazar todos (el menú se recrea)
RedirecciónOmitir si el source existe
Área de widgetsOmitir si el nombre existe
WidgetsReemplazar todos (el área se recrea)
SecciónOmitir si el slug existe
AjustesActualizar (los ajustes están pensados para cambiar)
ContenidoOmitir si el slug existe en la colección

Validación

Los archivos seed se validan antes de aplicarse:

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));

Comprobaciones de validación:

  • Los campos obligatorios están presentes
  • Los slugs siguen convenciones de nomenclatura (minúsculas, guiones bajos)
  • Los tipos de campo son válidos
  • Las referencias apuntan a contenido existente
  • Los padres de términos jerárquicos existen
  • Las rutas de redirección son URLs locales seguras
  • Los sources de redirección son únicos
  • No hay slugs duplicados dentro de las colecciones

Comandos CLI

# Aplicar archivo seed
npx emdash seed .emdash/seed.json

# Aplicar sin contenido de ejemplo
npx emdash seed .emdash/seed.json --no-content

# Solo validar
npx emdash seed .emdash/seed.json --validate

# Exportar esquema actual como seed
npx emdash export-seed > seed.json

# Exportar con contenido
npx emdash export-seed --with-content > seed.json

Próximos pasos