Cette page décrit l’API publique utilisée par les pages, layouts et composants Astro pour lire et présenter un site EmDash. Elle n’inventorie pas chaque export de la racine du paquet emdash : dépôts de base de données, handlers API, utilitaires de migration, APIs pour auteurs de plugins et autres internals serveur ont des références séparées ou sont destinés au code d’intégration du framework.
Le contrat d’API des modèles de site de cette référence liste chaque fonction importée depuis emdash par les modèles de site maintenus par EmDash. Les imports réservés aux types comme MediaValue relèvent de la documentation du modèle de données correspondant. Les fonctions associées sont documentées dans les mêmes sections lorsqu’elles aident l’auteur du site, mais la page ne couvre pas les exports racine sans lien.
Contrat d’API des modèles de site
Les modèles maintenus appellent ces helpers d’exécution :
| Fonction | Usage |
|---|---|
decodeSlug | Décoder un paramètre de route dynamique avant la recherche |
getEmDashCollection | Lire et filtrer les entrées d’une collection |
getEmDashEntry | Lire une entrée par ID ou slug |
getMenuWithCacheHint | Afficher un menu de navigation avec invalidation du cache |
getSeoMeta | Résoudre les valeurs du panneau SEO d’une entrée et les replis de modèle |
getSiteSettings | Lire l’identité publique du site et autres paramètres globaux |
getSiteSettingsWithCacheHint | Lire les paramètres globaux avec invalidation du cache |
getTaxonomyTermsWithCacheHint | Afficher des filtres de taxonomie avec invalidation du cache |
getTermsForEntries | Charger en lot une taxonomie pour une liste d’entrées |
sanitizeHref | Rejeter les schémas URL dangereux avant d’afficher les liens stockés |
search | Rechercher du contenu publié dans toutes les collections |
Requêtes de contenu
Les fonctions de requête d’EmDash suivent le modèle live content collections d’Astro et renvoient { entries, error } ou { entry, error } pour une gestion d’erreurs maîtrisée.
getEmDashCollection()
Récupère toutes les entrées d’une collection. L’exemple suivant charge tous les posts et vérifie s’il y a une erreur :
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
Le paramètre options accepte le filtre suivant :
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 et offset s’excluent mutuellement. Les clés where peuvent nommer des champs de contenu, des taxonomies ou byline ; des objets de plage permettent des comparaisons ordonnées.
Returns
La fonction se résout en un 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)
}
Examples
Les exemples suivants filtrent par statut et taxonomie, limitent les résultats et gèrent les erreurs :
// 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()
Récupère une seule entrée par slug ou ID. L’exemple suivant charge un post et redirige s’il est absent :
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 |
options | { locale?: string; references?: ReferenceSelection } | Optional. Locale for slug resolution, and the reference fields to load |
Le mode preview est géré automatiquement : lorsque la requête a un jeton _preview valide, la requête sert du contenu brouillon. Aucun paramètre n’est requis pour l’état preview.
references nomme les champs reference à charger, indexés par slug de champ. true demande la première page avec la limite par défaut de 50 entrées ; la forme objet accepte limit (au plus 100) et le cursor d’une page précédente. Les champs omis ne sont pas lus, et un appel sans l’option n’exécute aucune requête reference.
Returns
La fonction se résout en un 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
}
Examples
Les exemples suivants récupèrent par slug et ID, lisent l’état preview et distinguent erreurs et absence :
// 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()
Récupère les traductions disponibles d’une entrée par slug de collection et ID de base de données :
import { getTranslations } from "emdash";
const { translations, error } = await getTranslations("posts", post.data.id);
Le résultat contient la translationGroup partagée, un tableau translations et un error optionnel. Chaque résumé de traduction inclut id, locale, slug et status.
resolveEmDashPath()
Résout un pathname public par rapport aux motifs URL configurés pour les collections routables :
import { resolveEmDashPath } from "emdash";
const result = await resolveEmDashPath("/blog/hello-world");
if (result) {
console.log(result.collection, result.entry.data.title);
}
Le résultat contient la collection correspondante, l’entry et les params de route. La fonction renvoie null lorsqu’aucun motif URL configuré ne correspond.
getEditMeta()
Lit les métadonnées d’édition visuelle non énumérables attachées à une valeur Portable Text :
import { getEditMeta } from "emdash";
const meta = getEditMeta(post.data.content);
Renvoie { collection, id, field } pour une valeur annotée, ou undefined lorsque la valeur n’a pas d’annotation.
Types de contenu
ContentEntry
Les fonctions de requête renvoient des entrées de la forme suivante :
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
}
Le proxy edit fournit des annotations d’édition visuelle. Étendez-le sur les éléments pour activer l’édition inline : {...entry.edit.title}. Hors mode édition, il ne produit aucune sortie.
L’objet data contient tous les champs de contenu plus les champs système :
id- Unique identifierslug- URL-friendly identifierstatus- “draft” | “published” | “archived”createdAt- Creation time as aDateupdatedAt- Last update time as aDatepublishedAt- Publication time as aDate, ornull; retained when content is unpublished- Plus all custom fields defined in your collection schema
ReferencePage
entry.references contient une page par champ reference demandé à getEmDashEntry, indexée par slug de champ :
interface ReferencePage<T = Record<string, unknown>> {
entries: ContentEntry<T>[];
nextCursor?: string; // Set when the field holds more entries than the limit
}
Chaque entrée est mappée comme une entrée chargée directement, sauf une exception : les bylines et termes de taxonomie ne sont pas hydratés, donc data.bylines et data.terms sont absents.
getEmDashReferences()
Récupère une page d’un seul champ reference sans relire l’entrée à laquelle il est rattaché. Utilisez-le pour dépasser la première page avec le nextCursor renvoyé par cette page.
| Parameter | Type | Description |
|---|---|---|
collection | string | Collection slug of the entry that holds the field |
slugOrId | string | Entry slug or ID |
field | string | Reference field slug |
options | { limit?: number; cursor?: string; locale?: string } | Optional. Defaults to 50 entries, at most 100 |
L’exemple suivant charge les 20 entrées liées suivantes :
import { getEmDashReferences } from "emdash";
const { entries, nextCursor, error } = await getEmDashReferences(
"posts",
post.id,
"related_posts",
{ cursor, limit: 20 },
);
error n’est défini que pour de vraies erreurs. Un champ inconnu, une entrée manquante ou une entrée invisible pour la requête donnent un tableau entries vide.
Le résultat inclut aussi un cacheHint nommant les lignes lues par la page. Une route qui met en cache son rendu doit le fusionner dans son propre hint, comme getEmDashEntry({ references }) intègre celui de la première page, afin qu’une écriture sur l’une de ces entrées expire la page.
Helpers d’URL
decodeSlug() and slugify()
Utilisez decodeSlug() sur un paramètre de route dynamique avant de le passer à une requête de contenu. Renvoie undefined pour un paramètre absent ; sinon applique decodeURIComponent(), qui lève une exception si l’encodage pourcentuel est mal formé :
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) convertit du texte en slug minuscule séparé par des tirets. Utilisez-le lorsqu’un modèle doit construire un slug à partir d’un libellé ; les slugs d’entrée stockés viennent déjà d’EmDash.
sanitizeHref() and isSafeHref()
Ces helpers rejettent les schémas de lien dangereux comme javascript:. isSafeHref(value) renvoie un booléen. sanitizeHref(value) renvoie l’URL sûre d’origine ou "#" lorsque la valeur est vide ou dangereuse.
import { sanitizeHref } from "emdash";
const href = sanitizeHref(menuItem.url);
Système de preview
generatePreviewToken()
Génère un jeton preview pour du contenu brouillon. L’exemple suivant crée un jeton qui expire dans une heure :
import { generatePreviewToken } from "emdash";
const token = await generatePreviewToken({
contentId: "posts:01HXK5MZSN...",
secret: process.env.EMDASH_PREVIEW_SECRET!,
expiresIn: 3600, // 1 hour
});
contentId doit utiliser le format collection:id. expiresIn accepte des secondes ou une durée se terminant par s, m, h, d ou w et vaut par défaut "1h". Gardez le secret de signature sur le serveur.
verifyPreviewToken()
Vérifie un jeton preview et lit sa charge utile :
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"
}
Passez token ou url avec le secret de signature. Un jeton invalide renvoie { valid: false, error }, où error vaut "none", "malformed", "invalid" ou "expired".
parseContentId()
Scinde la valeur collection:id de la charge preview en ses deux parties :
import { parseContentId } from "emdash";
const parsed = parseContentId(result.payload.cid);
Renvoie { collection, id } et lève une exception si la valeur n’a pas de séparateur deux-points.
getPreviewUrl() and buildPreviewUrl()
getPreviewUrl() crée et signe une URL preview. Elle accepte collection, id et secret, plus les valeurs optionnelles expiresIn, baseUrl, pathPattern et locale :
import { getPreviewUrl } from "emdash";
const previewUrl = await getPreviewUrl({
collection: "posts",
id: post.id,
secret: process.env.EMDASH_PREVIEW_SECRET!,
pathPattern: "/blog/{id}",
});
Sans baseUrl, renvoie une URL relative au site. Utilisez buildPreviewUrl({ path, token, baseUrl? }) lorsqu’un jeton existe déjà.
isPreviewRequest()
Vérifie si une requête inclut un jeton preview, puis lisez-le :
import { isPreviewRequest, getPreviewToken } from "emdash";
if (isPreviewRequest(Astro.url)) {
const token = getPreviewToken(Astro.url);
// Verify and show preview content
}
getPreviewToken() renvoie le paramètre de requête _preview ou null s’il est absent. Le middleware EmDash vérifie les requêtes preview normales et fournit l’état preview à getEmDashEntry() automatiquement ; ces helpers servent aux routes preview personnalisées et au tooling.
Convertisseurs de contenu
Convertit entre les formats Portable Text et 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);
Paramètres du site
Lisez les paramètres globaux du site avec getSiteSettings et 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");
Les paramètres sont en lecture seule via l’API runtime. Utilisez l’API admin pour les mettre à jour.
getSiteSettings() renvoie un objet partiel car les clés non définies sont omises. Les paramètres média comme le logo et le favicon du site sont résolus en objets media-reference avant le retour de la fonction.
getSiteSettingsWithCacheHint() renvoie { data, cacheHint }. Passez le hint à Astro.cache.set() lorsqu’un cache de route Astro doit être invalidé après un changement de paramètres du site.
SEO
Résolvez les valeurs du panneau SEO et les replis de contenu d’une page avec getSeoMeta() :
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}`,
});
Renvoie le titre, la description, les valeurs Open Graph, l’URL canonique et la valeur robots résolus. getContentSeo(content) renvoie l’objet SEO brut sans replis de modèle.
Pour du contenu traduit, getHreflangAlternates(collection, entryId, { siteUrl? }) renvoie des variantes de locale publiées et routables sous forme d’objets { hreflang, href } et ajoute une entrée x-default. Renvoie un tableau vide lorsque l’internationalisation est désactivée, l’entrée courante est marquée noindex ou qu’aucune URL absolue du site n’est disponible.
Commentaires
Récupère les commentaires approuvés et leur nombre pour une entrée :
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 imbrique les réponses sous leur commentaire parent. Le sort par défaut est "oldest" ; "best" classe les commentaires de premier niveau par réactions et active automatiquement les compteurs de réactions. Les requêtes de commentaires rendues côté serveur renvoient au plus 500 commentaires approuvés ; utilisez l’API REST lorsqu’un client a besoin de pagination.
Menus
Récupère les menus de navigation et parcourt leurs éléments, y compris les enfants imbriqués :
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? }) suit la chaîne de repli de locale configurée. getMenus({ locale? }) liste les résumés de menu pour la locale de requête résolue ou configurée ; sans internationalisation, liste chaque locale. getMenuWithCacheHint() renvoie { data, cacheHint } pour les routes utilisant le cache Astro.
Bylines
Récupère un profil d’auteur par ID ou slug, ou liste les entrées créditées à un byline :
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) renvoie un profil ou null. La recherche par slug accepte une locale optionnelle et suit la chaîne de repli de locale. Les requêtes de contenu hydratent déjà les crédits ordonnés d’une entrée dans entry.data.bylines ; utilisez ces helpers autonomes pour les pages auteur et archives byline.
Taxonomies
Récupère des termes de taxonomie, un terme, les termes d’une entrée ou des entrées par terme :
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? }) liste une définition par taxonomie, et getTaxonomyDef(name, { locale? }) renvoie une définition ou null lorsqu’aucune locale ne définit la taxonomie. Les deux résolvent une taxonomie dans chaque locale ; Traduire taxonomies et termes explique quel libellé de locale ils utilisent. Les recherches de termes suivent la chaîne de repli configurée.
getTaxonomyTerms(name, { locale?, includeCounts? }) renvoie un arbre pour les taxonomies hiérarchiques et inclut par défaut les comptes d’entrées visibles. Passez includeCounts: false lorsque le modèle n’affiche pas les comptes. Les requêtes de contenu hydratent aussi les termes assignés dans data.terms de chaque entrée ; utilisez getEntryTerms() lorsque vous n’avez que le nom de collection et l’ID d’entrée.
Pour les pages d’archive qui affichent des termes à côté de nombreuses entrées, regroupez la recherche au lieu d’appeler getEntryTerms() en boucle :
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() renvoie une map de l’ID d’entrée vers les termes de la taxonomie demandée. getAllTermsForEntries() renvoie une map de l’ID d’entrée vers les termes groupés par nom de taxonomie. getTaxonomyTermsWithCacheHint() renvoie { data, cacheHint } pour une route mise en cache par Astro.
Zones de widgets
Récupère les zones de widgets et les widgets qu’elles contiennent :
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) renvoie { data, cacheHint } pour les routes utilisant le cache Astro. Les zones de widgets et leurs widgets sont ordonnés selon l’ordre configuré.
Sections
Récupère des sections et les filtre :
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?) renvoie { items: Section[]; nextCursor?: string }. Les options sont source ("theme" | "user" | "import"), search, limit (par défaut 50, max. 100) et cursor.
Recherche
Exécute une recherche globale dans les collections. Les résultats incluent des extraits surlignés :
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,
});
}
Avec scope: "title", la requête ne correspond qu’au champ title de chaque collection au lieu du texte indexé complet — utile pour les sélecteurs et l’autocomplétion où des correspondances dans le corps surprendraient. Les collections dont le champ title n’est pas indexé pour la recherche ne renvoient aucun résultat sous ce scope.
Gestion des erreurs
Les requêtes de contenu renvoient les erreurs opérationnelles dans leur résultat au lieu de lever une exception. Une entrée manquante n’est pas une erreur opérationnelle : entry est null et error reste 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");
}
Les helpers sans enveloppe de résultat peuvent lever une exception lorsque l’entrée est invalide ou qu’une opération de base de données échoue. Gérez ces erreurs à la frontière de la route lorsque la page peut fournir un repli utile. Les classes d’erreur repository et handler utilisées par les intégrations serveur de bas niveau sont hors de cette API de modèles de site.