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
- Wrap the import in an options object: `getImage({ src: hero, width: 800, height: 600 })`.
- In MDX, prefer passing the import to `<Image src={hero} ... />` where the component accepts metadata directly.
- 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
- Standardize on getImage({ src: imported }) in code review checklists.
- In MDX, prefer component syntax over script-block getImage calls.
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
- ExpectedImage
- ExpectedImageOptions
- IncompatibleDescriptorOptions
- LocalImageUsedWrongly
- RemoteImageNotAllowed
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)