JavaScript API Reference

On this page

EmDash exports functions for querying content, managing media, and working with the database.

Content Queries

EmDash’s query functions follow Astro’s live content collections pattern, returning { entries, error } or { entry, error } for graceful error handling.

getEmDashCollection()

Fetch all entries from a collection.

import { getEmDashCollection } from "emdash";

const { entries: posts, error } = await getEmDashCollection("posts");

if (error) {
	console.error("Failed to load posts:", error);
}

Parameters

ParameterTypeDescription
collectionstringCollection slug
optionsCollectionFilterOptional filter options

Options

interface CollectionFilter {
	status?: "draft" | "published" | "archived";
	limit?: number;
	where?: Record<string, string | string[]>; // Filter by field or taxonomy
}

Returns

interface CollectionResult<T> {
	entries: ContentEntry<T>[]; // Empty array if error or none found
	error?: Error; // Set if query failed
}

Examples

// Get all published posts
const { entries: posts } = await getEmDashCollection("posts", {
	status: "published",
});

// Get latest 5 posts
const { entries: latest } = await getEmDashCollection("posts", {
	limit: 5,
	status: "published",
});

// Filter by taxonomy
const { entries: newsPosts } = await getEmDashCollection("posts", {
	status: "published",
	where: { category: "news" },
});

// Handle errors
const { entries, error } = await getEmDashCollection("posts");
if (error) {
	return new Response("Server error", { status: 500 });
}

getEmDashEntry()

Fetch a single entry by slug or ID.

import { getEmDashEntry } from "emdash";

const { entry: post, error } = await getEmDashEntry("posts", "my-post-slug");

if (!post) {
	return Astro.redirect("/404");
}

Parameters

ParameterTypeDescription
collectionstringCollection slug
slugOrIdstringEntry slug or ID

Preview mode is handled automatically — the middleware detects _preview tokens and serves draft content via AsyncLocalStorage. No options parameter is needed.

Returns

interface EntryResult<T> {
	entry: ContentEntry<T> | null; // null if not found
	error?: Error; // Set only for actual errors, not "not found"
	isPreview: boolean; // true if draft content is being served
}

Examples

// Get by slug
const { entry: post } = await getEmDashEntry("posts", "hello-world");

// Get by ID
const { entry: post } = await getEmDashEntry("posts", "01HXK5MZSN0FVXT2Q3KPRT9M7D");

// Preview is automatic — isPreview is true when a valid _preview token is present
const { entry, isPreview, error } = await getEmDashEntry("posts", slug);

// Handle errors vs not-found
if (error) {
	return new Response("Server error", { status: 500 });
}
if (!entry) {
	return Astro.redirect("/404");
}

Content Types

ContentEntry

The entry returned by query functions:

interface ContentEntry<T = Record<string, unknown>> {
	id: string;
	data: T;
	edit: EditProxy; // Visual editing annotations
}

The edit proxy provides visual editing annotations. Spread it onto elements to enable inline editing: {...entry.edit.title}. In production, this produces no output.

The data object contains all content fields plus system fields:

  • id - Unique identifier
  • slug - URL-friendly identifier
  • status - “draft” | “published” | “archived”
  • createdAt - ISO timestamp
  • updatedAt - ISO timestamp
  • publishedAt - ISO timestamp or null
  • Plus all custom fields defined in your collection schema

Database Functions

createDatabase()

Create a database connection.

import { createDatabase } from "emdash";

const db = createDatabase({ url: "file:./data.db" });

runMigrations()

Run pending database migrations.

import { createDatabase, runMigrations } from "emdash";

const db = createDatabase({ url: "file:./data.db" });
const { applied } = await runMigrations(db);
console.log(`Applied ${applied.length} migrations`);

getMigrationStatus()

Check migration status.

import { createDatabase, getMigrationStatus } from "emdash";

const db = createDatabase({ url: "file:./data.db" });
const status = await getMigrationStatus(db);
// { applied: ["0001_core", ...], pending: [] }

Repositories

Low-level data access through repositories.

ContentRepository

import { ContentRepository, createDatabase } from "emdash";

const db = createDatabase({ url: "file:./data.db" });
const repo = new ContentRepository(db);

// Find many
const { items, nextCursor } = await repo.findMany("posts", {
	limit: 10,
	where: { status: "published" },
});

// Find by ID
const post = await repo.findById("posts", "01HXK5MZSN...");

// Create
const newPost = await repo.create({
	type: "posts",
	slug: "new-post",
	data: { title: "New Post", content: [] },
	status: "draft",
});

// Update
const updated = await repo.update("posts", "01HXK5MZSN...", {
	data: { title: "Updated Title" },
});

// Delete
await repo.delete("posts", "01HXK5MZSN...");

MediaRepository

import { MediaRepository, createDatabase } from "emdash";

const db = createDatabase({ url: "file:./data.db" });
const repo = new MediaRepository(db);

// List media
const { items } = await repo.findMany({ limit: 20 });

// Get by ID
const media = await repo.findById("01HXK5MZSN...");

// Create (after upload)
const newMedia = await repo.create({
	filename: "photo.jpg",
	mimeType: "image/jpeg",
	size: 12345,
	storageKey: "uploads/photo.jpg",
});

Schema Registry

Programmatic schema management.

import { SchemaRegistry, createDatabase } from "emdash";

const db = createDatabase({ url: "file:./data.db" });
const registry = new SchemaRegistry(db);

// List collections
const collections = await registry.listCollections();

// Get collection with fields
const postsSchema = await registry.getCollectionWithFields("posts");

// Create collection
await registry.createCollection({
	slug: "products",
	label: "Products",
	labelSingular: "Product",
	supports: ["drafts", "revisions"],
});

// Add field
await registry.createField("products", {
	slug: "price",
	label: "Price",
	type: "number",
	required: true,
});

Preview System

generatePreviewToken()

Generate a preview token for draft content.

import { generatePreviewToken } from "emdash";

const token = await generatePreviewToken({
	contentId: "posts:01HXK5MZSN...",
	secret: process.env.EMDASH_ADMIN_SECRET,
	expiresIn: 3600, // 1 hour
});

verifyPreviewToken()

Verify a preview token.

import { verifyPreviewToken } from "emdash";

const result = await verifyPreviewToken({
	token,
	secret: process.env.EMDASH_ADMIN_SECRET,
});

if (result.valid) {
	const { cid, exp, iat } = result.payload;
	// cid is "collection:id" format, e.g. "posts:my-draft-post"
}

isPreviewRequest()

Check if a request includes a preview token.

import { isPreviewRequest, getPreviewToken } from "emdash";

if (isPreviewRequest(Astro.request)) {
	const token = getPreviewToken(Astro.request);
	// Verify and show preview content
}

Content Converters

Convert between Portable Text and ProseMirror formats.

import { prosemirrorToPortableText, portableTextToProsemirror } from "emdash";

// From ProseMirror (editor) to Portable Text (storage)
const portableText = prosemirrorToPortableText(prosemirrorDoc);

// From Portable Text to ProseMirror
const prosemirrorDoc = portableTextToProsemirror(portableText);

Site Settings

import { getSiteSettings, getSiteSetting } from "emdash";

// Get all settings
const settings = await getSiteSettings();

// Get single setting
const title = await getSiteSetting("siteTitle");

Settings are read-only from the runtime API. Use the admin API to update them.

import { getMenu, getMenus } from "emdash";

// Get all menus
const menus = await getMenus();

// Get specific menu with items
const primaryMenu = await getMenu("primary");

if (primaryMenu) {
	primaryMenu.items.forEach(item => {
		console.log(item.label, item.url);
		// Nested items for dropdowns
		item.children.forEach(child => console.log("  -", child.label));
	});
}

Taxonomies

import { getTaxonomyTerms, getTerm, getEntryTerms, getEntriesByTerm } from "emdash";

// Get all terms for a taxonomy (tree structure for hierarchical)
const categories = await getTaxonomyTerms("category");

// Get single term
const news = await getTerm("category", "news");

// Get terms assigned to a content entry
const postCategories = await getEntryTerms("posts", "post-123", "category");

// Get entries with a specific term
const newsPosts = await getEntriesByTerm("posts", "category", "news");

Widget Areas

import { getWidgetArea, getWidgetAreas } from "emdash";

// Get all widget areas
const areas = await getWidgetAreas();

// Get specific widget area with widgets
const sidebar = await getWidgetArea("sidebar");

if (sidebar) {
	sidebar.widgets.forEach(widget => {
		console.log(widget.type, widget.title);
	});
}

Sections

import { getSection, getSections, getSectionCategories } from "emdash";

// Get all sections
const sections = await getSections();

// Filter sections
const heroes = await getSections({ category: "hero" });
const themeSections = await getSections({ source: "theme" });
const results = await getSections({ search: "newsletter" });

// Get single section
const cta = await getSection("newsletter-cta");

// Get categories
const categories = await getSectionCategories();
import { search, searchCollection } from "emdash";

// Global search across collections
const results = await search("hello world", {
	collections: ["posts", "pages"],
	status: "published",
	limit: 20,
});

// Results include snippets with highlights
results.forEach(result => {
	console.log(result.title);
	console.log(result.snippet); // Contains <mark> tags
	console.log(result.score);
});

// Collection-specific search
const posts = await searchCollection("posts", "typescript", {
	limit: 10,
});

Error Handling

EmDash exports error classes for handling specific failures:

import {
  EmDashDatabaseError,
  EmDashValidationError,
  EmDashStorageError,
  SchemaError,
} from "emdash";

try {
  await repo.create({ ... });
} catch (error) {
  if (error instanceof EmDashValidationError) {
    console.error("Validation failed:", error.message);
  }
  if (error instanceof SchemaError) {
    console.error("Schema error:", error.code, error.details);
  }
}