Astro content collections turn a folder of Markdown into a typed, validated data source. The build fails if a required field is missing, which is exactly what you want from an archive that is meant to last.

Define the shape first

The collection definition lives in src/content.config.ts. A loader points at the folder, and a schema describes every entry.

import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';

const journal = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/journal' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    publishedAt: z.coerce.date(),
    tags: z.array(z.string()).default([]),
    draft: z.boolean().default(false),
  }),
});

export const collections = { journal };

Two details matter here. z.coerce.date() accepts both YAML dates and date-like strings, which avoids a class of frontmatter surprises. Defaults keep new entries from failing because of fields that are usually empty.

Query at build time

Pages call getCollection() and receive fully typed entries. Drafts are filtered once, in a small helper, rather than in every page.

import { getCollection } from 'astro:content';

const entries = (await getCollection('journal'))
  .filter((entry) => !entry.data.draft)
  .sort(
    (a, b) => b.data.publishedAt.valueOf() - a.data.publishedAt.valueOf(),
  );

Sorting belongs next to the query. If the order is a product decision, it should be visible in the code that makes it.

Render the body

Astro 7 exposes render(entry), which returns the compiled content and its headings.

import { render } from 'astro:content';

const { Content, headings } = await render(entry);

headings is useful for a table of contents without parsing the Markdown twice.

When to leave the filesystem

Content collections are excellent for documents that change with a deploy: essays, notes, documentation, project pages. They are a poor fit for anything that must change without a build — comments, analytics, live collection items, subscriber records.

The plan here is deliberately split. Editorial content starts in collections. When the dashboard arrives, the same shapes move to D1, and the public site reads from the database. The schema is the contract; the storage is an implementation detail.

End note

This entry is part of the Knowledge. It belongs to the world of Greed.

Last updated 14 October 2026