withastro/astro · error · AstroError

ExpectedImage

ExpectedImage

Error message

Expected `src` property for `getImage` or `<Image />` to be either an ESM imported image or a string with the path of a remote image. Received `${src}` (type: `${typeofOptions}`).

Full serialized options received: `${fullOptions}`.

What it means

`verifyOptions()` runs inside the base image service for every `<Image>`/`<Picture>`/`getImage()` transform (packages/astro/src/assets/services/service.ts:148). The first check rejects a `src` that is falsy or neither an ESM-imported image (ImageMetadata) nor a remote URL string, throwing `ExpectedImage` with `typeof src` and the full serialized options in the message.

Solutions

  1. Pass either an ESM-imported image object or a full remote URL string (starting with a protocol) as `src`.
  2. Guard dynamic data before render: `if (typeof src !== 'string' && !isESMImport) return null`.
  3. Type your components' props (`src: ImageMetadata | string`) so mismatches surface at compile time.

Example fix

// before — CMS id, not a URL
<Image src={product.imageAssetId} alt={product.name} />

// after — resolve to the actual URL first
<Image src={`https://cdn.example.com/assets/${product.imageAssetId}`} alt={product.name} width={800} height={600} />
Defensive patterns

Strategy: type-guard

Validate before calling

const validSrc =
  (typeof src === 'string' && /^https?:\/\//.test(src)) || isImageMetadata(src);
if (!validSrc) throw new Error(`Invalid src for <Image>: ${JSON.stringify(src)}`);

Type guard

import type { ImageMetadata } from 'astro';

function isUsableImageSrc(value: unknown): value is string | ImageMetadata {
  if (typeof value === 'string') return value.length > 0;
  return (
    typeof value === 'object' && value !== null &&
    'src' in value && 'width' in value && 'height' in value
  );
}

Prevention

When it happens

Trigger: `src` is a number, boolean, plain object, empty string, or null — e.g. `src={123}`, `src={ someObj.src }` where the property does not exist, or a CMS field rendered without a fallback.

Common situations: Untyped CMS payloads where the image field is occasionally absent/typed differently; passing `src={image.id}` instead of `src={image.url}`; templating that yields `src="[object Object]"`-like values.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

Thrown at packages/astro/src/assets/services/service.ts:159

}

export type BaseServiceTransform = {
	src: string;
	width?: number;
	height?: number;
	format?: string;
	quality?: string | null;
	fit?: ImageFit;
	position?: string;
	background?: string;
};

const sortNumeric = (a: number, b: number) => a - b;

export function verifyOptions(options: ImageTransform): void {
	// `src` is missing or is `undefined`.
	if (!options.src || (!isRemoteImage(options.src) && !isESMImportedImage(options.src))) {
		throw new AstroError({
			...AstroErrorData.ExpectedImage,
			message: AstroErrorData.ExpectedImage.message(
				JSON.stringify(options.src),
				typeof options.src,
				JSON.stringify(options, (_, v) => (v === undefined ? null : v)),
			),
		});
	}

	if (!isESMImportedImage(options.src)) {
		// User passed an `/@fs/` path or a filesystem path instead of the full image.
		if (
			options.src.startsWith('/@fs/') ||
			(!isRemotePath(options.src) && !options.src.startsWith('/'))
		) {
			throw new AstroError({
				...AstroErrorData.LocalImageUsedWrongly,
				message: AstroErrorData.LocalImageUsedWrongly.message(options.src),

View on GitHub (pinned to e294953aa8)