Skip to main content
Content Collections are the best way to manage sets of similar content in Astro projects. They provide type safety, validation, automatic TypeScript typings, and powerful APIs for querying and rendering content.

What are Content Collections?

A collection is a set of data with a similar structure, such as blog posts, product listings, or author profiles. Collections can contain:
  • Markdown, MDX, Markdoc files
  • JSON, YAML, TOML files
  • Remote data from a CMS or API

TypeScript Configuration

Content collections require specific TypeScript settings. Ensure your tsconfig.json includes:
{
  "extends": "astro/tsconfigs/base",
  "compilerOptions": {
    "strictNullChecks": true,
    "allowJs": true
  }
}

Defining Collections

Create a src/content.config.ts file to define your collections:
import { defineCollection, z } from 'astro:content';
import { glob, file } from 'astro/loaders';

const blog = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
  })
});

const authors = defineCollection({
  loader: file("src/data/authors.json"),
  schema: z.object({
    name: z.string(),
    portfolio: z.string().url(),
  }),
});

export const collections = { blog, authors };

Built-in Loaders

glob() Loader

Loads files from directories using glob patterns:
const blog = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }),
  schema: z.object({
    title: z.string(),
  })
});
Supports multiple patterns:
loader: glob({ 
  pattern: ['*.md', '!draft-*'], 
  base: 'src/data/posts' 
})

file() Loader

Loads multiple entries from a single file:
const dogs = defineCollection({
  loader: file("src/data/dogs.json"),
  schema: z.object({
    id: z.string(),
    breed: z.string(),
  }),
});
Use the parser option for custom file types:
import { parse as parseCsv } from "csv-parse/sync";

const cats = defineCollection({
  loader: file("src/data/cats.csv", {
    parser: (text) => parseCsv(text, { columns: true, skipEmptyLines: true })
  })
});

Custom Loaders

Create inline loaders for remote data:
const countries = defineCollection({
  loader: async () => {
    const response = await fetch("https://restcountries.com/v3.1/all");
    const data = await response.json();
    return data.map((country) => ({
      id: country.cca3,
      ...country,
    }));
  },
  schema: z.object({
    name: z.string(),
    population: z.number(),
  })
});

Schema Definition

Schemas enforce data validation using Zod:
const blog = defineCollection({
  loader: glob({ pattern: "**/*.md", base: "./src/data/blog" }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    tags: z.array(z.string()),
    draft: z.boolean().default(false),
  })
});

Collection References

Reference entries from other collections:
import { defineCollection, reference, z } from 'astro:content';

const blog = defineCollection({
  loader: glob({ pattern: '**/[^_]*.md', base: "./src/data/blog" }),
  schema: z.object({
    title: z.string(),
    author: reference('authors'),
    relatedPosts: z.array(reference('blog')),
  })
});

Querying Collections

Use helper functions to query your collections:
---
import { getCollection, getEntry } from 'astro:content';

// Get all entries
const allPosts = await getCollection('blog');

// Get a single entry
const post = await getEntry('blog', 'my-post');
---

Filtering Queries

Filter entries based on criteria:
const publishedPosts = await getCollection('blog', ({ data }) => {
  return data.draft !== true;
});
Filter by environment:
const posts = await getCollection('blog', ({ data }) => {
  return import.meta.env.PROD ? data.draft !== true : true;
});

Sorting Collections

Sort entries manually as needed:
---
const posts = (await getCollection('blog')).sort(
  (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);
---

Rendering Content

Render Markdown and MDX content using the render() function:
---
import { getEntry, render } from 'astro:content';

const entry = await getEntry('blog', 'post-1');
const { Content, headings } = await render(entry);
---

<h1>{entry.data.title}</h1>
<p>Published: {entry.data.pubDate.toDateString()}</p>
<Content />

Generating Routes

Static Sites

Use getStaticPaths() to generate pages:
---
import { getCollection, render } from 'astro:content';

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

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

<h1>{post.data.title}</h1>
<Content />

SSR Sites

Fetch entries on demand:
---
import { getEntry, render } from "astro:content";

const { id } = Astro.params;
if (!id) return Astro.redirect("/404");

const post = await getEntry("blog", id);
if (!post) return Astro.redirect("/404");

const { Content } = await render(post);
---

<h1>{post.data.title}</h1>
<Content />

Type Safety

Use CollectionEntry type for components:
---
import type { CollectionEntry } from 'astro:content';

interface Props {
  post: CollectionEntry<'blog'>;
}

const { post } = Astro.props;
---

<article>
  <h2>{post.data.title}</h2>
  <p>{post.data.description}</p>
</article>

Accessing Referenced Data

Query referenced entries separately:
---
import { getEntry, getEntries } from 'astro:content';

const blogPost = await getEntry('blog', 'welcome');
const author = await getEntry(blogPost.data.author);
const relatedPosts = await getEntries(blogPost.data.relatedPosts);
---

<h1>{blogPost.data.title}</h1>
<p>Author: {author.data.name}</p>

<h2>Related Posts:</h2>
{relatedPosts.map(post => (
  <a href={post.id}>{post.data.title}</a>
))}

JSON Schemas

Astro generates JSON Schema files for collections in .astro/collections/. Use them in your editor for IntelliSense:
{
  "$schema": "../../../.astro/collections/authors.schema.json",
  "name": "Jane Doe",
  "skills": ["Astro", "TypeScript"]
}

When to Use Collections

Use content collections when you:
  • Have multiple files sharing the same structure
  • Need type safety and validation
  • Want optimized querying for thousands of entries
  • Need to fetch remote content from a CMS
Don’t use collections when you:
  • Have only one or a few unique pages
  • Are displaying unprocessed static files
  • Need real-time data updates