Skip to main content

Node Versioning

Node versioning allows you to evolve your nodes over time while maintaining backward compatibility for existing workflows. This guide covers both light versioning and full versioning strategies.

Why Version Nodes?

Versioning is essential when:
  • Adding new features that change parameters
  • Fixing bugs that alter behavior
  • Changing API endpoints or data structures
  • Improving performance with different approaches
  • Deprecating old functionality
Existing workflows continue to use the node version they were created with. Only new instances use the latest version.

Light Versioning

Light versioning uses version arrays in the node description. All versions share the same implementation.

When to Use Light Versioning

  • ✅ Minor bug fixes
  • ✅ Adding optional parameters
  • ✅ Small behavior changes
  • ✅ UI improvements
  • ✅ Adding new operations without breaking existing ones

Implementation

export class GmailTrigger implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'Gmail Trigger',
    name: 'gmailTrigger',
    icon: 'file:gmail.svg',
    group: ['trigger'],
    version: [1, 1.1, 1.2, 1.3], // Light versioning
    description: 'Fetches emails from Gmail',
    // ... rest of description
  };

  async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
    const node = this.getNode();
    
    // Check version for conditional behavior
    if (node.typeVersion > 1.1) {
      // New behavior for v1.2+
      const includeDrafts = filters.includeDrafts ?? false;
    } else {
      // Old behavior for v1.0-1.1
      const includeDrafts = filters.includeDrafts ?? true;
    }

    // Rest of implementation
  }
}

Version-Specific Behavior

Use this.getNode().typeVersion to check the version:
const node = this.getNode();

if (node.typeVersion >= 1.2) {
  // New behavior
} else {
  // Old behavior
}

Full Versioning

Full versioning creates separate implementations for each major version using the VersionedNodeType class.

When to Use Full Versioning

  • ✅ Major breaking changes
  • ✅ Complete parameter restructuring
  • ✅ Different execution logic
  • ✅ API version changes
  • ✅ Fundamental architecture changes

Implementation

Full versioning requires three files:
Set/
├── Set.node.ts          # Main versioned node
├── v1/
│   └── SetV1.node.ts    # Version 1 implementation
└── v2/
    └── SetV2.node.ts    # Version 2 implementation
1

Create Main Node File

// Set.node.ts
import type { 
  INodeTypeBaseDescription, 
  IVersionedNodeType 
} from 'n8n-workflow';
import { VersionedNodeType } from 'n8n-workflow';

import { SetV1 } from './v1/SetV1.node';
import { SetV2 } from './v2/SetV2.node';

export class Set extends VersionedNodeType {
  constructor() {
    const baseDescription: INodeTypeBaseDescription = {
      displayName: 'Set',
      name: 'set',
      icon: 'fa:pen',
      group: ['input'],
      description: 'Add or edit fields on an input item',
      defaultVersion: 3.4,
    };

    const nodeVersions: IVersionedNodeType['nodeVersions'] = {
      1: new SetV1(baseDescription),
      2: new SetV1(baseDescription),  // v2 still uses V1 impl
      3: new SetV2(baseDescription),
      3.1: new SetV2(baseDescription),
      3.2: new SetV2(baseDescription),
      3.3: new SetV2(baseDescription),
      3.4: new SetV2(baseDescription),
    };

    super(nodeVersions, baseDescription);
  }
}
2

Create Version 1 Implementation

// v1/SetV1.node.ts
import type {
  IExecuteFunctions,
  INodeExecutionData,
  INodeType,
  INodeTypeBaseDescription,
  INodeTypeDescription,
} from 'n8n-workflow';

export class SetV1 implements INodeType {
  description: INodeTypeDescription;

  constructor(baseDescription: INodeTypeBaseDescription) {
    this.description = {
      ...baseDescription,
      version: [1, 2],
      defaults: {
        name: 'Set',
        color: '#0000FF',
      },
      inputs: ['main'],
      outputs: ['main'],
      properties: [
        // V1-specific parameters
        {
          displayName: 'Keep Only Set',
          name: 'keepOnlySet',
          type: 'boolean',
          default: false,
          description: 'Keep only the values set',
        },
        // ... more V1 parameters
      ],
    };
  }

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    // V1 implementation
    const items = this.getInputData();
    const keepOnlySet = this.getNodeParameter('keepOnlySet', 0) as boolean;

    // V1 logic
    return [items];
  }
}
3

Create Version 2 Implementation

// v2/SetV2.node.ts
import type {
  IExecuteFunctions,
  INodeExecutionData,
  INodeType,
  INodeTypeBaseDescription,
  INodeTypeDescription,
} from 'n8n-workflow';

export class SetV2 implements INodeType {
  description: INodeTypeDescription;

  constructor(baseDescription: INodeTypeBaseDescription) {
    this.description = {
      ...baseDescription,
      version: [3, 3.1, 3.2, 3.3, 3.4],
      defaults: {
        name: 'Set',
        color: '#0000FF',
      },
      inputs: ['main'],
      outputs: ['main'],
      properties: [
        // V2-specific parameters (completely different)
        {
          displayName: 'Mode',
          name: 'mode',
          type: 'options',
          options: [
            {
              name: 'Manual',
              value: 'manual',
            },
            {
              name: 'Expression',
              value: 'expression',
            },
          ],
          default: 'manual',
        },
        // ... more V2 parameters
      ],
    };
  }

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    // V2 implementation (completely different from V1)
    const items = this.getInputData();
    const mode = this.getNodeParameter('mode', 0) as string;

    // V2 logic
    return [items];
  }
}

Version Migration

When users update node versions, their workflows don’t automatically migrate. Consider providing migration helpers:

Document Breaking Changes

description: INodeTypeDescription = {
  // ... other properties
  version: [3, 3.1],
  hints: [
    {
      type: 'warning',
      message: 'This version has breaking changes. Please review your configuration.',
      whenToDisplay: 'afterExecution',
      location: 'outputPane',
    },
  ],
};

Provide Clear Documentation

In your node documentation, include migration guides:
Breaking Changes in v3.0:
  • Removed keepOnlySet parameter
  • Added new mode parameter
  • Changed output format
Migration Steps:
  1. Update to latest version
  2. Reconfigure mode parameter
  3. Test your workflow

Version Strategy Best Practices

Follow semantic versioning principles:
  • Major (1.0 → 2.0): Breaking changes
  • Minor (1.0 → 1.1): New features, backward compatible
  • Patch (1.1 → 1.1.1): Bug fixes, backward compatible
// Bug fix - increment patch
version: [1, 1.1, 1.1.1]

// New feature - increment minor
version: [1, 1.1, 1.2]

// Breaking change - increment major
version: [1, 2]

Real-World Example: Gmail Trigger Versions

The Gmail Trigger uses light versioning to evolve behavior:
export class GmailTrigger implements INodeType {
  description: INodeTypeDescription = {
    displayName: 'Gmail Trigger',
    name: 'gmailTrigger',
    icon: 'file:gmail.svg',
    group: ['trigger'],
    version: [1, 1.1, 1.2, 1.3],
    // ... rest of description
  };

  async poll(this: IPollFunctions): Promise<INodeExecutionData[][] | null> {
    const node = this.getNode();
    const workflowStaticData = this.getWorkflowStaticData('node');

    // v1.1+: Support for multiple trigger instances
    let nodeStaticData;
    if (node.typeVersion > 1) {
      const nodeName = node.name;
      const dictionary = workflowStaticData as Dictionary;
      if (!(nodeName in workflowStaticData)) {
        dictionary[nodeName] = {};
      }
      nodeStaticData = dictionary[nodeName];
    } else {
      nodeStaticData = workflowStaticData;
    }

    // v1.2+: Changed default for includeDrafts
    let includeDrafts = false;
    if (node.typeVersion > 1.1) {
      includeDrafts = filters.includeDrafts ?? false; // New default
    } else {
      includeDrafts = filters.includeDrafts ?? true;  // Old default
    }

    // v1.3+: Filter out sent emails not in inbox
    if (
      node.typeVersion > 1.2 &&
      fullMessage.labelIds?.includes('SENT') &&
      !fullMessage.labelIds?.includes('INBOX')
    ) {
      continue; // Skip this message
    }

    // Rest of implementation
  }
}

Version Evolution:

VersionChange
1.0Initial release
1.1Support multiple trigger instances in same workflow
1.2Changed default includeDrafts to false
1.3Filter out sent emails not in inbox

Deprecation Strategy

When deprecating old versions:
1

Add Deprecation Notice

description: INodeTypeDescription = {
  // ... other properties
  hints: [
    {
      type: 'warning',
      message: 'This version is deprecated. Please upgrade to v3.',
      whenToDisplay: 'always',
      location: 'outputPane',
    },
  ],
};
2

Provide Migration Path

Document how to migrate in the deprecation notice:
{
  type: 'info',
  message: 'To upgrade: 1) Duplicate this node 2) Select v3 3) Reconfigure 4) Test 5) Replace old node',
  whenToDisplay: 'always',
  location: 'outputPane',
}
3

Keep Supporting Old Versions

Continue supporting deprecated versions for at least 6-12 months.
4

Remove After Grace Period

Only remove old versions after the grace period and multiple warnings.

Version Comparison Table

AspectLight VersioningFull Versioning
Use CaseMinor changesMajor rewrites
ImplementationSingle fileMultiple files
Code SharingShared execute()Separate implementations
ComplexityLowMedium to High
MaintenanceEasyModerate
Best ForBug fixes, small featuresBreaking changes, new architecture
ExampleGmail Trigger (v1-1.3)Set node (v1-3.4)

Checklist for Adding New Version

1

Decide Versioning Strategy

  • Is this a breaking change?
  • Can it be handled with light versioning?
  • Do you need full versioning?
2

Update Version Number

  • Follow semantic versioning
  • Update version array
  • Update defaultVersion (if applicable)
3

Implement Version Logic

  • Add version checks in code
  • Maintain backward compatibility
  • Test old and new versions
4

Document Changes

  • Update node documentation
  • Add migration guide
  • Add deprecation notices (if needed)
5

Test Thoroughly

  • Test all supported versions
  • Test version-specific behavior
  • Test migration paths
  • Test with existing workflows

Common Pitfalls

Avoid these common versioning mistakes:
  • ❌ Changing behavior without incrementing version
  • ❌ Breaking old versions when adding new features
  • ❌ Not testing all supported versions
  • ❌ Removing old versions too quickly
  • ❌ Not documenting breaking changes
  • ❌ Using full versioning for minor changes
  • ❌ Forgetting to update defaultVersion

Next Steps

Overview

Review node development fundamentals

Testing

Test your versioned nodes