---
title: JavaScript API Reference
description: Programmatic API for querying and managing EmDash content.
---

import { Aside } from "@astrojs/starlight/components";

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](https://docs.astro.build/en/reference/experimental-flags/live-content-collections/) pattern, returning `{ entries, error }` or `{ entry, error }` for graceful error handling.

### `getEmDashCollection()`

Fetch all entries from a collection.

```ts
import { getEmDashCollection } from "emdash";

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

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

#### Parameters

| Parameter    | Type               | Description             |
| ------------ | ------------------ | ----------------------- |
| `collection` | `string`           | Collection slug         |
| `options`    | `CollectionFilter` | Optional filter options |

#### Options

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

#### Returns

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

#### Examples

```ts
// 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.

```ts
import { getEmDashEntry } from "emdash";

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

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

#### Parameters

| Parameter    | Type     | Description      |
| ------------ | -------- | ---------------- |
| `collection` | `string` | Collection slug  |
| `slugOrId`   | `string` | Entry 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

```ts
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

```ts
// 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:

```ts
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.

```ts
import { createDatabase } from "emdash";

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

<Aside type="caution">
	Direct database access is for advanced use cases. Prefer the query functions for content access.
</Aside>

### `runMigrations()`

Run pending database migrations.

```ts
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.

```ts
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`

```ts
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`

```ts
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.

```ts
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.

```ts
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.

```ts
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.

```ts
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.

```ts
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

```ts
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.

## Menus

```ts
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

```ts
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

```ts
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

```ts
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();
```

## Search

```ts
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:

```ts
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);
  }
}
```
