The Tests tab runs after a response. The Pre-request Script tab runs before a request goes out. That window — between you clicking Send and the bytes hitting the wire — is where you set up everything the request needs: unique emails, fresh timestamps, computed signatures, conditional headers, on-the-fly auth. This lesson covers the patterns you'll use most: built-in dynamic variables, generated test data, conditional logic, and the auto-refresh-token pattern that keeps a long-running test suite from breaking on expired credentials.
Where pre-request scripts live
Open any request and click the Pre-request Script tab — it sits between Body and Tests. The editor is the same JavaScript sandbox as Tests, with the same pm object available. The difference is when the script runs: before the request, not after. So pm.response doesn't exist yet (there's no response). You're working with pm.request, environment/collection variables, and any plain JavaScript you want.
The lifecycle, in five steps:
Step 1 of 5
Pre-request script runs
Postman executes the JS in the Pre-request Script tab. Set or update variables, compute signatures, conditionally fetch tokens.
The script-→-substitute-→-send order is the key insight. Anything you set in the pre-request script is available to the request body and headers as {{var}}. That's how you generate fresh data per request without editing the body manually each time.
Built-in dynamic variables — no script needed
For the common cases — random emails, names, UUIDs, timestamps — Postman ships dynamic variables you can use directly in URLs, headers, and bodies. They're prefixed with $ and re-evaluate on every request:
Send. Postman substitutes a fresh value each time — john.doe@example.com once, mary.smith@test.io next. Useful when you need uniqueness but don't care about the specific values.
The full list (currently 100+) is searchable via Postman's docs; the ones you'll reach for daily are $randomFirstName, $randomLastName, $randomEmail, $randomUUID, $timestamp, $randomInt, $randomBoolean, $randomCity, $randomCompanyName.
When dynamic variables aren't enough — you need a deterministic timestamp, or a value derived from another variable — drop into a pre-request script.
Setting variables from a script
The basic pattern: compute, then set. The variable becomes available to the request that's about to fire.
// Generate a unique email per runconst timestamp = Date.now();pm.environment.set("testEmail", `user_${timestamp}@test.com`);// Random integer 1–10 inclusivepm.environment.set("randomQuantity", Math.floor(Math.random() * 10) + 1);// Current date in ISO formatpm.environment.set("currentDate", new Date().toISOString());
Note {{randomQuantity}} is not quoted — Postman substitutes the raw value 7. Quoting it would send "7" (a string), which most APIs will reject if the field is typed as a number.
Computing values before sending
Some APIs require a computed signature on each request — an HMAC of the timestamp, a hash of the body, a Base64-encoded JWT segment. Postman ships CryptoJS so this fits in 5 lines:
Then the request headers reference {{requestTimestamp}} and {{authSignature}}. The same pattern handles MD5/SHA1/SHA256 hashing, Base64 encoding, and most other crypto-flavoured signing schemes.
Conditional logic
Pre-request scripts are full JavaScript, so if statements work the same as anywhere else. A common case is sending different data per environment:
const cachedToken = pm.collectionVariables.get("authToken");if (!cachedToken) { // No token yet — set a flag so we know to fetch one pm.environment.set("needsLogin", "true");}
Collection-level pre-request scripts
The same pre-request script tab exists at the collection level (right-click collection → Edit → Pre-request Script). Anything there runs before every request in the collection. This is where the auto-refresh-token pattern lives.
The pattern: check whether the cached token has expired. If so, call the login endpoint, store the new token. Skip the work if the token is still good.
const tokenExpiry = pm.collectionVariables.get("tokenExpiry");const now = Date.now();if (!tokenExpiry || now > parseInt(tokenExpiry, 10)) { pm.sendRequest({ url: pm.environment.get("baseUrl") + "/auth/refresh", method: "POST", header: { "Content-Type": "application/json" }, body: { mode: "raw", raw: JSON.stringify({ refreshToken: pm.collectionVariables.get("refreshToken") }) } }, (err, res) => { if (err) { console.error("Token refresh failed:", err); return; } const data = res.json(); pm.collectionVariables.set("authToken", data.token); // Tokens typically last 1 hour — store expiry one minute early to be safe pm.collectionVariables.set("tokenExpiry", (now + 59 * 60 * 1000).toString()); console.log("Refreshed auth token"); });}
What's happening:
pm.sendRequest is the only way to make an HTTP call from inside a script. It's asynchronous — pass a callback.
The callback runs before the main request fires, because Postman waits for pm.sendRequest to complete before sending the actual request.
Storing tokenExpiry as Date.now() + 59 minutes (slightly under the typical 60-minute lifetime) avoids race conditions where the token expires during a long collection run.
Drop this in once at the collection level and you can run a 200-request suite for an hour without thinking about auth.
Reading and modifying the request
You can also inspect or mutate the about-to-go-out request:
Less common in tests; useful for advanced cases like signing the body's bytes after the body has been resolved.
Order of execution — collection vs request
When both a collection-level and request-level pre-request script exist, the collection script runs first, then the request script. Same for tests. So a collection-level token-refresh runs before a request-level "augment with extra header" script — which is the order you usually want.
The Postman Console (Cmd/Ctrl+Alt+C) shows the script execution order with console.log output from both. When something feels off, the console is the answer.
A complete pre-request example
A request that POSTs a new user with auto-generated unique data, a computed signature, and a freshly-refreshed token:
// 1. Refresh the auth token if needed (would normally live at collection level)// ... see auto-refresh snippet above ...// 2. Generate unique user data for this runconst ts = Date.now();pm.environment.set("testEmail", `qa_${ts}@example.com`);pm.environment.set("testName", `QA Test ${ts}`);// 3. Compute a request signatureconst secret = pm.environment.get("apiSecret");const sig = CryptoJS.HmacSHA256(ts.toString(), secret).toString();pm.environment.set("requestTimestamp", ts.toString());pm.environment.set("requestSignature", sig);console.log("Pre-request setup complete:", { email: pm.environment.get("testEmail") });
…with Authorization: Bearer {{authToken}}, X-Timestamp: {{requestTimestamp}}, X-Signature: {{requestSignature}} in the headers tab. The script handled every dynamic piece; the request shape stays declarative.
⚠️ Common mistakes
Treating pm.sendRequest as synchronous. It takes a callback. Code after the pm.sendRequest(...) call runs before the callback fires. Put any code that depends on the response inside the callback, not after the call.
Quoting numeric variables in JSON bodies."quantity": "{{qty}}" sends a string; "quantity": {{qty}} sends a number. Match the variable's role to the field's type — most APIs reject the wrong one.
Pre-request scripts that silently fail. A typo in pm.environment.gett(...) throws and the request still fires (without your variable). Watch the Postman Console for errors after every script change.
🎯 Practice task
Set up dynamic data with pre-request scripts. 25-30 minutes.
Open POST Create Post in your collection. In the Pre-request Script tab, paste:
const ts = Date.now();pm.collectionVariables.set("testTitle", `Post created at ${ts}`);pm.collectionVariables.set("testUserId", Math.floor(Math.random() * 10) + 1);
In the request body, replace the title and userId with {{testTitle}} and {{testUserId}}. Send a few times. Confirm each call has a different title in the response.
Same request — try built-in dynamic variables. Set the body to:
Send. Each call uses a different random value — no script needed.
Open the Postman Console (Cmd/Ctrl+Alt+C). Re-run the request. Confirm the resolved request body shows the actual values your script generated, not the {{vars}} literals.
Conditional logic. Add to the pre-request script:
if (pm.environment.name === "JSONPlaceholder Mirror") { pm.collectionVariables.set("testTitle", "Mirror env post");}
Switch between your two environments and re-send. Confirm the title changes.
Token refresh skeleton. Open your collection's Edit → Pre-request Script tab. Paste a no-op version of the auto-refresh snippet (without pm.sendRequest) — just the if check and a console.log saying "would refresh now". Run any request and watch the console. This is the skeleton you'd flesh out for a real auth-protected API.
Stretch: generate an HMAC signature with CryptoJS:
const sig = CryptoJS.HmacSHA256("hello", "secret").toString();console.log("HMAC:", sig);
Run any request, watch the console, confirm a 64-character hex string is logged.
That ends Chapter 3 — you can now write full-fidelity tests, validate any response shape, and set up dynamic data before the request even goes out. Chapter 4 takes Postman further: chaining requests, running data-driven suites, configuring real authentication, and standing up mock servers when the real backend isn't ready.
// tip to track lessons you complete and pick up where you left off across devices.