withastro/astro · error · Error

ID must be a non-empty string

Error message

ID must be a non-empty string

What it means

Loaders persist entries through the scoped data store's `set()`. It coerces ids to string (numeric YAML ids are fine) but rejects falsy ones outright with a plain Error — an empty or missing id would corrupt the collection keyed by ''.

Solutions

  1. Guard in your loader: skip, log, or throw a descriptive error when `!raw.id` before calling `store.set`.
  2. Derive an id from another field (slugified title, hash, index) when the source id is blank.
  3. Validate your data source once at load time instead of per-entry mid-sync so failures are actionable.

Example fix

// before
for (const raw of rows) {
  store.set({ id: raw.id, data: raw }); // raw.id can be '' or undefined
}

// after
for (const raw of rows) {
  if (!raw.id) {
    logger.warn(`Skipping row without id: ${JSON.stringify(raw)}`);
    continue;
  }
  store.set({ id: raw.id, data: raw });
}
Defensive patterns

Strategy: validation

Validate before calling

for (const raw of rows) {
  if (!raw.id || String(raw.id).trim() === '') {
    logger.warn(`Skipping entry without id: ${JSON.stringify(raw)}`);
    continue;
  }
  store.set({ id: raw.id, data: raw });
}

Type guard

const hasUsableId = (raw: unknown): raw is { id: string } =>
  typeof (raw as any)?.id === 'string' && (raw as any).id.length > 0;

if (hasUsableId(raw)) store.set({ id: raw.id, data: raw });

Prevention

When it happens

Trigger: A custom Loader calling `store.set({ id: raw.id, data })` where `raw.id` is undefined, null, 0-with-coercion... specifically '', undefined or null; e.g. API rows or object keys where the id field is blank.

Common situations: Writing a custom loader (API/CSV/headless CMS) without guarding ids; data where some rows legitimately lack ids; spreading defaults that turn missing ids into empty strings.

Related errors


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

Appendix: source

Thrown at packages/astro/src/content/mutable-data-store.ts:409

		return {
			get: <TData extends Record<string, unknown> = Record<string, unknown>>(key: string) =>
				this.get<DataEntry<TData>>(collectionName, key),
			entries: () => this.entries(collectionName),
			values: () => this.values(collectionName),
			keys: () => this.keys(collectionName),
			set: ({
				id: key,
				data,
				body,
				filePath,
				deferredRender,
				digest,
				rendered,
				assetImports,
				imageImports: incomingImageImports,
			}) => {
				if (!key) {
					throw new Error(`ID must be a non-empty string`);
				}
				const id = String(key);
				if (digest) {
					const existing = this.get<DataEntry>(collectionName, id);
					if (existing && existing.digest === digest) {
						return false;
					}
				}
				const foundAssets = new Set<string>(assetImports);
				const imageImports: (string | number)[][] = [];
				const seenImageImportPaths = new Set();
				const recordImageImport = (imagePath: (string | number)[]) => {
					const pathKey = JSON.stringify(imagePath);
					if (seenImageImportPaths.has(pathKey)) {
						return;
					}
					seenImageImportPaths.add(pathKey);
					imageImports.push(imagePath);

View on GitHub (pinned to e294953aa8)