Astro shows “Content collection is missing a loader definition” when a collection in src/content.config.ts is defined without a loader. Older Astro versions inferred collections from folders in src/content/; current versions require you to say where content comes from. The message: “Collections must have a loader defined.” Add glob() for a folder of files or file() for a single data file.
Key Takeaways
- Every
defineCollection()needsloader.- Folders of Markdown, MDX, JSON or YAML:
glob({ pattern, base }).- One JSON or YAML file holding many entries:
file('path').- Replace any
type: 'content'ortype: 'data'with a loader.
Before and after
Old style, which now errors:
// src/content.config.ts
const blog = defineCollection({
type: 'content',
schema: z.object({ title: z.string() }),
});
Current style:
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob, file } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({ title: z.string() }),
});
const authors = defineCollection({
loader: file('src/data/authors.json'),
schema: z.object({ name: z.string() }),
});
export const collections = { blog, authors };
base is relative to the project root. The pattern above skips files starting with an underscore, a handy convention for drafts and partials.
Other changes that come with it
Projects hitting this error are usually upgrading, so expect these too:
- The config file location. It must be
src/content.config.ts. See Legacy content config file found. entry.slugis nowentry.id. Update links andgetStaticPaths().- Rendering. Replace
entry.render()withrender(entry)imported fromastro:content. - Deprecated helpers.
getEntryBySlug()andgetDataEntryById()becomegetEntry(); see Invalid use of getEntryBySlug.
Temporary escape hatch
legacy.collectionsBackwardsCompat: true in astro.config.mjs restores the old behaviour while you migrate. Use it to get a build passing, then remove it once collections have loaders.
Custom loaders
A loader doesn’t have to read files. An async function returning objects with an id works:
const releases = defineCollection({
loader: async () => {
const res = await fetch('https://api.example.com/releases');
return (await res.json()).map((r: any) => ({ id: String(r.id), name: r.name }));
},
schema: z.object({ name: z.string() }),
});
The Astro content collections guide has a full migration checklist and loader examples, and the content docs for Komal themes show the config in a theme. The official upgrade notes are in the Astro v6 upgrade guide.
FAQ
Do I still need the src/content folder?
No. glob() can point anywhere with base. src/content/ remains a common convention.
What’s the difference between glob() and file()?
glob() makes one entry per file. file() makes many entries from one JSON or YAML file.