Content Collections
Content collections are Astro’s way of managing structured content (Markdown, MDX, YAML, JSON) with schema validation and type safety.
Why content collections
If you have local authored content (blog posts, docs, how-to guides), content collections give you:
- Schema validation with Zod
- TypeScript types auto-generated from your schema
- Querying APIs (
getCollection(),getEntry()) - Frontmatter enforcement (required fields, default values)
- MDX support via
@astrojs/mdx
This replaces the gray-matter + manual file reading pattern common in Next.js projects.
Defining a collection
Create a content.config.ts (or .js) at the project root:
// content.config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content', // Markdown/MDX content
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.date(),
author: z.string().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
const authors = defineCollection({
type: 'data', // JSON/YAML data
schema: z.object({
name: z.string(),
bio: z.string(),
avatar: z.string(),
}),
});
export const collections = { blog, authors };
Collection types
content- Markdown, MDX, or Markdoc files that render to HTMLdata- JSON, YAML, or TOML files that provide structured data
File structure
src/content/
blog/
first-post.md
second-post.mdx
authors/
peter.json
sarah.yaml
Each file’s name (without extension) becomes its id.
Querying collections
---
import { getCollection, getEntry } from 'astro:content';
// Get all entries
const allPosts = await getCollection('blog');
// Filter entries
const publishedPosts = await getCollection('blog', ({ data }) => {
return !data.draft;
});
// Get single entry
const post = await getEntry('blog', 'first-post');
---
Rendering content
---
import { getEntry } from 'astro:content';
const post = await getEntry('blog', 'first-post');
const { Content } = await post.render();
---
<article>
<h1>{post.data.title}</h1>
<time>{post.data.pubDate.toLocaleDateString()}</time>
<Content />
</article>
Using collections with dynamic routes
---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.id },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<h1>{post.data.title}</h1>
<Content />
Image references in schemas
Content collection schemas can reference and validate images:
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: ({ image }) => z.object({
title: z.string(),
cover: image(), // validates image exists, enables optimization
}),
});
Live collections (Astro 6)
For frequently-updated remote data, Astro 6 introduced live collections that fetch at request time:
import { defineCollection, z } from 'astro:content';
const news = defineCollection({
type: 'content',
loader: async () => {
const response = await fetch('https://api.example.com/articles');
const articles = await response.json();
return articles.map(article => ({
id: article.slug,
body: article.content,
data: {
title: article.title,
pubDate: new Date(article.published_at),
},
}));
},
schema: z.object({
title: z.string(),
pubDate: z.date(),
}),
});
Caveats of live collections:
- No MDX support at runtime
- No image optimization at runtime
- Performance cost per request (unless cached)
For a news site backed by an existing API, it is often simpler to fetch directly in the page frontmatter rather than using live collections. Reserve content collections for local authored content where schema validation and type safety matter.
MDX integration
Install @astrojs/mdx to use .mdx files in collections:
npx astro add mdx
MDX files can import and use components:
---
title: My Guide
---
import Callout from '../../components/Callout.astro';
# Getting Started
<Callout type="info">
This is a tip inside MDX.
</Callout>
Regular markdown continues here.
Comparison with Next.js content handling
| Feature | Next.js | Astro |
|---|---|---|
| Local Markdown | Manual (gray-matter + next-mdx-remote or similar) | Content collections with built-in schema validation |
| Type safety | Manual | Auto-generated from Zod schemas |
| Querying | fs.readFileSync + import.meta.glob | getCollection(), getEntry() |
| MDX | @next/mdx or next-mdx-remote | @astrojs/mdx integration |
| Remote content | Fetch in page/layout | Fetch in frontmatter or live collections |
| Image validation | Manual | Schema-level image() helper |