Files
moreminimore-redesign/skills/wordpress-theme-to-emdash/references/emdash-api.md
2026-04-01 10:44:22 +01:00

10 KiB

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 pattern, returning structured results for graceful error handling.

getEmDashCollection

Fetch multiple entries from a collection.

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.

import { getEmDashEntry } from "emdash";

// Returns { entry, error, isPreview }
const { entry: post } = await getEmDashEntry("posts", "hello-world");

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

Entry Shape

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:

interface ImageField {
	src: string; // The resolved URL
	alt?: string;
}
{/* CORRECT */}
{post.data.featured_image?.src && (
  <img
    src={post.data.featured_image.src}
    alt={post.data.featured_image.alt || post.data.title}
  />
)}

{/* WRONG - renders [object Object] */}
<img src={post.data.featured_image} />

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.

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.

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.

import { getMenu } from "emdash";

const menu = await getMenu("primary");

if (menu) {
	console.log(menu.items); // MenuItem[]
}

getMenus

Get all menus (names only).

import { getMenus } from "emdash";

const menus = await getMenus();
// [{ id, name, label }]

MenuItem Shape

interface MenuItem {
	id: string;
	label: string;
	url: string; // Resolved URL
	target?: string;
	children: MenuItem[];
}

Rendering Menus

---
import { getMenu } from "emdash";

const primaryMenu = await getMenu("primary");
---
<nav>
  {primaryMenu?.items.map(item => (
    <a href={item.url}>{item.label}</a>
  ))}
</nav>

Taxonomies

getTaxonomyTerms

Get all terms for a taxonomy.

import { getTaxonomyTerms } from "emdash";

const categories = await getTaxonomyTerms("categories");
const tags = await getTaxonomyTerms("tags");

getTerm

Get a single term by slug.

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.

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.

import { getEntriesByTerm } from "emdash";

const posts = await getEntriesByTerm("posts", "categories", "news");

TaxonomyTerm Shape

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.

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.

import { getSection } from "emdash";

const cta = await getSection("newsletter-cta");
// Returns { id, slug, title, content, keywords, source }

getSections

List sections with optional filters.

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

Global search across collections.

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 <mark> highlights
	console.log(r.score); // Relevance score
});

LiveSearch Component

Ready-to-use search with instant results:

---
import LiveSearch from "emdash/ui/search";
---

<LiveSearch
  placeholder="Search..."
  collections={["posts", "pages"]}
/>

Features:

  • Debounced instant search
  • Prefix matching (automatic * suffix)
  • Porter stemming ("run" finds "running")
  • Result snippets with <mark> 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

---
import { PortableText } from "emdash/ui";
---

<PortableText value={post.data.content} />

CLI Commands

Seed Validation

Validate seed files before applying:

# Validate default seed file (.emdash/seed.json)
emdash seed --validate

# Validate a specific file
emdash seed path/to/seed.json --validate

Catches common mistakes:

  • 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.)

Apply Seed

# Apply seed with content
emdash seed

# Apply seed without sample content
emdash seed --no-content

# Specify database path
emdash seed --database ./my-data.db

Export Seed

# 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

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

// 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

---
import { getEmDashCollection, getSiteSettings } from "emdash";
import Base from "../layouts/Base.astro";

const settings = await getSiteSettings();
const { entries: posts } = await getEmDashCollection("posts", { limit: 10 });
---
<Base title={settings.title}>
  {posts.map(post => (
    <article>
      <a href={`/posts/${post.data.slug}`}>{post.data.title}</a>
    </article>
  ))}
</Base>

Category Archive

---
import { getTerm, getEntriesByTerm } from "emdash";

const { slug } = Astro.params;
const category = await getTerm("categories", slug);
const posts = await getEntriesByTerm("posts", "categories", slug);
---
<h1>{category?.label}</h1>
{posts.map(post => (
  <a href={`/posts/${post.data.slug}`}>{post.data.title}</a>
))}

Dynamic Navigation

---
import { getMenu, getSiteSettings } from "emdash";

const settings = await getSiteSettings();
const primaryMenu = await getMenu("primary");
---
<header>
  <a href="/">{settings.title}</a>
  <nav>
    {primaryMenu?.items.map(item => (
      <a href={item.url}>{item.label}</a>
    ))}
  </nav>
</header>