withastro/astro · error · AstroError

ExpectedNotESMImage

ExpectedNotESMImage

Error message

An ESM-imported image cannot be passed directly to `getImage()`. Instead, pass an object with the image in the `src` property.

What it means

The ESM `import img from './img.png'` syntax produces an ImageMetadata object (with src/width/height/format). `getImage()` detects when that metadata object itself is passed as the whole options argument (`isImageMetadata(options)`, packages/astro/src/assets/internal.ts:70) and throws `ExpectedNotESMImage` — the image must be nested under `src`.

Solutions

  1. Wrap the import in an options object: `getImage({ src: hero, width: 800, height: 600 })`.
  2. In MDX, prefer passing the import to `<Image src={hero} ... />` where the component accepts metadata directly.
  3. If a function returns an image, `resolveSrc` inside `getImage` awaits it for you — just put it under `src`.

Example fix

// before
import hero from './hero.png';
const img = await getImage(hero);

// after
import hero from './hero.png';
const img = await getImage({ src: hero, width: 800, height: 600 });
Defensive patterns

Strategy: type-guard

Validate before calling

import type { ImageMetadata } from 'astro';

function isImageMetadata(value: unknown): value is ImageMetadata {
  return (
    typeof value === 'object' && value !== null &&
    'src' in value && 'width' in value && 'height' in value && 'format' in value
  );
}

// reject before calling: metadata must be nested under src
if (isImageMetadata(options)) {
  throw new TypeError('Pass the imported image as { src: image }, not as the options object');
}

Type guard

function isImageMetadata(value: unknown): value is ImageMetadata {
  return (
    typeof value === 'object' && value !== null &&
    typeof (value as any).src === 'string' &&
    typeof (value as any).width === 'number' &&
    typeof (value as any).height === 'number'
  );
}

Prevention

When it happens

Trigger: `import hero from './hero.png'` followed by `getImage(hero)` instead of `getImage({ src: hero, ... })`; common when porting `<img src={hero.src}>`-style code or writing MDX where inlined imports return metadata objects.

Common situations: MDX files where an import expression resolves to the image metadata; refactors between `<img>` and `<Image>`; copying snippets that use `hero` directly as an attribute value.

Related errors


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

Appendix: source

Thrown at packages/astro/src/assets/internal.ts:72

	if (!options || typeof options !== 'object') {
		throw new AstroError({
			...AstroErrorData.ExpectedImageOptions,
			message: AstroErrorData.ExpectedImageOptions.message(JSON.stringify(options)),
		});
	}
	if (typeof options.src === 'undefined') {
		throw new AstroError({
			...AstroErrorData.ExpectedImage,
			message: AstroErrorData.ExpectedImage.message(
				options.src,
				'undefined',
				JSON.stringify(options),
			),
		});
	}

	if (isImageMetadata(options)) {
		throw new AstroError(AstroErrorData.ExpectedNotESMImage);
	}

	const service = await getConfiguredImageService();

	// If the user inlined an import, something fairly common especially in MDX, or passed a function that returns an Image, await it for them
	const resolvedOptions: ImageTransform = {
		...options,
		src: await resolveSrc(options.src),
	};

	let originalWidth: number | undefined;
	let originalHeight: number | undefined;

	// Infer size for remote images if inferSize is true
	if (resolvedOptions.inferSize) {
		delete resolvedOptions.inferSize; // Delete so it doesn't end up in the attributes

		if (isRemoteImage(resolvedOptions.src) && isRemotePath(resolvedOptions.src)) {

View on GitHub (pinned to e294953aa8)