QA How-To
Postman pre-request scripts: A Practical Guide (2026)
Master Postman pre-request scripts for variables, headers, tokens, dynamic data, request signing, async setup, debugging, reuse, and CI-safe automation.
25 min read | 3,037 words
TL;DR
Postman pre-request scripts are JavaScript hooks that run before an HTTP request and prepare variables, headers, authentication, signatures, or data. Put shared setup at collection or folder scope, request-specific logic at request scope, use the narrowest appropriate variable scope, and keep scripts deterministic, secret-safe, and compatible with the Postman app or CLI runner that will execute them.
Key Takeaways
- Use pre-request scripts only for preparation that must happen before transmission, then keep response assertions in post-response scripts.
- Choose variable scope intentionally: local for one request, collection for workflow state, environment for target configuration, and iteration data for cases.
- Understand script order, because collection-level code runs before folder-level code, which runs before request-level code.
- Use header upsert instead of add when a script owns a header, otherwise duplicate authorization or correlation headers can be sent.
- Reference computed variables from URL, headers, and raw body templates rather than trying to mutate an immutable request body object.
- Cache short-lived access tokens with an expiry buffer, and never log client secrets or tokens in the Postman Console or CI reports.
- Make asynchronous setup explicit with await or callbacks, bound its failure behavior, and verify the same scripts in the intended runner.
Postman pre-request scripts run JavaScript immediately before a request is sent. They are the right place to generate a correlation ID, derive a timestamp, select test data, refresh an access token, or attach a computed header. A maintainable script prepares the request through documented variables and supported pm APIs, fails clearly when configuration is missing, and does not hide business assertions.
The feature is powerful enough to create fragile collections if scope and ordering are ignored. A collection-level script can silently affect hundreds of requests, an asynchronous token call can race with execution, and a debug statement can leak a credential. This practical guide builds from small scripts to reusable authentication and signing patterns, with current Postman scripting APIs and CI considerations.
TL;DR
const runId = pm.variables.replaceIn('{{$guid}}');
const timestamp = new Date().toISOString();
pm.variables.set('runId', runId);
pm.variables.set('requestTimestamp', timestamp);
pm.request.headers.upsert({ key: 'X-Correlation-Id', value: runId });
pm.request.headers.upsert({ key: 'X-Request-Time', value: timestamp });
| Task | Best location | Preferred mechanism |
|---|---|---|
| Apply a correlation header to every request | Collection | pm.request.headers.upsert |
| Configure one target base URL | Environment | pm.environment or runner injection |
| Generate a value for the current request only | Request | pm.variables.set |
| Share a created ID across a workflow | Collection | pm.collectionVariables.set after creation |
| Drive rows from JSON or CSV | Runner data | pm.iterationData.get |
| Fetch or refresh a token | Collection or auth folder | awaited pm.sendRequest with expiry caching |
| Assert the API response | Post-response | pm.test and pm.expect |
Use a pre-request script when preparation truly needs code. A normal variable, built-in authorization helper, or data file is clearer when it can express the same behavior.
1. What Postman pre-request scripts Do
A pre-request script executes in the Postman Sandbox before Postman transmits the associated HTTP request. It can read and write supported variable scopes, inspect the pending request, modify its headers, generate dynamic data, call another HTTP endpoint, import approved packages, log safe diagnostics, or decide to skip a request in supported collection runs. The same concept applies at request, folder, and collection scope.
Pre-request code should answer a preparation question. What target should this request call? Which tenant and test case apply? Does a token need refreshing? What canonical string must be signed? By contrast, checks about the received status, headers, schema, or business data belong in the post-response script because no response exists before transmission.
A clean lifecycle looks like this:
- Resolve configuration and input data.
- Validate that required inputs exist.
- Compute one-request values.
- Add or update headers and variables used by the request template.
- Send the request.
- Parse and assert the response in post-response scripts.
- Save only workflow state that a later request genuinely needs.
This separation makes failures readable in the Collection Runner, Newman, and the Postman CLI. For complete assertion design, see API error handling and negative testing.
2. Understand Execution Order and Scope
For a request inside a folder, Postman runs pre-request scripts from broadest to narrowest: collection, folder, then request. This lets collection code establish defaults, folder code specialize a capability, and request code finish request-specific setup. The ordering also means a narrower script can overwrite a value produced earlier. Treat that as an explicit override, not a convenient accident.
Suppose the collection script creates correlationId, the Admin folder selects role=admin, and a request computes its own resourceId. That is a reasonable hierarchy. It is not reasonable for three layers to set baseUrl differently without documentation. When debugging, log the event name, request name, and nonsecret configuration rather than every variable:
console.info({
event: pm.info.eventName,
request: pm.info.requestName,
iteration: pm.info.iteration,
target: pm.variables.get('baseUrl')
});
pm.info.eventName is prerequest for this hook, and pm.info.requestName identifies the saved request. These properties help locate inherited behavior. Remove or gate verbose output after diagnosis, especially in monitors and CI.
Scripts at folder scope apply to direct child requests under the folder according to the collection hierarchy. Test the actual nesting after moving requests. A collection reorganization can change which script applies even when request code is untouched. Run a focused folder in the Collection Runner and inspect the Console before merging structural changes.
3. Choose the Correct Variable Scope
Postman exposes several variable scopes. The narrowest value with a matching name wins when referenced through pm.variables.get or {{name}}. Scope errors are responsible for many scripts that work manually but fail in a runner.
| Scope | Script API | Appropriate use | Risk to control |
|---|---|---|---|
| Local | pm.variables |
Value for the current request execution | Disappears after the request |
| Iteration data | pm.iterationData |
Read-only JSON or CSV test case | Cannot be set from the script |
| Environment | pm.environment |
Target URL, tenant, nonsecret environment config | Local and shared values can differ |
| Collection | pm.collectionVariables |
Workflow state and collection defaults | Can couple request order |
| Global | pm.globals |
Rare workspace-wide compatibility need | Broad ownership and collision risk |
| Vault | pm.vault |
Local secret access in supported contexts | Requires access permission and await |
Use pm.variables.get('baseUrl') when the script should honor normal precedence, and use pm.environment.get('baseUrl') when environment scope is part of the contract. Set a temporary value with pm.variables.set, a cross-request workflow value with pm.collectionVariables.set, and remove stale state with the matching .unset method.
Do not store CI secrets through persistent collection or global setters. Inject them through the runner's secret mechanism and read them without printing. Vault is useful for supported local Postman workflows, but CI execution must use a secret system available to its runner. Collections should fail with a safe message if a required secret is absent rather than fall back to a committed credential.
4. Generate Dynamic Values and Request Bodies
Dynamic values are simplest when the script computes a variable and the request references it. Postman supports dynamic variable expressions, and scripts can resolve them through pm.variables.replaceIn. Create one value and reuse it if multiple fields must agree. Calling replaceIn('{{$guid}}') three times creates three independent values.
const orderId = pm.variables.replaceIn('{{$guid}}');
const createdAt = new Date().toISOString();
const quantity = Number(pm.iterationData.get('quantity') ?? 1);
if (!Number.isInteger(quantity) || quantity < 1) {
throw new Error('quantity must be a positive integer');
}
pm.variables.set('orderId', orderId);
pm.variables.set('createdAt', createdAt);
pm.variables.set('quantity', String(quantity));
Configure the raw JSON body as a template:
{
"clientOrderId": "{{orderId}}",
"createdAt": "{{createdAt}}",
"quantity": {{quantity}}
}
The unquoted quantity placeholder preserves JSON number syntax. Validate it before insertion so an empty or malicious data value cannot corrupt the body. For more complex objects, serialize once into a local variable with JSON.stringify(payload) and make the entire raw body {{requestBody}}.
The current pm.request.body object is available for inspection but is immutable from scripts. Do not rely on invented methods such as pm.request.body.update. Use variables in the body editor or a stored request template. This approach is also visible to reviewers, whereas hidden mutation would make the UI body disagree with transmitted data.
5. Set and Normalize Request Headers Safely
Use pm.request.headers.upsert when the script owns a header. upsert inserts the header if absent or updates the existing one. add can produce duplicates, which is occasionally correct for list-valued headers but dangerous for Authorization, idempotency keys, and correlation IDs.
const correlationId = pm.variables.replaceIn('{{$guid}}');
const accessToken = pm.variables.get('accessToken');
if (!accessToken) {
throw new Error('accessToken is required');
}
pm.request.headers.upsert({
key: 'Authorization',
value: `Bearer ${accessToken}`
});
pm.request.headers.upsert({
key: 'X-Correlation-Id',
value: correlationId
});
pm.variables.set('correlationId', correlationId);
Do not log pm.request.headers wholesale after adding authorization. If a diagnostic must confirm setup, log booleans and safe suffix-free metadata: console.info('Authorization configured:', Boolean(accessToken)). Remove a temporary header with pm.request.headers.remove('X-Debug-Mode').
Decide whether authentication belongs in Postman's Authorization UI or script code. Built-in Bearer Token and OAuth helpers are clearer for normal schemes. A script is justified when the protocol requires a custom token exchange, canonical signature, or coordinated refresh cache. Keep one owner for each header. A request-level Authorization setting plus a collection script can otherwise create two competing values.
6. Fetch and Cache an Access Token
A collection can obtain a client-credentials token before protected requests. Cache it with its expiration time so the suite does not call the identity provider before every request. Refresh a little early to absorb clock skew and execution delay. Use await pm.sendRequest in current Postman runtimes for readable sequential logic.
const cachedToken = pm.collectionVariables.get('accessToken');
const expiresAt = Number(pm.collectionVariables.get('accessTokenExpiresAt') || 0);
const refreshBufferMs = 30_000;
if (!cachedToken || Date.now() + refreshBufferMs >= expiresAt) {
const tokenUrl = pm.variables.get('tokenUrl');
const clientId = pm.variables.get('clientId');
const clientSecret = pm.variables.get('clientSecret');
if (!tokenUrl || !clientId || !clientSecret) {
throw new Error('tokenUrl, clientId, and clientSecret are required');
}
const response = await pm.sendRequest({
url: tokenUrl,
method: 'POST',
header: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: {
mode: 'urlencoded',
urlencoded: [
{ key: 'grant_type', value: 'client_credentials' },
{ key: 'client_id', value: clientId },
{ key: 'client_secret', value: clientSecret }
]
}
});
if (response.code !== 200) {
throw new Error(`Token request failed with status ${response.code}`);
}
const token = response.json();
if (!token.access_token || !Number.isFinite(Number(token.expires_in))) {
throw new Error('Token response is missing access_token or expires_in');
}
pm.collectionVariables.set('accessToken', token.access_token);
pm.collectionVariables.set(
'accessTokenExpiresAt',
String(Date.now() + Number(token.expires_in) * 1000)
);
}
A later line or request-level script can upsert the Bearer header from accessToken. In highly parallel collection runs, separate processes do not share this cache, which is normally desirable. Within one collection, concurrent token refresh logic can still add complexity. Prefer a dedicated authentication step or a well-scoped collection hook, and verify identity-provider rate limits.
7. Sign Requests with Web Crypto
Some APIs require an HMAC calculated from method, path, timestamp, and body. Postman's sandbox exposes Web Crypto objects in current versions, so a script can use standards-based crypto.subtle without importing an unpinned package. The exact canonicalization must come from the API specification. Changing whitespace, query order, encoding, or newline delimiters changes the signature.
The example below signs a simple documented canonical string with HMAC-SHA-256 and encodes the result as lowercase hexadecimal:
const secret = pm.variables.get('signingSecret');
const timestamp = Math.floor(Date.now() / 1000).toString();
const path = pm.request.url.getPathWithQuery();
const canonical = `${pm.request.method}\n${path}\n${timestamp}`;
if (!secret) {
throw new Error('signingSecret is required');
}
const encoder = new TextEncoder();
const key = await crypto.subtle.importKey(
'raw',
encoder.encode(secret),
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signatureBuffer = await crypto.subtle.sign(
'HMAC',
key,
encoder.encode(canonical)
);
const signature = Array.from(new Uint8Array(signatureBuffer))
.map(byte => byte.toString(16).padStart(2, '0'))
.join('');
pm.request.headers.upsert({ key: 'X-Timestamp', value: timestamp });
pm.request.headers.upsert({ key: 'X-Signature', value: signature });
This code is runnable in a current Postman HTTP pre-request script when the request URL and signingSecret exist. It intentionally omits a body hash because every provider defines its own canonical contract. Add fields only as specified, using the exact resolved bytes the server verifies. Test with a known fixed vector before using live timestamps, and never log the canonical string if it contains sensitive data.
8. Reuse Scripts Without Creating Hidden Coupling
Put behavior at the narrowest shared level. A correlation header used by every request belongs at collection scope. A merchant signature used only under Payments belongs on that folder. A one-off malformed identifier belongs on the request. Copying the same token code into twenty requests guarantees divergent fixes. Moving every behavior to collection scope creates the opposite problem, invisible global complexity.
For substantial reuse, Postman can load team Package Library code and supported external npm or JSR packages with pm.require. Pin external packages to an exact version, for example pm.require('npm:package-name@1.2.3'), after confirming that package and version are approved and sandbox-compatible. Omitting a version can produce different resolved versions across collaborators. Packages also add supply-chain, size, licensing, and runner-compatibility considerations.
A reusable helper should accept explicit inputs and return a value instead of mutating many scopes. Document it with JSDoc and test known inputs. Keep orchestration in the collection script, where reviewers can see which variables and headers change. If the logic grows into a mini application with branches, networking, cryptography, and persistence, consider moving that behavior to a small authenticated test service or code-first API framework.
A collection is easiest to maintain when requests reveal their contract in the UI. URL and body templates should display named placeholders, collection descriptions should explain scope, and scripts should contain short validation and computation steps. Hidden cleverness is not reuse.
9. Handle Async Work and Failure Semantics
pm.sendRequest sends an asynchronous HTTP request from a script. Current Postman supports awaiting its returned Promise, as shown in the token example, and also supports a callback form. Await sequential dependencies. If two setup calls are independent, parallel execution may save time, but it also complicates error reporting and rate limits. Start sequentially unless measured runtime justifies concurrency.
A callback version remains valid:
pm.sendRequest('https://postman-echo.com/get', (error, response) => {
if (error) {
console.error('Setup request failed:', error.message);
return;
}
pm.test('setup endpoint is reachable', () => {
pm.expect(response.code).to.eql(200);
});
});
This demonstration records a test, but production authentication setup should stop protected execution when token acquisition fails. With awaited code, throw a sanitized error after checking the response. Do not catch an exception, print it, and allow the main request to run with an old token.
pm.execution.runRequest can execute a saved collection request by ID and returns a Promise in supported contexts. It has a per-script call limit and request-ID portability concerns, so use it when a reviewed stored request genuinely improves reuse. Use pm.sendRequest for a directly defined setup call. For CI, run the exported collection through the chosen runner early, because app-only assumptions and unavailable package features surface there.
10. Debug Postman pre-request scripts Systematically
Open the Postman Console and isolate one request. First confirm which collection, folder, and request scripts run. Then inspect only safe inputs, their types, and the scope they came from. A variable can visually exist but resolve to another scope's value. Compare pm.variables.get('key') with scope-specific getters when diagnosing precedence.
Use assertions about script prerequisites where a test result is appropriate, and throw for conditions that make transmission invalid. Include the request name and missing key, never its secret value. Validate types after reading iteration data, because CSV inputs arrive as strings and JSON data can still contain unexpected nulls. Check generated raw JSON with the Console only if it contains synthetic nonsecret data.
A disciplined sequence is:
- Replace dynamic input with a fixed known value.
- Confirm run order with
pm.info.requestNameand safe markers. - Compare local, data, environment, and collection scopes.
- Check whether a request template references the variable name exactly.
- Verify asynchronous work is awaited or completed through its callback.
- Confirm headers use
upsertand no conflicting authorization helper is active. - Run the same collection through the intended CLI or CI job.
If the script works on Send but fails in a Collection Runner, look for iteration data, order dependence, and persistent local values. If it works in the app but fails in Newman, confirm collection format, supported packages, environment export, and runner version. See Postman Newman in CI for a complete pipeline pattern.
11. Make Scripts Deterministic and Secure in CI
CI scripts should start from explicit state. Do not depend on a developer's local environment value, cookie jar, Vault entry, or a token cached during yesterday's manual run. Inject configuration, generate a unique run namespace, and clear or overwrite workflow variables at a defined start point. When exporting a final environment for diagnosis, assume it can contain tokens and generated data, and restrict or avoid the artifact.
Random values need a purpose. Use a GUID to prevent data collisions, not to make every field unpredictable. Store the generated value once so assertions and cleanup can reference it. Time-dependent signatures should use current time because the protocol demands it, while ordinary test data should prefer fixed clocks or expected ranges when possible.
Apply timeouts to scripts and setup calls through the runner. A hung token provider should fail fast with a clear error rather than consume the entire job. Do not build unbounded polling loops in a pre-request hook that runs before every request. Put environment readiness in a pipeline step or a dedicated collection request with a maximum attempt count.
Finally, review scripts like application code. Require clear names, small functions, no pasted secrets, no unpinned imports, explicit error paths, and a collection run in pull-request checks. A pre-request hook can influence every outgoing call, so its security and correctness impact is larger than its line count.
Interview Questions and Answers
Q: In what order do Postman pre-request scripts run?
For a request within a folder, collection-level pre-request code runs first, followed by folder-level code and then request-level code. A narrower script can override values established earlier. I keep defaults broad and request-specific computation narrow.
Q: Which variable scope should a pre-request script use?
I use local variables for one execution, iteration data for read-only cases, environment variables for target configuration, and collection variables for deliberate cross-request workflow state. I avoid globals because their ownership is too broad. Secrets come from the runtime's protected mechanism.
Q: How do you change a request body from a pre-request script?
I compute a value or serialized payload, save it to a variable, and reference that variable in the raw body template. The current pm.request.body is immutable, so I do not invent a mutation method. The template keeps the sent shape visible to reviewers.
Q: Why use headers.upsert instead of headers.add?
upsert ensures a script-owned header has one current value. add can create duplicates, which is dangerous for authorization, idempotency, and signature headers. I use add only when the protocol explicitly permits repeated header fields.
Q: How do you prevent a token request before every API request?
I cache the access token and calculated expiry in collection scope, then refresh when it is missing or within a buffer of expiration. The client secret remains in protected runtime configuration. A failed refresh stops the request with a sanitized error.
Q: How do asynchronous setup calls work?
pm.sendRequest is asynchronous and current runtimes support awaiting its Promise or using a callback. I await dependent calls, check both transport and HTTP outcomes, and throw when setup fails. I avoid fire-and-forget authentication logic.
Q: What makes a pre-request script CI-safe?
It starts from injected configuration, avoids local-only state, uses supported runner APIs, bounds network work, produces deterministic shared values, and never logs secrets. I test the exported collection in the same runner and version used by CI.
Q: When should logic move out of Postman scripts?
I move it when it becomes a large stateful program, requires unsupported dependencies, needs strong unit-test tooling, or obscures the request contract. A package, test helper service, or code-first API framework can then provide better ownership and testing.
Common Mistakes
- Putting response assertions in a pre-request script. The response does not exist yet, so use the post-response hook.
- Setting every value in environment or global scope. Choose the narrowest lifetime and reserve persistent scopes for intentional sharing.
- Calling
replaceIn('{{$guid}}')repeatedly when fields must share one ID. Generate once, store locally, and reuse. - Trying to call an invented request-body update method. Use variables in the body template because
pm.request.bodyis immutable. - Adding duplicate
Authorizationheaders. Let one mechanism own authentication and useupsert. - Fetching a token before every request without caching. Respect expiry and identity-provider limits.
- Starting asynchronous work without awaiting it or handling its callback. Dependent setup must complete before the main request.
- Logging full variables, headers, canonical strings, or token responses. Log only allowlisted nonsecret metadata.
- Hiding large shared behavior at collection scope. Keep global hooks small and move domain-specific logic to folders, requests, or packages.
- Assuming app success guarantees CLI success. Verify packages, variables, collection format, and runtime behavior in the target runner.
Conclusion
Postman pre-request scripts are best used as small, explicit preparation hooks. Understand collection-to-request execution order, choose variable lifetimes deliberately, generate each shared value once, use header upsert, template computed bodies, await setup calls, and stop safely when required configuration is unavailable.
Begin by moving one repeated header or token flow to the narrowest shared script and run the collection in both the Postman app and your CI runner. Add safe diagnostics and expiry handling, then document the variables the script reads and writes. That discipline keeps dynamic collections powerful without making them mysterious.
Interview Questions and Answers
What are Postman pre-request scripts used for?
They prepare an outgoing request by setting variables, generating data, adding headers, acquiring tokens, or calculating signatures. They execute before transmission in the Postman Sandbox. Response validation belongs in post-response scripts.
Explain pre-request script execution order.
Postman runs collection, folder, and request pre-request scripts in that order. Broader scripts should establish shared defaults, while narrower scripts handle capability-specific or request-specific setup. I document any override of a value set at an earlier level.
How do you choose a variable scope in Postman?
I match scope to lifetime and ownership: local for one request, iteration data for cases, environment for target configuration, and collection for explicit workflow state. I avoid globals and inject secrets from the runner. Scope-specific getters help diagnose precedence.
How should a script build a dynamic JSON body?
The script validates inputs, creates the object or individual values, and stores a serialized result in a local variable. The raw body references that variable. This is necessary because `pm.request.body` is immutable and it keeps the template reviewable.
How do you safely add authorization headers?
I let one mechanism own authorization and use `pm.request.headers.upsert` for a script-managed header. The token comes from a protected source or expiry-aware cache. Logs confirm only that configuration exists, never the token itself.
How do you handle asynchronous authentication setup?
I await `pm.sendRequest` in a supported current runtime or use its callback, validate transport and HTTP outcomes, and throw a sanitized error on failure. I cache valid tokens with a refresh buffer. Fire-and-forget setup is not acceptable for a dependent request.
How do you debug variable precedence problems?
I compare `pm.variables.get` with local, iteration, environment, and collection getters while logging only safe metadata. I confirm the collection-folder-request order and inspect the exact template name. Then I reproduce the same run mode and environment used in CI.
When should Postman script logic be extracted?
I extract it when it is large, stateful, duplicated, dependent on complex packages, or difficult to test independently. A pinned Postman package can handle focused reuse, while a code-first framework or helper service is better for substantial programs. The extraction point should improve both testability and ownership.
Frequently Asked Questions
What is a Postman pre-request script?
It is JavaScript that runs in the Postman Sandbox before an HTTP request is sent. It prepares variables, headers, authentication, dynamic data, or other request inputs.
What order do Postman pre-request scripts run in?
Collection-level code runs first, then folder-level code, then request-level code. This order lets narrower scripts specialize or override broader defaults, but those overrides should be intentional.
How do I set a variable before a Postman request?
Use `pm.variables.set` for the current request, `pm.collectionVariables.set` for deliberate workflow state, or `pm.environment.set` for environment-scoped state. Choose the narrowest scope that matches the value's lifetime.
Can a pre-request script modify the raw request body?
The `pm.request.body` object is immutable. Compute a value or JSON string in the script, save it as a variable, and reference that variable in the body editor.
Can Postman fetch a token in a pre-request script?
Yes. Use `await pm.sendRequest` or its callback form to call the token endpoint, validate the response, and cache the token with an expiry. Keep client credentials in a protected runtime store and never log them.
How do I add a header in a Postman pre-request script?
Use `pm.request.headers.upsert({ key, value })` when the script owns the header. It inserts or updates the value and avoids accidental duplicates.
Why does my pre-request script work manually but fail in CI?
It may depend on local environment values, Vault data, cookies, an unsupported package, collection order, or a different runner version. Export the correct artifact, inject all configuration, and reproduce with the CI runner locally.
Related Guides
- Postman collection variables and scopes: A Practical Guide (2026)
- Postman data driven testing: A Practical Guide (2026)
- Postman mock server: A Practical Guide (2026)
- Postman Newman in CI: A Practical Guide (2026)
- API contract testing with Pact: A Practical Guide (2026)
- API error handling and negative testing: A Practical Guide (2026)