October 31, 2026 · 13 min read

n8n Workflow Tutorial: Build, Test, Export

Title card for “n8n Workflow Tutorial: Build, Test, Export” with three boxes joined by arrows and a lightning bolt for the trigger.
Graphic: HumanITy

An n8n workflow is a chain of nodes: a trigger starts it, the following nodes fetch, check and reshape data, and at the end there is a result. You build one in the editor by adding a trigger, attaching and connecting nodes, and testing with "Execute workflow". To make it run on its own, you publish it: since n8n 2.0 that is called "Publish" and replaces the old active toggle. Every workflow is a JSON file that you can export, import into another instance or create through the API.

I ran n8n in my own Kubernetes cluster for a long time and built processes in it. The two examples below come from a test on 31 October 2026 with n8n 2.45.7 in Docker on a Mac. Neither needs credentials, so you can rebuild both exactly. What a workflow is in general is covered in What Is a Workflow? Simply Explained. How to set up n8n yourself is in n8n Self-Hosted: Docker or Kubernetes.

What an n8n workflow is: trigger, nodes, data

The docs define a workflow as "a collection of nodes connected together to automate a process". Three terms are enough to start:

  • Trigger: the first node. It decides when the workflow runs, for example on a schedule, via webhook or manually. According to the docs, every production workflow needs at least one.
  • Nodes: the steps in between. Each node receives data as JSON, works on it and passes JSON on.
  • Expressions: values in double curly braces, for example {{ $json.body.email }}. They give you access to the data of the previous node.

For testing, n8n on your own machine is enough. In the test it ran with essentially this command (plus time zone settings and its own names): docker run -d --name n8n -p 127.0.0.1:5678:5678 -v n8n_data:/home/node/.n8n n8nio/n8n:2.45.7. Binding to 127.0.0.1 only means nobody on your network can reach the instance. The first time you open http://localhost:5678 you create an owner account that exists only in this instance.

Build an n8n workflow: contact request via webhook

The first example accepts a request from a form, checks the required fields and replies with a confirmation or a rejection. It has five nodes:

Node Setting in the test
Webhook method POST, path anfrage, Respond: "Using 'Respond to Webhook' Node"
If ("Pflichtfelder da?", required fields present?) {{ $json.body.email }} contains @ and {{ $json.body.name }} is not empty
Edit Fields (Set) prepare the fields, see below
Respond to Webhook (true) JSON with status "angenommen" (accepted), code 200
Respond to Webhook (false) JSON with status "abgelehnt" (rejected), code 400

The Edit Fields node set these four fields (field names in German, as in the export):

name      {{ $json.body.name.trim() }}
email     {{ $json.body.email.trim().toLowerCase() }}
anliegen  {{ $json.body.nachricht }}
eingang   {{ $now.toISO() }}

The docs recommend exactly this split: prepare data in Edit Fields first instead of spreading complex expressions across many nodes. Later you replace the reply with a node that writes the request to your CRM or a spreadsheet. Only then do you need credentials.

Test and publish: test URL and production URL

Every Webhook node has two addresses. The test URL (/webhook-test/anfrage) accepts a call after you select "Listen for test event" in the editor and, according to the docs, stays open for 120 seconds. You then see the data right on the canvas. The production URL (/webhook/anfrage) is only registered once you publish the workflow.

Before publishing, the production URL answered like this in the test:

HTTP 404
{"code":404,"message":"The requested webhook \"POST anfrage\" is not registered.",
 "hint":"The workflow must be active for a production URL to run successfully. ..."}

The hint still talks about the active toggle; what it means is the Publish button at the top right. After publishing, four calls returned these responses:

$ curl -X POST http://127.0.0.1:5678/webhook/anfrage -H 'Content-Type: application/json' \
    -d '{"name":"  Erika Muster ","email":" Erika@Example.com","nachricht":"Bitte um Rückruf wegen Angebot"}'
{"status":"angenommen","name":"Erika Muster","eingang":"2026-10-31T11:23:46.830+01:00"}   HTTP 200

-d '{"name":"Max","email":"max-at-example.com"}'      -> {"status":"abgelehnt",...}   HTTP 400
-d '{"name":"Max"}'                                   -> {"status":"abgelehnt",...}   HTTP 400
GET instead of POST -> "This webhook is not registered for GET requests."             HTTP 404

The Edit Fields node removed the spaces and capital letters. Production calls don't appear on the canvas but in the workflow's "Executions" tab.

Error cases from the test

Three things behaved differently from what you might expect.

Missing fields are not an error. In the first version, the If node only checked the email. A request without a name went through: $json.body.name.trim() returned null, the reply was "angenommen" with "name":null, and the execution showed "Success". Only the second condition "name is not empty" caught it. So check every required field explicitly in the If node.

Rejected doesn't mean failed. The HTTP 400 replies show up as "Success" in the executions list, because the workflow did what it was built to do. If you want to count rejections, you have to record them yourself.

A real error stops the workflow. More on that in the second example.

Second n8n workflow example: fetch data from an API

The second example starts manually, calls the Open-Meteo weather API via HTTP Request (no key needed; free for non-commercial use only, per its terms), picks three fields and branches with an If node: below 10 degrees "Jacke mitnehmen" (take a jacket), otherwise "Keine Jacke nötig" (no jacket needed).

Connect the nodes in this order: Manual Trigger → Wetter abrufen → Felder auswählen → Unter 10 Grad? Connect the true output to ‚Hinweis: kalt‘ and the false output to ‚Hinweis: mild‘. The node names and field names below match the German names used in the test:

Node Setting
HTTP Request „Wetter abrufen“ GET https://api.open-meteo.com/v1/forecast; enable query parameters
Query parameters latitude=52.52, longitude=13.41, current=temperature_2m,wind_speed_10m, timezone=Europe/Berlin
Edit Fields „Felder auswählen“ zeit (String): {{ $json.current.time }}; temperatur (Number): {{ $json.current.temperature_2m }}; wind_kmh (Number): {{ $json.current.wind_speed_10m }}
If „Unter 10 Grad?“ Number comparison: {{ $json.temperatur }} is less than 10
Edit Fields „Hinweis: kalt“ hinweis (String): Jacke mitnehmen: {{ $json.temperatur }} °C; enable „Include Other Input Fields“
Edit Fields „Hinweis: mild“ hinweis (String): Keine Jacke nötig: {{ $json.temperatur }} °C; enable „Include Other Input Fields“

For the error branch, set the HTTP Request node’s ‚On Error‘ option to ‚Continue (using error output)‘. Connect its error output to another Edit Fields node. Set fehler to {{ $json.error.message }} and zeitpunkt to {{ $now.toISO() }}. To reproduce this branch, temporarily replace latitude=52.52 with latitude=abc, then restore it to 52.52.

The last node returned in the test:

{ "zeit": "2026-10-31T11:15", "temperatur": 10.9, "wind_kmh": 9.5,
  "hinweis": "Keine Jacke nötig: 10.9 °C" }

Then the error case: latitude abc instead of 52.52. The HTTP Request node failed and the execution showed "Error":

Bad request - please check your parameters
400 - "{\"reason\":\"Invalid value: Cannot initialize Float from invalid String value abc\",\"error\":true}"

For cases like this, every node has an "On Error" setting. With "Continue (using error output)" the node gets a second output. In the test, an Edit Fields node "Fehler festhalten" (record error) hung off it and stored the error message and time; the execution then ran to the end. To get notified about every failure, the docs say to create a separate error workflow starting with the Error Trigger and select it in the workflow settings under "Error workflow".

Import and export n8n workflows

Workflows are JSON. In the editor, the workflow menu (three dots) has "Download", "Import from File" and "Import from URL". For several instances or backups, there is the command line inside the container:

docker exec -u node n8n n8n export:workflow --id=<ID> --pretty --output=/tmp/anfrage.json
docker exec -u node n8n n8n import:workflow --input=/tmp/anfrage.json
docker exec -u node n8n n8n list:workflow

Three observations from the test:

  1. The export contains more than the workflow. The shared field held the project name with the account's name and email address. The docs also warn that credential names and headers imported from cURL can end up in the JSON. Read the file before you pass it on.
  2. Same ID overwrites. Importing the same file again did not create a second workflow; there were still two entries. Only without the id field did a copy appear. The docs say workflows with the same ID are overwritten.
  3. n8n execute next to the running server failed at first: "n8n Task Broker's port 5679 is already in use." With -e N8N_RUNNERS_BROKER_PORT=5681 the command ran.

The public API does the same with a key (in n8n under Settings, n8n API, with limited scopes): POST /api/v1/workflows creates, POST /api/v1/workflows/<id>/activate publishes. Careful: in the test, a PUT on a published workflow put the new version live immediately. In the editor, by contrast, changes stay a draft until you click Publish. The API itself is explained in What Is an API? Explained for Beginners.

n8n workflow templates: use them with care

On 31 October 2026, the template library at n8n.io/workflows listed 13,825 community workflows in its page title. According to the docs, a template is a ready-made workflow where you usually have to add credentials and adjust settings. Import it into a test instance first, read every node, and check HTTP Request and Code nodes in particular: where does the data go?

Frequently asked questions

What is an n8n workflow?

A series of connected nodes started by a trigger. n8n stores it as JSON. Every run is an execution; on paid plans, the docs say, automatic runs count towards the quota.

Why does my webhook return 404?

Usually the workflow isn't published or the method is wrong. The production URL only exists after Publish, the test URL only for 120 seconds after "Listen for test event". A GET on a POST webhook also returned 404 in the test.

How do I import an n8n workflow?

In the editor via the workflow menu with "Import from File" or "Import from URL", on the server with n8n import:workflow --input=<file>. Check the file for names, addresses and credentials first.

Is there an n8n tutorial for beginners?

The official docs have quickstart guides. This tutorial gives you two small workflows that run without external service accounts; you still create the local n8n owner account when setting up the instance. Rebuild them and then change one step at a time; that way you learn the interface on a real task.

Do I need credentials for an n8n workflow?

Not for the logic. Webhook, If, Edit Fields and HTTP Request to an open API run without them. You only need credentials for services that require a login, such as a mailbox, a CRM or a language model.

Where to go next

Once your first workflow runs, the next step is obvious: an AI building block inside it. How an agent is built in n8n and what I took away from running it is in n8n AI Agent: What I Learned Building One. Whether a workflow builder is the right tool at all is compared in n8n, Make, or Claude: What For?. What executions cost in the cloud is in n8n Pricing Explained: Cloud vs Self-Hosted. Everything about agents is collected on the AI agents page.

If your workflow gets stuck: bring the error message or the export file (without credentials) to the calls of the AI Employee Framework, and we look at it together.

Kevin Welter

Kevin Welter

Developer, IT architect, author of technical books (Kubernetes, cloud infrastructures) and speaker. Runs his business with an AI workforce of fourteen AI employees and shows solo business owners in his community how to hire their first AI employee.

More about AI employees

Your first AI employee up and running within an hour

Join the community

$5/month founding price, first 50 members