skip to content

In a Postman script, what does pm.variables.replaceIn('{{base}}/orders/{{id}}') return, and when would you use it?

level: middleimportance: should knowfreq 32%

answer

  1. It expands text, not one name
  2. The same rules as the request text
  3. Strings, arrays and objects are walked
  4. An unanswered token comes back literal

basics

~20 s

The template with every token it can answer already replaced, and any it cannot left literal. It runs the same expansion the runtime runs over a request, on a string the script hands it, and returns the expanded result.

solid answer

~50 s

`pm.variables` is a `VariableScope`, and `replaceIn` is that scope's own method for expanding a template on demand. It hands the template to the SDK's `Property.replaceSubstitutionsIn` together with the scope's values and its layers, so the result is what the same text would become if it were part of the request — same pattern, same repeated passes, same rule that an unanswered token comes back literal. It accepts a string, an array or an object and walks every string inside it, returning anything else unchanged. Reach for it when the script itself composed the text: a URL or a body fragment built in code never passes through the runtime's own resolution step, so `replaceIn` is how you apply it by hand. It is also the honest way to preview what a template will become before relying on it.

code

javascript · 8 lines
javascript
const template = '{{base}}/orders/{{id}}';
const expanded = pm.variables.replaceIn(template);

if (expanded.includes('{{')) {
    throw new Error('unresolved token in ' + expanded);
}

console.log(expanded);

go deeper

for a junior

Remember the shape of the call: you hand it text containing tokens and get text back, which is not the same as asking the store for one value by its name.

for a middle

Explain that it delegates to the same SDK substitution the request path uses, so nesting, repeated passes and the literal-token outcome all behave identically to what a sent request would see.

for a senior

Show where it belongs in a real script — text the script composed is never resolved for you — and demonstrate checking the result for leftover braces so a silent misconfiguration becomes a named failure.

for a principal

Own the convention for your collections: decide whether templates are composed in scripts at all, since every one is a piece of configuration that no schema validates and only an explicit check will catch.

## What replaceIn is Inside a Postman script, `pm.variables` is a `VariableScope` — an SDK object — and `replaceIn` is a method on that class. Given a template, it returns the expanded text. It is not a second, simpler resolver written for scripts: it delegates to the same SDK entry point the rest of the machinery uses, `Property.replaceSubstitutionsIn`, passing the scope's own values along with its layers. The consequence is the one that makes it trustworthy — **whatever `replaceIn` gives you is what the same text would have become had it been part of the request**. Everything true of expansion in a request is therefore true here: - the same extraction pattern matches a double-brace span whose contents are free of braces; - the same repeated passes run, so a nested or chained token resolves; - the same gate applies, so a value that is not a string, number or boolean is declined; - an **unanswered token comes back literal**, exactly as it would arrive at a server. ## Reading a value versus expanding a template These are different operations and choosing the wrong one is the usual mistake. | What you want | What to call | |---|---| | the value stored under one name | `pm.variables.get(name)` | | a whole piece of text expanded | `pm.variables.replaceIn(template)` | `get` takes a **name** and gives you a value. `replaceIn` takes **text that may contain tokens** and gives you text. Passing a template to `get` looks for a variable whose name is the entire template — braces and all — and naturally finds nothing. Passing a bare name to `replaceIn` is equally pointless: with no braces in it there is nothing to match, so the string comes back untouched. ## What it accepts and what comes back The method is deliberately forgiving about shape: 1. **A string** is expanded and returned as a string. 2. **An array** is walked, and every string inside it is expanded. 3. **An object** is walked recursively, and every string value inside it is expanded, with the structure preserved. 4. **Anything else** — a number, a boolean, `null` — is returned unchanged, because there is no text in it to substitute. That object handling is what makes it usable on a whole payload rather than field by field: you can hand it a request body you assembled as an object and get the same object back with its tokens resolved. ## Where it earns its place The runtime resolves the request it is about to send. Text the script itself builds is outside that step, so it never passes through the resolution the request enjoys. That is the gap `replaceIn` fills, and there are three recurring uses: - **Text composed in code.** A URL or body fragment assembled inside a script holds tokens that nothing else will expand. Calling `replaceIn` applies the same rules by hand. - **Previewing a template.** Before trusting a chain of composed names, expanding it in a script and logging the result tells you immediately whether every link resolved or whether one came back with braces still attached. - **Comparing like with like.** When an assertion needs the resolved form of a stored template — the expected URL, say — expanding it is more honest than re-implementing the composition. ## The trap: same rules, including the silent one Because `replaceIn` inherits the rule that an unanswered token is returned as itself, its result can contain braces, and that result is a perfectly ordinary string. Code that goes on to use it will happily build a request around a literal `{{id}}`, and nothing raises an error — the same silence you get on the wire, just moved earlier. That is a feature when you use it deliberately: a script can call `replaceIn` and then check the result for leftover braces, turning a silent misconfiguration into an explicit, named failure at a point where you still control the message. It is a hazard when you assume expansion must have succeeded because the call returned a string. - **Do** check the result when the template's inputs come from outside the collection. - **Do** use it on the object form when a whole body needs resolving. - **Do not** treat a returned string as proof that every token was answered. - **Do not** reach for it to read a single value — that is what a `get` on the scope is for.

  • How does replaceIn differ from calling a get on the scope?
    `get` takes a name and returns the value stored under it. `replaceIn` takes text that may contain tokens and returns text with those tokens expanded. Handing a template to `get` searches for a variable whose name is the whole template, braces included, and finds nothing; handing a bare name to `replaceIn` finds no tokens and returns the name unchanged.
  • Can replaceIn be given something other than a string?
    Yes. An array is walked and every string in it expanded; an object is walked recursively with its structure preserved, so a whole assembled body can be resolved in one call. Anything with no text in it — a number, a boolean, `null` — comes back unchanged, since there is nothing to substitute.

saying these in an interview costs you the question

  • Confuses replaceIn with reading a single value by name
  • Assumes replaceIn throws when a token cannot be answered
  • Says it uses a simpler resolver than the request path does
  • Thinks it only accepts a string and never an object
  • Treats a returned string as proof every token resolved