Skip to main content
Astro’s Content Collections provide type-safe content management with automatic validation and TypeScript support.

What are Content Collections?

Content Collections are a way to organize and validate markdown/MDX content in Astro projects. They provide:
  • Type safety: Automatic TypeScript types for your content
  • Schema validation: Ensure all content has required fields
  • Better performance: Optimized content loading and querying
  • Developer experience: Autocomplete and type checking in your editor

Collection Configuration

The work collection is defined in src/content/work/config.ts:
src/content/work/config.ts
import { defineCollection, z } from 'astro:content';

export const collections = {
  work: defineCollection({
    schema: z.object({
      title: z.string(),
      description: z.string(),
      publishDate: z.coerce.date(),
      tags: z.array(z.string()),
      img: z.string(),
      img_alt: z.string().optional(),
    }),
  }),
};

Schema Breakdown

Each field in the schema defines a validation rule using Zod:

title

title: z.string()
  • Type: String
  • Required: Yes
  • Validation: Must be a non-empty string
  • Example: "VST Plugins"

description

description: z.string()
  • Type: String
  • Required: Yes
  • Validation: Must be a non-empty string
  • Example: Multi-line project description

publishDate

publishDate: z.coerce.date()
  • Type: Date
  • Required: Yes
  • Coercion: Automatically converts strings to Date objects
  • Format: YYYY-MM-DD HH:MM:SS or any valid date string
  • Example: 2024-03-10 00:00:00
z.coerce.date() automatically converts string dates from frontmatter into JavaScript Date objects, making it easy to sort and format dates.

tags

tags: z.array(z.string())
  • Type: Array of strings
  • Required: Yes
  • Validation: Must be an array with at least one string element
  • Example: ["AI", "Web App", "React"]

img

img: z.string()
  • Type: String
  • Required: Yes
  • Format: Path relative to public/ directory
  • Example: /assets/stock-2.jpg

img_alt

img_alt: z.string().optional()
  • Type: String
  • Required: No
  • Purpose: Accessibility - alternative text for images
  • Example: "AudioGPT application interface"

Querying Collections

Access content collections in your Astro components:

Get All Projects

import { getCollection } from 'astro:content';

const allProjects = await getCollection('work');

Get Sorted Projects

import { getCollection } from 'astro:content';

const projects = await getCollection('work');
const sortedProjects = projects.sort(
  (a, b) => b.data.publishDate.valueOf() - a.data.publishDate.valueOf()
);

Filter Projects by Tag

import { getCollection } from 'astro:content';

const projects = await getCollection('work');
const webProjects = projects.filter(project =>
  project.data.tags.includes('Web Development')
);

Get Single Project

import { getEntry } from 'astro:content';

const project = await getEntry('work', 'audiogpt');

Type Safety

Astro automatically generates TypeScript types for your collections:
import type { CollectionEntry } from 'astro:content';

// Type for a work collection entry
type WorkProject = CollectionEntry<'work'>;

// Type for just the frontmatter data
type WorkData = CollectionEntry<'work'>['data'];

// Use in component props
interface Props {
  project: WorkProject;
}

Example Component with Types

src/components/ProjectCard.astro
---
import type { CollectionEntry } from 'astro:content';

interface Props {
  project: CollectionEntry<'work'>;
}

const { project } = Astro.props;
const { title, description, img, tags } = project.data;
---

<article>
  <img src={img} alt={project.data.img_alt} />
  <h2>{title}</h2>
  <p>{description}</p>
  <ul>
    {tags.map(tag => <li>{tag}</li>)}
  </ul>
</article>

Validation Errors

If your content doesn’t match the schema, you’ll get helpful error messages:
Error: Invalid frontmatter for src/content/work/my-project.md:
  - title: Required
  - publishDate: Expected date, received string "invalid-date"
  - tags: Expected array, received undefined

Extending the Schema

Add new fields to the schema as needed:
src/content/work/config.ts
import { defineCollection, z } from 'astro:content';

export const collections = {
  work: defineCollection({
    schema: z.object({
      title: z.string(),
      description: z.string(),
      publishDate: z.coerce.date(),
      tags: z.array(z.string()),
      img: z.string(),
      img_alt: z.string().optional(),
      // New fields
      featured: z.boolean().default(false),
      githubUrl: z.string().url().optional(),
      liveUrl: z.string().url().optional(),
      status: z.enum(['completed', 'in-progress', 'archived']).default('completed'),
    }),
  }),
};

Using Extended Schema

src/content/work/my-project.md
---
title: My Project
description: A great project
publishDate: 2024-03-15 00:00:00
tags: ["Web Development"]
img: /assets/project.jpg
featured: true
githubUrl: https://github.com/user/project
liveUrl: https://project.example.com
status: in-progress
---

Schema Validation Options

Optional Fields

field: z.string().optional()  // Can be undefined

Default Values

featured: z.boolean().default(false)  // Defaults to false if not provided

Enums

status: z.enum(['draft', 'published', 'archived'])

URLs

website: z.string().url()  // Validates URL format

Numbers

rating: z.number().min(1).max(5)  // Number between 1 and 5

Arrays with Validation

tags: z.array(z.string()).min(1).max(10)  // 1-10 tags

Multiple Collections

You can define multiple collections:
src/content/config.ts
import { defineCollection, z } from 'astro:content';

export const collections = {
  work: defineCollection({
    schema: z.object({
      // work schema...
    }),
  }),
  blog: defineCollection({
    schema: z.object({
      title: z.string(),
      author: z.string(),
      publishDate: z.coerce.date(),
      content: z.string(),
    }),
  }),
};

Best Practices

Keep schemas strict

Require essential fields and use optional() sparingly to maintain content quality.

Use coercion wisely

z.coerce.date() is helpful for date strings, but validate other types explicitly.

Leverage TypeScript

Use the generated types throughout your application for type safety.

Document your schema

Add comments explaining the purpose and format of each field.

Check Content Validity

Run Astro’s content checking:
npm run astro check
This validates all content against your schemas and checks for TypeScript errors.

Next Steps

Build docs developers (and LLMs) love