Module 5: Testing in Sandbox Before Production
5. Pinned data and execution replay
Description
By the end of this lesson you will be able to make an order-triage test reproducible: run it today, tomorrow, and after a change, always with exactly the same inputs, so you can attribute any difference in the result to the change and not to chance. You're going to know how to use pinned data to freeze a node's output, with its limits clearly marked; and you're going to use n8n's debugging engine —"Debug in editor" and "Copy to editor"— to take a past execution and run it again in the editor, not to put out a fire in production, but as a repeatable testing tool.
This matters because reproducibility is the fourth property of a real test (lesson 1), and it's what turns your synthetic data (lesson 3) from "a piece of data I built once" into "a test case I run whenever I want, identical." Without fixed inputs, every run is a different experiment: you change the workflow, run it, see a different result, and you don't know whether it changed because of your adjustment or because the input was different. With fixed inputs, you isolate the variable —the workflow— and the test starts telling the truth.
Connection to the module: in lesson 3 you designed the synthetic data; this lesson freezes it so the test repeats identically. It's the checklist's fourth point. It connects backward to Module 3: you're going to see that pinned data is exactly the pinData field you learned to delete when normalizing the JSON, and that tension —useful for testing, noise for versioning— has to be resolved with judgment. And it connects forward to lessons 6 and 7: pinning the AI agent's output is what makes its tests deterministic, and fixed inputs are the foundation for writing stable assertions.
The frozen frame and the black box
Think of a food photographer wanting to perfect a dish's lighting. If they use a real plate of food, they have a problem: the food changes between one photo and the next —the ice cream melts, the salad wilts, the steam disperses. When they adjust a light and take another photo, they don't know whether the difference came from the light or from the dish no longer being the same. So professional studios use fake prop food: a resin plate that looks identical but never changes. Now, when the photographer adjusts the light and compares, they know the only thing that changed was the light, because the dish is exactly the same in both photos. They froze the variable they didn't want moving.
Pinning data is putting prop food into your workflow. You take a node's output and freeze it: you tell n8n "from now on, on every manual run, don't execute this node; use exactly this data I'm freezing." The Webhook node that would normally wait for a new order starts always returning the same frozen synthetic order. Now, when you change the agent's logic and run again, you know the input was identical —the same order, byte for byte— so any difference in the classification came from your change, not from a different piece of data. You froze the variable you didn't want moving.
Let's define the term precisely. Pinned data is a node's output that n8n saves and reuses on manual runs, instead of running the node again to get it. "Pin" is n8n's word for "nail in place," like you pin a note to a corkboard so it doesn't move while everything around it changes. While a piece of data is pinned, the node doesn't run: n8n substitutes its output with the frozen data and continues the flow.
Notice the double benefit. Reproducibility: always the same input, so you can compare. And savings: if the pinned node was one calling an API with quota or cost —the CRM, or the agent's language model— pinning its output means you no longer call it on every test. You freeze one good response once and test a hundred times against it, without spending quota or money. This connects directly with lesson 4's dry run and with lesson 6's zero cost: pinning data is also a way of not burning budget while testing.
Worked example: pinning a test order at the Webhook
Let's see it with order-triage. You want to test the large-order-manual-review case over and over while adjusting the agent's prompt, without having to send the order via HTTP every time.
Step 1 — Get the data once. You send the synthetic order to the Webhook a first time (by hand, or with lesson 3's generator), so the Webhook node produces its output: the 52,000-peso order.
Step 2 — Pin it. In the node's output view (OUTPUT), you click the pin icon. n8n freezes that output and shows you a banner indicating the node has pinned data. From now on, that pin is visible on the node inside the canvas.
Step 3 — Run as many times as you want. You run the workflow. The Webhook doesn't wait for any new order: it instantly returns the frozen 52,000 order, and the flow continues to the agent. You change the agent's prompt, run again: the same exact order again. You adjust something else, run: identical. The input never moved.
What to expect: every run uses exactly the same order, so if the classification changes from one run to the next, you know for certain it was your change to the agent, not a different input. You see the pin on the Webhook node, you see the "pinned data" banner, and you see the Webhook responding instantly without waiting. That immediacy is the sign it's using the frozen data and not actually running.
To change the pinned data: the output's JSON view has an Edit button letting you edit the frozen data by hand —perfect for testing an edge case: you pin an order, change its amount by hand to 0, and now you have the edge-zero-amount case without sending anything again. To unpin: the banner has a link to remove the pin (unpin); once removed, the next run goes back to actually executing the node.
That Edit button is more useful than it looks at first glance, and it's worth pausing on for a second. It lets you generate a whole family of edge cases from a single good piece of data, without going back to the source. You pin one normal order once; then you mentally duplicate it and edit: in one copy you empty out the name (missing-customer-name), in another you put the amount as text with commas (dirty-amount-as-string), in another you leave it at zero (edge-zero-amount). Every edit gives you a new case, pinned and ready to run, without depending on the source system sending you exactly that weird order. It's the fastest way to turn a happy case into lesson 3's family of dummies, directly in the editor.
Pinning versus mocking: two cousins that get confused
n8n's docs group two techniques under one roof —"data mocking and pinning"— and since they look alike, it's worth telling them apart so you don't confuse them.
Mocking is generating test data that never existed: it's exactly what you did in lesson 3 with the Code node and the fixed dataset. You invent an order from scratch. You're the source of the data.
Pinning is freezing the real output of a node that did actually run at least once: the Webhook received an order, and you nail that output down to reuse it. The source of the data was a real execution.
The difference in one sentence: mocking invents the data; pinning freezes data that occurred. And they complement each other beautifully. A very common pattern is mocking with a Code node —you generate the synthetic order— and then pinning that Code's output, so the invented order stays frozen and doesn't regenerate on every run. You mock once, pin the result, and now you have an input that's invented and reproducible. It's the best of lessons 3 and 5 combined: you control the data (mock) and it doesn't change between runs (pin).
The limits of pinned data (read them carefully)
Pinned data is powerful, but it has hard limits n8n documents, and skipping past them leads to ugly confusion. These are, confirmed in the official docs —check them for your version anyway, because the fine detail can change between versions:
They don't work in production. This is the most important one. Production executions completely ignore pinned data. The pin only acts on manual executions, the ones you trigger from the editor. This is a deliberate n8n safeguard: it would be a disaster if a workflow active in production responded with a frozen test order instead of the real orders. So you can pin data freely to test; it's never going to "contaminate" production. But it also means pinning data is a testing-only technique, not an operational one.
Only on nodes with a single main output. You can pin the output of a node with one main output. Nodes with several outputs —an IF has two, true and false— can't be pinned (error outputs don't count toward this limit). That's why the natural place to pin in order-triage is the Webhook or an input Code node: they have a single output.
They don't work with binary data. If a node's output includes binary data —a file, an image, a PDF— it can't be pinned. Pinned data is for JSON data, like an order. For order-triage this doesn't get in the way, because orders are JSON; but keep it in mind if you ever test a workflow that moves files.
They get saved with the workflow. Pinned data gets saved inside the workflow, in its JSON's pinData field. This has a big consequence connecting to Module 3, and deserves its own section.
The tension with Module 3's normalization
Here's an important intersection between this lesson and what you learned in Module 3, and it's worth resolving with judgment instead of tripping over it.
Remember Module 3's lesson 4, where you normalized the JSON for clean diffs? The first field you deleted with del(.pinData, ...) was, precisely, pinData. We classified it as volatile: noise that dirties the diff, because it changes depending on what you're testing with and doesn't describe the workflow's logic. And now, in this lesson, pinData is a valuable testing tool. Contradiction?
No, it's a design tension, and it has a clean resolution once you see it. Pinned data is valuable while you test, in your editor. But you don't want it traveling in the versioned JSON you promote to production, for two reasons: it's noise in the diff (Module 3) and, moreover, in production it gets ignored anyway (the limit above), so it adds nothing to the artifact you promote. The resolution:
- Pin data freely while you iterate on your
devinstance. It lives in the editor, it serves you, perfect. - When exporting and versioning, normalize and remove
pinData—exactly as Module 3 taught. The workflow that goes to Git and production carries no pinned data. - Save your test cases separately, in lesson 3's fixtures file (
test/fixtures/orders.json), not inside the workflow'spinData. That way the test data is versioned as data, in its own file, and the workflow stays clean.
In other words: pinData is your temporary workbench for testing, not your test case storage. The case storage is the fixtures file. When you want to re-pin a case, you take it from the fixture and pin it in the editor; when you export, the fixture stays in its file and the pinData goes away in normalization. Each piece of data in its place: the working ones in the pin, the versioned ones in the fixture.
This is one of those decisions separating someone who "uses pin data because it's convenient" from someone who understands how it fits into a professional repository. If you pin a bunch of data and commit it inside the workflow, you clutter the repo and drag noise into production. If you keep it as fixtures and use the pin only as a workbench, you get the best of both: convenient testing and a clean repo.
Execution replay: repeating a past run
The lesson's second tool is replaying an execution. The idea: take an execution that already happened —with its exact data— and load it back into the editor to run it again.
Think of it as an airplane's black box. After a flight, the black box saves everything that happened: every piece of data, every reading. Investigators can load that recording into a simulator and "re-fly" the exact flight, with the same data, as many times as they need, to understand what happened or to test a change. n8n saves a "black box" for every execution —what data came in, what each node produced— and lets you load it back into the editor.
n8n has two doors to this, depending on how the execution you want to repeat ended:
- "Copy to editor", for successful executions. You take a run that went well —for example, one where
order-triageclassified an order and everything worked— and copy it into the editor. - "Debug in editor", for failed executions. You take a run that failed and load it into the editor to investigate and fix it.
In both cases, n8n copies the execution's data into your current workflow and pins it at the workflow's first node. Notice the elegance: replay uses pinned data under the hood. Loading a past execution is, at bottom, automatically pinning at the input node the exact data that run had. It's pin data done for you, from a real run, in one click.
Check availability. According to the docs, these debugging and replay functions are available on n8n Cloud and on registered Community plans (the Community edition's free registration). If on your instance you don't see "Debug in editor" or "Copy to editor" in the executions list, check that your Community is registered. Also check the exact button names on your version: the interface changes, and this guide was written in 2026.
Replay as a TESTING tool, not an incident tool
Here's a distinction the guide takes care to keep straight. Replay was born for incident diagnosis: a production execution failed, you load it with "Debug in editor," see what data it failed with, fix the workflow, and re-run it. That use —investigating a real production failure— isn't part of this module; it lives in the production maintenance guide, because it's hot diagnosis of something that already went wrong live.
In this module we use the same engine for something different and cold: building a repeatable test case from a good run. The flow is this:
- You run
order-triageindevwith a synthetic order that came out as expected. - With "Copy to editor", you load that successful execution into the editor. n8n pins its data at the input node.
- Now you have that case "nailed down." Every time you change the workflow, you run it against that pinned data and compare.
The difference is intent: maintenance uses replay to understand why something broke in production; we use it to capture a good run and turn it into a reproducible test case. Same button, opposite purpose. Keeping this distinction in mind keeps you from mixing cold testing (this module) with hot diagnosis (the other guide).
There's a privacy caution worth flagging here, because it connects with lesson 3. Replay captures the exact data from the execution you copy —if you copy a run that used real data, that real data stays pinned in your editor and, if you export without normalizing, in the JSON. That's why, when you use "Copy to editor" to build a test case, do it on runs that used synthetic data, not on real production runs. And if you ever load a real run to diagnose (the other guide's use), remember to remove that pinData before versioning, so you don't leak a customer's information into the repository. Replay is convenient precisely because it pins everything; that same convenience is what can smuggle in real data where it shouldn't be.
A note about line-by-line tracing
Recent versions of n8n's debugging engine (2026) have been adding capabilities for inspecting an execution in more detail —for example, following how data changes throughout the flow, and in some cases observing behavior inside code nodes. Since these capabilities evolve fast and their exact shape depends on your version, I'm not going to describe specific fields or buttons that might not exist in your install. Check your version's panel and the official docs for what your debugging engine offers for inspecting an execution step by step. The principle that is stable and that you take away from here: n8n saves each execution's data, and you can reload it to observe and repeat it. Confirm the interface details yourself; don't trust a six-month-old screenshot.
Reproducibility across the whole chain: where randomness sneaks in
Pinning the input solves half of reproducibility. But there's a source of variation the Webhook's pin doesn't touch, and it's worth seeing now because it's the bridge to lesson 6: the AI Agent node isn't deterministic by nature.
Think of it this way. You pin the input order, perfect: always the same input. The flow reaches the agent, which sends that order to a language model. And here's the thing: a language model can give different responses to the same input. You send it the same order twice and once it classifies it "manual review" and another time "approved," without you changing anything. The randomness wasn't in the input —you pinned that— it was inside the model. You froze the prop food, but the photographer changes mood between photos.
This has two remedies, and both live in lesson 6, so here I just name them so you see the map:
- Pinning the agent's output, not just the input. Just like you pin the Webhook, you can pin the AI Agent node's output with a good response you captured. That way, while you test the logic after the agent —the gate, the CRM— the agent always answers the same thing, because its output is frozen. You stop depending on its mood.
- Lowering the model's temperature. Many models have a parameter (temperature) controlling how much randomness they inject; at zero, they tend to respond more stably to the same input. It doesn't make it perfectly deterministic, but it reduces the variation.
The practical consequence for your tests: decide what you're testing and pin everything else. If you're testing the logic after the agent, pin the agent's output (and that way you don't even call the model, saving cost). If you're testing the agent itself —does it classify correctly?— then you can't pin its output, because that's exactly what you want to evaluate; that's where lesson 7 comes in (assertions on the agent's output, tolerating its variation with a threshold). Pinning is freezing what you're not testing, to isolate what you are. What you freeze depends on what questions you're asking.
Common mistakes
Pinning data and believing it also applies in production (conceptual and dangerous the other way around). What happens: someone pins a test order at the Webhook, promotes the workflow, and fears —or hopes— production responds with that frozen order. Why it happens: it isn't clear the pin is only for manual executions. How to spot it: if you believe pinned data is going to act in a production execution, you have the wrong mental model. How to fix it: remember the hard limit —production ignores pinned data. In one sense it's reassuring (you're not going to contaminate prod with test data); in another it's a reminder that pinning is an editor technique, not an operational one. And because of that same thing, when promoting, pinData is unnecessary: it does nothing in prod and only clutters the JSON.
Committing the workflow with pinData inside (practical). What happens: someone pins several test cases, exports the workflow, and commits it without normalizing. Now the repo has an order-triage.json with a fat pinData full of test data, cluttering every diff and traveling to production without adding anything. Why it happens: pinning is convenient and it's easy to forget it stayed saved in the JSON. How to spot it: if opening your exported order-triage.json you see a pinData field with data, you didn't normalize it. How to fix it: apply Module 3's normalization —del(.pinData, ...)— before committing, and save your test cases in the fixtures file, not in the pin. The pin is a workbench; the fixture is the versioned storage.
Sneaking randomness into the pinned node without noticing (conceptual). What happens: someone pins a node's output, but the node before the pinned one generates something random, and since the pin only freezes its own node, they believe the whole test is reproducible when it isn't upstream. Why it happens: "I pinned a node" gets confused with "I pinned the whole input." How to spot it: if something before the pinned node varies between runs, reproducibility breaks before the pin. How to fix it: pin at the input node —the Webhook or the first Code— so the whole downstream chain always receives the same thing. Pinning in the middle of the flow only freezes from there forward; what's before stays loose.
Exercises
Exercise 1 — Can it be pinned? For each node, say whether you can pin its output and why: (a) order-triage's input Webhook; (b) an IF node splitting large orders from small ones; (c) a Code node that returns an order in JSON; (d) a node that downloads an invoice PDF; (e) the AI Agent node that classifies.
See solution
(a) Yes — the Webhook has a single main output and produces JSON. It's the ideal place to pin the test's input. (b) No — an IF has two outputs (true and false); pinned data only works on nodes with a single main output. (c) Yes — a single-output Code node returning JSON can be pinned. (d) No — a PDF is binary data, and you can't pin binary data. (e) Yes (usually) — the AI Agent has one main output and returns JSON/text; pinning its output is exactly what lesson 6 is going to leverage for deterministic testing.
Why it works: the two deciding questions are "single main output?" and "is it JSON, not binary?" The IF fails on the first, the PDF on the second. The Webhook, the Code, and the AI Agent pass both, and that's why they're the nodes you're going to pin in practice.
Exercise 2 — Resolve the tension with the repo. A teammate pinned the six test cases at the Webhook and is going to commit the workflow as is, "so the cases stay saved in Git." Explain why that's a bad idea and what they should do instead.
See solution
It's a bad idea for three reasons. One: the pinData with the six cases clutters every workflow diff —Module 3 classified it as volatile for exactly this reason— so every time someone touches the logic, the diff is going to mix the real change with the pinned-data noise. Two: that pinData travels to production, where it gets completely ignored, so it adds nothing to the promoted artifact: it's dead weight. Three: saving the cases inside the workflow ties them to that workflow, when actually they're data you'd want to be able to reuse and version separately.
What they should do: save the six cases in the fixtures file (test/fixtures/orders.json), versioned as data in its own file (lesson 3). When exporting the workflow, normalize and remove the pinData (Module 3). When they want to test, take a case from the fixture and pin it in the editor as a temporary workbench. That way the cases stay versioned —fulfilling their intent— but as data, not stuffed into the workflow's JSON.
Why it works: it separates the case storage (the fixture, versioned) from the workbench (the pin, temporary). It's the same discipline of separating data from logic running through the whole guide.
Exercise 3 — Replay to capture a case. Describe, step by step, how you'd use "Copy to editor" to turn a good order-triage run into a reproducible test case, and how that differs from using "Debug in editor" for a production incident.
See solution
To capture a test case: (1) you run order-triage in dev with a synthetic order that comes out as expected —say, the large one going to manual review. (2) In the executions list, you open that successful run and choose "Copy to editor." (3) n8n copies that execution's data into the editor and pins it at the input node. (4) Now you have that case nailed down: every time you change the workflow, you run it against that pinned data and compare the result.
The difference from "Debug in editor" for an incident: "Debug in editor" is for a failed execution of production, and the goal is to diagnose —see what real data it broke with, fix the workflow, re-run. That's hot diagnosis of a real incident, and it lives in the maintenance guide. "Copy to editor" in our case is for a successful test run, and the goal is to capture a good case and turn it reproducible. Same replay engine; one diagnoses a real failure, the other captures a test.
Why it works: both buttons load a past execution by pinning its data, but the intent and the origin differ —real failure vs. test run, diagnosis vs. capture. Being clear on that boundary keeps you from mixing this module's work (cold testing) with the production guide's (hot incidents).
Summary and next step
In this lesson you saw how to make a test reproducible. Pinned data freezes a node's output —like the photographer's prop food— so every manual run uses exactly the same input, isolating your change to the workflow from any variation in the data; and along the way it saves quota and money, because the pinned node no longer gets called. You locked in its hard limits: it doesn't work in production (only on manual executions), only on nodes with a single main output, not with binary data, and it gets saved in the workflow's pinData —which creates a tension with Module 3's normalization resolved this way: pin freely while you iterate, but save your cases in a fixtures file and remove pinData when versioning. And you saw replay: "Copy to editor" (successful runs) and "Debug in editor" (failed runs) load a past execution by pinning its data at the first node, and we use it as a test-case capture tool, not incident diagnosis (that's a different guide). You marked "check on your version" for the fine details of step-by-step tracing, since they evolve fast.
With this you check off the checklist's fourth point: inputs are pinned so the test is reproducible. And you gained, along the way, two things you weren't directly after but are worth gold: tests that no longer call the source (saving quota and money) and a way to capture any good run as a permanent case.
Before moving on you should be able to: explain what pinning data is and its double benefit (reproducibility and savings); name pinned data's four limits; and resolve the tension between pinData as a testing tool and as noise to normalize.
You already have reproducible tests. Now comes the special case hurting your wallet the most: the AI Agent node. Every time you test order-triage, the agent calls a language model, and if that model is paid, every run costs —right when testing well means running many times. Lesson 6 solves this: testing the agent against a local Ollama model (Llama 3, Mistral) from the Starter Kit, at zero cost; when a live cloud model is still worth using; and how to pin prompts and outputs for deterministic agent tests.
Resources
- Data mocking and pinning — n8n Docs — the official pinned data reference: how to pin, edit, and remove the pin, and the limits (production, one output, no binary).
- Pin and mock data — n8n Docs — the guide for pinning and mocking data, with the pin icon, the banner, and the JSON view's Edit button.
- Debug and re-run past executions — n8n Docs — "Debug in editor" and "Copy to editor," the replay that loads a past execution and pins it at the first node; check availability by plan there.
- View executions — n8n Docs — where you find the executions list you trigger a replay from.
- Module 3, lesson 4 — "Normalizing the JSON for clean diffs" (in this same guide): the
del(.pinData, ...)that removes pinned data when versioning; review it to resolve the tension between the pin as a testing tool and as diff noise.