n8n-ctl
v0.2.1
Published
GitOps CLI for n8n workflows
Maintainers
Readme
n8n-ctl
GitOps-style CLI for managing n8n workflows — inspired by Terraform Workspaces and Git.
n8n-ctl brings version control discipline to your n8n automation workflows. Manage multiple environments (dev, staging, prod), track changes, extract JavaScript Code nodes into real files, and deploy with confidence — all from your terminal.
✨ Features
- Environment Management — Maintain separate n8n workflow instances per environment (dev, staging, prod, or any custom name)
- Code Node Extraction — JavaScript inside Code nodes is extracted to individual
.jsfiles in acodes/folder, making them properly version-controllable in Git - Manifest-based Approach — A
n8n-ctl.jsonmanifest maps your local code files back to their workflow nodes — the originalworkflow.jsonstays structurally intact and importable directly into n8n at any time - Status Diffing — See exactly what changed between your local files and the live n8n workflow before pushing
- Safe Push — Preview all changes and confirm before applying them to the n8n host
- Isolated Local Dev —
n8n-ctl upspins up a local n8n instance with its own isolated database, so it never conflicts with your global n8n installation - Credential Mapping — Map credentials per environment using
.env.{ENV}files without ever storing secrets in Git
📋 Requirements
- Node.js >= 22.x (managed with Volta — version is pinned in
package.json) - An n8n instance (local or remote) with the API enabled
- An n8n API key
🚀 Installation
npm install -g n8n-ctlOr to use it locally from source:
git clone https://github.com/williamegomezo/n8n-ctl.git
cd n8n-ctl
npm install
npm run build
npm link📁 Project Structure
Once initialized, your GitOps workspace will look like this:
my-workflow/
├── workflow.json # Raw n8n workflow export — untouched, always importable
├── n8n-ctl.json # Manifest: environment config + code node mappings
├── credentials.json # Maps credential names to n8n host IDs (gitignored)
├── codes/
│ ├── Format_Data.js # Extracted JavaScript from Code nodes
│ └── Calculate_Totals.js
├── .env.dev # Environment variables for the dev environment
├── .env.prod # Environment variables for the prod environment
└── .n8n-ctl/ # CLI state & local n8n database (gitignored)
├── state.json
└── db/ # Isolated SQLite DB used by `n8n-ctl up`Important:
credentials.jsonand.n8n-ctl/are automatically added to.gitignoreoninit. Never commit credentials or local state to Git.
🛠 Commands
n8n-ctl init
Initialize a new gitops workspace in the current directory.
n8n-ctl initPrompts:
- n8n Instance URL (e.g.,
http://localhost:5678orhttps://your-instance.app.n8n.cloud) - n8n API Key
- Environment name (e.g.,
dev,staging,prod) - Import an existing workflow or create a new one
Creates:
workflow.json— raw export of the workflown8n-ctl.json— manifest with environment and code mappingscodes/— extracted JavaScript from Code nodes.env.{ENV}— environment variable file for secretscredentials.json— local credential ID mapping.n8n-ctl/state.json— CLI session state
n8n-ctl status
Compare your local workflow (including code from codes/) against the live n8n workflow.
n8n-ctl statusOutput example:
✔ Loading local workflow
✔ Fetching remote workflow
✔ Comparing workflows
✓ Local workflow is up to date with remote.Or if there are differences:
Found 2 differences:
~ nodes.1.parameters.jsCode: (old code) -> (new code)
+ nodes.2.name: "New Step"n8n-ctl pull
Download the latest workflow from the n8n host, overwriting local files.
n8n-ctl pull- Fetches remote
workflow.json - Re-extracts all Code nodes into
codes/ - Updates
n8n-ctl.jsonmanifest
n8n-ctl push
Build a local bundle (injecting code from codes/) and push it to the n8n host.
n8n-ctl pushFlow:
- Builds the workflow bundle in-memory (injects
codes/files into nodes) - Compares it against the remote workflow
- Displays a diff of all changes
- Asks for confirmation before applying
The local
workflow.jsonis never modified during a push — only the in-memory bundle is sent to the API.
n8n-ctl up
Start a local, isolated n8n instance and automatically sync your current workflow into it.
n8n-ctl upFlow:
- Checks if n8n is already running on
localhost:5678 - If not, starts
n8nusing an isolated database at.n8n-ctl/db/(so it won't conflict with your global~/.n8n) - Polls
localhost:5678/healthzuntil n8n is ready - Pushes the current workflow bundle to the local instance
After running, open: http://localhost:5678
n8n-ctl environments list
List all configured environments and highlight the current one.
n8n-ctl environments listOutput:
Available environments:
* dev (current)
staging
prodn8n-ctl environments select <env>
Switch to a different environment.
n8n-ctl environments select prodThe CLI will block this action if there are uncommitted local changes. Run
n8n-ctl statusfirst to check.
n8n-ctl environments create <env>
Create a new environment, based on the current one.
n8n-ctl environments create staging- Creates a new entry in
n8n-ctl.json - Creates a
.env.stagingfile - Switches the active environment to
staging
You'll then be prompted to link or push a workflow to this new environment.
🗝 Credential Management
n8n's API does not expose credential values (by design). n8n-ctl handles credentials through a local mapping file:
credentials.json maps human-readable names to the actual credential IDs in your n8n instance, per environment:
{
"dev": {
"Stripe-API-Key": "n8n_cred_id_abc123",
"Postgres-DB": "n8n_cred_id_xyz789"
},
"prod": {
"Stripe-API-Key": "n8n_cred_id_def456",
"Postgres-DB": "n8n_cred_id_uvw012"
}
}.env.{ENV} holds the actual values used by your workflow:
# .env.dev
STRIPE_API_KEY=sk_test_51MockKey...
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_PASSWORD=my_dev_password⚠️
credentials.jsonis gitignored by default. Never commit it.
📖 The Manifest: n8n-ctl.json
This is the core of n8n-ctl. It stores two things:
- Environments — which n8n workflow ID corresponds to each environment
- Code Mapping — which local
.jsfile maps to which Code node
{
"environments": {
"dev": {
"workflowId": "tK6rtBxL3dCQkzIq"
},
"prod": {
"workflowId": "pR9mZvKj2wXqL4Hs"
}
},
"codeMapping": {
"Format Data": "./codes/Format_Data.js",
"Calculate Totals": "./codes/Calculate_Totals.js"
}
}During a push or up, the CLI reads workflow.json + the codes/ files using this manifest, builds a complete workflow JSON in-memory, and sends it to the API. The source files on disk are never polluted with n8n internals.
👀 Example Workflow
See examples/basic-workflow/ for a complete reference implementation of how a GitOps workspace looks, including:
- A sample
workflow.json(E-Commerce Ingestion workflow) n8n-ctl.jsonmanifestcodes/Format_Data.jsandcodes/Calculate_Totals.jscredentials.jsonmapping.env.devconfiguration
🧪 Testing
Tests use Vitest with the native @vitest/coverage-v8 provider. No extra configuration needed.
# Run all tests
npm test
# Run tests with coverage report
npm run test:coverage RUN v4.1.9 /path/to/n8n-ctl
✓ tests/services/workflow.test.ts (18 tests)
✓ tests/services/n8n.test.ts (17 tests)
Test Files 2 passed (2)
Tests 35 passed (35)
% Coverage report from v8
-------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
n8n.ts | 100 | 100 | 100 | 100 |
workflow.ts | 100 | 100 | 100 | 100 |
-------------|---------|----------|---------|---------|-------------------Service layer (
src/services/) achieves 100% coverage. Command files (src/commands/) are interactive CLI entry points and are suited for integration/e2e testing.
🏗 Development
# Build
npm run build
# Run in dev mode (tsx, no build step)
npm run dev -- --help
# Link globally for testing
npm link🔒 Security
.n8n-ctl/(session state and local database) is gitignoredcredentials.json(credential ID mapping) is gitignored.env*files are gitignored (except.env.example)- API keys are stored only in local state files, never committed
📄 License
This project uses a custom No-AI License.
| | | |---|---| | ✅ Use this software in your projects | ✅ Modify and redistribute it | | ✅ Use it commercially | ✅ Share and fork it | | ❌ Train AI/LLM models with this code | ❌ Use it in AI training datasets |
See the LICENSE file for the full legal terms.
