Astro Content Collections — Type-Safe Content Management
In this tutorial, you will learn about Astro Content Collections. We cover key concepts, practical examples, and best practices to help you master this topic.
Learn Astro content collections: define collections, validate frontmatter with Zod, query Markdown and MDX content with type safety across your site.
In this lesson, you'll create content collections in src/content/, define Zod schemas for frontmatter validation, and query collection entries with type-safe APIs.
What You'll Learn
How to structure src/content/ directories, define collection schemas, query entries with getCollection(), and render content with the <Content /> component.
Why It Matters
Content collections provide type-safe content management with automatic frontmatter validation. Typos in frontmatter are caught at build time instead of silently breaking pages.
Real-World Use
DodaTech manages thousands of tutorials using Astro content collections, with schemas that enforce required fields like title, description, and SEO metadata.
flowchart LR
A[src/content/blog/] --> B[defineCollection]
B --> C[Zod Schema]
C --> D[Type-Safe Queries]
D --> E[Render Pages]
style A fill:#ff5a03,color:#fff
Defining a Collection
Create src/content/config.ts:
import { defineCollection, z } from "astro:content";
const blogCollection = defineCollection({
schema: z.object({
title: z.string(),
description: z.string().max(165),
date: z.date(),
tags: z.array(z.string()).optional(),
draft: z.boolean().default(false),
}),
});
const docsCollection = defineCollection({
schema: z.object({
title: z.string(),
order: z.number(),
category: z.enum(["basics", "advanced", "reference"]),
}),
});
export const collections = {
blog: blogCollection,
docs: docsCollection,
};
Now create src/content/blog/my-first-post.md:
---
title: My First Post
description: "A short description of this blog post for SEO."
date: 2026-06-28
tags: [astro, tutorial]
---
## My First Post
This is the content of my first blog post.
Querying Collections
Use getCollection() to query entries:
---
import { getCollection } from "astro:content";
const posts = await getCollection("blog");
---
<ul>
{posts.filter(p => !p.data.draft).map(post => (
<li>
<a href={`/blog/${post.slug}/`}>
{post.data.title}
</a>
<time>{post.data.date.toLocaleDateString()}</time>
</li>
))}
</ul>
Output: A list of published blog posts with links and dates. Draft posts are filtered out. The slug is derived from the filename.
Rendering Collection Content
Use the <Content /> component to render the body:
---
import { getCollection } from "astro:content";
import { render } from "astro:content";
export async function getStaticPaths() {
const posts = await getCollection("blog");
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
Output: Each blog post generates a static HTML page at /blog/my-first-post/ with the title from frontmatter and the rendered Markdown content.
Query by Field
Filter collections by frontmatter fields:
---
import { getCollection } from "astro:content";
const astroPosts = await getCollection("blog", ({ data }) => {
return data.tags?.includes("astro");
});
---
{astroPosts.map(post => <p>{post.data.title}</p>)}
Output: Only posts with the "astro" tag are returned. The filter function runs at build time.
Common Mistakes
- Not creating
src/content/config.ts: Without a config file, collections have no schema validation and queries return untyped data. - Mismatching collection directory names: The directory name in
src/content/must match the key in the collections export.src/content/blog/maps toblogin the config. - Forgetting
awaitongetCollection(): The function returns a Promise. Withoutawait, the component receives a pending promise instead of data. - Using invalid Zod types: Date fields need
z.date(), notz.string(). String dates in frontmatter fail validation. - Not handling optional fields: Access optional fields with
?.to avoid undefined errors in templates.
Practice Questions
Where do you define collection schemas? Answer: In
src/content/config.ts, usingdefineCollection()andz.object()fromastro:content.How do you query all entries from a collection? Answer: Use
getCollection("collectionName"). It returns an array of typed entry objects.What does the
<Content />component render? Answer: The rendered body of a Markdown or MDX entry from a content collection.How do you filter collection entries? Answer: Pass a filter function as the second argument to
getCollection(), e.g.,getCollection("blog", (entry) => !entry.data.draft).
Challenge
Create a "projects" content collection with fields for title, description, image URL, and live demo link. Query it on a portfolio page and render project cards.
Mini Project
Build a blog section with two collections: "posts" and "reviews". Each has a different schema. Display posts on a blog index and reviews on a separate reviews page.
FAQ
What's Next
Learn about Astro Collection Schemas for advanced validation patterns including references, images, and custom types.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro