Astro Content Collections — Type-Safe Markdown That Fails the Build

(Updated: July 16, 2026 ) Astro Content Collections Zod Markdown

Astro Content Collections turn a pile of Markdown files into a typed content store. Instead of discovering a missing title in production, the build fails when frontmatter does not match a Zod schema. If you have ever shipped “Untitled” because of a typo’d key, collections are the fix.

Why collections beat raw import.meta.glob

Globbing Markdown works until you need consistent dates, tags, and descriptions across a hundred posts. Collections give you:

  • Schema validation at build time
  • Generated TypeScript types for frontmatter
  • Helpers like getCollection / getEntry with filters
  • A clear place to put content config next to the files

You still write Markdown/MDX; you simply stop treating frontmatter as untyped any.

Minimal setup

Put files in src/content/<collection>/ and define the collection in the content config (Astro’s current docs use src/content.config.ts on newer versions — follow the version you run):

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

const blog = defineCollection({
  type: "content",
  schema: z.object({
    title: z.string().min(1),
    description: z.string().min(1),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});

export const collections = { blog };

z.coerce.date() accepts 2025-02-15 strings and turns them into Date objects — fewer custom parsers.

Querying with types

import { getCollection } from "astro:content";

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

p.data.title is a string; typos fail in the editor. Rendering still uses await render(post) (or the pattern your Astro version documents) to get HTML from the body.

Drafts, locales, and multiple collections

Split by responsibility:

CollectionExample use
blogLong-form posts
blog-enEnglish locale tree
docsVersioned documentation
changelogShort dated entries

Filter draft: true out of production sitemaps. For localization, separate collections or a locale field both work — pick one convention and stick to it so getStaticPaths stays readable.

MDX, images, and references

MDX collections can allow components in content — constrain what authors may import if you open that door. For images, prefer Astro assets or explicit relative paths validated by convention; put image alt requirements in code review if schema cannot express them fully.

Reference other entries (authors, related posts) with reference() in schemas when you want broken links to fail the build instead of 404 at runtime.

Migration from a loose folder

  1. Move Markdown into src/content/blog/.
  2. Add the schema with only the fields you already use.
  3. Run the build; fix frontmatter errors in batches.
  4. Tighten the schema (min lengths, enums for tags) once green.
  5. Replace glob-based pages with getCollection.

Do not invent fifteen optional fields on day one — validate what you already depend on.

Authoring ergonomics

  • Keep a TEMPLATE.md with the required frontmatter
  • Lint tag spelling via z.enum([...]) when the set is small
  • Reject empty descriptions — they become poor SEO snippets
  • Store dates in ISO form for sorting sanity

Common build failures and fixes

ErrorFix
Date parse failuresUse ISO strings + coerce.date
Unexpected keyStrip unknown keys or add to schema
Duplicate slugsRename files; slug comes from path
Draft leaked to prodFilter in getCollection and sitemaps

Bottom line

Content Collections make blog management boring in the best way: schemas catch typos, types autocomplete frontmatter, and getCollection replaces hand-rolled globs. Spend an hour on the schema once — every future post inherits the guardrails.

Closing notes

Keep this page next to your incident runbook. The value is a reusable mental model for the next outage or launch — not a checklist you read once and forget. Trim personal notes down to the three steps you actually used last time; short runbooks get followed, long ones get skipped under pressure.