Build Your First EmDash Website: Complete Tutorial
I built dashtro.com on EmDash in a weekend. This tutorial walks through the same process — from an empty directory to a working site with content types, templates, and published content. Hosting starts at $0/month on Cloudflare’s free tier. You’ll have a running site in about 30 minutes.
You need Node.js 20 or later and a free Cloudflare account. Everything else comes from EmDash.
Create the project
Run the EmDash scaffolding command. It sets up an Astro project with EmDash pre-configured:
npm create emdash@latest my-site Pick a starter template when prompted. The blog starter is a good choice for your first site — it comes with a posts collection, categories, tags, and a working layout. You can customize or replace everything later.
The scaffolding installs Astro, EmDash, the Cloudflare adapter, and creates the project structure. Once it finishes, cd into the directory and start the dev server:
cd my-site && npx emdash dev The first start takes a moment — it runs database migrations, applies the seed file, and generates TypeScript types for your collections. Open http://localhost:4321 to see your site, and http://localhost:4321/_emdash/admin for the admin panel.
EmDash reads your seed.json on first start and creates the database schema, admin interfaces, and TypeScript types automatically.
Set up admin access
The first time you visit the admin panel, EmDash walks you through a setup wizard. You'll create your admin account using a passkey — a hardware-backed credential stored on your device. No passwords, no sessions, no CSRF tokens. Just your fingerprint or Face ID.
After setup, you'll land in the admin dashboard. Browse the content types, check out the media library, and look at the menu builder. Everything is functional out of the box.
Understand the project structure
EmDash sites are standard Astro projects. Here are the files that matter:
| astro.config.mjs | Astro config with emdash() integration, database bindings, and storage settings |
| seed/seed.json | Content types, fields, taxonomies, menus, and sample content |
| src/pages/ | Astro pages, all server-rendered |
| src/layouts/ | Shared layout components |
| emdash-env.d.ts | Auto-generated TypeScript types for your collections |
The seed file is the most important one to understand. It's where you define your entire content model — collections, fields, taxonomies, and menus. EmDash reads it on first start and creates the database schema automatically.
Define your content types
Open seed/seed.json. A collection defines a content type — like posts, pages, or products. Each collection gets its own database table and admin interface. Here's what a basic blog collection looks like:
Each field has a slug (the database column name), a label (what editors see), a type (string, text, image, portableText, reference, select, etc.), and optional validation. The seed file also defines taxonomies (categories, tags), navigation menus, and sample content.
For dashtro.com, I defined four collections: learn (articles with pillar/sub-pillar/child hierarchy), docs (documentation with sections), changelog (release notes), and pages (standalone pages). You can model whatever your site needs.
Build your first page template
Pages in EmDash are standard Astro components that query the CMS for content. Here's the pattern for a list page:
Import getEmDashCollection from 'emdash' and call it with your collection slug. It returns typed entries and a cacheHint. Always call Astro.cache.set(cacheHint) so EmDash can invalidate the cache when editors publish changes.
For detail pages, use getEmDashEntry with a slug parameter. The entry includes an edit proxy — spread it onto HTML elements to enable click-to-edit for logged-in admins.
Rich text content comes as Portable Text (structured JSON). Render it with the PortableText component from 'emdash/ui'. Images are objects, not strings — use the Image component from the same package.
Create and publish content
You have three ways to create content:
Admin panel
Visual editor at /_emdash/admin. Rich text editing, media uploads, taxonomy management. Best for day-to-day content work by editors.
MCP server
33 tools for content, schema, media, search, and taxonomies. Connect via Claude, Cursor, or any MCP client. Best for bulk operations and AI-assisted content creation.
CLI
npx emdash content create and related commands. Scriptable and composable. Best for automated pipelines and batch operations.
I use the MCP server for most content on dashtro.com. It's faster than the admin panel for creating structured content, and the AI can help with formatting Portable Text blocks correctly.
Add navigation and search
EmDash manages navigation menus in the database. Define them in the seed file and render them in your layout using getMenu('primary'). Menu items can be custom URLs or references to content entries.
For search, EmDash includes LiveSearch — a React component that provides instant search across collections. Import it from 'emdash/ui/search' and drop it into your nav. It works out of the box with zero configuration.
Add SEO meta
EmDash handles SEO through two mechanisms. EmDashHead generates title, description, canonical, Open Graph tags, and JSON-LD automatically. For content pages, use getSeoMeta to build SEO data from entry fields and site settings.
Don't add manual title or meta description tags — EmDashHead handles those. Duplicating them creates conflicting meta tags that confuse search engines.
Deploy to Cloudflare
When your site is ready, deploying to Cloudflare takes three steps:
- Create a D1 database and R2 bucket in your Cloudflare dashboard
- Update wrangler.jsonc with your D1 database ID and R2 bucket name
- Connect your repo to Cloudflare Pages and push
Cloudflare Pages builds your site automatically on every push. The first deploy takes a minute or two. After that, the admin panel is live at your-domain.com/_emdash/admin and you can manage content from anywhere.
For a detailed deployment walkthrough, read Deploy EmDash to Cloudflare in 10 Minutes .
What's next
You have a working EmDash site. From here:
- Customize your design with Tailwind CSS
- Add more collections for different content types
- Set up taxonomies to organize your content
- Build custom Portable Text block types for special content
- Connect the MCP server for AI-assisted content management
The platform is young but the foundation is solid. Everything you build now — templates, content types, plugins — has first-mover advantage in an ecosystem that's just getting started.
Deploy to Cloudflare →How the stack worksEmDash for developers →