n8n Node Versioning: Change Nodes Without Breaking Workflows
In this video
This is the written version of Part 9 of Build your own n8n node. A teammate ships a small change: one field in the API gets a better name, name becomes full_name, and the old one goes away next month.
Somewhere, a customer has a workflow that has run every hour for months with your node in the middle. Rename the field in your node and publish, and their workflow keeps running, keeps saying success, and starts sending an empty value.
This part makes that change safely. The old workflow carries on untouched, and new workflows get the better field. Most tutorials end at publishing. This is what the year after looks like.
What n8n remembers
When someone saves a workflow, n8n saves their choices with it: which fields they filled in, and what they put there. Each value is stored under the field's name.
Rename the field in your node and publish, and the saved value has nowhere to go. It was stored under name. The new field, fullName, has never heard of it.
The quiet failure
The worst part is how it fails. There's no crash and no red error. The node sends an empty name, everything keeps running, and every run says success.
Somebody notices a week later, when a hundred contacts have come through with no name. It's the same kind of bug as the missing contacts in Part 5: the quiet kind, which is always the expensive kind.
Two kinds of change
Sort every change into one of two piles.
| Safe: just publish | Careful: needs a new node version |
|---|---|
| A new operation | Renaming a field |
| A new optional field | Removing a field or an option |
| A whole new resource | Changing a default value |
| Better help text | Making an optional field required |
Safe changes add things. Old workflows don't know they exist and don't care; nothing they saved is touched. Bump the package version and release, the same as in Part 7.
Careful changes reach into what people already saved. For those, n8n has a tool, and it's a kind one.
One node, two versions
A node can be two versions at once. Every workflow remembers which version of your node it was built with. Workflows saved with version 1 stay on version 1 for as long as the user leaves them alone. Anyone who adds your node to a canvas today gets the latest version.
So the old workflow still sees the old field, with its saved value right there, and new users get the new field. Both are true at the same time.
n8n calls the simplest form light versioning, and it works for declarative and programmatic nodes alike. In the node's description, make version an array:
version: [1, 2],
defaultVersion: 2,
Versioned fields in code
Two fields, each shown only in its own version, using displayOptions from Part 3 with the special key @version:
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
displayOptions: { show: { resource: ['contact'], operation: ['create'], '@version': [1] } },
},
{
displayName: 'Full Name',
name: 'fullName',
type: 'string',
default: '',
displayOptions: { show: { resource: ['contact'], operation: ['create'], '@version': [2] } },
},
Inside execute(), ask the node which version it is, read the matching field, and send the API what it wants now:
const nodeVersion = this.getNode().typeVersion;
const fullName =
nodeVersion === 1
? (this.getNodeParameter('name', i) as string)
: (this.getNodeParameter('fullName', i) as string);
await acmeRequest.call(this, 'POST', '/contacts', { email, full_name: fullName });
That's it. The old workflow keeps running untouched, new ones get the better field, and nobody's Tuesday gets ruined.
For bigger changes, n8n also has feature-based versioning and full versioning, where each version is its own file. The versioning docs cover both. Light versioning is enough for most field-level changes.
What it costs
Every careful change adds a small fork to the code: if version 1, do this; if version 2, do that. One fork is nothing. But your API doesn't stop changing, and after a year there's a handful of them and the file is harder to read.
That's the price. It's worth paying, and it's worth knowing it's there. Keeping the version checks in one small function per field, rather than scattered through execute(), keeps it manageable.
A release routine
Versioning is the easy part. The hard part is that somebody has to remember to do it. Your API team ships every week, and the node gets updated when someone remembers it exists, which is usually right after a customer writes in to say it's broken.
Here's a short routine:
- Put the node on your API release checklist, as an actual line: "check the n8n node".
- Adding something? Bump the package version and release.
- Changing or removing something? Add a node version, then release.
- Keep a changelog. People read it more than you'd think.
The whole road
Look how far it's come. An empty folder. A tile with a name on it. The first 401. The first real contact. All 1,240 contacts. A node that feels good to use. The green build on npm. The badge. And now a node that can change without breaking anyone.
You can do all of it by hand now.
There's one part left. In Part 10 the same API becomes the same node a different way, generated from its OpenAPI spec, and the two go side by side so you can judge.
One more thing. Keeping a node in step with a changing API is what NativeShip, the tool we build, was made for. You give it the updated OpenAPI spec, it shows you what changed, and you rebuild.
Frequently asked questions
n8n stores each field's value under the field's name. Rename the field and the saved value has nowhere to go, so the workflow keeps running and sends an empty value, without an error.
Set version to an array like [1, 2]. Workflows saved with version 1 keep using version 1; new nodes added to a canvas get the latest version. Use '@version' in displayOptions to show fields per version, and this.getNode().typeVersion in code.
Changes that touch what users already saved: renaming or removing a field, changing a default, or making an optional field required. Adding a new operation, resource or optional field doesn't.