Content Collections turn a folder of Markdown files into a typed database. Half an hour of setup pays back every time you add a field or rename a slug.
Background
Without Content Collections, an Astro site treats each Markdown file as loose text. With them, you get schema validation, autocompletion, and a single query API for all articles. For sites past 30 posts, this is the difference between confidence and chaos.
How to tell
- You have or expect more than 30 Markdown / MDX files.
- You want compile-time errors when frontmatter is wrong.
- Multiple templates need to read the same content (homepage, hub page, sitemap, RSS).
- You plan to maintain the site over years, not weeks.
Quick verdict
If you’re starting a content site in Astro in 2026, use Content Collections from day one. Retrofitting later costs more than learning them now.
Step by step
- Create
src/content/articles/and drop a few MDX files with frontmatter:
---
title: "Hello world"
description: "A test post to verify content collections work."
publishedAt: 2026-05-22
tags: ["intro", "test"]
author: "alice"
---
# Hello
This is the body.
- Define the schema at
src/content/config.ts. The schema is the contract — every file must satisfy it or the build fails loudly:
import { defineCollection, reference, z } from 'astro:content';
const articles = defineCollection({
type: 'content',
schema: ({ image }) => z.object({
title: z.string().min(10).max(80),
description: z.string().min(120).max(160),
publishedAt: z.date(),
updatedAt: z.date().optional(),
tags: z.array(z.string()).min(1),
cover: image().optional(),
author: reference('authors'),
draft: z.boolean().default(false),
featured: z.boolean().default(false),
}),
});
const authors = defineCollection({
type: 'data', // JSON / YAML, not markdown
schema: z.object({
name: z.string(),
bio: z.string(),
twitter: z.string().optional(),
}),
});
export const collections = { articles, authors };
- Run
npm run dev. Astro validates every file and tells you exactly which one is malformed — fix everything red before continuing:
npm run dev
# Could not parse content collection 'articles':
# src/content/articles/old-post.mdx
# description: String must contain at most 160 character(s)
- Add the author data files at
src/content/authors/:
# src/content/authors/alice.yaml
name: Alice Chen
bio: Indie developer writing about content sites.
twitter: alicewrites
- Render a list page. Notice the result is typed — your editor will autocomplete
entry.data.title:
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
const posts = (await getCollection('articles', e => !e.data.draft))
.sort((a, b) => +b.data.publishedAt - +a.data.publishedAt);
---
<ul>
{posts.map(p => (
<li>
<a href={`/blog/${p.slug}/`}>{p.data.title}</a>
<time datetime={p.data.publishedAt.toISOString()}>
{p.data.publishedAt.toLocaleDateString()}
</time>
</li>
))}
</ul>
- Add the per-article route at
src/pages/blog/[...slug].astroand resolve the author reference:
---
import { getCollection, getEntry } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('articles', e => !e.data.draft);
return posts.map(p => ({ params: { slug: p.slug }, props: { entry: p } }));
}
const { entry } = Astro.props;
const author = await getEntry(entry.data.author);
const { Content } = await entry.render();
---
<article>
<h1>{entry.data.title}</h1>
<p>By {author.data.name}</p>
<Content />
</article>
- Production sanity check — build and confirm no schema errors slipped through:
npm run build
# Generating static routes
# ✓ generated 247 routes
Common pitfalls
- Skipping the schema and using loose Markdown — you lose every benefit Content Collections provide.
- Putting required fields as optional to “make builds pass”, which lets bad data through.
- Storing image references as strings instead of using the
image()schema helper, which breaks optimization. - Forgetting to restart the dev server after editing the schema — old types stick around.
- Treating Content Collections like a CMS for non-technical editors — they still write Markdown.
Who this is for
Astro builders making content-heavy sites who want type safety and reliability.
When to skip this
Tiny sites with under 10 pages where the schema overhead doesn’t pay back.
FAQ
- Do Content Collections work with MDX?: Yes — they support
.mdand.mdxout of the box. MDX files can include components alongside frontmatter. - Can I store non-text content like JSON?: Yes, define a collection with
type: "data"for JSON or YAML entries. Useful for authors, tags, and config. - What happens when frontmatter is invalid?: The build fails with a clear message pointing to the file and field. This is the feature, not a bug.
- How do I migrate an existing folder of Markdown?: Move files into
src/content/<name>/, write the schema, then runnpm run dev. Fix every reported file before shipping.
Related
- Building a Markdown / MDX content site that scales
- When Astro is the right choice (and when it isn’t)
- Building category and tag pages in Astro
Tags: #Indie dev #Astro #Content Collections #MDX #Getting started