Module 3: Exporting, Normalizing, and Structuring the Repository
2. Exporting with n8n's CLI
Description
By the end of this lesson you will be able to export any workflow from your instance with a terminal command, without touching the editor: one at a time by its identifier, or all at once, each in its own file. You'll know what n8n's CLI is, how it's run depending on whether your instance is installed with npm or inside Docker, and what each of n8n export:workflow's flags does. And you'll understand the most silent mistake of all —exporting against an empty instance and believing you have no workflows— and how to avoid it.
This matters because exporting is the first link in the whole module's chain. Everything that comes after —separating credentials, normalizing, structuring, documenting, automating— operates on the file you produce here. If this step isn't reproducible, nothing else is. And there's a practical detail: exporting by command is the only kind that can be automated and put into a script, which is where we arrive in lesson 7. The editor's button can't be automated; a command can.
Connection to the module: lesson 1 showed you why the editor's download button produces napkins. This one gives you the tool that produces cookbooks: the CLI. Here you export workflows; lesson 3 exports credentials, which are a separate and dangerous case. Lesson 4 takes the JSON you get out here and normalizes it. So pay attention to the exact shape of the output, because in the next lesson we're going to intentionally mess it up to learn how to clean it.
On flags and versions. This guide was written with n8n 2.x in July 2026. n8n publishes minor versions almost every week, and every so often adjusts a command's behavior. Every flag you'll see here I confirmed against the official documentation on that date, and I'm going to tell you which ones. Even so, adopt a habit worth gold starting now: before trusting a flag, run
n8n export:workflow --helpon your own instance. That command prints the real list of flags for your version. If something in this guide doesn't match what--helpshows you, your instance rules. It's not that the guide is wrong; it's that the software moves.
What n8n's CLI is
Let's start with the name. CLI stands for command-line interface. It's the way of giving a program orders by typing text into a terminal, instead of clicking buttons. You already used a CLI in Module 2 without calling it that: every time you typed git commit you were using Git's CLI.
n8n, besides its visual browser editor, ships its own CLI. It's the same n8n, with the same capabilities, but operated by commands. While the editor is good for building workflows —dragging nodes, connecting, testing— the CLI is good for operating on them in bulk: exporting them all, importing them, moving them between instances. They're two doors into the same house.
Think of it as the difference between a store's counter and its warehouse. At the counter (the editor) you serve one customer at a time, calmly, looking at each product. In the warehouse (the CLI) you move whole crates at once with a forklift. Nobody does the warehouse's entire inventory by serving customers one by one at the counter; that's what the forklift is for. The CLI is your forklift.
Anatomy of a CLI command
Before running anything, let's look at how a command is put together, because once you recognize the pieces, every CLI command reads the same way. Take this one, which is the one you're about to run:
n8n export:workflow --all --output=./workflows --separate --pretty
Four kinds of piece:
n8n— the program's name. It tells the terminal: "call n8n." It's like sayinggitto call Git.export:workflow— the command. It reads as "topic : action": the topic isexport, and the object isworkflow. Its sibling, which you'll see in lesson 3, isexport:credentials. n8n's CLI has several commands in thistopic:objectshape.--all,--separate,--pretty— the flags. Options that modify how the command behaves. They start with two dashes.--allsays "export everything";--prettysays "with readable formatting." A flag can stand alone because it's a yes/no switch.--output=./workflows— a flag that takes a value. Here--outputisn't yes/no: it needs to know where to write, and that "where" is./workflows. The value comes after the equals sign (or, in some cases, separated by a space).
That ./workflows is a path: it tells n8n which folder to put the files in. The ./ means "starting from the folder I'm currently standing in, in the terminal." If you're standing in /home/ana/cumbre-automations, then ./workflows is /home/ana/cumbre-automations/workflows. It's the same path logic you used with Git.
When you see a long command, don't panic: it's always this. The program, the command, and a list of flags fine-tuning it. Nothing more.
How to run the CLI: two worlds
Here's the point that confuses people the most and where most people lose an afternoon. The exact way of running the CLI depends on how n8n is installed on your machine, and there are two very different scenarios. Before exporting anything, you have to know which one you're in.
The underlying reason is this: n8n's CLI needs to talk to the same database where your workflows live. Your workflows aren't in a loose file; they're saved in your instance's database. The export:workflow command pulls them out of that database and writes them to files. So the command has to run in the same place as your instance, with access to the same database. If you run it somewhere else, it's going to read a different database —probably empty— and export nothing.
Scenario A: n8n installed with npm on your machine
If you installed n8n directly on your system with npm (npm install n8n -g) and start it by typing n8n in the terminal, then the CLI is already at your disposal. You run the commands as-is:
n8n export:workflow --all --output=./workflows --separate
Direct. The n8n program you already have installed includes every CLI command.
Scenario B: n8n running inside Docker
This is today's most common scenario, and the one this guide defaults to —Module 4 sets up the environments precisely with Docker. If your n8n runs inside a Docker container, the CLI also lives inside that container, not on your machine. To run it, you have to "go into" the container and execute the command in there. The tool for that is docker exec.
The official form is this:
docker exec -u node -it <container-name> n8n export:workflow --all --output=./workflows --separate
Let's break it down, because every piece has a reason:
docker exec— "execute a command inside a container that's already running." It's the door into the container.-u node— the-uflag (for user) says which user to run the command as inside. n8n's official image runs as a user callednode, and the configuration files belong to that user. If you don't put-u node, the command likely runs asroot, giving you weird permission errors. Always include it.-it— two flags together:-i(interactive) and-t(terminal). They give the command a real terminal inside the container, so you can see the output and respond if it asks you something. It's a standard combination; memorize it as a block:-it.<container-name>— your n8n container's name. It's not literal: you have to replace it with the real name, which you find out in a second.n8n export:workflow --all ...— from here on, it's the same command as scenario A. Everything after the container's name runs inside it.
How do you know your container's name? With this command, which lists the containers currently running:
docker ps
What to expect: a table with one row per container. The last column, NAMES, has the name you need. If you started n8n with a docker-compose.yml, the name is usually something like n8n or cumbre-automations-n8n-1. Copy that exact name and put it in the command.
The trap: npx against an instance that's already running
You're going to find tutorials that say "run npx n8n export:workflow." npx is a tool that downloads and runs an npm program without installing it permanently. And here's the trap: if your real n8n runs in Docker, and you run npx n8n export:workflow --all on your machine, npx is going to start a new, clean, empty n8n, with its own database with not a single workflow. The command is going to finish without error and export... nothing. Zero files, or an empty file. And you're going to think you lost your workflows.
You didn't lose them. You simply asked the wrong n8n for its workflows —a freshly born one— instead of your real instance.
The rule for never falling into this is simple: the export command has to run in the same place your instance lives. If your n8n is in Docker, use docker exec. If you installed it with npm and that same n8n is the one you start every day, run the command directly. npx is only useful if you don't have a persistent instance and you're doing a disposable test, which isn't our case.
Exporting workflows: n8n export:workflow and its flags
Now, the module's central command. These are its flags, all confirmed against n8n's official documentation as of July 2026:
| Flag | What it does |
|---|---|
--all | Exports every workflow on the instance. |
--id=<id> | Exports a single workflow, the one with that identifier. |
--output=<path>, -o | Where to write: a file (if it's a single one, together) or a folder (if you use --separate). |
--separate | Exports one file per workflow. Requires --output to be a folder. Designed precisely for versioning. |
--pretty | Formats the JSON readably (with indentation), instead of all on one line. |
--backup | Shortcut: turns on --all --pretty --separate at once. You can optionally add --output. |
--published | Exports the published version (the one running in production) instead of the current draft. |
--version=<versionId> | Exports a specific historical version. Doesn't combine with --all or --published. |
--help | Prints this same flag list for your version. |
Notice something before running anything: by default, without --pretty, the JSON comes out compressed on one giant line. It works the same, but it's unreadable and, worse, gives horrible diffs in Git —the entire file is "one line," so any change looks like everything changed. For versioning, --pretty isn't optional. You're going to include it always. And the --backup shortcut sets it for you, along with --all and --separate, which are the standard combination for a repository.
Worked example: exporting Cumbre's order-triage
Let's do it in full. I'm going to assume scenario B (Docker), which is the guide's, and I'll also show you each command in scenario A's direct form in parentheses. If you have an instance handy, follow along; if not, read it and do it later.
Step 0 — Position yourself in your repository. In the terminal, stand in Cumbre's repository folder, the one you put under Git in Module 2:
cd cumbre-automations
This matters, because --output=./something paths are relative to where you're standing. If you're not in the repository, the files land somewhere else.
Step 1 — Find order-triage's id. To export a single workflow with --id, you need its identifier. The fastest way: open order-triage in the editor and look at the browser's address bar. The URL looks similar to https://your-n8n/workflow/aBcD1234EfGh5678. That last part, aBcD1234EfGh5678, is the workflow's id. Copy it.
(If you'd rather not depend on the browser, in a moment you're going to export all of them with --separate and each file is going to carry the id as its name; you can read it from there too.)
Step 2 — Export only order-triage to a file. With the id in hand:
docker exec -u node -it n8n n8n export:workflow --id=aBcD1234EfGh5678 --output=order-triage.json --pretty
(Scenario A, direct: n8n export:workflow --id=aBcD1234EfGh5678 --output=order-triage.json --pretty)
What to expect: the terminal prints a confirmation line, something like Successfully exported 1 workflow., and the file order-triage.json shows up in your folder. Open it: it's order-triage's JSON, with readable indentation thanks to --pretty, with its Webhook, its AI Agent node, and its HTTP Request node. It's the same workflow the editor's button would give you, but you got it with a repeatable command, not with clicks.
Pause for a second on the name order-triage. In the command, you chose the file's name (--output=order-triage.json). That's because you exported a single one and were able to name it. When you export all of them at once, you won't be able to name them one by one, and there n8n uses the ids as names. Keep that difference in mind; lesson 5 handles giving the repository nice names.
Step 3 — Export the entire instance, one file per workflow. Now the form you're really going to use for the repository: every workflow, each in its own file, inside a workflows/ folder.
docker exec -u node -it n8n n8n export:workflow --all --separate --output=./workflows --pretty
(Scenario A: n8n export:workflow --all --separate --output=./workflows --pretty)
What to expect: the terminal confirms something like Successfully exported 4 workflows. —Cumbre's four— and four files show up inside workflows/. Their names aren't order-triage.json, they're the ids: something like aBcD1234EfGh5678.json, Xy9Z...json, and so on. Each file is a complete workflow, readably formatted. We'll fix those ugly names in lesson 5; for now, what matters is that with a single command you pulled out the entire instance.
Notice why --separate matters for versioning. Without it, n8n puts all four workflows into one giant file. With it, every workflow is its own file. Why does it matter? Because when you change only order-triage, you want the git diff to touch only order-triage's file, not a giant file mixing all four. One file per workflow is one diff per workflow. n8n's documentation describes --separate exactly that way: "useful for version control."
Step 4 — The --backup shortcut. Since --all --pretty --separate is the combination you're going to use every time, n8n gives you a shortcut:
docker exec -u node -it n8n n8n export:workflow --backup --output=./workflows
What to expect: exactly the same result as step 3 —four readable files, one per workflow. --backup is --all --pretty --separate written shorter. Use it when you want the complete backup; use --id when you want a specific one.
You just did, with four commands, what with the editor would be dozens of clicks spread across four sessions. And most importantly: these four commands can be pasted into a script and run on their own. That's lesson 7.
A Docker detail: where the files land
If you run the CLI inside a container with docker exec, there's a subtlety that confuses people the first time and is worth being clear on: the files you export land inside the container, not on your machine. When you write --output=./workflows, that ./workflows is a folder inside the container, not your repository's folder on your disk. It makes sense: the command runs inside, so it writes inside. But it means exporting isn't enough; afterward you have to get the files out of the container into your repository.
There are two ways to solve it, and it depends on how you set up your n8n.
Way 1 — export to a folder that's already shared (a volume). Docker lets you "share" a folder between your machine and the container: what gets written on one side shows up on the other. That's called a volume, and n8n almost always mounts one to save its data (typically the container's ~/.n8n folder). If you export to a path falling inside that shared volume, the files automatically show up on your machine, no extra steps. It's the cleanest way, and the one lesson 7's script is going to prefer.
Way 2 — copy the files out with docker cp. If you exported to an internal folder that isn't shared, you use docker cp to copy them to your repository:
docker cp n8n:/home/node/workflows ./workflows
Let's break it down: docker cp copies files between the container and your machine; n8n:/home/node/workflows is "the workflows folder inside the container called n8n"; and ./workflows is where to bring them on your disk. It's the same old cp, but crossing the container's border.
Don't worry about mastering this right now. What matters is recognizing the symptom: if you export with docker exec and don't see the files in your repository, they didn't fail —they're inside the container. Module 4, when it sets up the environments with Docker Compose, leaves the volumes properly set up so this step is transparent. For now, if you're practicing, Way 2 with docker cp always works.
Why the CLI beats the editor's button
It's worth putting side by side what you gained, because it isn't just convenience:
| Editor's button | n8n's CLI | |
|---|---|---|
| How many at once | One by one, by hand | All in one command (--all) |
| Reproducible | No: depends on your clicks | Yes: same command, same result |
| Automatable | No | Yes: goes into a script (lesson 7) |
| One file per workflow | Yes, but one at a time | Yes, all at once (--separate) |
| Readable format | Yes | Yes (--pretty) |
| Published vs draft version | Not easily distinguished | Yes (--published) |
The row that weighs the most is "automatable." The editor's button is a dead end: it's good for today, but you can't build anything on top of it. The CLI is a foundation: normalization, the export script, and later, promotion between environments all stand on it. Learning the CLI isn't learning "another way to export"; it's opening the door to everything automatable in the rest of the guide.
There's an honest nuance worth recognizing: for exporting a single workflow once, the editor's button is faster —two clicks versus typing a command. Nobody opens a terminal to download one loose workflow. The CLI wins when the work is repeated or in bulk: backing up all four workflows every week, or exporting before every commit, or running the process with nobody watching. It's the same difference as between washing one dish by hand (fast, one dish) and using the dishwasher (worth it when there are many, and it runs on its own). Don't abandon the button for one-off tasks; adopt the CLI for everything that repeats. And since versioning a repository is, by definition, something that repeats with every change, the CLI is this module's tool.
A glimpse at the way back: import:workflow
Exporting is pulling workflows out of the instance into files. The reverse operation —putting files back into an instance— exists and is called n8n import:workflow. We're not going to use it in depth in this module, but it's worth knowing it's there, because it's the half Module 6 uses to promote a workflow from one environment to another.
Its main flag is --input, the mirror of --output: where to read the files from. And it also has --separate, for reading a folder full of individual files:
n8n import:workflow --separate --input=./workflows
For now just hold on to the idea of symmetry: export pulls out to files with --output; import puts them back in from files with --input. When in Module 6 you promote order-triage from staging to prod, you're going to be exporting from one instance and importing into the other. What you're learning today is the foundation.
Common mistakes
Exporting against an empty instance and believing you lost your workflows (practical, and the most common one). What happens: your n8n runs in Docker, but you run npx n8n export:workflow --all on your machine. The command finishes without complaint and exports zero workflows. You panic. Why it happens: npx started a new, empty n8n, with its own database, and you asked that one. Your real instance, the Docker one, never found out. How to spot it: if the command says Successfully exported 0 workflows but you know you have workflows, you didn't export against your instance. How to fix it: use docker exec -u node -it <container> n8n export:workflow ... to export against the instance that's actually running. The rule: the command goes where the database lives.
Using --separate without giving it a folder with --output (practical). What happens: you run n8n export:workflow --all --separate and get an error or an unexpected result. Why it happens: --separate needs to know which folder to put each workflow's file in; without --output pointing to a folder, it has nowhere to write them. How to spot it: if you put --separate and didn't put --output=<a-folder>, that's the problem. How to fix it: add --output=./workflows. The documentation is explicit: --separate requires setting a folder with --output.
Forgetting --pretty and ending up with an unreadable diff (practical). What happens: you export without --pretty, everything comes out compressed on one line, and when you do git diff to review a small change, Git flags "the whole line" as modified, meaning the entire file. Why it happens: without --pretty, n8n writes the JSON in the most compact format, with no line breaks. Git compares line by line, and if the whole file is one line, any change looks like a total change. How to spot it: open the exported file; if it's one very long line, you're missing --pretty. How to fix it: export again with --pretty (or with --backup, which already includes it). This habit is half the road to clean diffs; the other half is lesson 4's normalization.
Running the command without -u node inside Docker (practical). What happens: you use docker exec but without -u node, and you get permission errors, or the files end up with the wrong owner and afterward you can't edit them comfortably. Why it happens: without -u node, the command usually runs as root, which isn't the user owning n8n's configuration inside the container. How to spot it: if you see messages about "permission denied" or files you can't modify, check whether you put -u node. How to fix it: always include -u node in your n8n docker exec commands. Memorize it as part of the fixed block: docker exec -u node -it.
Exercises
Exercise 1 — Identify your scenario and run --help. On your own machine, find out whether your n8n runs with npm or in Docker. If it's Docker, run docker ps and note your container's exact name. Then run n8n export:workflow --help (directly, or with docker exec -u node -it <container> in front) and compare the flag list it prints against this lesson's table. Note whether any flag is extra or missing.
See solution
There's no single answer, because it depends on your installation and your version. What most people find is that the core flags —--all, --id, --output, --separate, --pretty, --backup— show up the same, and there might be one or two new flags or slightly different descriptions depending on the version.
Why it works: this exercise's real goal isn't exporting anything, it's installing the habit of verifying against your own instance before trusting a command. Documentation —this guide included— is always one step behind the software. --help is the only source that's exactly up to date with the version you have in your hands. That reflex is going to serve you when you read this guide two years from now with n8n 3.x on screen.
Exercise 2 — Build the right command for three situations. Without running anything, write the complete export:workflow command (in the form matching your scenario) for each case: (a) backing up the whole instance, each workflow in its own file, readable, inside ./workflows; (b) exporting only the workflow with id Xy9Z00Kw to a single file called order-triage.json with readable formatting; (c) exporting everything, but the published version in production, not the drafts.
See solution
(a) n8n export:workflow --backup --output=./workflows — --backup is already --all --pretty --separate, which is exactly what's asked. It's also fine written out long: n8n export:workflow --all --separate --pretty --output=./workflows.
(b) n8n export:workflow --id=Xy9Z00Kw --output=order-triage.json --pretty — a single workflow via --id, to a chosen-name file, with --pretty. Here --output is a file, not a folder, because it's just one.
(c) n8n export:workflow --all --published --separate --pretty --output=./workflows — --published changes which version gets exported (the one running in production instead of the draft). Watch out: --published doesn't combine with --version, because they're two different ways of choosing which version to pull.
If your instance is Docker, all three get docker exec -u node -it <container> in front.
Why it works: building the command from memory forces you to tell apart what selects which workflows (--all, --id, --published) from what controls how they're written (--separate, --pretty, --output). These are two different questions, and every export command is an answer to both.
Exercise 3 — Diagnose an empty export. A coworker writes to you: "I ran npx n8n export:workflow --all --output=./workflows on my laptop, no error came up, but the workflows folder is empty. Did my workflows get deleted?" Their n8n runs in a Docker container. Explain in two or three sentences what happened and what command they should run.
See solution
Nothing got deleted; their workflows are safe and sound in the container. What happened is that npx n8n started a new, empty n8n instance on their laptop, with its own database with no workflows, and exported that one —hence the empty folder and "zero errors." It never touched their real Docker instance.
What they should run is the command inside their container: first docker ps to see the container's name, and then docker exec -u node -it <name> n8n export:workflow --all --separate --pretty --output=./workflows. That way the command runs in the same place the database with their real workflows lives.
Why it works: this is, by far, the number one stumble with n8n's CLI, and the most distressing one because it looks like you lost your work. Having the rule crystal clear —"the command runs where the database lives"— saves you the scare and lets you diagnose it in seconds when it happens to someone on your team.
Summary and next step
In this lesson you met n8n's CLI: the command door into the same instance you manage in the editor, good for operating in bulk on what the editor does one at a time. You learned to read a command by its pieces —the n8n program, the export:workflow command, the --... flags, and the ones taking a value like --output— and to run it in both worlds: direct if you installed with npm, or with docker exec -u node -it <container> if it runs in Docker. You memorized the rule that avoids the most common scare: the export command has to run where the database lives, so npx on your machine against a Docker instance exports an empty one. And you exported order-triage and Cumbre's entire instance with the flags confirmed against the official docs: --all, --id, --separate, --pretty, --output, --backup, --published, --version, plus the essential --help to verify against your version.
Before moving on you should be able to: say which scenario your instance is in and run export:workflow --help; build from memory the command for backing up the entire instance into separate, readable files; and explain why running npx n8n export on your laptop against a Docker n8n exports nothing.
Lesson 3 is the module's most delicate one. You're going to export what isn't visible in the editor and what must never touch the repository: credentials. You're going to meet export:credentials and its --decrypted flag, which is a real security risk; you're going to understand what N8N_ENCRYPTION_KEY is and why a credential never gets pushed to the repo, not even encrypted; and you're going to set up the .gitignore that shields your secrets from the first commit. It's the lesson that separates someone who delivers a secure system from someone who, without knowing it, publishes their client's CRM key.
Resources
- Use the command line — n8n Docs — the official CLI reference: every command, every
export:workflowandimport:workflowflag, and how to run them withdocker exec. This is the source I confirmed for this lesson. - Export and import workflows — n8n Docs — the comparison between exporting from the editor and from the command line, with examples of both.
- Docker installation — n8n Docs — how n8n runs inside Docker, useful for understanding why the CLI lives inside the container and not on your machine.
- Release notes 2.x — n8n Docs — the version history, for confirming which one you have and whether any flag changed from what this lesson says.