Content Collections
Type-safe content management with the Astro 6 Content Layer API — glob loaders, Zod schemas, and zero config.
What are Content Collections?
Content Collections are Astro’s built-in system for managing structured content files (Markdown, MDX, JSON, YAML). You define a schema using Zod and Astro validates every file against it at build time — giving you autocomplete, type safety, and helpful error messages when frontmatter is wrong.
Astro 6 introduced the Content Layer API — a new, more flexible architecture where collections use explicit loaders to pull content from any source: the local filesystem, a CMS, a REST API, or a database.
The cards below are built from a typed content collection.
In a real Astro project you'd call getCollection('blog')
and get the same structure with full TypeScript types.
loader: glob({ pattern: '**/*.mdx', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
publishedAt: z.date(),
tags: z.array(z.string()),
}),
}
Getting Started with Astro 6
Everything you need to build your first Astro 6 site from scratch.
Jan 14, 2025
Astro Team
getting-started
The New Content Layer API
How glob loaders, remote loaders, and typed schemas work in Astro 6.
Jan 31, 2025
Fred K. Schott
content-layer
Smooth View Transitions
Add cinematic page transitions with just one directive.
Feb 17, 2025
Matthew Phillips
view-transitions
Setting up a collection
Create src/content.config.ts
Astro 6 moved the config file location. Create it at src/content.config.ts
(not src/content/config.ts — that was the Astro 4/5 location).
// src/content.config.ts
import { defineCollection, z } from 'astro:content'
import { glob } from 'astro/loaders'
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string(),
publishedAt: z.date(),
author: z.string().default('Team'),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
})
export const collections = { blog } Query the collection in your page
Use getCollection() to fetch all entries, with optional filtering.
---
import { getCollection } from 'astro:content'
// Filter out drafts automatically
const posts = await getCollection('blog', post => !post.data.draft)
// Sort newest first
posts.sort((a, b) => b.data.publishedAt.valueOf() - a.data.publishedAt.valueOf())
--- Render an entry
Use render() to convert an MDX/Markdown entry into a renderable Content component.
---
import { render } from 'astro:content'
const { Content, headings } = await render(entry)
---
<article>
<Content />
</article> Content Layer loaders
| Loader | Source |
|---|---|
glob() | Local .md, .mdx, .json, .yaml files |
| Custom loader | CMS, REST API, database, anything |
// Custom loader example — pull from a CMS
const blog = defineCollection({
loader: async () => {
const res = await fetch('https://my-cms.com/api/posts')
const data = await res.json()
return data.posts.map(p => ({ id: p.slug, ...p }))
},
schema: z.object({ title: z.string(), body: z.string() }),
})
Every entry needs a unique id. The glob() loader uses the filename (without extension)
as the ID automatically. Custom loaders must set id explicitly.
The src/content.config.ts file must be at the root of src/. Astro will not pick
it up from src/content/config.ts in Astro 6.