Starter templates for building sites with EmDash CMS. Each template includes a seed file with demo content, so you can see how everything works right away.
A clean, minimal blog with posts, pages, categories, tags, and search.
Features:
- Featured post hero on homepage
- Post grid with reading time estimates
- Category and tag archives
- Full-text search
- RSS feed
- SEO metadata and JSON-LD
- Dark/light mode
Pages: Homepage, post archive, single post, single page, category archive, tag archive, search results, 404
A landing page template for products and services with modular content blocks.
Features:
- Hero, features, testimonials, pricing, and FAQ blocks
- Contact form with validation
- Portable Text content editing
- SEO metadata and JSON-LD
- Dark/light mode
Pages: Homepage, pricing, contact, 404
A portfolio for showcasing creative work with project pages and tag filtering.
Features:
- Project grid with hover effects
- Tag-based filtering on work page
- Individual project pages with galleries
- Contact page
- RSS feed for new projects
- SEO metadata and JSON-LD
- Dark/light mode
Pages: Homepage, work listing, single project, about, contact, 404
Each template has two variants:
- Node.js (
templates/blog,templates/marketing,templates/portfolio) — uses SQLite and local file storage - Cloudflare (
templates/blog-cloudflare, etc.) — uses D1 and R2
# Copy the template you want
cp -r templates/blog my-site
cd my-site
# Install dependencies
pnpm install
# Initialize the database and seed demo content
pnpm bootstrap
# Start the dev server
pnpm devOpen http://localhost:4321 to see your site, and http://localhost:4321/\_emdash/admin for the CMS.
templates/blog/
├── src/
│ ├── components/ # Astro components
│ ├── layouts/ # Page layouts
│ ├── pages/ # Route pages
│ ├── utils/ # Helper functions
│ └── live.config.ts # EmDash content loader
├── seed/
│ └── seed.json # Demo content
├── astro.config.mjs # Astro + EmDash config
├── package.json
└── tsconfig.json
The cloudflare variants share most of their code with the base templates. Only these files differ:
astro.config.mjs(cloudflare adapter, D1/R2 storage)package.json(different dependencies)wrangler.jsonc(cloudflare config)
Everything else is synced from the base template using a script:
./scripts/sync-cloudflare-templates.shRun this after making changes to src/, seed/, tsconfig.json, emdash-env.d.ts, or .gitignore in any base template. It copies those files to the corresponding cloudflare variant.
The primary Node demo is also synced from the blog template:
./scripts/sync-blog-demos.shThis script does two kinds of sync:
- full template sync for
templates/blog->demos/simple - frontend-only sync (keeping runtime-specific files) for:
templates/blog-cloudflare->demos/cloudflaretemplates/blog-cloudflare->demos/previewtemplates/blog->demos/postgres
Template screenshots live in assets/templates/{template}/latest/ and are used in the README. To update them after making visual changes:
# Screenshot all templates (starts each dev server automatically)
pnpm screenshots
# Screenshot specific templates
pnpm screenshots blog
pnpm screenshots blog marketingThe script starts each template's dev server, captures screenshots, then stops the server before moving to the next template.
Page definitions are in templates/screenshots.json. Each page is captured at desktop (1440x900) and mobile (390x844) in both light and dark mode at 2x resolution. Screenshots are JPEG at 80% quality.
Output goes to assets/templates/{template}/{datetime}/ and is copied to assets/templates/{template}/latest/. The dated directories are gitignored; only latest/ is committed. The README references images from latest/.
Filenames follow the pattern {page}-{mode}-{breakpoint}.jpg, e.g. homepage-light-desktop.jpg, post-dark-mobile.jpg.
To add pages for a template, edit templates/screenshots.json.
- Create the base template in
templates/{name}/ - Include a seed file at
seed/seed.json(or configure the path inpackage.jsonunderemdash.seed) - Add the
typecheckscript topackage.json - Create the cloudflare variant in
templates/{name}-cloudflare/with the appropriate adapter config - Add the template pair to
scripts/sync-cloudflare-templates.sh - Add the template's pages to
templates/screenshots.jsonand run the screenshot script - Update the README template gallery
Each template includes a seed file with demo content. The seed file format is documented in the CLI (emdash seed --help). Key points:
- Use
status: "published"and includepublished_atfor content that should appear on the site - Reference media by URL — the seeder downloads and uploads images automatically
- Use the
taxonomiesobject for categories and tags - The
emdash.seedfield inpackage.jsonspecifies the seed file location
Templates are covered by smoke tests that verify:
- Seed files parse correctly
- Seeds apply without errors
- The database passes doctor checks after seeding
Run the smoke tests:
pnpm --filter emdash exec vitest run --config vitest.smoke.config.ts