packages/cli/src/modules/breaking-changes/README.md
A rule-based system for detecting breaking changes before migrating to a new n8n version.
This module scans the n8n instance (workflows, configuration, environment) to identify issues that will be affected by breaking changes in the target version.
breaking-changes/
types/
rule.types.ts # Core interfaces and enums
detection.types.ts # Report types
index.ts
rules/
v2/ # V2-specific rules
removed-nodes.rule.ts
process-env-access.rule.ts
file-access.rule.ts
...
index.ts # Side-effect imports for all rules
breaking-changes.service.ts # Detection orchestration
breaking-changes.rule-registry.service.ts # Rule management
breaking-changes.controller.ts # REST API
breaking-changes.module.ts # Module definition
Rules are registered at startup using the @BreakingChangeRule decorator from
@n8n/decorators (following the same pattern as @BackendModule / ModuleMetadata).
Each rule file is explicitly imported in rules/index.ts as a side-effect
import, which triggers the decorator and registers the rule class. Because the
decorator lives in @n8n/decorators, rules can be defined anywhere in the codebase.
Module.init()
→ import './rules' side-effect imports of all rule files
→ @BreakingChangeRule fires on each class
→ BreakingChangeRuleMetadata.register()
→ import controller
→ BreakingChangeService constructor
→ registerRules() reads from metadata, resolves via DI
→ RuleRegistry.registerAll()
GET /breaking-changes/report?version=v2
Returns:
{
"report": {
"generatedAt": "2025-10-17T00:00:00.000Z",
"targetVersion": "v2",
"currentVersion": "1.x.x",
"instanceResults": [
{
"ruleId": "process-env-access-v2",
"ruleTitle": "Process Environment Access Restrictions",
"ruleDescription": "Access to process.env is now restricted",
"ruleSeverity": "high",
"instanceIssues": [
{
"title": "Environment access detected",
"description": "Workflows using process.env may be affected",
"level": "warning"
}
],
"recommendations": [
{
"action": "Review environment variable usage",
"description": "Update workflows to use secure environment variable access"
}
]
}
],
"workflowResults": [
{
"ruleId": "removed-nodes-v2",
"ruleTitle": "Removed Deprecated Nodes",
"ruleDescription": "Several deprecated nodes have been removed",
"ruleSeverity": "critical",
"affectedWorkflows": [
{
"id": "wf-001",
"name": "Customer Onboarding",
"active": true,
"numberOfExecutions": 142,
"lastUpdatedAt": "2025-10-15T10:30:00.000Z",
"lastExecutedAt": "2025-10-16T14:22:00.000Z",
"issues": [
{
"title": "Node 'n8n-nodes-base.spontit' with name 'Spontit' has been removed",
"description": "The node type 'n8n-nodes-base.spontit' is no longer available",
"level": "error",
"nodeId": "node-123",
"nodeName": "Spontit"
}
]
}
],
"recommendations": [
{
"action": "Update affected workflows",
"description": "Replace removed nodes with their updated versions or alternatives"
}
]
}
]
},
"shouldCache": false
}
The system supports three types of rules:
IBreakingChangeWorkflowRule)getMetadata(): Returns rule metadata (version, title, description, severity, etc.)detectWorkflow(workflow, nodesGroupedByType): Checks a single workflow and returns issuesgetRecommendations(workflowResults): Returns recommendations based on detected issuesWorkflowDetectionReport with workflow-specific issuesIBreakingChangeInstanceRule)getMetadata(): Returns rule metadata (version, title, description, severity, etc.)detect(): Checks the entire instance and returns issuesInstanceDetectionReport with instance-level issues and recommendationsIBreakingChangeBatchWorkflowRule)getMetadata(): Returns rule metadatacollectWorkflowData(workflow, nodesGroupedByType): Called per workflow during scanningproduceReport(): Called after all workflows scanned to produce final reportreset(): Resets internal state before a new detection rungetRecommendations(workflowResults): Returns recommendationsBatchWorkflowDetectionReport.rule.ts file in the appropriate version directory.rules/index.ts.The @BreakingChangeRule decorator handles registration automatically on import.
Create rules/v2/my-workflow-rule.rule.ts:
import type { BreakingChangeAffectedWorkflow, BreakingChangeRecommendation } from '@n8n/api-types';
import type { WorkflowEntity } from '@n8n/db';
import type { INode } from 'n8n-workflow';
import { BreakingChangeRule } from '@n8n/decorators';
import type {
BreakingChangeRuleMetadata,
IBreakingChangeWorkflowRule,
WorkflowDetectionReport,
} from '../../types';
import { BreakingChangeCategory } from '../../types';
@BreakingChangeRule({ version: 'v2' })
export class MyWorkflowRule implements IBreakingChangeWorkflowRule {
id: string = 'my-workflow-rule-v2';
getMetadata(): BreakingChangeRuleMetadata {
return {
version: 'v2',
title: 'My Workflow Breaking Change',
description: 'Description of what changed in workflows',
category: BreakingChangeCategory.workflow,
severity: 'high',
documentationUrl: 'https://docs.n8n.io/migration/v2/...',
};
}
async getRecommendations(
_workflowResults: BreakingChangeAffectedWorkflow[],
): Promise<BreakingChangeRecommendation[]> {
return [
{
action: 'Update affected workflows',
description: 'Replace or update the affected nodes',
},
];
}
async detectWorkflow(
_workflow: WorkflowEntity,
nodesGroupedByType: Map<string, INode[]>,
): Promise<WorkflowDetectionReport> {
const affectedNodes = nodesGroupedByType.get('n8n-nodes-base.someNode') ?? [];
if (affectedNodes.length === 0) {
return { isAffected: false, issues: [] };
}
return {
isAffected: true,
issues: affectedNodes.map((node) => ({
title: `Node '${node.type}' with name '${node.name}' is affected`,
description: 'This node requires updates for v2 compatibility',
level: 'warning',
nodeId: node.id,
nodeName: node.name,
})),
};
}
}
Create rules/v2/my-instance-rule.rule.ts:
import { BreakingChangeRule } from '@n8n/decorators';
import type {
BreakingChangeRuleMetadata,
IBreakingChangeInstanceRule,
InstanceDetectionReport,
} from '../../types';
import { BreakingChangeCategory } from '../../types';
@BreakingChangeRule({ version: 'v2' })
export class MyInstanceRule implements IBreakingChangeInstanceRule {
id: string = 'my-instance-rule-v2';
getMetadata(): BreakingChangeRuleMetadata {
return {
version: 'v2',
title: 'My Instance Breaking Change',
description: 'Description of what changed at instance level',
category: BreakingChangeCategory.instance,
severity: 'medium',
documentationUrl: 'https://docs.n8n.io/migration/v2/...',
};
}
async detect(): Promise<InstanceDetectionReport> {
const hasIssue = false; // Your detection logic here
if (!hasIssue) {
return { isAffected: false, instanceIssues: [], recommendations: [] };
}
return {
isAffected: true,
instanceIssues: [
{
title: 'Configuration issue detected',
description: 'Description of the issue',
level: 'warning',
},
],
recommendations: [
{
action: 'Fix the configuration',
description: 'Steps to resolve the issue',
},
],
};
}
}
The rule will be automatically:
BreakingChangeRuleMetadata by the @BreakingChangeRule decoratorRuleRegistry when the service startsTo add breaking changes for a new version (e.g., v3):
Create the version directory:
mkdir -p packages/cli/src/modules/breaking-changes/rules/v3
Add rule files with @BreakingChangeRule({ version: 'v3' }).
Add side-effect imports for the new rule files in rules/index.ts.