withastro/astro · error · Error

ID must be a non-empty string

Error message

ID must be a non-empty string

What it means

Thrown as a plain Error (no AstroError code) from the scoped store's set() method when the provided id (key) is falsy (undefined, null, '', 0, false). Every content entry must have a non-empty string identifier, so a missing id is treated as a loader bug.

Source

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

			// If there are pending writes, we need to write again to ensure we flush the latest data.
			if (this.#pending.has(fileKey)) {
				this.#pending.delete(fileKey);
				// Call ourself recursively to write the file again
				await this.#writeFileAtomic(filePath, data, depth + 1);
			}
		}
	}

	scopedStore(collectionName: string): DataStore {
		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 }) => {
				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);
				// Check for image imports in the data. These will have been prefixed during schema parsing
				forEach(data, (_, val) => {
					if (typeof val === 'string' && val.startsWith(IMAGE_IMPORT_PREFIX)) {
						const src = val.replace(IMAGE_IMPORT_PREFIX, '');
						foundAssets.add(src);
					}
				});

				const entry: DataEntry = {

View on GitHub (pinned to d081033d5f)

Solutions

  1. In your loader, validate the id before calling store.set: if (!id) throw ... or skip the entry.
  2. Ensure generateId (glob loader) always returns a non-empty string.
  3. Inspect the source data for rows/objects lacking an id and fix or filter them.
  4. Default to a derived slug when the field is absent.

Example fix

// before - custom loader
for (const row of rows) {
  context.store.set({ id: row.key, data: row });
}
// after
for (const row of rows) {
  const id = row.key ?? slugify(row.title);
  if (!id) continue;
  context.store.set({ id, data: row });
}
Defensive patterns

Strategy: validation

Validate before calling

function setEntry(store: DataStore, id: unknown, data: Record<string, unknown>) {
  if (typeof id !== 'string' || id.length === 0) throw new TypeError('id must be non-empty string');
  store.set({ id, data });
}

Type guard

const isNonEmptyString = (v: unknown): v is string => typeof v === 'string' && v.length > 0;

Prevention

When it happens

Trigger: A collection loader calls context.store.set({ id: someValue, data }) where someValue is undefined/empty — e.g. deriving id from a missing field, or passing an object whose id property is absent.

Common situations: Custom loader reading a data file whose rows are missing the id field; slug/id generation returning empty for edge-case filenames; glob loader generateId returning ''; conditional id logic that yields undefined.

Related errors


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