Querying Content
EmDash provides query functions to retrieve content in your Astro pages and components. These functions follow Astro’s live content collections pattern, returning structured results with error handling.
Query Functions
Section titled “Query Functions”EmDash exports two primary query functions:
| Function | Purpose | Returns |
|---|---|---|
getEmDashCollection | Retrieve all entries of a content type | { entries, error } |
getEmDashEntry | Retrieve a single entry by ID or slug | { entry, error, isPreview } |
Import them from emdash:
import { getEmDashCollection, getEmDashEntry } from "emdash";Get All Entries
Section titled “Get All Entries”Use getEmDashCollection to retrieve all entries of a content type:
---import { getEmDashCollection } from "emdash";
const { entries: posts, error } = await getEmDashCollection("posts");
if (error) { console.error("Failed to load posts:", error);}---
<ul> {posts.map((post) => ( <li>{post.data.title}</li> ))}</ul>Filter by Locale
Section titled “Filter by Locale”When i18n is enabled, filter by locale to retrieve content in a specific language:
// French postsconst { entries: frenchPosts } = await getEmDashCollection("posts", { locale: "fr", status: "published",});
// Use the current request localeconst { entries: localizedPosts } = await getEmDashCollection("posts", { locale: Astro.currentLocale, status: "published",});For single entries, pass locale as the third argument:
const { entry: post } = await getEmDashEntry("posts", "my-post", { locale: Astro.currentLocale,});When locale is omitted, it defaults to the request’s current locale. If no translation exists for the requested locale, the fallback chain is followed.
Filter by Status
Section titled “Filter by Status”Retrieve only published, draft, or archived content:
// Only published postsconst { entries: published } = await getEmDashCollection("posts", { status: "published",});
// Only draftsconst { entries: drafts } = await getEmDashCollection("posts", { status: "draft",});
// Only archivedconst { entries: archived } = await getEmDashCollection("posts", { status: "archived",});Limit Results
Section titled “Limit Results”Restrict the number of returned entries:
// Get the 5 most recent postsconst { entries: recentPosts } = await getEmDashCollection("posts", { status: "published", limit: 5,});Filter by Taxonomy
Section titled “Filter by Taxonomy”Filter entries by category, tag, or custom taxonomy terms:
// Posts in the "news" categoryconst { entries: newsPosts } = await getEmDashCollection("posts", { status: "published", where: { category: "news" },});
// Posts with the "javascript" tagconst { entries: jsPosts } = await getEmDashCollection("posts", { status: "published", where: { tag: "javascript" },});
// Posts matching any of multiple termsconst { entries: featuredNews } = await getEmDashCollection("posts", { status: "published", where: { category: ["news", "featured"] },});The where filter uses OR logic when multiple values are provided for a single taxonomy.
Error Handling
Section titled “Error Handling”Always check for errors when reliability matters:
const { entries: posts, error } = await getEmDashCollection("posts");
if (error) { // Log and handle gracefully console.error("Failed to load posts:", error); return new Response("Server error", { status: 500 });}Get a Single Entry
Section titled “Get a Single Entry”Use getEmDashEntry to retrieve one entry by its ID or slug:
---import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) { return new Response("Server error", { status: 500 });}
if (!post) { return Astro.redirect("/404");}---
<article> <h1>{post.data.title}</h1> <div set:html={post.data.content} /></article>Entry Return Type
Section titled “Entry Return Type”getEmDashEntry returns a result object:
interface EntryResult<T> { entry: ContentEntry<T> | null; // null if not found error?: Error; // Only set for actual errors (not "not found") isPreview: boolean; // true if viewing preview/draft content}
interface ContentEntry<T> { id: string; data: T; edit: EditProxy; // Visual editing annotations}The data object within entry contains all fields defined for the content type. The edit proxy provides visual editing annotations (see below).
Preview Mode
Section titled “Preview Mode”EmDash handles preview automatically via middleware. When a URL contains a valid _preview token, the middleware verifies it and sets up the request context. Your query functions then serve draft content without any special parameters:
---import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;
// No special preview handling needed — middleware does it automaticallyconst { entry, isPreview, error } = await getEmDashEntry("posts", slug);
if (error) { return new Response("Server error", { status: 500 });}
if (!entry) { return Astro.redirect("/404");}---
{isPreview && ( <div class="preview-banner"> Viewing preview. This content is not published. </div>)}
<article> <h1>{entry.data.title}</h1> <PortableText value={entry.data.content} /></article>Visual Editing
Section titled “Visual Editing”Every entry returned by query functions includes an edit proxy for annotating your templates. Spread it onto elements to enable inline editing for authenticated editors:
<article {...entry.edit}> <h1 {...entry.edit.title}>{entry.data.title}</h1> <div {...entry.edit.content}> <PortableText value={entry.data.content} /> </div></article>In edit mode, {...entry.edit.title} produces a data-emdash-ref attribute that the visual editing toolbar uses to enable inline editing. In production, the proxy spreads produce no output — zero runtime cost.
Sorting Results
Section titled “Sorting Results”getEmDashCollection does not guarantee sort order. Sort results in your template:
const { entries: posts } = await getEmDashCollection("posts", { status: "published",});
// Sort by publication date, newest firstconst sorted = posts.sort( (a, b) => (b.data.publishedAt?.getTime() ?? 0) - (a.data.publishedAt?.getTime() ?? 0),);Common Sort Patterns
Section titled “Common Sort Patterns”// Alphabetical by titleposts.sort((a, b) => a.data.title.localeCompare(b.data.title));
// By custom order fieldposts.sort((a, b) => (a.data.order ?? 0) - (b.data.order ?? 0));
// Random orderposts.sort(() => Math.random() - 0.5);TypeScript Types
Section titled “TypeScript Types”Generate TypeScript types for your collections:
npx emdash typesThis creates .emdash/types.ts with interfaces for each collection. Use them for type safety:
import { getEmDashCollection, getEmDashEntry } from "emdash";import type { Post } from "../.emdash/types";
// Type-safe collection queryconst { entries: posts } = await getEmDashCollection<Post>("posts");// posts is ContentEntry<Post>[]
// Type-safe entry queryconst { entry: post } = await getEmDashEntry<Post>("posts", "my-post");// post is ContentEntry<Post> | nullStatic vs. Server Rendering
Section titled “Static vs. Server Rendering”EmDash content works with both static and server-rendered pages.
Static (Prerendered)
Section titled “Static (Prerendered)”For static pages, use getStaticPaths to generate routes at build time:
---import { getEmDashCollection, getEmDashEntry } from "emdash";
export async function getStaticPaths() { const { entries: posts } = await getEmDashCollection("posts", { status: "published", });
return posts.map((post) => ({ params: { slug: post.data.slug }, }));}
const { slug } = Astro.params;const { entry: post } = await getEmDashEntry("posts", slug);---Server-Rendered
Section titled “Server-Rendered”For server-rendered pages, query content directly:
---export const prerender = false;
import { getEmDashEntry } from "emdash";
const { slug } = Astro.params;const { entry: post, error } = await getEmDashEntry("posts", slug);
if (error) { return new Response("Server error", { status: 500 });}
if (!post) { return new Response(null, { status: 404 });}---Performance Considerations
Section titled “Performance Considerations”Caching
Section titled “Caching”EmDash uses Astro’s live content collections, which handle caching automatically. For server-rendered pages, consider adding HTTP cache headers:
---const { entries: posts } = await getEmDashCollection("posts", { status: "published",});
// Cache for 5 minutesAstro.response.headers.set("Cache-Control", "public, max-age=300");---Avoid Redundant Queries
Section titled “Avoid Redundant Queries”Query once and pass data to components:
---import { getEmDashCollection } from "emdash";import PostList from "../components/PostList.astro";import Sidebar from "../components/Sidebar.astro";
// Query onceconst { entries: posts } = await getEmDashCollection("posts", { status: "published",});
const featured = posts.filter((p) => p.data.featured);const recent = posts.slice(0, 5);---
<PostList posts={featured} /><Sidebar posts={recent} />Next Steps
Section titled “Next Steps”- Create a Blog - Build a complete blog
- Taxonomies - Filter by categories and tags
- Working with Content - Admin CRUD operations
- Internationalization - Multilingual content and translations