How to Fix Deprecated Nodes in n8n Before the 3.0 Upgrade
Introduction
Starting with n8n 2.42.6, released on 9 October 2026, your instance refuses to create or update any workflow that contains a deprecated node. If you self-host n8n and your workflows were built over the last few years, there is a good chance at least one of them still carries a Function, Cron, or Item Lists node, and the next time you open one to make an unrelated edit, the save will be rejected. This tutorial shows you how to inventory every deprecated node across your instance, replace each one with its supported successor, and arrive at the n8n 3.0 upgrade, which removes these nodes entirely, with nothing left to break.
Quick answer
Find every deprecated node with a workflow-JSON inventory, replace each with its successor (Function with a Code node, Cron with a Schedule Trigger), test, and re-save on n8n 2.42.6 or newer. Repeat until no workflow is blocked.
Evidence
The timeline is short and official. n8n 2.42.6 shipped on 9 October 2026, and its release notes list exactly one core feature: “Block create/update of workflows with deprecated nodes”. This is not a warning banner or a soft nudge. The server now rejects the save itself, which makes 2.42.6 the enforcement gate in front of n8n 3.0’s full removal of these nodes.
The cleanup surface is larger than most teams expect. As of October 2026, the official deprecated-nodes reference lists 41 deprecated nodes, from the Function and Cron nodes to retired AI-era nodes like Motorhead, Zep, and the old OpenAI Assistant. The same page lists 4 fully removed nodes (Automizy, crowd.dev, Kitemaker, and Spontit, removed in n8n 2.0), a reminder of what happens to deprecated nodes that nobody migrates: they disappear, and workflows that use them fail.
The most common offender has been sitting in workflows for years. The Function node was deprecated in n8n 0.198.0 in favor of the Code node, which means any workflow written in roughly the last four years may still carry one. The vendor’s policy on these nodes is plain: “n8n won’t release further updates or bug fixes for deprecated nodes. Existing workflows that use them continue to run, but you should migrate to a supported alternative.” Running code that its own vendor will no longer patch is the argument for doing this migration now rather than during the 3.0 upgrade window.
Prerequisites
- A self-hosted n8n instance on 2.42.6 or newer. If you installed via npm, migrate to Docker first, because 3.0 drops npm installs entirely; our npm to Docker migration guide covers the move.
- An n8n API key with access to all workflows you own (create one under Settings, n8n API).
- Python 3 with the
requestspackage for the inventory script. - A full backup of your workflows and credentials. Export every workflow as JSON before touching anything.
Step-by-step guide
Step 1: Understand exactly what 2.42.6 blocks
Before 2.42.6, deprecated nodes were a cosmetic problem: they stayed hidden from the nodes panel (you could not add new ones), but saves went through and everything kept working. From 2.42.6 onward, the server rejects two operations on any workflow that contains a deprecated node: creating it and updating it. In practice that means a brand-new workflow cannot be saved if it somehow contains one, and an existing workflow cannot be saved even if your edit touched a completely different node. The block is server-side, so it applies to the editor, the public API, the CLI import command, and duplicated workflows alike.
What it does not do is stop executions. Deprecated nodes are still functional, so your active production workflows keep running. The pain arrives the moment you need to change something, which is why teams discover this during an incident fix or a routine tweak, exactly when they can least afford it.
Step 2: Inventory every deprecated node with the API
Clicking through workflows in the editor does not scale, and the nodes panel will not show you deprecated nodes anyway. The reliable approach is to pull each workflow’s JSON through the n8n public API and match node types against the official deprecated list. Every node in a workflow export carries a type field like n8n-nodes-base.function and a typeVersion field, so a script can flag problems across hundreds of workflows in seconds.
Save this as find_deprecated.py, set your instance URL and API key, and run it:
import requests
BASE = "https://n8n.example.com" # change to your instance URL
HEADERS = {"X-N8N-API-KEY": "YOUR_API_KEY"}
DEPRECATED = {
"n8n-nodes-base.function",
"n8n-nodes-base.functionItem",
"n8n-nodes-base.cron",
"n8n-nodes-base.interval",
"n8n-nodes-base.itemLists",
"n8n-nodes-base.workflowTrigger",
"n8n-nodes-base.executeWorkflow",
"n8n-nodes-base.splitInBatches",
"n8n-nodes-base.htmlExtract",
"n8n-nodes-base.readBinaryFile",
"n8n-nodes-base.readBinaryFiles",
"n8n-nodes-base.writeBinaryFile",
"n8n-nodes-base.readPdf",
"n8n-nodes-base.spreadsheetFile",
"n8n-nodes-base.moveBinaryData",
"n8n-nodes-base.openAi",
"n8n-nodes-base.motorhead",
"n8n-nodes-base.orbit",
"n8n-nodes-base.zep",
"n8n-nodes-base.zepVectorStore",
"n8n-nodes-base.line",
"n8n-nodes-base.simulate",
"n8n-nodes-base.simulateTrigger",
"n8n-nodes-base.supabase",
}
def all_workflows():
url = f"{BASE}/api/v1/workflows"
params = {"limit": 100}
while url:
r = requests.get(url, headers=HEADERS, params=params, timeout=30)
r.raise_for_status()
data = r.json()
yield from data["data"]
cursor = data.get("nextCursor")
url = f"{BASE}/api/v1/workflows?cursor={cursor}" if cursor else None
params = {"limit": 100}
found = 0
for wf in all_workflows():
detail = requests.get(
f"{BASE}/api/v1/workflows/{wf['id']}", headers=HEADERS, timeout=30
).json()
hits = [n["name"] for n in detail.get("nodes", []) if n.get("type") in DEPRECATED]
if hits:
found += 1
print(f"{detail['name']} ({wf['id']}): {', '.join(hits)}")
print(f"\n{found} workflow(s) contain deprecated nodes.")
The output is your migration backlog, one line per affected workflow, naming the exact nodes to replace. Cross-check any unfamiliar type against the official deprecated-nodes reference before editing, since the list can grow between releases.
Step 3: Replace each deprecated node with its successor
Most deprecated nodes map cleanly to a modern replacement. Work through your inventory with this mapping:
| Deprecated node | Replace with |
|---|---|
| Function | Code node, “Run Once for All Items” mode |
| Function Item | Code node, “Run Once for Each Item” mode |
| Cron | Schedule Trigger |
| Interval | Schedule Trigger |
| Item Lists | Split Out, Aggregate, Sort, Limit, or Remove Duplicates |
| Workflow Trigger | Execute Sub-workflow Trigger |
| Execute Workflow | Execute Sub-workflow |
| Split In Batches | Loop Over Items |
| HTML Extract | HTML |
| Read Binary File / Read Binary Files | Read/Write Files from Disk |
| Write Binary File | Read/Write Files from Disk |
| Read PDF | Extract From File |
| Spreadsheet File | Extract From File or Convert To File |
| Convert To/From Binary Data | Extract From File or Convert To File |
| OpenAI Assistant / OpenAI Model | Current OpenAI Chat Model nodes |
| Motorhead / Zep / Orbit / Line | Remove, or migrate memory to a supported store |
Three of these deserve a closer look.
Function to Code is usually the easiest win. The classic Function pattern carries over almost unchanged into a Code node set to “Run Once for All Items”:
const results = [];
for (const item of $input.all()) {
results.push({ json: { name: item.json.name.toUpperCase() } });
}
return results;
That same loop runs in the Code node, because both modes accept $input.all() and an array of { json } objects. The one trap is item linking: when your code returns a different number of items than it received, downstream nodes lose track of which output item came from which input item, a caveat the old Function docs called out explicitly. Keep the item count identical when you can, and when you cannot, test the downstream path node by node. Our Code node JavaScript tutorial walks through both run modes in depth.
Cron to Schedule Trigger is a settings translation. A Cron node set to run at minute 0 of hour 2 every day becomes a Schedule Trigger configured for a daily trigger at 02:00. Copy the schedule across first, then delete the Cron node, then reconnect the wires. Do not try to keep both in the canvas during the swap, or you will forget which one is live.
Item Lists has no one-to-one replacement. That single node bundled sorting, limiting, deduplication, splitting, and aggregation. Look at what the node actually does in each workflow: a Sort operation becomes the Sort node, a deduplication step becomes Remove Duplicates, a split becomes Split Out. Migrate the operation, not the node.
For AI-era nodes like Motorhead, Zep, and the old OpenAI Assistant, the replacement is the current OpenAI Chat Model or a supported memory node. These also need their credentials re-selected, because the credential type changed with the node, and a stale credential reference will fail at execution time even though the workflow saves.
Step 4: Test, then re-save on 2.42.6
Replace one workflow at a time. Execute it manually from the editor and check the output of every node downstream of a replacement, not just the replaced node itself. Only when the execution is green, save the workflow. On 2.42.6, a successful save is itself the verification: the block rejects any workflow that still contains a deprecated node, so a clean save proves the workflow is clean. When every workflow in your inventory saves successfully, re-run the find_deprecated.py script and confirm it reports zero affected workflows.
Step 5: Fold the result into your 3.0 upgrade plan
Clearing the deprecated-nodes list is the hardest manual part of the 3.0 upgrade, so once it is done, fold it straight into the rest of the checklist in our n8n 3.0 upgrade guide: Docker-only deployment, the Settings migration report, removed environment variables, and a staged upgrade with backups at each step. If you installed n8n with our Ubuntu VPS guide, the Docker Compose layout there is already the layout 3.0 expects.
Troubleshooting
I upgraded to 2.42.6 and now I cannot save any change to a workflow
That is the block working as intended. The server rejects the update because the workflow contains at least one deprecated node, even if your edit touched something else. Run the inventory script from Step 2 to find the offending nodes, replace them per the mapping table, and save again. Do not downgrade to dodge this: every later release carries the block, and 3.0 removes the nodes entirely.
After converting a Function node to a Code node, downstream nodes get wrong data
This is the item-linking trap. When your code returns fewer or more items than it received, n8n can no longer pair each output item with its input item, and downstream expressions resolve against the wrong data. The fix is to return the same number of items in the same order when possible. If the transform genuinely changes the item count, restructure the workflow so the pairing happens naturally, for example by moving the per-item logic into a Code node in “Run Once for Each Item” mode, which preserves the one-to-one pairing by design.
I only edited an unrelated node, but the save is still rejected
The block checks the whole workflow, not just the node you edited. One leftover deprecated node anywhere on the canvas, even disabled, blocks every save. Re-run the inventory script rather than eyeballing the canvas: disabled and collapsed nodes are easy to miss visually, but their type field still shows up in the workflow JSON.
My HTTP Request node is on an old version but the save works fine
Old node versions are not deprecated nodes. n8n distinguishes versioned nodes (newer versions exist, but the old one is still supported) from deprecated nodes (retired entirely), and only the deprecated list triggers the block. You can bump the node version in the node’s settings at your convenience, but it is not required to unblock 2.42.6.
Conclusion
n8n 2.42.6 turned deprecated nodes from a hygiene issue into a hard gate: no workflow containing one can be created or updated anymore, and 3.0 will remove them entirely. An API-driven inventory, the replacement mapping in this guide, and a test-then-save loop per workflow will clear the backlog in an afternoon for most instances. Once every workflow saves cleanly, you have finished the hardest manual part of the 3.0 upgrade; continue with the full 3.0 upgrade checklist to cover the remaining breaking changes.
Frequently asked questions
Will my existing workflows stop running after I upgrade to n8n 2.42.6?
No. Deprecated nodes are still functional, so active workflows keep executing. The 2.42.6 block only rejects creating a new workflow or saving changes to an existing one that contains a deprecated node. Your production schedules keep running, but you cannot edit anything until the deprecated nodes are replaced.
Which deprecated node am I most likely to have?
The Function node. It was deprecated in n8n 0.198.0 in favor of the Code node, so any workflow built in the last four years may still carry one. After that, the most common are Cron, Interval, Item Lists, and Workflow Trigger nodes, all of which have direct modern replacements.
Do old node versions count as deprecated nodes for the 2.42.6 block?
No. Versioned nodes, such as an HTTP Request node still on v3, are a separate category from deprecated nodes and are not blocked. n8n's reference keeps the two lists apart: deprecated means the node is retired entirely, while versioned means newer node versions exist and you can upgrade the node version in the node's settings when convenient.
Can I just stay on n8n 2.42.5 to avoid the block?
You can delay, but it is a dead end. Every later release carries the block, and n8n 3.0 removes the deprecated nodes entirely, so the migration work is unavoidable. Staying behind also means missing security fixes. Treat 2.42.6 as the rehearsal and migrate now.
Does the block apply to the n8n API, CLI import, and workflow duplication too?
Yes. It is enforced server-side, so it covers the editor, the public API, the n8n CLI import command, and duplicated workflows. Any create or update path that would persist a deprecated node is rejected, which is why the inventory script in this guide checks everything through the API rather than by clicking through the editor.