withastro/astro · error · AstroError

LegacyContentConfigError

LegacyContentConfigError

Error message

Found legacy content config file in "${relativePath}". Please move this file to "src/content.config.${filename.split('.').at(-1)}" and ensure each collection has a loader defined.

What it means

Astro 5 moved the content config from src/content/config.ts to src/content.config.ts and requires every collection to declare a loader. When getContentPaths finds only the legacy file and the legacy.collectionsBackwardsCompat flag is not enabled, it throws this error instructing you to migrate.

Solutions

  1. Move src/content/config.ts to src/content.config.ts (in src/, not inside src/content/) and give each collection a loader such as glob().
  2. As a temporary bridge, set legacy: { collectionsBackwardsCompat: true } in astro.config.mjs.
  3. Delete the legacy file after migrating so only the new config is found.

Example fix

// before: src/content/config.ts (Astro <=4)
import { defineCollection } from 'astro:content';
const blog = defineCollection({ schema: z.object({ title: z.string() }) });
export const collections = { blog };

// after: src/content.config.ts
import { defineCollection, glob } from 'astro/loaders';
const blog = defineCollection({
  loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
  schema: z.object({ title: z.string() }),
});
export const collections = { blog };
Defensive patterns

Strategy: validation

Validate before calling

// Migration guard: fail with a clear message if only the legacy config exists
import { existsSync } from 'node:fs';
const modern = 'src/content.config.ts';
const legacy = 'src/content/config.ts';
if (!existsSync(modern) && existsSync(legacy)) {
  throw new Error('Run the Astro 5 migration: move src/content/config.ts to src/content.config.ts');
}

Prevention

When it happens

Trigger: Upgrading a project to Astro 5+ while src/content/config.ts (or .mjs) still exists; scaffolding a new project by copying an Astro 4-era layout; the modern config file missing so the legacy one is detected.

Common situations: astro upgrade from v3/v4; CI failing after a major bump; following tutorials or templates predating Astro 5.

Related errors


AI-assisted analysis of withastro/astro@e294953aa8 (2026-08-18). Data as JSON: /api/errors/48dc63f799d60531. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/content/utils.ts:737

};

export function getContentPaths(
	{ srcDir, root }: Pick<AstroConfig, 'root' | 'srcDir'>,
	fs: typeof fsMod = fsMod,
	legacyCollectionsBackwardsCompat = false,
): ContentPaths {
	const pkgBase = new URL('../../', import.meta.url);
	const configStats = searchConfig(fs, srcDir);

	if (!configStats.exists) {
		const legacyConfigStats = searchLegacyConfig(fs, srcDir);
		if (legacyConfigStats.exists) {
			if (!legacyCollectionsBackwardsCompat) {
				const relativePath = path.relative(
					fileURLToPath(root),
					fileURLToPath(legacyConfigStats.url),
				);
				throw new AstroError({
					...AstroErrorData.LegacyContentConfigError,
					message: AstroErrorData.LegacyContentConfigError.message(relativePath),
				});
			}
			// Use legacy config path when backwards compat is enabled
			return getContentPathsWithConfig(root, srcDir, pkgBase, legacyConfigStats, fs);
		}
	}

	const liveConfigStats = searchLiveConfig(fs, srcDir);
	return {
		root: new URL('./', root),
		contentDir: new URL('./content/', srcDir),
		assetsDir: new URL('./assets/', srcDir),
		typesTemplate: new URL('templates/content/types.d.ts', pkgBase),
		virtualModTemplate: new URL('templates/content/module.mjs', pkgBase),
		config: configStats,
		liveConfig: liveConfigStats,

View on GitHub (pinned to e294953aa8)