Pre-Request Scripts — Setting Up Dynamic Data

8 min read

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:

{{$randomFirstName}}
{{$randomEmail}}
{{$randomUUID}}
{{$timestamp}}
{{$isoTimestamp}}
{{$randomInt}}
{{$randomBoolean}}

Drop them straight into a request body:

{
  "name": "{{$randomFirstName}} {{$randomLastName}}",
  "email": "{{$randomEmail}}",
  "uuid": "{{$randomUUID}}",
  "createdAt": "{{$isoTimestamp}}"
}

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 run
const timestamp = Date.now();
pm.environment.set("testEmail", `user_${timestamp}@test.com`);
 
// Random integer 1–10 inclusive
pm.environment.set("randomQuantity", Math.floor(Math.random() * 10) + 1);
 
// Current date in ISO format
pm.environment.set("currentDate", new Date().toISOString());

Use them in the request body or URL:

{
  "email": "{{testEmail}}",
  "quantity": {{randomQuantity}},
  "createdAt": "{{currentDate}}"
}

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:

const secret = pm.environment.get("apiSecret");
const timestamp = Date.now().toString();
const signature = CryptoJS.HmacSHA256(timestamp, secret).toString();
 
pm.environment.set("requestTimestamp", timestamp);
pm.environment.set("authSignature", signature);

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 env = pm.environment.name;
 
if (env === "Production") {
    pm.environment.set("testMode", "false");
} else {
    pm.environment.set("testMode", "true");
}

Or skipping setup work that's already been done:

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:

console.log("Method:", pm.request.method);
console.log("URL:", pm.request.url.toString());
console.log("Body raw:", pm.request.body && pm.request.body.raw);

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 run
const ts = Date.now();
pm.environment.set("testEmail", `qa_${ts}@example.com`);
pm.environment.set("testName", `QA Test ${ts}`);
 
// 3. Compute a request signature
const 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") });

Then the request body is just:

{ "email": "{{testEmail}}", "name": "{{testName}}" }

…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.

  1. 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.
  2. Same request — try built-in dynamic variables. Set the body to:
    { "title": "{{$randomCatchPhrase}}", "body": "{{$randomLoremParagraph}}", "userId": {{$randomInt}} }
    Send. Each call uses a different random value — no script needed.
  3. 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.
  4. 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.
  5. 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.
  6. 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.