AstroShowcase
Data Beginner Astro 6.3

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.

▶ Collection Query Demo

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.

const blog = defineCollection({
  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.

#astro #beginner #tutorial

Jan 14, 2025

Astro Team

getting-started

The New Content Layer API

How glob loaders, remote loaders, and typed schemas work in Astro 6.

#content #api #advanced

Jan 31, 2025

Fred K. Schott

content-layer

Smooth View Transitions

Add cinematic page transitions with just one directive.

#ux #animation

Feb 17, 2025

Matthew Phillips

view-transitions

Setting up a collection

1

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 }
2

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())
---
3

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

LoaderSource
glob()Local .md, .mdx, .json, .yaml files
Custom loaderCMS, 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() }),
})
Tip

Every entry needs a unique id. The glob() loader uses the filename (without extension) as the ID automatically. Custom loaders must set id explicitly.

Warning

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.

Further reading