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
- Guard in your loader: skip, log, or throw a descriptive error when `!raw.id` before calling `store.set`.
- Derive an id from another field (slugified title, hash, index) when the source id is blank.
- 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
- Validate ids at ingest in custom loaders; never pass source data through unguarded.
- Numeric ids are fine (they are stringified) — only empty/missing ids throw.
- Log skipped rows so silent data loss stays visible.
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
- File path must be relative to the site root. Got
- [content] Could not read the chunked data store at
- ContentLoaderInvalidDataError
- ContentLoaderReturnsInvalidId
- data store cleared (force)
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)