Provide demo content
A theme can provide an optional demo dataset in the demo field of theme.json. When the theme is active, an administrator with theme:configure permission can apply it from Appearance → Seed demo.
The dataset is part of theme.json; the current manifest contract does not load it from a separate demo.json file. Keeping it in the manifest also means it is included in the signed theme package.
Add the demo declaration
Section titled “Add the demo declaration”The demo object supports four optional sections:
| Field | Purpose |
|---|---|
settings |
Values merged into the active theme’s existing settings. |
contentTypes |
Content types to create when their keys do not already exist. |
contents |
Pages, posts, or other entries associated with a declared content type. |
menus |
Menus and nested menu items to create. |
This example creates a home page, a post, and a primary menu:
{ "id": "com.example.theme.corporate", "name": "Corporate", "version": "0.1.0", "author": { "name": "Example Studio" }, "engine": ">=0.1.0", "entry": "dist/index.mjs", "styles": "dist/theme.css", "templates": ["home", "page", "post", "archive", "notFound", "error"], "menuLocations": [{ "key": "primary", "name": "Primary menu" }], "settingsSchema": { "type": "object", "properties": { "siteTitle": { "type": "string", "title": "Site title", "default": "" }, "tagline": { "type": "string", "title": "Tagline", "default": "" } } }, "demo": { "settings": { "siteTitle": "Acme Studio", "tagline": "A demonstration site" }, "contentTypes": [ { "key": "page", "name": "Page", "pluralName": "Pages", "routePrefix": "", "hasBlocks": true, "fields": [] }, { "key": "post", "name": "Post", "pluralName": "Posts", "routePrefix": "blog", "hasBlocks": true, "fields": [] } ], "contents": [ { "contentType": "page", "locale": "en", "slug": "", "title": "Welcome to Acme Studio", "excerpt": "The home page included with the theme demo.", "blocks": [ { "id": "demo-home-intro", "type": "core/richtext", "props": { "html": "<h2>Build something clear.</h2><p>Edit this page in Z-CMS Admin.</p>" } } ], "seo": { "title": "Acme Studio", "description": "A demonstration site built with Z-CMS." }, "status": "PUBLISHED" }, { "contentType": "post", "locale": "en", "slug": "hello-world", "title": "Hello world", "data": { "readingTime": 2 }, "blocks": [] } ], "menus": [ { "key": "primary", "name": "Primary menu", "items": [ { "label": "Home", "url": "/" }, { "label": "Blog", "url": "/blog", "children": [ { "label": "Hello world", "url": "/blog/hello-world" } ] } ] } ] }}Every item in contents must reference a key included in demo.contentTypes. The seeder does not infer a content type from the site’s existing content model.
Content type fields
Section titled “Content type fields”Each content type requires key, name, and pluralName. It can also declare:
description,icon, andfieldsfor the editor.isSingletonandisRoutableto control its behavior.routePrefixfor routed entries, such asblog.hasBlocksto enable the block editor.
If the site already has a content type with the same key, Z-CMS reuses it without changing its schema. Make demo entries compatible with both the declaration in the theme and an existing type that uses that key.
Content fields and translations
Section titled “Content fields and translations”Each content item requires contentType, locale, slug, and title. Optional fields are translationGroup, excerpt, data, blocks, seo, status, and publishedAt.
datacontains values for the content type’s structured fields.blocksuses the same block document shape rendered byctx.renderBlocks().statusacceptsDRAFT,IN_REVIEW,SCHEDULED,PUBLISHED, orARCHIVEDand defaults toPUBLISHED.publishedAtis an ISO 8601 date-time string. If omitted, the seed time is used.- Give translations of the same entry an identical
translationGroupvalue. Without it, each item forms a separate translation group. - Use an empty slug (
"") for a root page only when that matches the site’s routing design.
Only use block types that the theme can render, and give every block a stable, unique id.
Menus and settings
Section titled “Menus and settings”A menu requires key, name, and an items array. Each item requires label and url; target and recursive children are optional. Match menu keys to locations declared in menuLocations when the demo is intended to fill those locations.
Keys in demo.settings should also exist in settingsSchema. Seeding merges these values over the active theme’s current settings; unrelated settings remain unchanged.
Understand reseeding
Section titled “Understand reseeding”Running Reseed demo replaces only content and menus previously created for the same theme. Ordinary site content and demo rows owned by other themes remain untouched. Existing content types are retained, and demo settings are merged again.
Because editors may have changed seeded rows, treat reseeding as a destructive operation for that theme’s demo content. Do not instruct users to reseed after they have started adapting demo entries into real content.
Test before packaging
Section titled “Test before packaging”- Activate the theme on an empty test site and run Seed demo.
- Verify routes, blocks, menus, settings, SEO, and every locale.
- Edit an ordinary non-demo entry, reseed, and confirm that the entry remains unchanged.
- Edit a seeded entry, reseed, and confirm that replacement behavior is clearly communicated.
- Test the theme on a site without demo data to verify its empty states.
- Build and package the theme, then install that package on a clean site and repeat the seed test.