Articles

Astro Content Collections, Explained

Astro content collections give markdown and MDX files a type-safe schema and a query API, catching bad frontmatter at build time instead of runtime.

Takina Takina · · 4 min read
Structured JSON-like data on a screen

Content collections are Astro’s built-in system for organizing groups of markdown, MDX, or data files, validating their frontmatter against a schema, and querying them with a fully typed API instead of hand-rolling file reads and frontmatter parsing. Instead of a folder of loosely structured markdown files that any typo in the frontmatter can silently break, a collection defines exactly what fields each entry must have — and Astro checks every entry against that definition at build time.

The problem plain markdown has

Without collections, a typical approach to a markdown-driven site is to glob a folder of .md files, parse each one’s frontmatter with a library, and hope every file actually has the fields the templates expect. Nothing enforces that pubDate is really a date, that tags is really an array, or that a required field wasn’t simply forgotten on one post. Those mistakes usually surface as a runtime error deep in a template, or worse, a page that silently renders with undefined where a title should be.

This is precisely the failure mode content collections are built to catch before a page ever ships — a bad or missing field fails the build instead of failing silently.

Defining a collection

A collection is declared in src/content.config.ts, pairing a loader — which tells Astro where the entries live and how to read them — with a schema — which describes and validates the shape of each entry’s data:

import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";

const blog = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
  schema: z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
    tags: z.array(z.string()).default([]),
  }),
});

export const collections = { blog };

The schema is built with Zod, which does double duty: it validates every entry’s frontmatter at build time, and it infers a matching TypeScript type automatically, so every place the collection is queried in the codebase gets full autocomplete and type-checking on title, pubDate, and tags with zero manual type definitions to keep in sync.

Loaders: not just local markdown

Astro 5’s content layer generalized what a collection’s source can be. The glob loader used above reads local files matching a pattern, but a loader is really just a function that returns entries — which means a collection can just as easily be backed by a file loader reading a single JSON or YAML file, or a custom loader that fetches from a CMS API, a database, or any other remote source, all normalized behind the same getCollection() query interface. A site can mix a glob-backed blog collection with a file-backed collection of static reference data — an authors list, a set of product SKUs — and query both the same way.

Querying entries

Once defined, a collection is queried with getCollection() and getEntry(), both of which return fully typed data:

import { getCollection } from "astro:content";

const posts = await getCollection("blog", ({ data }) => !data.draft);
const sorted = posts.sort(
  (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);

The optional filter callback on getCollection() runs against already-validated, already-typed data — there’s no need to guard against a missing field or parse a date string, because the schema already guaranteed both by the time this code runs. Rendering an individual entry’s markdown body works through a separate render() call, which returns the compiled <Content /> component along with the page’s heading structure, useful for building a table of contents.

Content collections vs a headless CMS vs raw frontmatter

Raw frontmatter parsingContent collectionsHeadless CMS
Schema validationNone (manual)Built in, fails the build on errorEnforced by the CMS
Type safetyNone, unless hand-writtenInferred automatically from the schemaDepends on generated types, if any
Data sourceLocal files onlyLocal files, remote APIs, or both, via loadersRemote API only
Editing experienceAny text editorAny text editor (or Git-based CMS on top)Dedicated editor UI
Build-time failure on bad dataNoYesDepends on the integration

Collections sit in a useful middle ground: all the type safety and validation of a schema-driven system, without requiring a separate service or database the way a headless CMS does. For a documentation site or blog maintained by developers directly in Git, that’s usually the better trade — for a marketing team that needs a visual editor and no comfort with markdown, a headless CMS (potentially wired in as a custom loader) is still the right call.

Where this fits in an Astro build

Because collections are resolved at build time, a validation failure shows up as a build error rather than a production bug — the same discipline that makes deploying an Astro site to Cloudflare Pages predictable: if the build passes locally, the content that shipped is schema-valid by construction. This pairs naturally with Astro’s default of shipping static HTML — see islands architecture for how that default extends to interactivity — and with static-site search, which typically indexes exactly the validated collection output rather than raw files.

The takeaway

Content collections turn a folder of markdown files from an untyped grab bag into a schema-validated, fully typed data source, catching malformed or missing frontmatter at build time instead of at runtime. The glob loader and Zod schema shown here cover the common case of a local markdown blog, but the same getCollection() API extends to remote and structured data sources through custom loaders — one query interface regardless of where the entries actually live.

Takina Takina · · 4 min read

Astro Islands Architecture Explained

Astro's islands architecture ships static HTML by default and hydrates only the interactive components that need JavaScript. Here's how it works.

#Astro #Web Development #Performance