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
- Remove `cascade: ['remove']` (or `cascade: true`) from exactly one side of the relation; keep it only on the owning/parent side.
- If both ends genuinely must clean up, drive removal from application code (explicit `repository.remove`) instead of DB cascade.
- 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
- Put `cascade` on exactly one side of a bidirectional relation and document which side owns it.
- Avoid `cascade: true` (which silently includes remove); list the operations you mean.
- Review inverse-side decorators whenever you add cascade to the owning side.
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
- OnUpdateType "${relation.onUpdate}" is not valid for ${drive
- Circular relations detected: ${path}. To resolve this issue
- Circular eager relations are disallowed. ${entityMetadata.ta
- Referenced column ${joinColumn.referencedColumnName} was not
- Referenced column ${joinColumn.referencedColumnName} was not
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/d30f5d46e747c8d7.
Report an issue: GitHub.