withastro/astro · error · AstroUserError

A content collection is defined with legacy features (e.g…

Error message

A content collection is defined with legacy features (e.g. missing a `loader` or has a `type`). Check your collection definitions in ${importerFilename ?? 'your content config file'} to ensure that all collections are defined using the current properties.

What it means

In the content-layer API, a collection with a `loader` is a content-layer collection and must not carry a legacy `type`. `defineCollection` throws this AstroUserError when `config.type` is present and is anything other than `'content_layer'`, catching half-migrated configs that combine the legacy (`type: 'content' | 'data'`) and loader-based APIs.

Solutions

  1. Delete the `type` property — it is inferred (`content_layer`) whenever a loader is present
  2. Audit the collection for other legacy keys per the content-layer migration guide and remove them
  3. Follow the Astro v4 -> v5 content collections migration guide to update all collections at once

Example fix

// before
export const collections = {
  posts: defineCollection({
    type: 'content',
    loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
  }),
};

// after
export const collections = {
  posts: defineCollection({
    loader: glob({ pattern: '**/*.md', base: './src/content/posts' }),
  }),
};
Defensive patterns

Strategy: validation

Validate before calling

// normalize a migrated collection before defineCollection
function modernizeCollection(config) {
  if ('loader' in config) {
    delete config.type; // inferred as 'content_layer' when a loader is present
  }
  return config;
}
export const collections = {
  posts: defineCollection(modernizeCollection(cfg)),
};

Prevention

When it happens

Trigger: `defineCollection({ type: 'content', loader: glob({...}) })` or `{ type: 'data', loader: file(...) }` in src/content.config.ts — an old Astro 2/3-style definition that gained a loader without dropping `type`.

Common situations: Upgrading a project from Astro <=4 to Astro 5, where `type` became optional/inferred and loaders became required; following an old tutorial that includes `type` and adding a loader on top; generated config templates that mix eras.

Related errors


AI-assisted analysis of withastro/astro@157c500c38 (2026-08-18). Data as JSON: /api/errors/25f17f2ab6db4e4f. Report an issue: GitHub.

Appendix: source

Thrown at packages/astro/src/content/config.ts:195

export function defineCollection<
	S extends BaseSchema,
	TLoader extends LoaderConstraint<{ id: string }> = LoaderConstraint<{ id: string }>,
>(config: CollectionConfig<S, TLoader>): CollectionConfig<S, TLoader> {
	const importerFilename = getImporterFilename();

	if (importerFilename?.includes('live.config')) {
		throw new AstroError({
			...AstroErrorData.LiveContentConfigError,
			message: AstroErrorData.LiveContentConfigError.message(
				'Collections in a live config file must use `defineLiveCollection`.',
				importerFilename,
			),
		});
	}

	if ('loader' in config) {
		if (config.type && config.type !== CONTENT_LAYER_TYPE) {
			throw new AstroUserError(
				`A content collection is defined with legacy features (e.g. missing a \`loader\` or has a \`type\`). Check your collection definitions in ${importerFilename ?? 'your content config file'} to ensure that all collections are defined using the current properties.`,
			);
		}
		if (
			typeof config.loader === 'object' &&
			typeof config.loader.load !== 'function' &&
			('loadEntry' in config.loader || 'loadCollection' in config.loader)
		) {
			throw new AstroUserError(
				`Live content collections must be defined in "src/live.config.ts" file. Check the loaders used in "${importerFilename ?? 'your content config file'}" to ensure you are not using a live loader to define a build-time content collection.`,
			);
		}
		config.type = CONTENT_LAYER_TYPE;
	}
	if (!config.type) config.type = 'content';
	return config;
}

View on GitHub (pinned to 157c500c38)