このページでは、Astro のページ、レイアウト、コンポーネントが EmDash サイトを読み取り、表示するために使う公開 API を説明します。emdash パッケージのルートからエクスポートされるすべての API を列挙するものではありません。データベースリポジトリ、API ハンドラー、マイグレーション用ユーティリティ、プラグイン作者向け API、その他のサーバー内部向け API は別のリファレンスにあり、フレームワーク統合コード向けです。
このリファレンスの サイトテンプレート API 契約 には、EmDash がメンテナンスするサイトテンプレートが emdash から import する実行時関数がすべて載っています。MediaValue のような型のみの import は、該当するデータモデルのドキュメントに属します。サイト作者の役に立つ関連関数は同じセクションで説明しますが、無関係なルート export はこのページの対象外です。
サイトテンプレート API 契約
メンテナンス対象のテンプレートは、次の実行時ヘルパーを呼び出します。
| 関数 | 用途 |
|---|---|
decodeSlug | ルックアップ前に動的ルートパラメータをデコードする |
getEmDashCollection | コレクション内のエントリを読み取り、フィルタする |
getEmDashEntry | ID または slug で 1 件のエントリを読み取る |
getMenuWithCacheHint | キャッシュ無効化付きでナビゲーションメニューを描画する |
getSeoMeta | エントリの SEO パネル値とテンプレートのフォールバックを解決する |
getSiteSettings | 公開サイトの識別情報などグローバル設定を読み取る |
getSiteSettingsWithCacheHint | キャッシュ無効化付きでグローバル設定を読み取る |
getTaxonomyTermsWithCacheHint | キャッシュ無効化付きでタクソノミーフィルタを描画する |
getTermsForEntries | エントリ一覧に対して 1 つのタクソノミーをバッチ読み込みする |
sanitizeHref | 保存済みリンクを描画する前に安全でない URL スキームを拒否する |
search | コレクション横断で公開コンテンツを検索する |
コンテンツクエリ
EmDash のクエリ関数は Astro の live content collections パターンに従い、エラーを丁寧に扱うために { entries, error } または { entry, error } を返します。
getEmDashCollection()
コレクションからすべてのエントリを取得します。次の例ではすべての投稿を読み込み、エラーがあるか確認します。
import { getEmDashCollection } from "emdash";
const { entries: posts, error } = await getEmDashCollection("posts");
if (error) {
console.error("Failed to load posts:", error);
}
パラメータ
| パラメータ | 型 | 説明 |
|---|---|---|
collection | string | コレクション slug |
options | CollectionFilter | 任意のフィルタオプション |
オプション
options パラメータは次のフィルタを受け付けます。
interface WhereRange {
gt?: string;
gte?: string;
lt?: string;
lte?: string;
}
interface CollectionFilter {
status?: "draft" | "published" | "archived";
limit?: number;
cursor?: string; // Keyset pagination — pass a previous `nextCursor`
offset?: number; // Offset pagination — skip N entries (use with `limit`)
where?: Record<string, string | string[] | WhereRange>;
orderBy?: Record<string, "asc" | "desc">;
locale?: string;
}
cursor と offset は同時に使えません。where のキーにはコンテンツフィールド、タクソノミー、byline を指定できます。範囲オブジェクトは順序付き比較に使えます。
戻り値
この関数は CollectionResult に解決されます。
interface CollectionResult<T> {
entries: ContentEntry<T>[]; // Empty array if error or none found
error?: Error; // Set if query failed
cacheHint: CacheHint; // Tags and last-modified time for Astro route caching
nextCursor?: string; // Cursor for the next keyset page, if any
hasMore?: boolean; // Whether more entries exist beyond this page (when `limit` is set)
}
例
次の例では、ステータスとタクソノミーでフィルタし、件数を制限し、エラーを処理します。
// 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" },
});
// Numbered archive page (e.g. /page/3) with offset pagination
const perPage = 20;
const page = Number(Astro.params.page ?? 1);
const { entries: pagePosts, hasMore } = await getEmDashCollection("posts", {
status: "published",
limit: perPage,
offset: (page - 1) * perPage,
orderBy: { published_at: "desc" },
});
// Handle errors
const { entries, error } = await getEmDashCollection("posts");
if (error) {
return new Response("Server error", { status: 500 });
}
getEmDashEntry()
slug または ID で 1 件のエントリを取得します。次の例では投稿を読み込み、見つからない場合はリダイレクトします。
import { getEmDashEntry } from "emdash";
const { entry: post, error } = await getEmDashEntry("posts", "my-post-slug");
if (!post) {
return Astro.redirect("/404");
}
パラメータ
| パラメータ | 型 | 説明 |
|---|---|---|
collection | string | コレクション slug |
slugOrId | string | エントリ slug または ID |
options | { locale?: string; references?: ReferenceSelection } | 任意。slug 解決用のロケールと、読み込む参照フィールド |
プレビューモードは自動で処理されます。リクエストに有効な _preview トークンがあるとき、クエリは下書きコンテンツを返します。プレビュー状態用のパラメータは不要です。
references は読み込む参照フィールドを、フィールド slug をキーにして指定します。true はデフォルト上限 50 件の最初のページを要求します。オブジェクト形式では limit(最大 100)と前ページの cursor を指定できます。省略したフィールドは読み込まれず、オプション自体を省略した呼び出しでは参照クエリは実行されません。
戻り値
この関数は EntryResult に解決されます。
interface EntryResult<T, R> {
entry: ContentEntry<T, R> | null; // null if not found
error?: Error; // Set only for actual errors, not "not found"
isPreview: boolean; // true if draft content is being served
fallbackLocale?: string; // Set when locale fallback returned another locale
cacheHint: CacheHint; // Tags and last-modified time for Astro route caching
}
例
次の例では slug と ID で取得し、プレビュー状態を読み取り、エラーと未検出を区別します。
// 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");
}
getTranslations()
コレクション slug とデータベース ID から、1 件のエントリで利用可能な翻訳を取得します。
import { getTranslations } from "emdash";
const { translations, error } = await getTranslations("posts", post.data.id);
結果には共有の translationGroup、translations 配列、任意の error が含まれます。各翻訳サマリーには id、locale、slug、status があります。
resolveEmDashPath()
ルーティング可能なコレクション用に設定された URL パターンに対して、公開 pathname を解決します。
import { resolveEmDashPath } from "emdash";
const result = await resolveEmDashPath("/blog/hello-world");
if (result) {
console.log(result.collection, result.entry.data.title);
}
結果には一致した collection、entry、ルート params が含まれます。設定された URL パターンに一致しない場合は null を返します。
getEditMeta()
Portable Text 値に付与された、列挙不可のビジュアル編集メタデータを読み取ります。
import { getEditMeta } from "emdash";
const meta = getEditMeta(post.data.content);
注釈付きの値に対して { collection, id, field } を返し、注釈がない値では undefined を返します。
コンテンツ型
ContentEntry
クエリ関数は次の形のエントリを返します。
interface ContentEntry<T = Record<string, unknown>, R = ReferencePages> {
id: string;
data: T;
references?: R; // One page per reference field requested; absent otherwise
edit: EditProxy; // Visual editing annotations
}
edit プロキシはビジュアル編集の注釈を提供します。要素に展開するとインライン編集が有効になります: {...entry.edit.title}。編集モード外では出力は生成されません。
data オブジェクトにはすべてのコンテンツフィールドに加え、次のシステムフィールドが含まれます。
id- 一意の識別子slug- URL 向けの識別子status- “draft” | “published” | “archived”createdAt- 作成日時(Date)updatedAt- 最終更新日時(Date)publishedAt- 公開日時(Date)、またはnull。非公開にしても保持されます- コレクションスキーマで定義したカスタムフィールドすべて
ReferencePage
entry.references には、getEmDashEntry で要求した参照フィールドごとに 1 ページ分が、フィールド slug をキーに格納されます。
interface ReferencePage<T = Record<string, unknown>> {
entries: ContentEntry<T>[];
nextCursor?: string; // Set when the field holds more entries than the limit
}
各エントリは直接読み込んだエントリと同様にマップされます。例外としてバイラインとタクソノミータームはハイドレートされないため、data.bylines と data.terms は存在しません。
getEmDashReferences()
親エントリを再読み込みせず、単一の参照フィールドの 1 ページ分を取得します。そのページが返した nextCursor で 2 ページ目以降をたどるときに使います。
| パラメータ | 型 | 説明 |
|---|---|---|
collection | string | フィールドを持つエントリのコレクション slug |
slugOrId | string | エントリ slug または ID |
field | string | 参照フィールド slug |
options | { limit?: number; cursor?: string; locale?: string } | 任意。デフォルト 50 件、最大 100 |
次の例では、リンク先エントリの次の 20 件を読み込みます。
import { getEmDashReferences } from "emdash";
const { entries, nextCursor, error } = await getEmDashReferences(
"posts",
post.id,
"related_posts",
{ cursor, limit: 20 },
);
error が設定されるのは実際のエラーの場合のみです。未知のフィールド、存在しないエントリ、リクエストから見えないエントリは、いずれも空の entries 配列に解決されます。
結果には、そのページが読んだ行を名指しする cacheHint も含まれます。描画結果をキャッシュするルートは、getEmDashEntry({ references }) が最初のページ分を取り込むのと同様に、自身の hint にマージすべきです。そうすると、それらのエントリへの書き込みでページキャッシュが失効します。
URL ヘルパー
decodeSlug() and slugify()
コンテンツクエリに渡す前に、動的ルートパラメータに decodeSlug() を使います。パラメータが欠けている場合は undefined を返し、それ以外では decodeURIComponent() を適用します。不正なパーセントエンコードでは例外が投げられます。
function decodeSlug(raw: string | undefined): string | undefined;
import { decodeSlug, getEmDashEntry } from "emdash";
const slug = decodeSlug(Astro.params.slug);
const { entry } = slug ? await getEmDashEntry("posts", slug) : { entry: null };
slugify(value) はテキストを小文字のハイフン区切り slug に変換します。ラベルから slug を組み立てるテンプレートで使います。保存済みエントリ slug はすでに EmDash 由来です。
sanitizeHref() and isSafeHref()
これらのヘルパーは javascript: など安全でないリンクスキームを拒否します。isSafeHref(value) は真偽値を返します。sanitizeHref(value) は安全な URL をそのまま返し、値が空または安全でないときは "#" を返します。
import { sanitizeHref } from "emdash";
const href = sanitizeHref(menuItem.url);
プレビューシステム
generatePreviewToken()
下書きコンテンツ用のプレビュートークンを生成します。次の例では 1 時間で失効するトークンを作成します。
import { generatePreviewToken } from "emdash";
const token = await generatePreviewToken({
contentId: "posts:01HXK5MZSN...",
secret: process.env.EMDASH_PREVIEW_SECRET!,
expiresIn: 3600, // 1 hour
});
contentId は collection:id 形式である必要があります。expiresIn は秒数、または s、m、h、d、w で終わる期間文字列を受け付け、デフォルトは "1h" です。署名用シークレットはサーバー側に置いてください。
verifyPreviewToken()
プレビュートークンを検証し、ペイロードを読み取ります。
import { verifyPreviewToken } from "emdash";
const result = await verifyPreviewToken({
token,
secret: process.env.EMDASH_PREVIEW_SECRET!,
});
if (result.valid) {
const { cid, exp, iat } = result.payload;
// cid is "collection:id" format, e.g. "posts:my-draft-post"
}
token または url と署名用 secret を渡します。無効なトークンは { valid: false, error } を返し、error は "none"、"malformed"、"invalid"、"expired" のいずれかです。
parseContentId()
プレビューペイロードの collection:id 値を 2 部分に分割します。
import { parseContentId } from "emdash";
const parsed = parseContentId(result.payload.cid);
{ collection, id } を返し、コロン区切りがない値では例外を投げます。
getPreviewUrl() and buildPreviewUrl()
getPreviewUrl() はプレビュー URL を作成して署名します。collection、id、secret に加え、任意で expiresIn、baseUrl、pathPattern、locale を受け付けます。
import { getPreviewUrl } from "emdash";
const previewUrl = await getPreviewUrl({
collection: "posts",
id: post.id,
secret: process.env.EMDASH_PREVIEW_SECRET!,
pathPattern: "/blog/{id}",
});
baseUrl がない場合はサイト相対 URL を返します。トークンがすでにある場合は buildPreviewUrl({ path, token, baseUrl? }) を使います。
isPreviewRequest()
リクエストにプレビュートークンが含まれるか確認し、読み取ります。
import { isPreviewRequest, getPreviewToken } from "emdash";
if (isPreviewRequest(Astro.url)) {
const token = getPreviewToken(Astro.url);
// Verify and show preview content
}
getPreviewToken() は _preview クエリパラメータを返し、ない場合は null です。EmDash ミドルウェアは通常のプレビューリクエストを検証し、getEmDashEntry() にプレビュー状態を自動で渡します。これらのヘルパーはカスタムプレビュールートやツール向けです。
コンテンツコンバータ
Portable Text と ProseMirror 形式の相互変換:
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);
サイト設定
getSiteSettings と getSiteSetting でサイト全体の設定を読み取ります。
function getSiteSettings(): Promise<Partial<SiteSettings>>;
function getSiteSetting<K extends SiteSettingKey>(key: K): Promise<SiteSettings[K] | undefined>;
import { getSiteSettings, getSiteSetting } from "emdash";
// Get all settings
const settings = await getSiteSettings();
// Get single setting
const title = await getSiteSetting("title");
実行時 API から設定は読み取り専用です。更新には管理 API を使います。
getSiteSettings() は未設定のキーが省略されるため、部分オブジェクトを返します。サイトロゴや favicon などのメディア設定は、返却前にメディア参照オブジェクトに解決されます。
getSiteSettingsWithCacheHint() は { data, cacheHint } を返します。サイト設定変更後に Astro ルートキャッシュを無効化する場合は、hint を Astro.cache.set() に渡します。
SEO
getSeoMeta() でページの SEO パネル値とコンテンツのフォールバックを解決します。
function getSeoMeta<T>(content: SeoContentInput<T>, options?: SeoMetaOptions): SeoMeta;
interface SeoMetaOptions {
siteTitle?: string;
siteUrl?: string;
titleSeparator?: string; // Default: " | "
path?: string;
defaultOgImage?: string;
defaultTitle?: string;
defaultDescription?: string;
}
interface SeoMeta {
title: string;
description: string | null;
ogTitle: string;
ogDescription: string | null;
ogImage: string | null;
canonical: string | null;
robots: string | null;
}
import { getSeoMeta } from "emdash";
const meta = getSeoMeta(post, {
siteTitle: "Example Blog",
siteUrl: "https://example.com",
path: `/blog/${post.data.slug}`,
});
解決済みの title、description、Open Graph 値、canonical URL、robots 値を返します。getContentSeo(content) はテンプレートのフォールバックを適用せず、生の SEO オブジェクトを返します。
翻訳コンテンツでは、getHreflangAlternates(collection, entryId, { siteUrl? }) が公開済みでルーティング可能なロケール別バリアントを { hreflang, href } オブジェクトとして返し、x-default エントリを追加します。国際化が無効、現在のエントリが noindex、絶対サイト URL がない場合は空配列を返します。
コメント
エントリに対する承認済みコメントと件数を取得します。
import { getCommentCount, getComments } from "emdash";
const { items: comments, total } = await getComments({
collection: "posts",
contentId: post.data.id,
threaded: true,
reactions: true,
sort: "best",
});
const count = await getCommentCount("posts", post.data.id);
threaded は返信を親コメントの下にネストします。デフォルトの sort は "oldest" です。"best" はトップレベルコメントをリアクションで並べ、リアクション数を自動で有効にします。サーバー描画のコメントクエリは承認済みコメントを最大 500 件まで返します。クライアントでページネーションが必要な場合は REST API を使います。
メニュー
ナビゲーションメニューを取得し、ネストした子を含めて項目を走査します。
function getMenu(name: string, options?: { locale?: string }): Promise<Menu | null>;
function getMenus(options?: { locale?: string }): Promise<MenuSummary[]>;
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));
});
}
getMenu(name, { locale? }) は設定されたロケールフォールバックチェーンに従います。getMenus({ locale? }) は解決されたリクエストまたは設定ロケール向けのメニューサマリーを一覧します。国際化がない場合は各ロケールを一覧します。getMenuWithCacheHint() は Astro キャッシュを使うルート向けに { data, cacheHint } を返します。
バイライン
ID または slug で著者プロフィールを取得するか、バイラインにクレジットされたエントリを一覧します。
import { getByline, getBylineBySlug, getEntriesByByline } from "emdash";
const profile = await getBylineBySlug("jane-doe", { locale: "en" });
const posts = profile
? await getEntriesByByline("posts", profile.translationGroup ?? profile.id)
: [];
getByline(id) は 1 件のプロフィールまたは null を返します。slug 検索は任意のロケールを受け付け、ロケールフォールバックチェーンに従います。コンテンツクエリはすでにエントリの順序付きクレジットを entry.data.bylines にハイドレートします。著者ページやバイラインアーカイブには、これらのスタンドアロンヘルパーを使います。
タクソノミー
タクソノミーターム、単一ターム、エントリのターム、ターム別エントリを取得します。
function getTaxonomyTerms(
taxonomyName: string,
options?: { locale?: string; includeCounts?: boolean },
): Promise<TaxonomyTerm[]>;
function getTerm(
taxonomyName: string,
slug: string,
options?: { locale?: string; includeCounts?: boolean },
): Promise<TaxonomyTerm | null>;
function getTermsForEntries(
collection: string,
entryIds: string[],
taxonomyName: string,
options?: { locale?: string },
): Promise<Map<string, TaxonomyTerm[]>>;
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");
getTaxonomyDefs({ locale? }) はタクソノミーごとに 1 件の定義を一覧し、getTaxonomyDef(name, { locale? }) は 1 件の定義、またはどのロケールでも定義がない場合は null を返します。どちらも各ロケールでタクソノミーを解決します。タクソノミーとタームの翻訳 で、どのロケールのラベルを使うかを説明しています。タームのルックアップは設定されたフォールバックチェーンに従います。
getTaxonomyTerms(name, { locale?, includeCounts? }) は階層型タクソノミー向けにツリーを返し、デフォルトで表示可能エントリ数を含めます。テンプレートで件数を描画しない場合は includeCounts: false を渡します。コンテンツクエリは割り当て済みタームを各エントリの data.terms にもハイドレートします。コレクション名とエントリ ID だけがある場合は getEntryTerms() を使います。
多くのエントリ横にタームを描画するアーカイブページでは、ループで getEntryTerms() を呼ぶ代わりにルックアップをバッチ化します。
import { getAllTermsForEntries, getTermsForEntries } from "emdash";
const termsByPost = await getTermsForEntries(
"posts",
posts.map(post => post.data.id),
"category",
);
const allTermsByPost = await getAllTermsForEntries(
"posts",
posts.map(post => post.data.id),
);
getTermsForEntries() はエントリ ID から要求したタクソノミーのタームへの Map を返します。getAllTermsForEntries() はエントリ ID からタクソノミー名ごとにグループ化したタームへの Map を返します。getTaxonomyTermsWithCacheHint() は Astro キャッシュ付きルート向けに { data, cacheHint } を返します。
ウィジェットエリア
ウィジェットエリアと、それに含まれるウィジェットを取得します。
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);
});
}
getWidgetAreaWithCacheHint(name) は Astro キャッシュを使うルート向けに { data, cacheHint } を返します。ウィジェットエリアとウィジェットは、設定された並び順どおりに並びます。
セクション
セクションを取得し、フィルタします。
import { getSection, getSections } from "emdash";
// Get all sections (paginated)
const { items, nextCursor } = await getSections();
// Filter sections
const { items: themeSections } = await getSections({ source: "theme" });
const { items: results } = await getSections({ search: "newsletter" });
// Get a single section by slug
const cta = await getSection("newsletter-cta");
getSections(options?) は { items: Section[]; nextCursor?: string } を返します。オプションは source("theme" | "user" | "import")、search、limit(デフォルト 50、最大 100)、cursor です。
検索
コレクション横断のグローバル検索を実行します。結果にはハイライト付きスニペットが含まれます。
function search(query: string, options?: SearchOptions): Promise<SearchResponse>;
interface SearchOptions {
collections?: string[]; // Default: every searchable collection
status?: string; // Default: "published"
locale?: string; // Default: all locales
limit?: number; // Default: 20
cursor?: string;
scope?: "all" | "title"; // Default: "all"
}
interface SearchResponse {
items: SearchResult[];
nextCursor?: string;
}
import { search } from "emdash";
const results = await search("hello world", {
collections: ["posts", "pages"],
status: "published",
limit: 20,
});
// search() resolves to { items, nextCursor? }
results.items.forEach(result => {
console.log(result.title);
console.log(result.snippet); // Contains <mark> tags
console.log(result.score);
});
// Paginate: pass the previous nextCursor back as `cursor` to get the next page.
// nextCursor is undefined once there are no more results.
if (results.nextCursor) {
const next = await search("hello world", {
collections: ["posts", "pages"],
limit: 20,
cursor: results.nextCursor,
});
}
scope: "title" のとき、クエリはインデックス全文ではなく各コレクションの title フィールドだけに一致します。本文一致が望ましくないピッカーやオートコンプリート向けです。title フィールドが検索用にインデックスされていないコレクションは、この scope では結果を返しません。
エラー処理
コンテンツクエリは例外を投げず、結果オブジェクト内に運用上のエラーを返します。エントリが見つからないことは運用上のエラーではありません。entry は null で error は undefined のままです。
const { entry, error } = await getEmDashEntry("posts", slug);
if (error) {
return new Response("Content could not be loaded", { status: 500 });
}
if (!entry) {
return Astro.redirect("/404");
}
結果ラッパーのないヘルパーは、入力が無効な場合やデータベース操作が失敗した場合に例外を投げることがあります。ページが有用なフォールバックを提供できるときは、ルート境界でそれらのエラーを処理してください。低レベルのサーバー統合で使うリポジトリおよびハンドラーのエラークラスは、このサイトテンプレート API の対象外です。