Content Collections in Astro
Welcome to Part 3! In Part 1 and Part 2, we covered Astro basics and components. Now let’s master content management with Content Collections.
What are Content Collections?
Content Collections provide a type-safe way to manage your content (Markdown, MDX, JSON) with:
- Type safety with Zod schemas
- Auto-completion in your editor
- Validation at build time
- Powerful query API
Setting Up Collections
1. Define Your Schema
Create src/content.config.ts:
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const articles = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/articles' }),
schema: z.object({
title: z.string(),
description: z.string().optional(),
tags: z.array(z.string()).default([]),
date: z.coerce.date().optional(),
draft: z.boolean().default(false),
}),
});
export const collections = { articles };2. Add Content
Create src/content/articles/hello-world.md:
---
title: "Hello World"
description: "My first post"
tags: [hello, blog]
date: 2025-01-01
---
# Hello World!
This is my first blog post.Querying Collections
Get All Entries
---
import { getCollection } from 'astro:content';
const posts = await getCollection('articles');
---
<ul>
{posts.map(post => (
<li>{post.data.title}</li>
))}
</ul>Filter Entries
---
const publishedPosts = await getCollection('articles', ({ data }) => {
return !data.draft;
});
---Get Single Entry
---
import { getEntry } from 'astro:content';
const post = await getEntry('articles', 'hello-world');
---
<h1>{post.data.title}</h1>Rendering Content
---
import { render } from 'astro:content';
const post = await getEntry('articles', 'hello-world');
const { Content } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>Dynamic Routes
Create src/pages/articles/[slug].astro:
---
import { getCollection, render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('articles');
return posts.map(post => ({
params: { slug: post.id.replace('.md', '') },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<time>{post.data.date?.toLocaleDateString()}</time>
<Content />
</article>Advanced Schemas
Nested Objects
const articles = defineCollection({
schema: z.object({
title: z.string(),
author: z.object({
name: z.string(),
email: z.string().email(),
}),
}),
});Enums
schema: z.object({
status: z.enum(['draft', 'published', 'archived']),
lang: z.enum(['ko', 'en']).default('ko'),
})References
schema: z.object({
relatedPosts: z.array(z.string()),
})Sorting and Filtering
Sort by Date
---
const posts = await getCollection('articles');
const sorted = posts.sort((a, b) =>
b.data.date.getTime() - a.data.date.getTime()
);
---Filter by Tag
---
const tag = 'astro';
const filtered = await getCollection('articles', ({ data }) =>
data.tags.includes(tag)
);
---Frontmatter Validation
Content Collections validate your frontmatter at build time:
---
title: "Valid Post"
date: 2025-01-01
tags: [astro]
---❌ This will fail:
---
title: 123 # Should be string!
date: "invalid-date"
---Working with MDX
import { defineCollection } from 'astro:content';
const articles = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/articles' }),
// ... schema
});Now you can use components in your content:
---
title: "Interactive Post"
---
import Button from '../../components/Button.astro';
# Hello!
<Button>Click me</Button>Best Practices
- Use TypeScript - Get full type safety
- Validate early - Define strict schemas
- Keep it organized - Use clear folder structure
- Use references - Link related content
Next Steps
You now know how to manage content in Astro! Check out the Astro docs for more advanced topics.
Summary
- Content Collections provide type-safe content management
- Define schemas with Zod
- Query with
getCollection()andgetEntry() - Render with
render() - Build dynamic routes with
getStaticPaths()
This series covered the fundamentals of Astro. Now go build something amazing! 🚀
Comments