n8n-io/n8n · error · TypeORMError

Relation ${entityMetadata.name}#${relation.propertyName} and

Error message

Relation ${entityMetadata.name}#${relation.propertyName} and ${relation.inverseRelation!.entityMetadata.name}#${relation.inverseRelation!.propertyName} both has cascade remove set. This may lead to unexpected circular removals. Please set cascade remove only from one side of relationship.

What it means

TypeORM forbids `cascade: ['remove']` (or `onDelete: 'CASCADE'` via cascade) being enabled on BOTH sides of a relation, because removing one entity would cascade-remove its partner, which would cascade-remove the first again, producing unbounded circular deletion. The validator walks every relation and rejects the pairing when `isCascadeRemove` is true on both sides.

Source

Thrown at packages/@n8n/typeorm/src/metadata-builder/EntityMetadataValidator.ts:235

			// todo: if there is a relation, and inverse side is specified only on one side, shall we give error
			// todo: with message like: "Inverse side is specified only on one side of the relationship. Specify on other side too to prevent confusion".
			// todo: add validation if there two entities with the same target, and show error message with description of the problem (maybe file was renamed/moved but left in output directory)
			// todo: check if there are multiple columns on the same column applied.
			// todo: check column type if is missing in relational databases (throw new TypeORMError(`Column type of ${type} cannot be determined.`);)
			// todo: include driver-specific checks. for example in mongodb empty prefixes are not allowed
			// todo: if multiple columns with same name - throw exception, including cases when columns are in embeds with same prefixes or without prefix at all
			// todo: if multiple primary key used, at least one of them must be unique or @Index decorator must be set on entity
			// todo: check if entity with duplicate names, some decorators exist
		});

		// make sure cascade remove is not set for both sides of relationships (can be set in OneToOne decorators)
		entityMetadata.relations.forEach((relation) => {
			const isCircularCascadeRemove =
				relation.isCascadeRemove &&
				relation.inverseRelation &&
				relation.inverseRelation!.isCascadeRemove;
			if (isCircularCascadeRemove)
				throw new TypeORMError(
					`Relation ${entityMetadata.name}#${
						relation.propertyName
					} and ${relation.inverseRelation!.entityMetadata.name}#${
						relation.inverseRelation!.propertyName
					} both has cascade remove set. ` +
						`This may lead to unexpected circular removals. Please set cascade remove only from one side of relationship.`,
				);
		}); // todo: maybe better just deny removal from one to one relation without join column?

		entityMetadata.eagerRelations.forEach((relation) => {});
	}

	/**
	 * Validates dependencies of the entity metadatas.
	 */
	protected validateDependencies(entityMetadatas: EntityMetadata[]) {
		const graph = new DepGraph();
		entityMetadatas.forEach((entityMetadata) => {

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Remove `cascade: ['remove']` (or `cascade: true`) from exactly one side of the relation; keep it only on the owning/parent side.
  2. If both ends genuinely must clean up, drive removal from application code (explicit `repository.remove`) instead of DB cascade.
  3. Prefer `onDelete: 'SET NULL'` on the FK side instead of cascade remove to avoid the cycle.

Example fix

// before — both sides cascade
@Entity() class Profile { @OneToOne(() => User, u => u.profile, { cascade: ['remove'] }) user: User; }
@Entity() class User { @OneToOne(() => Profile, p => p.user, { cascade: ['remove'] }) profile: Profile; }

// after — only the owning side cascades
@Entity() class Profile { @OneToOne(() => User, u => u.profile) user: User; }
@Entity() class User { @OneToOne(() => Profile, p => p.user, { cascade: ['remove'] }) profile: Profile; }
Defensive patterns

Strategy: validation

Validate before calling

// After building metadata, assert no relation has both sides cascading remove
for (const meta of dataSource.entityMetadatas) {
  for (const r of meta.relations) {
    if (r.isCascadeRemove && r.inverseRelation?.isCascadeRemove) {
      throw new Error(`Double cascade-remove on ${meta.name}#${r.propertyName} <-> ${r.inverseRelation.entityMetadata.name}#${r.inverseRelation.propertyName}`);
    }
  }
}

Try / catch

try { await dataSource.initialize(); } catch (e) { if (e instanceof TypeORMError && /both has cascade remove set/) { /* locate the OneToOne pair named in the message */ } throw e; }

Prevention

When it happens

Trigger: A bidirectional @OneToOne where both sides declare `cascade: ['remove']` (or one side uses `cascade: ['remove']` and the inverse uses `onDelete='CASCADE'` that TypeORM maps to isCascadeRemove). Two @ManyToOne/@OneToMany entities each marking remove cascade.

Common situations: Copy-pasting cascade config from one side to the inverse side. Adding `cascade: true` (which includes remove) on both ends of a 1:1 user<->profile link.

Related errors


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