Astro: Content collections

I talked about how to work with Markdown files in a basic way.

If you build a content-heavy site, Astro has a powerful feature you will want to know about: content collections.

To work with collections, you create a new src/content folder, and inside it, you create another folder with name of the type of content.

For example if you have blog posts you’ll have src/content/posts. Or if you build a course on your site, you’ll have lessons, so you’ll have src/content/lessons.

Here’s an example with blog posts:

  • src/content/posts/
  • src/content/posts/
  • src/content/posts/

Inside src/content you create a config.ts file that will define the collection.

Here’s the most simple example of a collection:

import { defineCollection } from 'astro:content'

const collection = defineCollection({
  type: 'content'

export const collections = {
  posts: collection,

We can add more configuration to add required fields for the frontmatter of each markdown file, so if you miss for example the tag on a post, Astro will complain. But this is a start.

When we used markdown files before, we created them in the src/pages folder, which automatically generated the route for us.

With content collections, we have to handle this.

We create a dynamic route under src/pages. Remember how we made a dynamic route for some test data before?

Now we’ll serve content from the posts collection.

Say you want to have a blog in /blog, and each post has the route /blog/<slug>, like /blog/first and /blog/second

Create a src/pages/blog/[slug].astro

Let’s replicate what we had made before with dynamic routes, we got the dynamic parameter from Astro.params and we had a frontmatter with a getStaticPaths() function exported:

import Layout from '../../layouts/Layout.astro'

const { slug } = Astro.params

export async function getStaticPaths() {
  return [

<Layout title=''>

Here’s how it works with collections:

import { getCollection } from 'astro:content'

import Layout from '../../layouts/Layout.astro'

const { slug } = Astro.params

export async function getStaticPaths() {
  const posts = await getCollection('posts')
  return => ({
    params: { slug: post.slug }, props: { post },

const { post } = Astro.props
const { Content } = await post.render()

<Layout title={}>
  <Content />

Now we also import getCollection from Astro, and we use that to query for all the posts data using await getCollection('posts').

We use this data to populate the posts data returned from getStaticPaths().

The component then when asked to render a single item gets the post data from Astro.props, and we use this to get a <Content /> component that’s responsible for displaying the content of the markdown file.

Finally, we return the markup.

Here’s the result:



Lessons in this unit:

0: Introduction
1: Your first Astro site
2: The structure of an Astro site
3: Astro components
4: Adding more pages
5: Dynamic routing
6: Markdown in Astro
7: Images
8: ▶︎ Content collections
9: CSS in Astro
10: JavaScript in Astro
11: Client-side routing and view transitions
12: SSR in Astro
13: API endpoints in Astro
Want to learn more? Check out our courses