withastro/astro · error · AstroError

ImageNotFound

ImageNotFound

Error message

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

What it means

During build, Astro's assets plugin resolves imported images and calls `emitImageMetadata(id)` to read their dimensions. If that returns nothing — almost always because the file does not exist on disk — the plugin throws ImageNotFound ('Could not find requested image `${id}`') instead of letting Vite produce a confusing resolution error further down.

Solutions

  1. Check the exact `id` in the error message against the filesystem: does the file exist at that path with that exact casing
  2. Fix the import path or restore/rename the file so they match (mind case sensitivity for CI on Linux)
  3. If the import is generated (content collections, glob), regenerate the references or make the generator skip missing files

Example fix

// before
import hero from '../images/Hero.png'; // file is hero.png

// after
import hero from '../images/hero.png';
Defensive patterns

Strategy: validation

Validate before calling

// verify every imported image exists before build (case-sensitive!)
import { existsSync } from 'node:fs';
import { join, dirname } from 'node:path';

function assertImportExists(importer: string, spec: string): void {
  if (spec.startsWith('.')) {
    const target = join(dirname(importer), spec);
    if (!existsSync(target)) {
      throw new Error(`Missing image: ${spec} imported from ${importer}`);
    }
  }
}

Try / catch

try {
  const meta = await emitImageMetadata(id);
  if (!meta) throw Object.assign(new Error(`Image not found: ${id}`), { code: 'ImageNotFound' });
} catch (err) {
  if ((err as { code?: string }).code === 'ImageNotFound') {
    // fail fast with the exact path so CI output is actionable
    process.exitCode = 1;
  }
  throw err;
}

Prevention

When it happens

Trigger: `import hero from '../images/hero.png'` where the file was deleted, renamed, or the relative path is wrong; casing mismatch on case-sensitive CI filesystems (Hero.png vs hero.png) that works locally on macOS/Windows; imports generated from content collections referencing removed files.

Common situations: Renaming assets in a PR while another branch still imports old names; git not tracking empty directories or lfs-filtered files after fresh clones; Windows-local works but Linux CI fails on path case; scaffolding templates referencing optional images.

Related errors


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

Appendix: source

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

					if (id !== removeQueryString(id)) {
						// If our import has any query params, we'll let Vite handle it, nonetheless we'll make sure to not delete it
						// See https://github.com/withastro/astro/issues/8333
						globalThis.astroAsset.referencedImages.add(removeQueryString(id));
						return;
					}

					// If the requested ID doesn't end with a valid image extension, we'll let Vite handle it
					if (!assetRegexEnds.test(id)) {
						return;
					}

					const fileEmitter = shouldEmitFile
						? (opts: Parameters<typeof this.emitFile>[0]) => emitClientAsset(this as any, opts)
						: undefined;
					const imageMetadata = await emitImageMetadata(id, fileEmitter);

					if (!imageMetadata) {
						throw new AstroError({
							...AstroErrorData.ImageNotFound,
							message: AstroErrorData.ImageNotFound.message(id),
						});
					}

					// We can only reliably determine if an image is used on the server, as we need to track its usage throughout the entire build.
					// Since you cannot use image optimization on the client anyway, it's safe to assume that if the user imported
					// an image on the client, it should be present in the final build.
					if (isAstroServerEnvironment(this.environment)) {
						// For SVGs imported directly (not via content collections), create a full
						// component that can be rendered inline. For content collection SVGs, the
						// component is reconstructed later in content/runtime.ts from __svgData
						// embedded in the metadata, avoiding a server-runtime import here that
						// would create a circular dependency when combined with TLA.
						if (id.endsWith('.svg')) {
							const contents = await fs.promises.readFile(imageMetadata.fsPath, {
								encoding: 'utf8',
							});

View on GitHub (pinned to e294953aa8)