I deleted a production workflow once. Not archived it — deleted it. Three months of iteration, a dozen conditional branches, and about 100+ hours of work, gone in a single misclick.
n8n had no recycle bin. The workflow wasn’t backed up anywhere. I rebuilt it from memory and Slack messages over the next two days, and I’m reasonably confident the rebuilt version missed at least one edge case I’d long since forgotten I’d handled.
That’s when I started treating my n8n workflows the same way I treat code: everything goes in Git, every change gets a commit message, nothing lives only in the n8n interface.
This post is about how I set that up, what actually works in practice, and the things I wish someone had told me before I started.
The promise of low-code tools is that you move faster because you’re not writing code. The irony is that most people end up managing their low-code workflows with less discipline than they’d manage actual code. No branching, no history, no rollback.
With n8n specifically, the problem compounds quickly. A typical n8n setup at even a small company can accumulate 1 to 100+ active workflows within the first year. If you’re self-hosting and experimenting, that number climbs faster. Each one is a small piece of infrastructure — and infrastructure needs version control.
The risks are real:
I’ve seen all four of these happen. The deletion story above was actually the second incident, not the first.
Before getting into setup, it helps to understand what you’re actually version controlling.
n8n workflows are JSON under the hood. Every node, connection, credential reference, and configuration value is serialized into a JSON structure. When you export a workflow manually (via File → Download), you get a .json file that contains everything needed to recreate it.
This means Git handles n8n workflows exactly the way it handles any other text-based file: diffs are readable, merge conflicts are resolvable, and history is complete.
Here’s a simplified example of what a workflow node looks like in JSON:
json
{
"nodes": [
{
"parameters": {
"url": "https://api.example.com/data",
"method": "GET"
},
"name": "Fetch Data",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [250, 300]
}
]
} Human-readable, diffable, committable. That’s the foundation everything else builds on.
If you’re running n8n (on a self-hosted instance with an enterprise license), you can use the built-in Source Control feature under Settings → Source Control.
It connects n8n directly to a Git repository. When you push from within n8n, it serializes all workflows, credentials metadata (not credential values — those stay encrypted), and variables into your repo. Pull in the opposite direction to sync updates from the repo back into n8n.
My experience with native Source Control:
I enabled this on our main instance after it shipped and it worked well for our primary use case: keeping a staging and production instance in sync. The workflow was roughly:
staging branchmainWhat I liked: the push/pull model is something the whole team can use without knowing anything about Git. What I didn’t like: you can’t commit granularly from within n8n. It’s a full snapshot of the current state, not a series of atomic changes. So your commit history ends up being coarse — “pushed from staging” rather than “added retry logic to Slack error handler.”
For most teams, that’s fine. For teams that care about a detailed audit trail, read on.
This is what I was doing before native Source Control existed, and it’s still what I use for personal projects where I want granular commit history.
The workflow:
In the n8n editor, go to the workflow you want to version → click the three-dot menu → Download. You get a .json file.
I name files descriptively: slack-error-notifier.json, hubspot-contact-sync.json, weekly-report-generator.json. Not My Workflow (3).json.
n8n-workflows/
├── production/
│ ├── slack-error-notifier.json
│ ├── hubspot-contact-sync.json
│ └── weekly-report-generator.json
├── staging/
│ └── hubspot-contact-sync.json
├── archived/
│ └── old-lead-scoring.json
└── README.md I keep production/ and staging/ mirroring what’s actually deployed. archived/ is for workflows I’ve retired but don’t want to lose.
git add production/hubspot-contact-sync.json
git commit -m "fix: add null check for missing email field on contact create" Treat it like code. Conventional commits (fix:, feat:, refactor:) make the history useful when you’re scrolling back through it months later.
To restore a workflow from a previous version, check out the relevant commit, grab the JSON, and import it in n8n via Import from File. It recreates the workflow exactly, including all node configurations.
I don’t use a complex Git flow for workflows. Two branches, one rule:
main = what’s running in productiondev = active workNew workflows start on dev. When they’re tested and stable, I merge to main and note the deployment date in the commit message. If I ever need to roll back, I check out the last stable commit on main and re-import.
For teams with more than 5 people touching workflows regularly, adding a staging branch between dev and main is worth the overhead. It gives you a checkpoint to run integration tests before anything touches production.
This is the question everyone asks, and the answer is: don’t put credential values in Git. Ever.
n8n’s native Source Control handles this correctly — it exports credential names and structures but not the actual secrets. If you’re doing manual exports, the JSON files don’t include credential values by default, only the credential IDs that the workflow references.
What you should document in your repo is which credentials a workflow depends on. I keep a README.md in each workflow’s directory (or at the root for simple repos) that lists:
markdown
## Dependencies
- **HubSpot API**: Credential name `HubSpot Production`
- **Slack Bot Token**: Credential name `Slack Notifications Bot` This makes it trivially easy for anyone setting up the workflow on a new instance to know exactly what they need to create.
After doing this manually for a few months, I got tired of the click-export-rename cycle. I wrote a short script that uses n8n’s REST API to pull all workflow JSONs and write them to the right directory automatically.
python
import requests
import json
import os
N8N_API_URL = "http://localhost:5678/api/v1"
N8N_API_KEY = os.environ["N8N_API_KEY"]
OUTPUT_DIR = "./n8n-workflows/production"
headers = {"X-N8N-API-KEY": N8N_API_KEY}
response = requests.get(f"{N8N_API_URL}/workflows", headers=headers)
workflows = response.json()["data"]
for workflow in workflows:
if workflow["active"]: # Only export active workflows
filename = workflow["name"].lower().replace(" ", "-") + ".json"
filepath = os.path.join(OUTPUT_DIR, filename)
with open(filepath, "w") as f:
json.dump(workflow, f, indent=2)
print(f"Exported: {filename}") Run this before committing. It takes about 30 seconds even for 3+ workflows. I’ve wired it into a pre-commit hook so I can’t forget.
Here’s what changed after I set this up properly:
Incidents prevented: I’ve had many cases where I needed to roll back a workflow change since implementing this. In all these cases, I had what I needed in Git and was back to a working state in under 2 minutes.
Onboarding: When a second person joined the team and needed to set up their own n8n instance, they cloned the repo, imported the workflows, created the credentials, and were running in about 3 hours. Previously, that would have required screen-sharing and manual recreation.
Change reviews: We now PR workflow changes the same way we PR code. It sounds overkill until the first time someone catches a logic error in a JSON diff before it hits production.
Audit trail: When something breaks and nobody knows why, git log tells us what changed and when. We’ve used this over 10 times in 2 months.
1. Committing after every save, not after every logical change. n8n auto-saves constantly. If you commit every time you open a workflow, your history becomes noise. Commit when a meaningful change is complete.
2. One repo for everything. I initially put n8n workflows in the same repo as the application code they integrated with. Bad idea — different cadences, different reviewers, different deployment contexts. Separate repos, or at minimum a clear monorepo structure.
3. Not testing imports before relying on them. Periodically do a test restore. Import a workflow from Git into a fresh n8n instance and verify it runs. Don’t discover that your backup is broken at 2am when something is down.
4. Ignoring workflow dependencies in commit messages. If a workflow change requires a new credential, a new environment variable, or a change to an external system, say so in the commit message. Future-you will be grateful.
Does n8n have built-in version control? Yes — n8n added native Source Control (Git integration) in version 2.28.1 for self-hosted enterprise instances. It connects to a Git repository and lets you push/pull workflows directly from the n8n UI. If you’re on n8n Cloud or an older version, you’ll need a manual workflow using exports and Git.
Will Git version control work with n8n Cloud? Native Source Control is only available on self-hosted instances. On n8n Cloud, you can still manually export workflow JSONs and commit them to a Git repo — you just lose the direct push/pull integration from within the n8n UI.
Can I see the diff between two versions of a workflow? Yes. Because n8n workflows are stored as JSON, standard git diff works. The output isn’t always pretty (JSON diffs can be noisy), but tools like json-diff or GitHub’s side-by-side view make it readable. Pay attention to the nodes and connections arrays — that’s where meaningful changes show up.
Should I version control credentials too? No. Never commit actual credential values to Git. n8n’s native Source Control exports credential metadata (names, types) but not values. If you’re doing manual exports, credential values aren’t included in the exported JSON. Document which credentials a workflow needs in a README, but store the actual secrets in a secrets manager.
What if two people edit the same workflow at the same time? This is the collaboration problem that version control doesn’t fully solve on its own — it helps you detect the conflict, but n8n doesn’t have a built-in merge mechanism for workflows. The practical answer is: treat active workflow editing like you’d treat editing a shared document. Communicate before you edit, push quickly, and keep editing sessions short.
Can I automate the export process? Yes. n8n exposes a REST API that lets you fetch all workflows programmatically. I use a short Python script (shared above) as a pre-commit hook to pull all active workflows before every commit. You can also trigger exports via a webhook or scheduled n8n workflow that commits to Git on a schedule.
Low-code doesn’t mean low-stakes. The workflows you build in n8n are real infrastructure — they move data, trigger actions, and talk to external services. Treating them with the same rigor you’d apply to code isn’t over-engineering; it’s just responsible.
The setup I’ve described takes about an afternoon. The time it saves over the course of a year, measured in restored workflows, sane onboarding, and confident deployments, is hard to overstate.
If you’re already running n8n and you haven’t set this up yet, make it this week’s project.
Have a different approach to version controlling your n8n workflows? I’d be curious what’s working — drop it in the comments.
Pasting URLs into ChatGPT one at a time to write meta descriptions is a process…
Claude can call APIs and MCP connectors now, so a one-prompt dashboard is a real…
Server-Side Google Tag Manager lets you relay conversion events to Meta through a server you…
TL;DR For WordPress content curation, Gemini is usually the better choice when you care most…
A ground-level guide to RPA and business automation in 2026 — covering where Droven.io fits…
⚡ TL;DR To automate core web vitals check with AI and n8n, the clean workflow…