n8n-io/n8n · error · TypeORMError
Cannot create relation id map for a single value because rel
Error message
Cannot create relation id map for a single value because relation contains multiple referenced columns.
What it means
Thrown by RelationMetadata.ensureRelationIdMap when a scalar id is passed for a relation whose join columns reference more than one column (composite foreign key). The method picks joinColumns (owning side) or inverseRelation.joinColumns, derives referencedColumns, and refuses when that array length exceeds 1 because a single value cannot populate composite FK columns.
Source
Thrown at packages/@n8n/typeorm/src/metadata/RelationMetadata.ts:378
// console.log("entity", entity);
// console.log("referencedColumns", referencedColumns);
return EntityMetadata.getValueMap(entity, referencedColumns);
}
/**
* Ensures that given object is an entity id map.
* If given id is an object then it means its already id map.
* If given id isn't an object then it means its a value of the id column
* and it creates a new id map with this value and name of the primary column.
*/
ensureRelationIdMap(id: any): ObjectLiteral {
if (ObjectUtils.isObject(id)) return id;
const joinColumns = this.isOwning ? this.joinColumns : this.inverseRelation!.joinColumns;
const referencedColumns = joinColumns.map((joinColumn) => joinColumn.referencedColumn!);
if (referencedColumns.length > 1)
throw new TypeORMError(
`Cannot create relation id map for a single value because relation contains multiple referenced columns.`,
);
return referencedColumns[0].createValueMap(id);
}
/**
* Extracts column value from the given entity.
* If column is in embedded (or recursive embedded) it extracts its value from there.
*/
getEntityValue(
entity: ObjectLiteral,
getLazyRelationsPromiseValue: boolean = false,
): any | undefined {
if (entity === null || entity === undefined) return undefined;
// extract column value from embeddeds of entity if column is in embedded
if (this.embeddedMetadata) {
// example: post[data][information][counters].id where "data", "information" and "counters" are embeddedsView on GitHub (pinned to 5ac6606e81)
Solutions
- Pass an object id-map keyed by the referenced column names instead of a scalar.
- If the relation truly has a single FK, audit @JoinColumn arrays and ensure only one join column is declared.
- Wrap relation lookups behind a helper that always emits the composite id-map shape.
Example fix
// before
await postRepo.createQueryBuilder().relation('category').of(5).loadOne();
// where category uses composite FK
// after
await postRepo
.createQueryBuilder()
.relation('category')
.of({ tenantId: 1, id: 5 })
.loadOne(); Defensive patterns
Strategy: type-guard
Validate before calling
function relationHasCompositeFk(
metadata: EntityMetadata,
relationPath: string,
): boolean {
const rel = metadata.relations.find((r) => r.propertyPath === relationPath);
if (!rel) return false;
const joinCols = rel.isOwning ? rel.joinColumns : rel.inverseRelation?.joinColumns ?? [];
return joinCols.length > 1;
}
// pass an object id when true
const idShape = relationHasCompositeFk(meta, 'category')
? { tenantId, id }
: categoryId; Type guard
type ScalarId = string | number;
type IdMap = Record<string, unknown>;
function isScalarId(id: unknown): id is ScalarId {
return typeof id === 'string' || typeof id === 'number';
}
function isCompositeFk(relation: RelationMetadata): boolean {
const cols = relation.isOwning
? relation.joinColumns
: relation.inverseRelation?.joinColumns ?? [];
return cols.length > 1;
} Try / catch
try {
await postRepo.createQueryBuilder().relation('category').of(id).loadOne();
} catch (err) {
if (err.message.includes('multiple referenced columns')) {
// re-issue with an id-map object
}
throw err;
} Prevention
- Centralize relation-id construction in a helper that inspects joinColumns.length.
- Document composite-FK relations explicitly so callers know an object id is required.
- Add a unit test asserting the join-column count for each composite-FK relation.
When it happens
Trigger: Calling a relation-id helper or repository method that funnels a raw scalar through ensureRelationIdMap on a relation backed by a composite FK (e.g. @ManyToOne joining on two columns).
Common situations: Multi-tenant schemas where relations use composite keys; converting a single-column FK to a composite one without updating callers that pass scalar ids.
Related errors
- Cannot use given entity id "${id}" because "${metadata.targe
- ${toolName} was registered without a runSubAgent callback, a
- No cancellation handler is available for "${request.subAgent
- No monitors available
- Failed to parse agent steps
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/42f1caaf8b67a7de.
Report an issue: GitHub.