withastro/astro · error · AstroError

ImageNotFound

ImageNotFound

Error message

Could not find requested image `${imagePath}`. Does it exist?

What it means

Thrown by the content assets Vite plugin when a content collection image import cannot be resolved by Vite's module resolver. The plugin's `resolveId` hook intercepts IDs flagged with `CONTENT_IMAGE_FLAG`, strips the flag, and delegates to `this.resolve()`. If resolution returns null (file does not exist or path is wrong), the error is thrown with the base image path.

Source

Thrown at packages/astro/src/content/vite-plugin-content-assets.ts:57

		name: 'astro:content-asset-propagation',
		enforce: 'pre',
		resolveId: {
			filter: {
				id: new RegExp(`(?:\\?|&)(?:${CONTENT_IMAGE_FLAG}|${CONTENT_RENDER_FLAG})(?:&|=|$)`),
			},
			async handler(id, importer, opts) {
				if (hasContentFlag(id, CONTENT_IMAGE_FLAG)) {
					const [base, query] = id.split('?');
					const params = new URLSearchParams(query);
					const importerParam = params.get('importer');

					const importerPath = importerParam
						? fileURLToPath(new URL(importerParam, settings.config.root))
						: importer;

					const resolved = await this.resolve(base, importerPath, { skipSelf: true, ...opts });
					if (!resolved) {
						throw new AstroError({
							...AstroErrorData.ImageNotFound,
							message: AstroErrorData.ImageNotFound.message(base),
						});
					}
					// Preserve the content image flag in the resolved ID so that downstream plugins
					// (e.g. astro:assets:esm) can detect content collection images and avoid creating
					// full SVG components, which would import from the server runtime and cause a
					// circular module dependency deadlock when combined with top-level await (TLA).
					resolved.id = `${resolved.id}?${CONTENT_IMAGE_FLAG}`;
					return resolved;
				}
				if (hasContentFlag(id, CONTENT_RENDER_FLAG)) {
					const base = id.split('?')[0];

					for (const { extensions, handlePropagation = true } of settings.contentEntryTypes) {
						if (handlePropagation && extensions.includes(extname(base))) {
							return this.resolve(`${base}?${PROPAGATED_ASSET_FLAG}`, importer, {
								skipSelf: true,

View on GitHub (pinned to d081033d5f)

Solutions

  1. Verify the image path in the error message exists on disk, exactly as spelled (case-sensitive).
  2. Ensure the path is relative to the content entry file, not the project root.
  3. If using an alias like `@assets/`, confirm it is mapped in `tsconfig.json` compilerOptions.paths.
  4. Check `git status` to confirm the image is committed if the error occurs in CI.
  5. Add the correct file extension (.png, .jpg, .webp, .svg).

Example fix

<!-- before -->
![alt](../asets/logo.png)

<!-- after -->
![alt](../assets/logo.png)
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync } from 'fs';
import { resolve, dirname } from 'path';
function imageExists(fromFilePath: string, imagePath: string): boolean {
  const resolved = resolve(dirname(fromFilePath), imagePath);
  return existsSync(resolved);
}

Prevention

When it happens

Trigger: A content entry (Markdown/MDX) references an image via a relative path or `@assets/` alias that does not resolve to an actual file. The `CONTENT_IMAGE_FLAG` query param triggers the handler, `this.resolve(base, importerPath)` returns null, and `ImageNotFound` is thrown.

Common situations: Typo in the image filename or extension. Image was deleted or not yet committed. Relative path is wrong relative to the entry file's location. Using an alias that isn't configured in `tsconfig.json` paths or Vite config. Case-sensitivity mismatch on Linux CI vs. macOS dev.

Related errors


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/9f8c55dd09880438. Report an issue: GitHub.