This Astro content collections cheatsheet puts the whole API on one page for Astro 7: where the config lives, the two built-in loaders, schema patterns, every query function, rendering, references and live collections. For explanations and worked examples, the Astro content collections guide goes deeper; this page is for quick lookups.
Key Takeaways
- Config:
src/content.config.ts, exportingcollections.- Loaders:
glob()for folders,file()for one JSON/YAML file, or your own function.- Queries:
getCollection(),getEntry(),getEntries(); render withrender(entry).- Import
zfromastro/zod.
Imports
import { defineCollection, reference } from 'astro:content'; // config
import { glob, file } from 'astro/loaders'; // config
import { z } from 'astro/zod'; // config
import { getCollection, getEntry, getEntries, render } from 'astro:content'; // pages
import type { CollectionEntry } from 'astro:content'; // types
Define a collection
// src/content.config.ts
const blog = defineCollection({
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/content/blog' }),
schema: ({ image }) => z.object({
title: z.string(),
pubDate: z.coerce.date(),
cover: image().optional(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
author: reference('authors'),
}),
});
const authors = defineCollection({
loader: file('src/data/authors.json'),
schema: z.object({ name: z.string() }),
});
export const collections = { blog, authors };
Loaders
| Loader | Entries | Id comes from |
|---|---|---|
glob({ pattern, base }) |
One per matching file | File path without extension, or slug frontmatter |
glob({ ..., generateId }) |
One per file | Your function |
file('data.json') |
One per array item or object key | The item’s id or the key |
async () => [...] |
One per returned object | The object’s id |
Object loader { name, load } |
Whatever load stores |
store.set({ id, data }) |
Schema patterns
z.string().max(70) // length limit
z.coerce.date() // "2026-10-28" -> Date
z.enum(['draft', 'live']) // fixed values
z.array(z.string()).default([]) // missing -> []
z.boolean().default(false) // missing -> false
z.url().optional() // valid URL or missing
z.number().min(1).max(5) // range
image() // local image, validated and optimisable
reference('authors') // id in another collection
z.array(reference('blog')) // list of ids
Query
const all = await getCollection('blog');
const live = await getCollection('blog', ({ data }) => !data.draft);
const sorted = live.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
const one = await getEntry('blog', 'first-post'); // or undefined
const author = await getEntry(post.data.author); // resolve a reference
const related = await getEntries(post.data.related); // resolve many
getCollection() doesn’t guarantee order. Always sort.
Entry shape
| Property | What it is |
|---|---|
entry.id |
Unique id within the collection |
entry.collection |
Collection name |
entry.data |
Validated frontmatter or data |
entry.body |
Raw Markdown body (file-based entries) |
entry.filePath |
Source file path (file-based entries) |
entry.rendered |
Pre-rendered HTML metadata, when available |
Render
---
const { Content, headings, remarkPluginFrontmatter } = await render(post);
---
<Content />
<Content components={{ h2: MyHeading }} /> <!-- MDX element mapping -->
headings powers a table of contents; remarkPluginFrontmatter carries values like reading time.
Static routes from a collection
---
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({ params: { slug: post.id }, props: { post } }));
}
const { post } = Astro.props;
---
Use [...slug].astro if ids contain slashes.
Types
type Post = CollectionEntry<'blog'>;
function card(post: Post) { return post.data.title; }
Live collections
// src/live.config.ts
import { defineLiveCollection } from 'astro:content';
Query with getLiveCollection() and getLiveEntry() on on-demand pages. They return { entries | entry, error } instead of throwing. Use them only for data that must be fresh per request.
CLI
npx astro sync # regenerate collection types
Errors you’ll meet
| Message | Fix |
|---|---|
| Frontmatter does not match schema | Fix the field or schema |
| Collection is missing a loader | Add glob() or file() |
| Legacy content config file found | Move to src/content.config.ts |
| Render an undefined entry | Guard getEntry() results |
| Duplicate content entry slug | Make ids unique |
The content docs for Komal themes show a theme’s collections, and the astro:content reference has full signatures.
FAQ
Where do content files have to live?
Anywhere. glob()’s base option points at the folder; src/content/ is only a convention.
Can one collection mix Markdown and JSON?
No. A collection is either file-per-entry Markdown/MDX (or data files) via glob(), or entries from file(). Use two collections and a reference() between them.