A practical guide to Astro content collections
Astro & static sites · updated july 2026
Content collections are Astro’s answer to “I have a folder of Markdown files and I want type safety and validation on their frontmatter,” and once you’ve used them on a real project it’s hard to go back to loosely-typed frontmatter you’re just trusting to be correct.
The core pieces are a content.config.ts file defining a collection with a loader and a Zod schema, and a folder of Markdown files validated against it. The loader tells Astro where the files live — glob() for a folder matching a pattern is the common case. The schema is where the value is: every file’s frontmatter gets parsed at build time, so a missing field or a typo’d date fails the build instead of failing silently in production. I’d rather a broken frontmatter field stop a deploy than ship a page with a missing description.
z.coerce.date() is worth calling out because it’s the one that trips people up. Frontmatter dates arrive as strings from YAML, and z.coerce.date() converts them to real Date objects rather than leaving you to parse strings yourself at every call site.
Relations between entries — a “related articles” field, shared tags — are just arrays of strings referencing other slugs, not a foreign key system with referential validation. Astro doesn’t check that a slug in a related array actually exists, so a typo won’t fail the build, it’ll just silently produce a broken link at render time. I’ve been bitten by this enough that on larger collections I write a small build-time check that cross-references every related entry against the actual set of slugs.
For querying, getCollection() is the main entry point, and it’s worth filtering there rather than fetching everything and filtering in your template — passing a filter function to getCollection('articles', ({ data }) => !data.draft) means unpublished content never even gets pulled into the page’s data, rather than being fetched and then discarded.
The last habit worth building early: keep the schema strict rather than optional-by-default. It’s tempting to make every field optional so you never hit a validation error while drafting, but that defeats most of the point. A schema that’s all optional fields catches nothing.
builder, codelabs.com.au
Stay up to date with AI coding
New articles roughly every couple of weeks. No spam, unsubscribe any time.