workflow-lint
Catch the mistakes in an n8n workflow before it runs: a node still called "HTTP Request", a placeholder URL, an API call with no retry, a Set node that drops its input. workflow-lint reports each one on the line where it lives and fixes the ones that have a safe fix. It runs from the terminal, a pre-commit hook, CI, or an AI agent over MCP.

Installation
Requires Node.js 24 or newer.
Run it without installing:
npx workflow-lint --help
Add it to a project:
npm install --save-dev workflow-lint
Build from source:
git clone https://github.com/LudwigGerdes/workflow-lint.git
cd workflow-lint
pnpm install && pnpm build
Getting started
Download a sample workflow that has several things wrong with it, and lint it:
curl -LO https://raw.githubusercontent.com/LudwigGerdes/workflow-lint/main/docs/demo/order-sync.json
npx workflow-lint lint order-sync.json
Expected output:
order-sync.json
1:1 warn n8n/typeversion-policy No n8n version is pinned, so typeVersion findings were withheld; set settings.n8nVersion or pass --n8n-version to check them.
4:5 info reliability/webhook-input-contract Webhook "Order Received" accepts its payload without checking it; validate the required fields and stop on bad input.
17:5 warn naming/decision-node-question-mark Decision node "Is Valid" should be phrased as a question ending in "?".
27:5 warn hygiene/no-placeholder-api-url Node "HTTP Request" still points at the placeholder URL "https://api.example.com/orders".
27:5 warn naming/external-node-name-format Node "HTTP Request" calls an external service; name it to match ^(GET|POST|PUT|PATCH|DELETE|UPSERT) .+ - .+$.
27:5 warn naming/no-default-node-name Node "HTTP Request" still has its default name; rename it to describe what it does.
27:5 warn reliability/http-retry-config Node "HTTP Request" calls out over the network but does not retry on failure.
27:5 warn structure/branch-entry-pass-through Branch 0 of "Is Valid" goes straight into "HTTP Request"; open it with a NoOp or a pass-through Set.
40:5 warn naming/no-default-node-name Node "Edit Fields" still has its default name; rename it to describe what it does.
40:5 warn structure/branch-entry-pass-through Pass-through Set "Edit Fields" on branch 1 of "Is Valid" does not set includeOtherFields, so it drops the incoming data.
40:5 error structure/set-pass-through-include-other-fields Pass-through Set "Edit Fields" assigns nothing and does not set includeOtherFields, so it emits empty items.
x 11 problems (1 error, 9 warnings, 1 info) 3 fixable with --fix
Apply the safe fixes:
npx workflow-lint lint order-sync.json --fix
Expected output:
order-sync.json
1:1 warn n8n/typeversion-policy No n8n version is pinned, so typeVersion findings were withheld; set settings.n8nVersion or pass --n8n-version to check them.
4:5 info reliability/webhook-input-contract Webhook "Order Received" accepts its payload without checking it; validate the required fields and stop on bad input.
27:5 warn hygiene/no-placeholder-api-url Node "HTTP Request" still points at the placeholder URL "https://api.example.com/orders".
27:5 warn naming/external-node-name-format Node "HTTP Request" calls an external service; name it to match ^(GET|POST|PUT|PATCH|DELETE|UPSERT) .+ - .+$.
27:5 warn naming/no-default-node-name Node "HTTP Request" still has its default name; rename it to describe what it does.
27:5 warn structure/branch-entry-pass-through Branch 0 of "Is Valid?" goes straight into "HTTP Request"; open it with a NoOp or a pass-through Set.
43:5 warn naming/no-default-node-name Node "Edit Fields" still has its default name; rename it to describe what it does.
x 7 problems (0 errors, 6 warnings, 1 info)
To use a workflow of your own, open it in n8n and choose Download from the … menu.
Usage
Lint every workflow under the current directory:
workflow-lint lint
Fix what can be fixed safely:
workflow-lint lint --fix
Tidy the canvas layout:
workflow-lint fmt
Write a config file, where you pin your n8n version and switch rules on or off:
workflow-lint init
Expected output:
Created workflow-lint.config.yaml
Adopt it on an existing repository by accepting today's findings and failing only on new ones:
workflow-lint lint --gen-baseline
Report in a format your CI understands:
workflow-lint lint --format sarif