n8n-io/n8n · error · TypeORMError

Set operation is only supported for many-to-one and one-to-o

Error message

Set operation is only supported for many-to-one and one-to-one relations. However given "${relation.propertyPath}" has ${relation.relationType} relation. Use .add() method instead.

What it means

RelationQueryBuilder.set supports only many-to-one and one-to-one (the single-valued, FK-on-this-side) relations. If relation.isManyToMany || relation.isOneToMany it throws TypeORMError pointing the developer at .add() instead, because .set would have to overwrite a whole collection which the FK-update strategy it uses cannot express.

Source

Thrown at packages/@n8n/typeorm/src/query-builder/RelationQueryBuilder.ts:55

	}

	/**
	 * Sets entity relation's value.
	 * Value can be entity, entity id or entity id map (if entity has composite ids).
	 * Works only for many-to-one and one-to-one relations.
	 * For many-to-many and one-to-many relations use #add and #remove methods instead.
	 */
	async set(value: any): Promise<void> {
		const relation = this.expressionMap.relationMetadata;

		if (!this.expressionMap.of)
			// todo: move this check before relation query builder creation?
			throw new TypeORMError(
				`Entity whose relation needs to be set is not set. Use .of method to define whose relation you want to set.`,
			);

		if (relation.isManyToMany || relation.isOneToMany)
			throw new TypeORMError(
				`Set operation is only supported for many-to-one and one-to-one relations. ` +
					`However given "${relation.propertyPath}" has ${relation.relationType} relation. ` +
					`Use .add() method instead.`,
			);

		// if there are multiple join columns then user must send id map as "value" argument. check if he really did it
		if (
			relation.joinColumns &&
			relation.joinColumns.length > 1 &&
			(!ObjectUtils.isObject(value) || Object.keys(value).length < relation.joinColumns.length)
		)
			throw new TypeORMError(
				`Value to be set into the relation must be a map of relation ids, for example: .set({ firstName: "...", lastName: "..." })`,
			);

		const updater = new RelationUpdater(this, this.expressionMap);
		return updater.update(value);
	}

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Switch to .add()/.remove() for collection relations: qb.relation(User,'groups').of(u).add([g1,g2]).
  2. Double-check the decorator type on the property named in relation.propertyPath and confirm the relation you intended.
  3. If you truly need to replace the whole collection, fetch current members and .remove() the difference, then .add() new ones inside a transaction.

Example fix

// before
await qb.relation(User,'groups').of(u).set([g1, g2]); // groups is many-to-many
// after
await qb.relation(User,'groups').of(u).add([g1, g2]);
Defensive patterns

Strategy: type-guard

Validate before calling

import { DataSource } from 'typeorm';

function isSingleValuedRelation(ds: DataSource, target: Function, path: string): boolean {
  const r = ds.getMetadata(target).findRelationWithPropertyPath(path);
  return !!r && (r.relationType === 'many-to-one' || r.relationType === 'one-to-one');
}
// if (!isSingleValuedRelation(ds, User, 'profile')) throw ...

Type guard

function supportsSet(ds: DataSource, target: Function, path: string): boolean {
  const r = ds.getMetadata(target).findRelationWithPropertyPath(path);
  return !!r && (r.isManyToOne || r.isOneToOne);
}

Prevention

When it happens

Trigger: Calling .set(newValue) on a @OneToMany or @ManyToMany property, e.g. qb.relation(User,'groups').of(u).set([g1,g2]). The owning side is a collection, so a single assignment is ambiguous.

Common situations: Misreading the decorator on the entity; inverse-side confusion (developer thinks the relation is mto but it is otm); copy-pasting a .set() call from a profile relation onto a tags relation.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/27931000b9ab87e3. Report an issue: GitHub.