Sigma Logic AI Lead with AI. Thrive with Innovation.
Automation

Version controlling n8n workflows

How to get n8n workflows into git so changes are reviewable and revertable, what the JSON export does not carry, and the promotion path between environments.

On this page 12 sections
  1. Key takeaways
  2. Who this applies to
  3. What the export contains, and what it does not
  4. Making diffs readable
  5. Pick a direction of truth
  6. A workable promotion path
  7. Where n8n’s own tooling fits
  8. What to also keep in the repository
  9. Why we do this on every engagement
  10. When this is overkill
  11. Frequently asked questions
  12. Next step

Workflows are JSON, so they belong in git like any other source. The friction is that the editor is the natural place to build and the repository is the natural place to keep things, and reconciling those two needs a deliberate promotion path. What the export does not carry is credentials, and that separation is a feature - it is also the thing that breaks a restore if you have not planned for it.

The test of whether your automation is under control: can you see what changed last Tuesday, and can you put it back?

Key takeaways

  • Workflow JSON commits and diffs, which already puts n8n ahead of most automation tools.
  • Credentials are never in the export. That is correct, and it means the repo is not a full backup.
  • Diffs are noisy by default - node positions and ids change when nothing meaningful did.
  • Decide the direction of truth: editor-first or repo-first. Ambiguity is what produces drift.
  • Environment promotion needs the credential names to match, not the credentials themselves.

Who this applies to

You are running n8n with more than one person able to change workflows, or with anything in production you would not want silently modified.

What the export contains, and what it does not

A workflow export is JSON describing nodes, their parameters, their connections, and their positions on the canvas.

It does not contain credentials. Those live encrypted in the database, referenced by id and name. This is the right design - you do not want API keys in a repository - and it has two consequences people meet at the wrong moment.

Your repository is not a backup. It holds the logic and not the ability to run it. A restore needs the database as well; see self-hosted n8n for what that involves and why the encryption key matters.

Promotion between environments needs matching credential names. A workflow referencing a credential called “CRM Production” will not run in staging unless a credential of that name exists there - pointing, correctly, at the staging system. Naming credentials consistently across environments is what makes promotion work, and it is a five-minute convention that saves a recurring afternoon.

What it isIn the workflow JSONWhere it actually livesWhat breaks on restore without it
Nodes, connections, parametersYesgitNothing - this is the part that versions well
Node positions and UI stateYes, and it churnsgit, and it pollutes every diffNothing, but it hides the real change
CredentialsNo - only a reference by idn8n’s own encrypted storeEvery external call. The workflow imports and fails on the first node
The encryption keyNoN8N_ENCRYPTION_KEY on the hostThe credential store cannot be decrypted at all
Static data and execution historyNoPostgresDeduplication state, so a re-run reprocesses everything
Environment variables and webhook URLsNoThe hostTriggers point at the wrong environment
What an n8n workflow export contains, and what it leaves behind Two columns. In the workflow JSON and therefore in git: nodes and connections, parameters and expressions, and node positions which churn and pollute diffs. Not in the export: credentials which live in n8n's encrypted store, the encryption key on the host, static data and execution history in Postgres, and environment variables and webhook URLs on the host. IN THE WORKFLOW JSON, SO IN GIT NOT IN THE EXPORT Nodes and connections the part that versions well Parameters and expressions the real change Node positions, UI state churns, and pollutes every diff Credentials n8n's encrypted store The encryption key the host environment Static data, execution history Postgres Env vars and webhook URLs the host A restore from git alone imports cleanly, then fails on the first external call
The repository holds the logic and nothing the logic needs to run. Back up the right column separately, or the restore imports perfectly and fails on the first external call.

Making diffs readable

The default export produces noisy diffs. Two causes.

Node positions. Moving a node on the canvas changes its coordinates, so a purely cosmetic tidy-up produces a diff touching every node. Once workflows are in review, that trains people to skim diffs, which defeats the point.

Ids and versions. Some fields change on save without any semantic change.

The practical fix is a small normalisation step before committing: strip or round position data, sort keys deterministically, and drop volatile fields you do not care about. Twenty lines of script, run as a pre-commit hook or in the export tooling, and the diff then shows only what actually changed.

That single step is the difference between workflows being reviewed and workflows being nominally in git.

Pick a direction of truth

The decision that prevents drift, and the one most teams leave implicit.

Editor-first. People build in the n8n UI, and an export step commits the result. Comfortable, matches how the tool wants to be used, and requires discipline: an export that does not happen means the repository is stale, and a stale repository is worse than none because it looks authoritative.

Repo-first. Changes are made to files, reviewed, and deployed into n8n through the API. Rigorous, and it fights the editor - complex workflows are genuinely easier to build visually.

The hybrid that works in practice: build in the editor on a development instance, export and commit as the promotion step, and treat production as deploy-only. Production is not edited directly; changes arrive by deployment. That keeps the editor where it is useful and keeps production reproducible.

Whichever you choose, write it down and make it the same for everyone. Drift comes from two people holding different assumptions about where the real version lives.

A workable promotion path

For a team of a few people:

  1. Development instance. Build and iterate freely.
  2. Export and normalise. Script it, so the human step is one command rather than a UI dance.
  3. Commit with a message that says why. “Add retry to CRM sync after timeout incident” is worth more than “update workflow”.
  4. Review. Someone reads the normalised diff. This is where a review catches a credential pointed at the wrong environment.
  5. Deploy to staging via the API, with staging credentials of the same names.
  6. Test with real-shaped data, particularly the error paths.
  7. Deploy to production. Same mechanism, no manual editing.
  8. Tag it, so “put it back to how it was on Tuesday” is one command.

Steps 5 and 7 use n8n’s API to create or update workflows from the JSON. That is the piece worth automating early; done by hand it will be skipped under pressure.

Where n8n’s own tooling fits

n8n includes source-control functionality in its higher tiers that pushes and pulls workflows against a git repository directly. Where you have it, use it - it removes the export scripting and handles the promotion mechanics.

Check two things before relying on it: what exactly it puts in the repository, and how it handles credential references across environments. The concerns above do not disappear because the tooling is built in; they are just handled somewhere you did not write.

Where you are on a tier without it, the export-and-commit path above is straightforward and it is what most self-hosted deployments run.

What to also keep in the repository

The workflow JSON is not the whole system. Alongside it:

  • The custom nodes, if any, as their own package
  • The environment configuration, minus secrets
  • A README naming the credentials each workflow expects and what they point at in each environment
  • The error workflow, which is a workflow like any other and is frequently forgotten
  • A note on the trigger - what fires this, on what schedule or from which system

That last item saves genuine time. A workflow’s JSON tells you what it does and not what causes it to run, and reconstructing that from a production instance during an incident is slow.

Why we do this on every engagement

Workflows go in the client’s repository from the first commit, with a normalisation step and API-based deployment.

The reason is handover rather than engineering aesthetics. A client whose workflows exist only inside a running n8n instance has automation they cannot review, cannot roll back, and cannot hand to another supplier without exporting by hand and hoping. That is a soft form of lock-in even when nobody intended it, and it is inconsistent with what we publish about ownership - see what you own after an AI project.

The practice that gets resisted is production being deploy-only, because editing production directly is faster when something is broken at 4pm. The honest counter: it is faster once, and the cost is that the repository silently stops matching reality, so the next rollback restores something that was never running. Where an emergency edit genuinely happens, the rule is that it gets exported and committed the same day, with a message saying it was an emergency.

Where our default is arguably too heavy: for a single-person deployment with five workflows, the full promotion path is more process than the risk justifies. Export and commit is still worth it; staging probably is not.

When this is overkill

One person, few workflows, low stakes. Export and commit occasionally. Skip the pipeline.

Genuinely experimental work. Do not put a promotion process around something you may delete next week.

n8n Cloud with one environment. The promotion path assumes environments to promote between. Version control still applies; the deployment mechanics simplify.

Frequently asked questions

Are credentials really never in the export?

Correct - only references by id and name. Which is why the repository is not a backup and the database is.

How do we handle credentials across environments?

Same names in each environment, pointing at that environment’s systems. The workflow then works unchanged wherever it is deployed.

Can we diff workflows meaningfully?

With normalisation, yes - parameter and connection changes read clearly. Without it, position noise buries the signal.

Should the error workflow be versioned too?

Yes, and it is the one most often missed because it is not attached to any particular workflow. It is production logic like anything else.

What about workflows built by non-technical staff?

Give them a development instance and make the export step somebody’s job. Do not let the answer be that some workflows are not tracked - that is precisely the set that will need a rollback.

Next step

If your production workflows exist only inside a running instance, the export-and-commit step is an afternoon and it changes what is possible during an incident. The n8n and Make engagement builds workflows under version control in your repository from the start.

Related: Self-hosted n8n · Running n8n in production · What you own after an AI project · Business process automation

Let's talk

Got a workflow this applies to?

Describe it in a couple of sentences. We will tell you whether it is worth automating, what we would build, and roughly what it takes.