n8n-io/n8n · error · TypeORMError

Cannot update entity because entity id is not set in the ent

Error message

Cannot update entity because entity id is not set in the entity.

What it means

ReturningResultsEntityUpdator.update throws TypeORMError when, in the no-returning-statement driver path, it needs to re-select updated rows by id but metadata.getEntityIdMap(entity) returns null/undefined — i.e. the entity object passed to .update() lacks the values for its primary column(s). This path is taken by drivers that do not support RETURNING/OUTPUT (older SQLite builds, some MySQL).

Source

Thrown at packages/@n8n/typeorm/src/query-builder/ReturningResultsEntityUpdator.ts:54

					const result = Array.isArray(updateResult.raw)
						? updateResult.raw[entityIndex]
						: updateResult.raw;
					const returningColumns = this.queryRunner.connection.driver.createGeneratedMap(
						metadata,
						result,
					);
					if (returningColumns) {
						this.queryRunner.manager.merge(metadata.target as any, entity, returningColumns);
						updateResult.generatedMaps.push(returningColumns);
					}
				} else {
					// for driver which do not support returning/output statement we need to perform separate query and load what we need
					const updationColumns = this.expressionMap.extraReturningColumns;
					if (updationColumns.length > 0) {
						// get entity id by which we will get needed data
						const entityId = this.expressionMap.mainAlias!.metadata.getEntityIdMap(entity);
						if (!entityId)
							throw new TypeORMError(
								`Cannot update entity because entity id is not set in the entity.`,
							);

						// execute query to get needed data
						const loadedReturningColumns = (await this.queryRunner.manager
							.createQueryBuilder()
							.select(
								metadata.primaryColumns.map(
									(column) => metadata.targetName + '.' + column.propertyPath,
								),
							)
							.addSelect(
								updationColumns.map((column) => metadata.targetName + '.' + column.propertyPath),
							)
							.from(metadata.target, metadata.targetName)
							.where(entityId)
							.withDeleted()
							.setOption('create-pojo') // use POJO because created object can contain default values, e.g. property = null and those properties might be overridden by merge process

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Ensure the entity passed carries its primary-key value(s) before .update().
  2. Prefer the criteria form manager.update(id, partial) over the entity form so the id is explicit.
  3. If running on SQLite/MySQL, gate any RETURNING-dependent logic and re-select using an explicit id you control.

Example fix

// before
await manager.update(User, { name: 'x' }, { name: 'y' }); // {name:'x'} has no PK on sqlite
// after
await manager.update(User, userId, { name: 'y' });
Defensive patterns

Strategy: validation

Validate before calling

import { DataSource } from 'typeorm';

function ensureHasId(ds: DataSource, target: Function, entity: Record<string, unknown>) {
  const idMap = ds.getMetadata(target).getEntityIdMap(entity);
  if (!idMap) throw new Error('entity is missing primary-key value(s) for re-select after update');
}

Type guard

function hasEntityId(ds: DataSource, target: Function, entity: unknown): boolean {
  return !!ds.getMetadata(target).getEntityIdMap(entity as Record<string, unknown>);
}

Prevention

When it happens

Trigger: Calling manager.update(target, entityWithoutId, ...) or qb.update().set(...).whereEntity(partialWithNoPk) on a driver without RETURNING; the entity was constructed from a partial DTO that omitted the id.

Common situations: Switching the test harness from Postgres (RETURNING supported) to SQLite and revealing partial entities; frontend sending a PATCH body without id that the backend forwards straight to .update.

Related errors


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