# EmDash API Reference Quick reference for EmDash-specific APIs used when porting themes. > **See also:** The `scaffold/` directory contains working examples of all these patterns. When in doubt, copy from there. ## Content Retrieval EmDash's query functions follow Astro's [live content collections](https://docs.astro.build/en/reference/experimental-flags/live-content-collections/) pattern, returning structured results for graceful error handling. ### getEmDashCollection Fetch multiple entries from a collection. ```typescript import { getEmDashCollection } from "emdash"; // Returns { entries, error } const { entries: posts } = await getEmDashCollection("posts"); // With filters const { entries: posts } = await getEmDashCollection("posts", { status: "published", limit: 10, where: { category: "news" }, }); ``` ### getEmDashEntry Fetch a single entry by slug. ```typescript import { getEmDashEntry } from "emdash"; // Returns { entry, error, isPreview } const { entry: post } = await getEmDashEntry("posts", "hello-world"); if (!post) { return Astro.redirect("/404"); } ``` ### Entry Shape ```typescript interface Entry { id: string; collection: string; data: { title: string; slug: string; content: PortableTextBlock[]; featured_image?: ImageField; // { src, alt } - NOT a string! // ... custom fields }; } ``` ## Field Types at Runtime **IMPORTANT:** Field types have specific runtime shapes. The most common mistake is treating image fields as strings. ### Image Fields Image fields are **objects**, not strings: ```typescript interface ImageField { src: string; // The resolved URL alt?: string; } ``` ```astro {/* CORRECT */} {post.data.featured_image?.src && ( {post.data.featured_image.alt )} {/* WRONG - renders [object Object] */} ``` ### Reference Fields In seed files use `"$ref:id"` format. At runtime they may be resolved objects or strings. ### PortableText Fields Rich content is an array of blocks with `_type` property. ## Site Settings ### getSiteSettings Get all site settings. ```typescript import { getSiteSettings } from "emdash"; const settings = await getSiteSettings(); console.log(settings.title); // "My Site" console.log(settings.logo?.url); // Resolved media URL ``` ### getSiteSetting Get a single setting. ```typescript import { getSiteSetting } from "emdash"; const title = await getSiteSetting("title"); const logo = await getSiteSetting("logo"); ``` ### Available Settings | Key | Type | Description | | ------------ | ---------------- | ------------------------ | | `title` | `string` | Site name | | `tagline` | `string` | Site tagline/description | | `logo` | `MediaReference` | Site logo with URL | | `favicon` | `MediaReference` | Favicon with URL | | `social` | `SocialLinks` | Social media URLs | | `timezone` | `string` | Site timezone | | `dateFormat` | `string` | Date display format | ## Navigation Menus ### getMenu Fetch a menu by name with resolved URLs. ```typescript import { getMenu } from "emdash"; const menu = await getMenu("primary"); if (menu) { console.log(menu.items); // MenuItem[] } ``` ### getMenus Get all menus (names only). ```typescript import { getMenus } from "emdash"; const menus = await getMenus(); // [{ id, name, label }] ``` ### MenuItem Shape ```typescript interface MenuItem { id: string; label: string; url: string; // Resolved URL target?: string; children: MenuItem[]; } ``` ### Rendering Menus ```astro --- import { getMenu } from "emdash"; const primaryMenu = await getMenu("primary"); --- ``` ## Taxonomies ### getTaxonomyTerms Get all terms for a taxonomy. ```typescript import { getTaxonomyTerms } from "emdash"; const categories = await getTaxonomyTerms("categories"); const tags = await getTaxonomyTerms("tags"); ``` ### getTerm Get a single term by slug. ```typescript import { getTerm } from "emdash"; const term = await getTerm("categories", "news"); console.log(term?.label); // "News" console.log(term?.count); // Number of entries ``` ### getEntryTerms Get terms assigned to a specific entry. > **IMPORTANT:** This function does NOT take a `db` parameter. ```typescript import { getEntryTerms } from "emdash"; // Get all terms for an entry const terms = await getEntryTerms("posts", post.id); // Get only categories const categories = await getEntryTerms("posts", post.id, "categories"); ``` ### getEntriesByTerm Get entries that have a specific term. ```typescript import { getEntriesByTerm } from "emdash"; const posts = await getEntriesByTerm("posts", "categories", "news"); ``` ### TaxonomyTerm Shape ```typescript interface TaxonomyTerm { id: string; name: string; // Taxonomy name slug: string; // Term slug label: string; // Display label children: TaxonomyTerm[]; count?: number; } ``` ## Widget Areas ### getWidgetArea Get a widget area by name. ```typescript import { getWidgetArea } from "emdash"; const sidebar = await getWidgetArea("sidebar"); if (sidebar) { console.log(sidebar.widgets); // Widget[] } ``` ### Widget Types | Type | Description | Key Fields | | ----------- | -------------------- | ---------------------- | | `content` | Rich text (PT) | `content` | | `menu` | Navigation menu | `menuName` | | `component` | Registered component | `componentId`, `props` | ## Sections (Reusable Blocks) Sections are reusable content blocks that editors can insert via `/section` slash command. ### getSection Get a single section by slug. ```typescript import { getSection } from "emdash"; const cta = await getSection("newsletter-cta"); // Returns { id, slug, title, content, keywords, source } ``` ### getSections List sections with optional filters. ```typescript import { getSections } from "emdash"; // Get all sections const all = await getSections(); // Filter by source: "theme" | "user" | "import" const imported = await getSections({ source: "import" }); ``` ### Section Sources | Source | Description | | -------- | --------------------------------------- | | `theme` | Defined in seed file | | `user` | Created by editors in admin | | `import` | Imported from WordPress reusable blocks | ## Search ### search Global search across collections. ```typescript import { search } from "emdash"; const results = await search("hello world", { collections: ["posts", "pages"], // Optional: limit to specific collections status: "published", // Optional: filter by status limit: 20, // Optional: max results }); // Returns { results: SearchResult[], total, nextCursor? } results.results.forEach((r) => { console.log(r.collection); // "posts" console.log(r.id); // Entry ID console.log(r.title); // Entry title console.log(r.slug); // Entry slug console.log(r.snippet); // HTML snippet with highlights console.log(r.score); // Relevance score }); ``` ### LiveSearch Component Ready-to-use search with instant results: ```astro --- import LiveSearch from "emdash/ui/search"; --- ``` Features: - Debounced instant search - Prefix matching (automatic `*` suffix) - Porter stemming ("run" finds "running") - Result snippets with `` highlights ### Search Configuration Search is enabled per-collection via admin UI: 1. Edit Content Type → check "Search" in Features 2. Edit fields → check "Searchable" for text fields Only collections with search enabled are indexed. ## Rendering Content ### PortableText Component ```astro --- import { PortableText } from "emdash/ui"; --- ``` ## CLI Commands ### Apply Seed The seed is inlined into the build and applied automatically on the first request when the database is empty and the setup wizard hasn't been completed. Validation runs at apply time — errors surface in the dev server logs. To re-seed during iteration, delete `data.db` and restart the dev server. Common validation errors caught: - Image fields with raw URLs (should use `$media`) - Reference fields with raw IDs (should use `$ref:id`) - PortableText not an array or missing `_type` - Type mismatches (string vs number, etc.) ### Export Seed ```bash # Export schema only emdash export-seed # Export schema and all content emdash export-seed --with-content # Export specific collections emdash export-seed --with-content=posts,pages ``` ## Configuration ### astro.config.mjs ```javascript 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", }), }), ], }); ``` ### live.config.ts ```typescript // src/live.config.ts import { defineLiveCollection } from "astro:content"; import { emdashLoader } from "emdash/runtime"; export const collections = { _emdash: defineLiveCollection({ loader: emdashLoader() }), }; ``` ## Common Patterns ### Homepage with Recent Posts ```astro --- import { getEmDashCollection, getSiteSettings } from "emdash"; import Base from "../layouts/Base.astro"; const settings = await getSiteSettings(); const { entries: posts } = await getEmDashCollection("posts", { limit: 10 }); --- {posts.map(post => ( ))} ``` ### Category Archive ```astro --- import { getTerm, getEntriesByTerm } from "emdash"; const { slug } = Astro.params; const category = await getTerm("categories", slug); const posts = await getEntriesByTerm("posts", "categories", slug); ---

{category?.label}

{posts.map(post => ( {post.data.title} ))} ``` ### Dynamic Navigation ```astro --- import { getMenu, getSiteSettings } from "emdash"; const settings = await getSiteSettings(); const primaryMenu = await getMenu("primary"); ---
{settings.title}
```