skip to content

In a Postman collection run, why can the next request not read a value written with `pm.variables.set`?

level: middleimportance: must knowfreq 60%

answer

  1. Not every store leaves the sandbox
  2. The runner asks for a specific list
  3. Four writable scopes, three carried back
  4. trackContext on the runtime's item command
  5. pm.variables.set writes an execution-local layer

basics

~20 s

Postman's runtime carries only three scopes back from a script - globals, environment and collectionVariables, the list its item command declares as trackContext. pm.variables.set writes a fourth, execution-local layer that is thrown away when the script ends.

solid answer

~40 s

A script's writes leave the sandbox only if the runtime asks for them back. The item command declares `trackContext: ['globals', 'environment', 'collectionVariables']`, and when a script execution finishes the runtime replays the recorded mutations for exactly those three scopes onto the run's state. `pm.variables.set` writes the sandbox's execution-local layer, which is not in that list, so nothing is carried out: the value reads back fine for the rest of that one script and then dies with the execution. That is why it looks like it worked while the next request sees nothing. To chain an id or a token forward, write it with `pm.environment.set`, `pm.collectionVariables.set` or `pm.globals.set` instead.

code

javascript · 4 lines
javascript
const id = pm.response.json().id;

pm.variables.set('orderId', id);
pm.collectionVariables.set('orderId', id);

go deeper

for a junior

Be ready to name the three stores a script can write into so a later request sees the value: environment, collection variables and globals. Recall that pm.variables.set is not one of them.

for a middle

Explain the mechanism, not just the rule: the runtime's item command declares trackContext with exactly three scope names, and only writes to those come back, as recorded mutations replayed after the script.

for a senior

Show how you would prove it in a real run rather than assert it - confirm the script executed, confirm which store it wrote into, and confirm the value is present when the dependent request is built.

for a principal

Own the convention for a shared collection: decide once which store handoffs travel through and how they are cleaned up, so no flow ever depends on a value that lives only inside one execution.

## What a script is allowed to change A Postman **script** is the JavaScript attached to a request's `prerequest` or `test` event. It does not execute inside the runner's own memory. It runs in a sandbox that is handed copies of the run's variable scopes, and the runner only learns that something changed if it deliberately collects the change afterwards. The runtime's item command names exactly what it collects: `trackContext: ['globals', 'environment', 'collectionVariables']`. Those three names are what decides whether passing a value forward is possible at all. The collection step happens when the script execution finishes, and it is a **diff** rather than a copy. Every `set` and `unset` a script performs on a tracked scope is recorded as a mutation, and the runtime replays those recorded mutations onto the run's own scopes. Nothing else crosses the boundary. ## The four-versus-three gap The sandbox gives a script four writable variable surfaces. Only three of them appear in `trackContext`: - `pm.globals` — the widest store, shared by everything the run touches. - `pm.environment` — the environment selected for this run. - `pm.collectionVariables` — the variables that belong to the collection itself. - `pm.variables` — the resolved view a script normally **reads** through; its `set` writes a **local** layer belonging to this one execution. The fourth one is the trap. `pm.variables.set` is a real call on a real object. It succeeds. Reading the same name straight back inside that script returns the value you just wrote, so every check you make while standing in the script says the handoff is fine. It simply has no route out: the runtime asks for three scopes and that layer is not one of them, so when the execution ends the value is discarded along with it. | Written with | Scope | Reaches the next request? | |---|---|---| | `pm.globals.set` | globals | yes — in `trackContext` | | `pm.environment.set` | environment | yes — in `trackContext` | | `pm.collectionVariables.set` | collection variables | yes — in `trackContext` | | `pm.variables.set` | execution-local layer | no — discarded with the execution | ## Why the failure is confusing to watch The symptom never appears where the mistake is. The request that wrote the value passes. Its assertions pass. The failure surfaces one step later, in a request that sends an empty value, a literal token or a URL with a missing segment — and the natural instinct is to suspect that request, the server, or a timing problem. None of those is involved. The write happened; it was just written somewhere the run never reads. A second reason it hides well: the same script can read its own local write back. A quick sanity check inside the writing script therefore proves nothing about chaining. The only meaningful check is made from the **other** request, after the handoff was supposed to have happened. ## Doing the handoff so it survives 1. Read the field out of the reply in the first request's test script. 2. Check that the field was actually present before storing it, because `undefined` stores just as happily as an identifier does. 3. Write it with one of the three tracked calls — `pm.environment.set`, `pm.collectionVariables.set` or `pm.globals.set`. 4. Have the dependent request refer to that name. 5. Remove the name when the flow is finished, with the matching `unset`, so a later pass cannot quietly read a value the current pass never wrote. ## When the local layer is the right tool None of this makes `pm.variables.set` a mistake in itself. It is the correct place for a value whose whole life is one execution: something you compute at the top of a pre-request script and reuse a few lines lower, or a normalised copy of an input you would rather not push into a store that everything else in the run can see. Because it never leaves the execution, it also never leaks: nothing downstream can accidentally depend on it, and nothing from a previous pass can be sitting there waiting for you. The rule that follows is small and worth saying out loud in an interview: **a scratch value stays local; a handoff goes into a tracked store.** Deciding which of those two you are writing, before you pick the call, is the whole of the skill here. Everything else — which of the three tracked stores you prefer, how wide you want the value's reach to be — is a downstream choice you only get to make after the value can survive the script at all.

  • Is `pm.variables.set` useless then, or does it have a job?
    It has a job: a value whose life is one execution. Compute something once at the top of a pre-request script, reuse it lower down, hand it to the request about to be sent. Because it never leaves the execution it also never leaks into later requests. What it cannot be is a handoff, since a handoff by definition has to outlive the script that made it.
  • Where in the request lifecycle are a script's tracked writes collected?
    When that script execution completes. A pre-request script's writes are collected before the send, so the request being built can use them; a test script's are collected after the response, so later requests can. A transport failure means the test script never runs at all, and nothing is collected from it.

The sandbox is a workshop with a customs desk at the door: you may build anything you like inside, but only the three declared categories get carried out. Everything else stays behind when the door closes.

saying these in an interview costs you the question

  • Says pm.variables.set persists into the next request
  • Assumes every pm store is written back to the run
  • Thinks a script mutates the run's state directly as it executes
  • Believes the whole scope is copied back rather than a diff
  • Treats an in-script read-back as proof the value chained