> ## 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 Management

> Manage your content with Astro Content Collections, frontmatter, and CMS integrations

## Overview

Astro provides powerful content management capabilities through Content Collections, allowing you to organize, validate, and query your content with full TypeScript support.

## Content Collections

Content Collections are the recommended way to manage content in Astro. They provide type-safety, validation, and optimized queries for your Markdown, MDX, and data files.

### Setting Up a Collection

<Steps>
  <Step title="Create the content directory">
    Create a `src/content/` directory and add a subdirectory for your collection:

    ```
    src/
    └── content/
        └── blog/
            ├── post-1.md
            ├── post-2.md
            └── post-3.mdx
    ```
  </Step>

  <Step title="Define the collection schema">
    Create `src/content.config.ts` to define and validate your content:

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

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

    export const collections = { blog };
    ```
  </Step>

  <Step title="Query your content">
    Use the type-safe `getCollection()` function to retrieve your content:

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

    const posts = await getCollection('blog');
    const sortedPosts = posts.sort(
      (a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()
    );
    ---

    <ul>
      {sortedPosts.map((post) => (
        <li>
          <a href={`/blog/${post.id}`}>{post.data.title}</a>
        </li>
      ))}
    </ul>
    ```
  </Step>
</Steps>

### Dynamic Routes with Collections

Generate pages dynamically from your content using `getStaticPaths()`:

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

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

type Props = CollectionEntry<'blog'>;

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

<BlogPost {...post.data}>
  <Content />
</BlogPost>
```

## Frontmatter

Frontmatter allows you to add metadata to your Markdown and MDX files using YAML syntax.

### Basic Frontmatter

```md title="src/content/blog/my-post.md" theme={null}
---
title: 'My First Blog Post'
description: 'This is my first post on my new Astro blog.'
pubDate: 2024-01-15
author: 'Jane Doe'
tags: ['astro', 'blogging', 'web development']
---

This is the content of my blog post...
```

### Accessing Frontmatter

When using Content Collections, frontmatter is available through the `data` property:

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

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

{posts.map((post) => (
  <article>
    <h2>{post.data.title}</h2>
    <p>{post.data.description}</p>
    <time>{post.data.pubDate.toLocaleDateString()}</time>
  </article>
))}
```

## Content Loaders

Content Collections support multiple loader types for flexible content sources.

<Accordion title="File-based Loader (Glob)">
  Load content from your filesystem:

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

  const docs = defineCollection({
    loader: glob({ 
      base: './src/content/docs',
      pattern: '**/*.md'
    }),
    schema: z.object({
      title: z.string(),
      order: z.number(),
    }),
  });

  export const collections = { docs };
  ```
</Accordion>

<Accordion title="Multiple Collections">
  Organize different content types with separate collections:

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

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

  const newsletter = defineCollection({
    loader: glob({ base: './src/content/newsletter', pattern: '**/*.md' }),
    schema: z.object({
      subject: z.string(),
      sendDate: z.coerce.date(),
    }),
  });

  export const collections = { blog, newsletter };
  ```
</Accordion>

## Schema Validation

Use Zod schemas to validate and type-check your content.

### Common Schema Patterns

<CardGroup cols={2}>
  <Card title="String Fields" icon="text">
    ```ts theme={null}
    title: z.string(),
    slug: z.string().regex(/^[a-z0-9-]+$/),
    email: z.string().email(),
    ```
  </Card>

  <Card title="Numbers & Dates" icon="calendar">
    ```ts theme={null}
    order: z.number(),
    rating: z.number().min(1).max(5),
    pubDate: z.coerce.date(),
    ```
  </Card>

  <Card title="Optional Fields" icon="check">
    ```ts theme={null}
    subtitle: z.string().optional(),
    updatedDate: z.coerce.date().optional(),
    tags: z.array(z.string()).default([]),
    ```
  </Card>

  <Card title="Images" icon="image">
    ```ts theme={null}
    schema: ({ image }) => z.object({
      coverImage: image(),
      thumbnail: z.optional(image()),
    })
    ```
  </Card>
</CardGroup>

### Custom Validation

Add custom validation logic:

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

const blog = defineCollection({
  loader: glob({ base: './src/content/blog', pattern: '**/*.md' }),
  schema: z.object({
    title: z.string().min(10, 'Title must be at least 10 characters'),
    draft: z.boolean().default(false),
    pubDate: z.coerce.date(),
  }).refine(
    (data) => data.draft || data.pubDate <= new Date(),
    { message: 'Published posts must have a past publish date' }
  ),
});

export const collections = { blog };
```

## Filtering and Sorting

Query and transform your content with JavaScript:

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

// Get all published posts
const allPosts = await getCollection('blog', ({ data }) => {
  return data.draft !== true;
});

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

// Filter by tag
const tagFilter = 'astro';
const filteredPosts = allPosts.filter((post) =>
  post.data.tags?.includes(tagFilter)
);

// Get only the 5 most recent
const recentPosts = sortedPosts.slice(0, 5);
---
```

## Working with MDX

MDX allows you to use JSX components in your Markdown content.

### Setup

```bash theme={null}
npm install @astrojs/mdx
```

```js title="astro.config.mjs" theme={null}
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';

export default defineConfig({
  integrations: [mdx()],
});
```

### Using Components in MDX

```mdx title="src/content/blog/using-mdx.mdx" theme={null}
---
title: 'Using MDX'
pubDate: 2024-01-20
---

import CustomComponent from '../../components/CustomComponent.astro';
import { Card } from '../../components/Card.jsx';

# Hello from MDX!

<CustomComponent message="This is an Astro component" />

<Card title="Interactive Card">
  This card component works inside my MDX content!
</Card>
```

## CMS Integrations

Astro works with headless CMS platforms for content management.

<CardGroup cols={2}>
  <Card title="Contentful" icon="database">
    Fetch content from Contentful's API and render it in your Astro pages.
  </Card>

  <Card title="Sanity" icon="database">
    Use Sanity's GROQ queries to pull structured content into Astro.
  </Card>

  <Card title="Strapi" icon="database">
    Connect to your Strapi backend API for content management.
  </Card>

  <Card title="WordPress" icon="database">
    Use WordPress as a headless CMS via the REST or GraphQL API.
  </Card>
</CardGroup>

### Example: Fetching from a Headless CMS

```astro title="src/pages/posts/[slug].astro" theme={null}
---
export async function getStaticPaths() {
  const response = await fetch('https://api.example.com/posts');
  const posts = await response.json();

  return posts.map((post) => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

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

<article>
  <h1>{post.title}</h1>
  <div set:html={post.content} />
</article>
```

## Best Practices

<Steps>
  <Step title="Use Content Collections">
    Always prefer Content Collections over manual file parsing for better type safety and performance.
  </Step>

  <Step title="Validate Your Schema">
    Define strict schemas to catch content errors at build time rather than runtime.
  </Step>

  <Step title="Organize Collections Logically">
    Create separate collections for different content types (blog, docs, products, etc.).
  </Step>

  <Step title="Optimize Images">
    Use the `image()` schema helper to ensure images are optimized automatically.
  </Step>
</Steps>
