withastro/astro · error · AstroError

ExpectedImageOptions

ExpectedImageOptions

Error message

**${collection}** entry is missing an ID.
Invalid data type: ${typeof data}

What it means

Thrown by the content layer's internal simpleLoader when a collection loader returns data that is neither an array nor an object after zod validation. The message reports the invalid typeof. NOTE: the implementation spreads AstroErrorData.ExpectedImageOptions (so the error name/title read 'ExpectedImageOptions' / 'Expected image options.') but constructs the message via ContentLoaderInvalidDataError.message — a mismatch bug; the intended error data is ContentLoaderInvalidDataError. In practice this branch is defensive/unreachable because loaderReturnSchema already restricts data to array|object.

Source

Thrown at packages/astro/src/content/content-layer.ts:534

		return;
	}
	if (typeof data === 'object') {
		for (const [id, raw] of Object.entries(data)) {
			if (raw.id && raw.id !== id) {
				throw new AstroError({
					...AstroErrorData.ContentLoaderInvalidDataError,
					message: AstroErrorData.ContentLoaderInvalidDataError.message(
						context.collection,
						`Object key ${JSON.stringify(id)} does not match ID ${JSON.stringify(raw.id)}`,
					),
				});
			}
			const item = await context.parseData({ id, data: raw });
			context.store.set({ id, data: item });
		}
		return;
	}
	throw new AstroError({
		...AstroErrorData.ExpectedImageOptions,
		message: AstroErrorData.ContentLoaderInvalidDataError.message(
			context.collection,
			`Invalid data type: ${typeof data}`,
		),
	});
}

View on GitHub (pinned to d081033d5f)

Solutions

  1. Ensure your custom loader's handler returns either an array of {id} objects or a Record<string, object> — never a primitive.
  2. If you maintain the loaderReturnSchema, verify it rejects non-array/non-object input so this branch never executes.
  3. File an Astro issue: the thrown error should spread ContentLoaderInvalidDataError, not ExpectedImageOptions (mismatched name/title vs message).

Example fix

// before - loader returns a primitive
export function myLoader(): CollectionLoader<string> {
  return () => fs.readFileSync('data.txt', 'utf-8');
}
// after - return an array of {id} entries
export function myLoader(): CollectionLoader<{id: string; content: string}> {
  return () => [{ id: 'data', content: fs.readFileSync('data.txt', 'utf-8') }];
}
Defensive patterns

Strategy: validation

Validate before calling

// In a custom loader, validate the return shape before yielding
function assertLoaderResult(value: unknown): asserts value is Array<{id:string}> | Record<string, any> {
  if (Array.isArray(value)) {
    for (const v of value) if (typeof v?.id !== 'string' && typeof v?.id !== 'number') throw new TypeError('entry missing id');
  } else if (value && typeof value === 'object') {
    // ok
  } else {
    throw new TypeError(`loader must return array or object, got ${typeof value}`);
  }
}

Type guard

function isLoaderData(v: unknown): v is Array<{id:string}> | Record<string, Record<string, unknown>> {
  return Array.isArray(v) || (typeof v === 'object' && v !== null);
}

Prevention

When it happens

Trigger: A custom collection loader (CollectionLoader<TData>) passed to the content layer returns a primitive (string, number, boolean) or null/undefined that somehow bypasses loaderReturnSchema, reaching the final fallback throw in simpleLoader().

Common situations: Authoring a custom loader whose handler() returns a non-array/non-object value; corrupt or unexpectedly-typed data from an upstream source the loader wraps; future schema changes that widen accepted types without updating the dispatch branches.

Related errors


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