1) Where pre-request scripts run and how scope affects behavior
Pre-request scripts run before Postman sends an HTTP request. Their job is to prepare anything the request needs at send time: computed headers, derived payload fields, temporary variables, or signatures. Because they execute right before the request is transmitted, they are ideal for values that must be fresh (timestamps, nonces) or consistent (correlation IDs) across a group of requests.
Execution order and inheritance
Postman can execute pre-request scripts at multiple levels. The effective behavior depends on where you place the script:
- Collection-level: runs for every request in the collection (unless a request is outside that collection).
- Folder-level: runs for every request in that folder (and nested folders).
- Request-level: runs only for that request.
When multiple scripts apply, they run from broader scope to narrower scope (collection → folder → request). This means a folder script can build on values produced by a collection script, and a request script can override or refine values produced earlier.
Variable scope choices inside scripts
Pre-request scripts often set variables. Choose scope intentionally to avoid surprising behavior:
- Local (temporary): exists only during the current request execution. Use for intermediate calculations you do not want to persist.
- Collection variables: shared across all requests in the collection; good for reusable computed values that should persist during a run.
- Environment variables: shared across requests but tied to the active environment; use when values differ by environment.
- Global variables: shared everywhere; use sparingly to avoid collisions.
In scripts, you typically read and write via the pm API. Examples:
Continue in our app.
You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.
Or continue reading below...Download the app
// Read (tries different scopes depending on what you call; shown explicitly here for clarity) const baseUrl = pm.environment.get('baseUrl'); const tenant = pm.collectionVariables.get('tenant'); // Write pm.collectionVariables.set('correlationId', 'abc-123'); pm.environment.set('lastRunAt', String(Date.now())); // Local (non-persistent) pm.variables.set('nonce', 'temp-value');2) Generating dynamic values (UUIDs, timestamps, random strings)
Many APIs require unique identifiers, time-based values, or random inputs to prevent collisions. Pre-request scripts are the safest place to generate these values because they are created immediately before the request is sent.
UUIDs
Use a UUID for correlation IDs, idempotency keys, or unique entity identifiers. In modern Postman runtimes, you can generate a UUID like this:
// Prefer built-in crypto when available const uuid = (crypto && crypto.randomUUID) ? crypto.randomUUID() : pm.variables.replaceIn('{{$guid}}'); pm.variables.set('uuid', uuid);If your Postman version supports dynamic variables, {{$guid}} is also commonly available. The snippet above uses it as a fallback.
Timestamps
APIs often require epoch timestamps (seconds or milliseconds) or ISO strings.
const nowMs = Date.now(); const nowSec = Math.floor(nowMs / 1000); const iso = new Date(nowMs).toISOString(); pm.variables.set('ts_ms', String(nowMs)); pm.variables.set('ts_sec', String(nowSec)); pm.variables.set('ts_iso', iso);Random strings
Random strings are useful for unique usernames, emails, and nonces. Keep them URL-safe and predictable in length.
function randomString(len) { const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'; let out = ''; for (let i = 0; i < len; i++) out += alphabet[Math.floor(Math.random() * alphabet.length)]; return out; } const suffix = randomString(8); pm.variables.set('rand8', suffix);Tip: When uniqueness matters, combine a timestamp with randomness (e.g., 1700000000-ab12cd34) to reduce collision risk.
3) Constructing derived headers and payload fields
Derived values are computed from other inputs (tenant IDs, user IDs, timestamps) and then injected into headers or request bodies. This is common for observability (correlation IDs), idempotency, or server-side routing.
Correlation ID header for tracing
A correlation ID is a unique identifier you attach to each request so logs across services can be tied together. A common pattern is to generate it once per run (or per folder execution) and reuse it for all requests in that scope.
// Create once per folder run: if already set, reuse it let corr = pm.collectionVariables.get('correlationId'); if (!corr) { corr = (crypto && crypto.randomUUID) ? crypto.randomUUID() : pm.variables.replaceIn('{{$guid}}'); pm.collectionVariables.set('correlationId', corr); } // Ensure header exists for this request pm.request.headers.upsert({ key: 'X-Correlation-ID', value: corr });upsert is important: it updates the header if it exists, otherwise inserts it. This avoids duplicates and makes the script stable.
Derived payload fields
You can compute fields and inject them into JSON bodies. A robust approach is to parse the current body, modify it, then write it back. This avoids fragile string concatenation.
// Only run if the request has a raw JSON body if (pm.request.body && pm.request.body.mode === 'raw') { const raw = pm.request.body.raw || ''; // Attempt to parse JSON; if it fails, skip safely try { const obj = JSON.parse(raw); // Example derived fields obj.meta = obj.meta || {}; obj.meta.correlationId = pm.collectionVariables.get('correlationId'); obj.meta.sentAt = new Date().toISOString(); pm.request.body.update(JSON.stringify(obj, null, 2)); } catch (e) { // Not JSON; do nothing } }When you want to reuse derived values across multiple requests, store them as variables and reference them in request templates (headers, query params, or JSON fields) using {{variableName}}.
4) Simple request signing patterns (hashing/canonical strings) conceptually
Some APIs require a signature to prove the request was constructed by a trusted client. While full signing schemes vary, most follow a similar conceptual pattern:
- Collect inputs: method, path, query, selected headers, timestamp/nonce, and sometimes a body hash.
- Canonicalize: build a deterministic string representation (same order, same casing rules, same separators).
- Sign: compute a hash or HMAC using a shared secret or private key.
- Attach: send the signature in a header (and often include timestamp/nonce headers too).
Canonical string example (deterministic input)
The key to avoiding fragile copy-paste is to compute the canonical string from the actual request object at runtime, rather than manually assembling pieces in the UI. The following example shows a simple canonical string that includes method, path, timestamp, and a SHA-256 hash of the body.
// Inputs you control const apiKey = pm.environment.get('apiKey'); const apiSecret = pm.environment.get('apiSecret'); // shared secret for HMAC const ts = String(Math.floor(Date.now() / 1000)); pm.request.headers.upsert({ key: 'X-Timestamp', value: ts }); // Build canonical components const method = pm.request.method.toUpperCase(); const url = pm.request.url; const path = '/' + url.path.join('/'); // Query canonicalization: key=value sorted by key const queryPairs = (url.query ? url.query.all() : []) .filter(q => q.key) .map(q => ({ key: q.key, value: q.value || '' })) .sort((a, b) => a.key.localeCompare(b.key)) .map(q => `${encodeURIComponent(q.key)}=${encodeURIComponent(q.value)}`); const canonicalQuery = queryPairs.join('&'); // Body hash (raw only; adjust for your API rules) let bodyRaw = ''; if (pm.request.body && pm.request.body.mode === 'raw') bodyRaw = pm.request.body.raw || ''; const bodyHash = CryptoJS.SHA256(bodyRaw).toString(CryptoJS.enc.Hex); // Canonical string const canonical = [method, path, canonicalQuery, ts, bodyHash].join('\n'); pm.variables.set('canonical_string', canonical);This canonicalization is intentionally simple. Real APIs may require specific header inclusion rules, normalized whitespace, or exact URL encoding rules. The technique remains the same: compute from pm.request so the signature always matches what you actually send.
Signing with HMAC (conceptual pattern)
Once you have a canonical string, compute an HMAC and attach it. Postman commonly supports CryptoJS in scripts.
// HMAC signature const signature = CryptoJS.HmacSHA256(pm.variables.get('canonical_string'), apiSecret) .toString(CryptoJS.enc.Hex); // Attach headers pm.request.headers.upsert({ key: 'X-API-Key', value: apiKey }); pm.request.headers.upsert({ key: 'X-Signature', value: signature });Practical stability tips:
- Sign what you send: compute the body hash from the final body (after any script modifications).
- Be deterministic: sort query parameters and use consistent separators.
- Keep secrets out of collections: read secrets from the active environment (or a secure source) rather than hardcoding.
- Version your scheme: if the API supports it, include a signature version header so changes are manageable.
5) Debugging scripts with console logs
Pre-request scripts can fail silently if you do not inspect what they computed. Use the Postman Console to verify values and quickly isolate issues.
Logging key values
console.log('Correlation ID:', pm.collectionVariables.get('correlationId')); console.log('Timestamp:', pm.request.headers.get('X-Timestamp')); console.log('Canonical string:', pm.variables.get('canonical_string')); console.log('Request URL:', pm.request.url.toString());Common debugging checks
- Header duplication: use
headers.upsertinstead ofadd. - JSON parsing errors: wrap
JSON.parseintry/catchand log the raw body when it fails. - Scope confusion: log where you stored a value (collection vs environment) and confirm you are reading from the same scope.
- Signature mismatches: log the canonical string and body hash; mismatches usually come from different encoding, parameter ordering, or body formatting.
Lab: Unique email/username per run + correlation ID header for every request in a folder
Goal
Create a folder-level pre-request script that (1) generates a unique username and email for each run and stores them for reuse, and (2) attaches a correlation ID header to every request in that folder.
Step 1: Create a folder for the lab
- Create (or choose) a folder that contains multiple requests (for example: create user, get user, update user).
- Ensure the requests in the folder reference variables for username/email in their bodies or query params, such as
{{run_username}}and{{run_email}}.
Step 2: Add a folder-level pre-request script
Open the folder, go to its Pre-request Script tab, and paste:
function randomString(len) { const alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789'; let out = ''; for (let i = 0; i < len; i++) out += alphabet[Math.floor(Math.random() * alphabet.length)]; return out; } // 1) Correlation ID for every request in this folder let corr = pm.collectionVariables.get('correlationId'); if (!corr) { corr = (crypto && crypto.randomUUID) ? crypto.randomUUID() : pm.variables.replaceIn('{{$guid}}'); pm.collectionVariables.set('correlationId', corr); } pm.request.headers.upsert({ key: 'X-Correlation-ID', value: corr }); // 2) Unique identity per run (username/email) // Generate once, reuse across all requests in the folder run let runUser = pm.collectionVariables.get('run_username'); let runEmail = pm.collectionVariables.get('run_email'); if (!runUser || !runEmail) { const ts = Date.now(); const suffix = randomString(6); runUser = `user_${ts}_${suffix}`; runEmail = `user_${ts}_${suffix}@example.test`; pm.collectionVariables.set('run_username', runUser); pm.collectionVariables.set('run_email', runEmail); } // Optional: log for verification console.log('run_username:', runUser); console.log('run_email:', runEmail); console.log('X-Correlation-ID:', corr);Step 3: Use the variables in requests
In any request body (raw JSON), reference the variables:
{ "username": "{{run_username}}", "email": "{{run_email}}" }In headers, you do not need to add X-Correlation-ID manually because the script upserts it for every request in the folder.
Step 4: Run the folder and verify
- Run the folder (via Collection Runner or by sending requests manually in sequence).
- Open the Postman Console and confirm the same
run_username/run_emailare reused across requests in the run, whileX-Correlation-IDis present on each request. - Re-run the folder and confirm a new unique username/email is generated for the new run (if you want a fresh identity each run, clear the stored collection variables before re-running, or adjust the script to always regenerate).