facebook/relay · error · Error
NormalizationEngine: Expected id of elements of field `${sto
Error message
NormalizationEngine: Expected id of elements of field `${storageKey}` to be strings. What it means
Relay resolves the streamed item's ID in order: a custom getDataID(options), the ID recorded previously at that list index, or a generated client ID. All three paths failed to produce a string — typically because a custom getDataID returned a non-string (e.g. a number or undefined) for this item, so Relay cannot key the record in the store. Relay requires all record IDs to be strings.
Source
Thrown at packages/relay-runtime/store/NormalizationEngine.js:538
const typeName = field.concreteType ?? (data as $FlowFixMe).__typename;
if (typeof typeName !== 'string') {
throw err(
'NormalizationEngine: Expected @stream field `' +
field.name +
'` to have a __typename.',
);
}
const getDataID = this._options.getDataID;
const itemID =
(typeof getDataID === 'function'
? getDataID(data as $FlowFixMe, typeName)
: null) ??
prevIDs?.[itemIndex] ??
generateClientID(parentID, storageKey, itemIndex);
if (typeof itemID !== 'string') {
throw err(
'NormalizationEngine: Expected id of elements of field `' +
storageKey +
'` to be strings.',
);
}
const selector = createNormalizationSelector(field, itemID, variables);
const nextParentRecord = RelayModernRecord.clone(parentRecord);
const nextIDs = [...prevIDs];
nextIDs[itemIndex] = itemID;
RelayModernRecord.setLinkedRecordIDs(nextParentRecord, storageKey, nextIDs);
this._parentRecords.set(parentID, {
fieldPayloads,
record: nextParentRecord,
});
const relayPayload = this._normalizeResponse(View on GitHub (pinned to 668b1b85e0)
Solutions
- Make your custom getDataID always return a string, coercing numbers with String(id)
- Ensure every streamed item actually contains the field your getDataID reads
- Return null (not a non-string) from getDataID when the id can't be derived so Relay falls back to prevIDs/client IDs
- Log getDataID inputs during normalization to find which items yield non-string ids
Example fix
// before const getDataID = (data) => data.id; // after const getDataID = (data) => (data.id != null ? String(data.id) : null);
Defensive patterns
Strategy: type-guard
Validate before calling
const data = payload.data as { id?: unknown };
if (typeof getDataID?.(data, typeName) !== 'string') {
console.warn('getDataID returned non-string for streamed item', data);
} Type guard
function hasStringId(d: unknown): d is { id: string } {
return typeof (d as any)?.id === 'string' && (d as any).id.length > 0;
} Prevention
- Coerce ids to string inside getDataID
- Return null from getDataID when id is unavailable so Relay falls back
- Cover union/interface list items in tests where id shape varies
When it happens
Trigger: Configuring the store with getDataID that returns a non-string (numeric ids, undefined when a field is missing) for items of the streamed field `storageKey`, causing `typeof itemID !== 'string'`.
Common situations: Apps with numeric (Int) database ids that implemented getDataID without String() conversion, ids nested under a different key than getDataID expects, or items where the id field is absent on some union members.
Related errors
- NormalizationEngine: Expected record `${parentID}` to have f
- Did not find item with data id ${__id} in the store.
- NormalizationEngine: Expected incremental response to have `
- NormalizationEngine: Expected @stream to be used on a plural
- NormalizationEngine: Expected the GraphQL @stream payload `d
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/a738dd38626aef35.
Report an issue: GitHub.