> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/withastro/astro/llms.txt
> Use this file to discover all available pages before exploring further.

# Content Collections

> Learn how to use Astro Content Collections for type-safe, organized content management with schemas and loaders

Content Collections are Astro's solution for managing content like blog posts, documentation, or any structured data. They provide type-safety, validation, and automatic TypeScript types for your content.

## What are Content Collections?

Content Collections organize your content into typed, validated groups. Instead of manually loading markdown files, you define collections with schemas and let Astro handle the rest.

<Info>
  **Key Benefits**: Type-safe frontmatter, automatic TypeScript types, content validation, and optimized image handling.
</Info>

## Setup

Create a `content.config.ts` file in your `src/` directory:

```ts title="src/content.config.ts" theme={null}
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';

const blog = defineCollection({
  loader: glob({ base: './src/content/blog', pattern: '**/*.{md,mdx}' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    pubDate: z.coerce.date(),
    author: z.string(),
    image: z.string().optional(),
  }),
});

export const collections = { blog };
```

Organize your content:

```
src/
├── content/
│   └── blog/
│       ├── post-1.md
│       ├── post-2.md
│       └── post-3.mdx
└── content.config.ts
```

## Defining Collections

Use `defineCollection()` to create a collection:

```ts theme={null}
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';

const blog = defineCollection({
  // Where to load content from
  loader: glob({ 
    base: './src/content/blog', 
    pattern: '**/*.{md,mdx}' 
  }),
  
  // Validate frontmatter
  schema: z.object({
    title: z.string(),
    pubDate: z.coerce.date(),
    tags: z.array(z.string()).default([]),
  }),
});

export const collections = { blog };
```

<Tabs>
  <Tab title="Basic Collection">
    ```ts theme={null}
    const blog = defineCollection({
      loader: glob({ pattern: '*.md' }),
      schema: z.object({
        title: z.string(),
        date: z.date(),
      }),
    });
    ```
  </Tab>

  <Tab title="With Images">
    ```ts theme={null}
    const blog = defineCollection({
      loader: glob({ pattern: '*.md' }),
      schema: ({ image }) => z.object({
        title: z.string(),
        cover: image(),
      }),
    });
    ```
  </Tab>

  <Tab title="Multiple Collections">
    ```ts theme={null}
    const blog = defineCollection({ /* ... */ });
    const docs = defineCollection({ /* ... */ });
    const authors = defineCollection({ /* ... */ });

    export const collections = { blog, docs, authors };
    ```
  </Tab>
</Tabs>

## Schema Validation

Schemas use Zod for runtime validation:

```ts theme={null}
import { z } from 'astro/zod';

const blog = defineCollection({
  loader: glob({ pattern: '**/*.md' }),
  schema: z.object({
    // Required string
    title: z.string(),
    
    // Optional string with default
    description: z.string().default('No description'),
    
    // Transform string to Date
    pubDate: z.coerce.date(),
    
    // Optional date
    updatedDate: z.coerce.date().optional(),
    
    // Array with default
    tags: z.array(z.string()).default([]),
    
    // Enum
    status: z.enum(['draft', 'published', 'archived']),
    
    // Boolean with default
    featured: z.boolean().default(false),
    
    // Nested object
    author: z.object({
      name: z.string(),
      email: z.string().email(),
    }),
  }),
});
```

### Image Schemas

Use the `image()` helper for optimized images:

```ts theme={null}
const blog = defineCollection({
  loader: glob({ pattern: '**/*.md' }),
  schema: ({ image }) => z.object({
    title: z.string(),
    heroImage: image(),
    thumbnail: z.optional(image()),
  }),
});
```

In your markdown:

```markdown title="src/content/blog/post.md" theme={null}
---
title: My Post
heroImage: ./hero.jpg
thumbnail: ./thumb.png
---

Post content here.
```

## Querying Collections

### Get All Entries

Use `getCollection()` to fetch all entries:

```astro title="src/pages/blog/index.astro" theme={null}
---
import { getCollection } from 'astro:content';

const posts = await getCollection('blog');
---

<ul>
  {posts.map((post) => (
    <li>
      <a href={`/blog/${post.id}`}>
        {post.data.title}
      </a>
      <time>{post.data.pubDate.toLocaleDateString()}</time>
    </li>
  ))}
</ul>
```

### Filter Entries

Filter with a callback function:

```astro theme={null}
---
import { getCollection } from 'astro:content';

// Get only published posts
const posts = await getCollection('blog', (entry) => {
  return entry.data.status === 'published';
});

// Get posts by tag
const reactPosts = await getCollection('blog', (entry) => {
  return entry.data.tags.includes('react');
});

// Sort by date
const sortedPosts = posts.sort((a, b) => 
  b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
);
---
```

### Get Single Entry

Use `getEntry()` to fetch a specific entry:

```astro title="src/pages/blog/[id].astro" theme={null}
---
import { getEntry } from 'astro:content';

const post = await getEntry('blog', Astro.params.id);

if (!post) {
  return Astro.redirect('/404');
}

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

<article>
  <h1>{post.data.title}</h1>
  <time>{post.data.pubDate.toLocaleDateString()}</time>
  <Content />
</article>
```

## Rendering Content

Call `render()` on an entry to get the content component:

```astro theme={null}
---
import { getEntry } from 'astro:content';

const post = await getEntry('blog', 'my-post');
const { Content, headings, remarkPluginFrontmatter } = await post.render();
---

<article>
  <h1>{post.data.title}</h1>
  
  <!-- Table of contents -->
  <nav>
    {headings.map((heading) => (
      <a href={`#${heading.slug}`}>
        {heading.text}
      </a>
    ))}
  </nav>
  
  <!-- Rendered content -->
  <Content />
</article>
```

<Note>
  The `Content` component is the rendered markdown/MDX. `headings` contains the document outline.
</Note>

## Dynamic Routes

Generate routes from collections:

```astro title="src/pages/blog/[...slug].astro" theme={null}
---
import { getCollection } from 'astro:content';
import BlogLayout from '../../layouts/BlogLayout.astro';

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

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

<BlogLayout
  title={post.data.title}
  description={post.data.description}
  pubDate={post.data.pubDate}
>
  <Content />
</BlogLayout>
```

## Loaders

Loaders determine where content comes from. The `glob()` loader reads files from disk:

```ts theme={null}
import { glob } from 'astro/loaders';

const blog = defineCollection({
  loader: glob({
    base: './src/content/blog',
    pattern: '**/*.{md,mdx}',
  }),
  schema: z.object({ /* ... */ }),
});
```

You can also create custom loaders to fetch content from APIs, databases, or CMSs.

## References Between Collections

Create relationships between collections:

```ts title="src/content.config.ts" theme={null}
import { defineCollection, reference } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';

const authors = defineCollection({
  loader: glob({ pattern: '*.json' }),
  schema: z.object({
    name: z.string(),
    bio: z.string(),
  }),
});

const blog = defineCollection({
  loader: glob({ pattern: '**/*.md' }),
  schema: z.object({
    title: z.string(),
    author: reference('authors'),
  }),
});

export const collections = { blog, authors };
```

Query referenced entries:

```astro theme={null}
---
import { getEntry } from 'astro:content';

const post = await getEntry('blog', 'my-post');
const author = await getEntry('authors', post.data.author);
---

<article>
  <h1>{post.data.title}</h1>
  <p>By {author.data.name}</p>
</article>
```

## Type Safety

Astro generates TypeScript types automatically:

```ts theme={null}
// Generated types are available globally
import type { CollectionEntry } from 'astro:content';

type BlogPost = CollectionEntry<'blog'>;

function formatPost(post: BlogPost) {
  // post.data is fully typed!
  return {
    title: post.data.title,
    date: post.data.pubDate.toISOString(),
  };
}
```

## Implementation Details

From the source code at `src/content/runtime.ts`, collections use:

1. **Data Store**: Content entries are stored in a global data store
2. **Schema Validation**: Zod schemas validate entries at build time
3. **Type Generation**: TypeScript types are auto-generated from schemas

```ts theme={null}
// Simplified from src/content/runtime.ts
export function createGetCollection({ liveCollections }) {
  return async function getCollection(
    collection: string,
    filter?: (entry: any) => unknown
  ) {
    const store = await globalDataStore.get();
    
    const result = [];
    for (const rawEntry of store.values(collection)) {
      const data = updateImageReferencesInData(
        rawEntry.data, 
        rawEntry.filePath,
        imageAssetMap
      );
      
      let entry = { ...rawEntry, data, collection };
      
      if (filter && !filter(entry)) {
        continue;
      }
      
      result.push(entry);
    }
    
    return result;
  };
}
```

## Practical Examples

### Blog with Categories

```ts title="src/content.config.ts" theme={null}
const blog = defineCollection({
  loader: glob({ pattern: '**/*.md' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    category: z.enum(['tech', 'design', 'business']),
    pubDate: z.coerce.date(),
    tags: z.array(z.string()).default([]),
  }),
});
```

```astro title="src/pages/blog/category/[category].astro" theme={null}
---
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const categories = ['tech', 'design', 'business'];
  
  return categories.map((category) => ({
    params: { category },
  }));
}

const { category } = Astro.params;
const posts = await getCollection('blog', (entry) => {
  return entry.data.category === category;
});
---

<h1>{category} Posts</h1>
<ul>
  {posts.map((post) => (
    <li><a href={`/blog/${post.id}`}>{post.data.title}</a></li>
  ))}
</ul>
```

### Documentation with Sidebar

```ts title="src/content.config.ts" theme={null}
const docs = defineCollection({
  loader: glob({ pattern: '**/*.mdx' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    order: z.number().default(0),
    sidebar: z.object({
      label: z.string(),
      order: z.number(),
    }),
  }),
});
```

```astro title="src/components/DocsSidebar.astro" theme={null}
---
import { getCollection } from 'astro:content';

const docs = await getCollection('docs');
const sorted = docs.sort((a, b) => 
  a.data.sidebar.order - b.data.sidebar.order
);
---

<nav>
  <ul>
    {sorted.map((doc) => (
      <li>
        <a href={`/docs/${doc.id}`}>
          {doc.data.sidebar.label}
        </a>
      </li>
    ))}
  </ul>
</nav>
```

## Best Practices

<Steps>
  <Step title="Define clear schemas">
    Use descriptive field names and provide defaults where appropriate.
  </Step>

  <Step title="Validate early">
    Let Zod catch errors at build time, not runtime.
  </Step>

  <Step title="Use TypeScript">
    Take advantage of auto-generated types for type safety.
  </Step>

  <Step title="Organize by collection">
    Group similar content together (blog, docs, authors).
  </Step>

  <Step title="Filter in queries">
    Filter collections at query time for flexibility.
  </Step>
</Steps>

<CardGroup cols={2}>
  <Card title="Use enums" icon="list">
    Define allowed values with `z.enum()` for better validation.
  </Card>

  <Card title="Reference related content" icon="link">
    Use `reference()` to create relationships between collections.
  </Card>

  <Card title="Optimize images" icon="image">
    Use the `image()` helper for automatic image optimization.
  </Card>

  <Card title="Default values" icon="settings">
    Provide sensible defaults to make frontmatter easier to write.
  </Card>
</CardGroup>

## Learn More

<CardGroup cols={2}>
  <Card title="Routing" href="/concepts/routing">
    Generate routes from collections
  </Card>

  <Card title="Layouts" href="/concepts/layouts">
    Create layouts for your content
  </Card>
</CardGroup>
