← Back to site

Content Queries

All content query functions live in src/utils/contentQueries.ts. They follow a composable pattern: generic utilities are combined into pre-composed functions for each collection.

Most query functions accept an optional locale parameter (type Locale from src/utils/i18n.ts, defaults to 'en'). Localized collections are filtered by their en/ or de/ id prefix, with automatic fallback to English when no entries exist for the requested locale.

Composable Utilities

Sorting

sortByDateDesc<T>(entries: T[]): T[]    // Newest first
sortBySlug<T>(entries: T[]): T[]        // Alphabetical by slug

Both return new arrays without mutating the original. They work with any collection entry that has the required field (data.date or slug).

Filtering

filterDrafts<T>(entries: T[]): T[]
filterByLocale<T>(entries: T[], locale: Locale): T[]
stripLocaleFromSlug(slug: string): string

filterDrafts: In development (import.meta.env.DEV), returns all entries including drafts. In production, removes entries where isDraft is true.

filterByLocale: Filters entries by their id prefix (e.g., en/ or de/). Falls back to the default locale (English) if no entries exist for the requested locale.

stripLocaleFromSlug: Removes the en/ or de/ prefix from a slug (e.g., en/rocket-launchrocket-launch). Used in dynamic routes to generate clean URL paths.

Pre-Composed Queries

Posts (Events + Projects)

All post query functions accept an optional locale parameter (defaults to 'en'):

FunctionReturnsNotes
getPublishedEvents(locale?)CollectionEntry<'events'>[]Filtered by locale + sorted by date
getPublishedProjects(locale?)CollectionEntry<'projects'>[]Filtered by locale + sorted by date
getPublishedPosts(locale?)Combined array with _collectionTypeEvents + projects merged, sorted
getLatestPosts(limit?, locale?)Sliced array of latest postsUsed by “Latest News”
getMeetTheTeamProjects(locale?)Projects with displayMeetTheTeam: trueFiltered + sorted by date

The _collectionType marker on combined posts is either 'events' or 'projects', used to route to the correct detail page:

const posts = await getPublishedPosts();
posts.forEach(post => {
  const href = `/${post._collectionType}/${post.slug}`;
});

Instagram

FunctionReturnsNotes
getPublishedInstagramPosts()CollectionEntry<'instagram'>[]Filtered + sorted by date
getLatestInstagramPosts(limit = 3)Sliced arrayUsed by landing page

Other Collections

FunctionReturnsNotes
getTestimonials(locale?)CollectionEntry<'people'>[]Reads the single-entry testimonials collection (list.mdx), resolves each item’s person reference, preserves array order, projects locale-correct role (from the person) and quote (from the item) onto data; skips + warns on unresolved refs
getFacesOfBearsPeople(locale?)CollectionEntry<'people'>[]Filters showInFaces: true, sorts by order, projects locale-correct role from roleEn/roleDe
getMediaPeople(locale?)CollectionEntry<'people'>[]Filters showInFaces: true AND has a coverImage, sorts by order, projects locale-correct role. Powers the People category on the Media page. (Same showInFaces toggle that gates Faces of BEARS — the coverImage filter is the only difference, since /media shouldn’t show placeholder portraits.)
getMediaItemsByCategory(category, locale?)ImageWithAlt[]Dispatches by Media-page category id (events, projects, hero, what-is-bears, about-us). events/projects return covers + inline <Img> blocks parsed from each post body (filtered by displayInMedia). about-us combines the about-us page hero + the “Our Mission” section image with the Faces of BEARS roster (people with showInFaces: true and a coverImage) — one combined accordion on /media because Faces of BEARS lives on the About Us page. hero aggregates the landing-page hero-slides carousel and the events/projects/sponsors/contact/media title banners — one combined “Hero Images” accordion. All entries respect their per-image displayInMedia toggle. Each branch projects into ImageWithAlt (image + alt + optional description, where description is a single field used for caption + photographer credit). The all category is assembled directly in media.astro. Returns [] for unknown categories.
getMeetTheTeamProjectsWithPeople(locale?)Array<{ project, person, displayName, displayRole }>Joins published displayMeetTheTeam projects to their referenced person; skips + warns on unresolved refs
getSponsorsByTier(){ diamond, platinum, gold, silver, bronze }Grouped by tier, sorted by order, ties on slug (not localized)
getPageContent(id, locale?)CollectionEntry<'page-text'> | undefinedSingle entry by ID and locale
getDocsBySection()Record<string, CollectionEntry<'docs'>[]>Grouped by section folder (not localized)
getLandingHeroSlides()CollectionEntry<'hero-slides'>[]Sorted by order field, ties on filename (not localized)

Sponsor tiers are derived from the folder structure. The getSponsorsByTier() function extracts the tier from the first segment of the entry’s ID:

const tier = sponsor.id.split('/')[0]; // "gold/acme-corp" → "gold"

The returned object has all five tiers, each sorted by the sponsor’s order frontmatter field in ascending order. Two sponsors with the same order break the tie on slug for deterministic output.

Page Content

getPageContent(id, locale?) fetches a single page-text entry by its ID path and locale. The id does not include the locale prefix — it is prepended automatically. Falls back to English if the translation is missing:

const content = await getPageContent('landing/what-is-bears', 'de');
// Tries "de/landing/what-is-bears.mdx" first
// Falls back to "en/landing/what-is-bears.mdx" if not found

A console warning is logged if no entry is found in either locale.

Docs Sections

getDocsBySection() groups docs by their folder path (e.g., guides/, dev/) and sorts within each section by the order field.

Hero Slide Ordering

getLandingHeroSlides() sorts hero slides in ascending order of the required order frontmatter field. When two slides share an order, the filename breaks the tie for deterministic output.

Adding a New Query

Follow this pattern for new collections:

export async function getMyCollectionSorted() {
  const all = await getCollection('my-collection');
  return sortBySlug(filterDrafts(all)); // or sortByDateDesc
}

For collections without isDraft, skip filterDrafts(). For collections with custom sorting, write the sort inline or create a new composable utility.