Skip to main content
All guides
Briefing 03 of 08

Managing content collections

Define, draft, or remove Astro content collections.

Last updated

Creating a collection

1. Define the schema

In src/content.config.ts:

import { defineCollection } from "astro:content";
import { glob } from "astro/loaders";
import { z } from "astro/zod";

const yourCollection = defineCollection({
  loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/<your-collection>" }),
  schema: z.object({
    title: z.string().min(1),
    description: z.string().optional(),
    draft: z.boolean().default(false),
  }),
});

export const collections = { /* ...existing, */ yourCollection };

title and description in the schema cover most pages. Add fields (dates, tags, images, authors) only when an entry actually needs them.

2. Add entries

Create .md or .mdx files in src/content/<your-collection>/. The filename (minus its extension) becomes the entry id and the URL slug.

---
title: My first entry
description: A one-line summary.
---

Body content here.

3. Index page

src/pages/<your-collection>/index.astro lists every non-draft entry:

---
import { getCollection } from "astro:content";

const entries = await getCollection("yourCollection", ({ data }) => !data.draft);
---
<ul>
  {entries.map((entry) => (
    <li>
      <a href={`/<your-collection>/${entry.id}`}>{entry.data.title}</a>
    </li>
  ))}
</ul>

4. Detail page

src/pages/<your-collection>/[slug].astro renders one entry per route:

---
import { getCollection, render } from "astro:content";

export async function getStaticPaths() {
  const entries = await getCollection("yourCollection");
  return entries.map((entry) => ({ params: { slug: entry.id }, props: { entry } }));
}

const { entry } = Astro.props;
const { Content } = await render(entry);
---
<article>
  <h1>{entry.data.title}</h1>
  <Content />
</article>

From here, swap a real layout in for <article>, add SEO via src/components/SEO.astro, and grow the schema as your entries actually demand it.

Real-world example

For a fuller, opinionated example with images, related entries, and tag pages, see the blog collection.

Drafting entries

The docs and blog collections (and any collection you define) support a draft field. Mark entries with draft: true in the frontmatter to write without publishing:

---
title: "Work in progress"
draft: true
---

Drafts are automatically filtered from:

  • List pages and pagination
  • RSS feeds
  • Sitemap (via built page filtering)
  • Tag taxonomy

Filter them out explicitly when querying a collection:

const entries = await getCollection("yourCollection", ({ data }) => !data.draft);

Deleting the example collection

If you don’t need blog, delete:

  • The collection block in src/content.config.ts
  • The src/content/<name>/ directory
  • The src/pages/<name>/ route(s)
  • Any helpers under src/lib/ and components under src/components/<name>/ referenced only by that collection

Cookieless analytics and no session replay. We respect your personal space.