This guide walks you through creating your first EmDash site, from installation to publishing your first post.
Prerequisites
- Node.js v22.12.0 or higher (odd-numbered versions are not supported)
- npm, pnpm, or yarn
- A code editor (VS Code recommended)
Create a New Project
npm
npm create emdash@latest pnpm
pnpm create emdash@latest yarn
yarn create emdash Follow the prompts to name your project and set up your preferences.
Start the Development Server
-
Navigate to your project directory:
cd my-emdash-site -
Install dependencies:
npm install -
Start the dev server:
npm run dev -
Open your browser to
http://localhost:4321
Complete the Setup Wizard
When you first visit the admin panel, EmDash’s Setup Wizard guides you through initial configuration:
-
Navigate to
http://localhost:4321/_emdash/admin -
You’ll be redirected to the Setup Wizard. Enter:
- Site Title — Your site’s name
- Tagline — A short description
- Admin Email — Your email address
-
Click Create Site to register your passkey
-
Your browser will prompt you to create a passkey using Touch ID, Face ID, Windows Hello, or a security key
Once your passkey is registered, you’re logged in and redirected to the admin dashboard.
Your First Post
-
In the admin sidebar, click Posts under Content.
-
Click New Post.
-
Enter a title and write some content using the rich text editor.
-
Set the status to Published and click Save.
-
Visit your site’s homepage. The post appears immediately.
Project Structure
Your EmDash project follows a standard Astro structure with a few additions:
my-emdash-site/
├── astro.config.mjs # Astro + EmDash configuration
├── src/
│ ├── live.config.ts # Live Collections configuration
│ ├── pages/
│ │ ├── index.astro # Homepage
│ │ └── posts/
│ │ └── [...slug].astro # Dynamic post pages
│ ├── layouts/
│ │ └── Base.astro # Base layout
│ └── components/ # Your Astro components
├── .emdash/
│ ├── seed.json # Template seed file
│ └── types.ts # Generated TypeScript types
└── package.json
Configuration Files
astro.config.mjs
The following configuration registers EmDash as an Astro integration with a local SQLite database and local file storage:
import { defineConfig } from "astro/config";
import emdash, { local } from "emdash/astro";
import { sqlite } from "emdash/db";
export default defineConfig({
integrations: [
emdash({
database: sqlite({ url: "file:./data.db" }),
storage: local({
directory: "./uploads",
baseUrl: "/_emdash/api/media/file",
}),
}),
],
});
src/live.config.ts
The following file connects EmDash to Astro’s content system through a single live collection:
import { defineLiveCollection } from "astro:content";
import { emdashLoader } from "emdash/runtime";
export const collections = {
_emdash: defineLiveCollection({ loader: emdashLoader() }),
};
Environment Variables
For production, generate an encryption key for plugin secrets with the following command:
npx emdash secrets generate
Add the generated value to your environment as EMDASH_ENCRYPTION_KEY, as shown below. This key encrypts plugin secrets at rest. Set it now so your deployment is ready when plugin secret encryption uses it.
EMDASH_ENCRYPTION_KEY=emdash_enc_v1_...
The key is never stored in the database — only encrypted ciphertext is. Back it up somewhere durable, because losing it means losing every secret encrypted with it.
The preview HMAC secret and commenter-IP hash salt are generated and stored in the database on first use, so you do not need to set them. Override the preview secret only if a separate process needs to verify the same tokens as your main site, as shown below.
EMDASH_PREVIEW_SECRET=your-preview-secret
Query Content in Pages
Use EmDash’s query functions in your Astro pages. These follow Astro’s live collections pattern, returning { entries, error } for collections and { entry, error } for single entries:
---
import { getEmDashCollection } from "emdash";
import Base from "../layouts/Base.astro";
const { entries: posts } = await getEmDashCollection("posts");
---
<Base title="Home">
<h1>Latest Posts</h1>
<ul>
{posts.map((post) => (
<li>
<a href={`/posts/${post.slug}`}>{post.data.title}</a>
</li>
))}
</ul>
</Base>
The following page fetches a single entry by slug:
---
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
const { entry: post } = await getEmDashEntry("posts", slug);
if (!post) {
return Astro.redirect("/404");
}
---
<h1>{post.data.title}</h1>
Generate TypeScript Types
For full type safety, generate types from your database schema:
npx emdash types
This creates .emdash/types.ts with interfaces for all your collections. Your editor will now autocomplete field names and catch type errors.
Next Steps
With the site running, the following pages cover what to do next:
- Core Concepts — Understand how EmDash works under the hood
- Working with Content — Learn to query and render content
- Media Library — Upload and manage images and files
- Create a Blog — Build a complete blog with categories and tags
- Deploy to Cloudflare — Take your site to production