Useful Scripts for Pre-request and Post-response in Postman
Scripts in Postman’s Pre-request and Post-response tabs let you refresh tokens and validate responses without manual work.
This article was published in 2024 and revised in September 2026. Some of the original code does not run in Postman’s sandbox and has been replaced.
jwt.sign()does not work.jsonwebtokenis not among Postman’s built-in modulesrequire('crypto')does not work either. The available Node.js modules are limited topath,buffer,util,url,querystring,streamand a few others- For the record,
require('uuid')does work—it is built in
What each tab is for
| Pre-request | Post-response | |
|---|---|---|
| Runs | Before the request is sent | After the response arrives |
| Used for | Preparing credentials and values | Assertions, passing values onward |
| Typical | Token refresh, timestamps, signatures | Status checks, schema validation |
The tab formerly called Tests was renamed to Post-response. Older articles referring to the “Tests tab” mean this one.
Know which modules exist
Not knowing this wastes time. Postman scripts run in a restricted sandbox, not in Node.js.
| Kind | Available |
|---|---|
| npm packages | ajv, chai, cheerio, csv-parse/lib/sync, lodash, moment, postman-collection, uuid, xml2js |
| Node.js modules | path, assert, buffer, util, url, punycode, querystring, string-decoder, stream, timers, events |
| Anything else | Load with pm.require('npm:package@version') |
crypto, fs and jsonwebtoken are not built in. For cryptography, load an external package or use the global crypto object (Web Crypto API).
Pre-request in practice
1. Refresh the access token automatically
The highest-value use. It removes the “ran the suite, token had expired” rework loop.
const expiresAt = Number(pm.environment.get('token_expires_at') || 0);
const now = Date.now();
// refresh only within 60 seconds of expiry
if (now < expiresAt - 60_000) {
return; // still valid
}
pm.sendRequest({
url: pm.environment.get('base_url') + '/auth/token',
method: 'POST',
header: { 'Content-Type': 'application/json' },
body: {
mode: 'raw',
raw: JSON.stringify({
client_id: pm.environment.get('client_id'),
client_secret: pm.environment.get('client_secret'),
}),
},
}, (err, res) => {
if (err) {
console.error('Token request failed', err);
return;
}
if (res.code !== 200) {
console.error('Token request failed', res.code, res.text());
return;
}
const data = res.json();
pm.environment.set('access_token', data.access_token);
pm.environment.set('token_expires_at', Date.now() + data.expires_in * 1000);
});
- Do not refresh every time. Store the expiry and only re-fetch near it—avoids hammering the auth server
- Check both
errandres.code.pm.sendRequestonly setserrwhen the transport fails; a 401 leaveserrasnull
2. Timestamps and random values
// UNIX timestamp in seconds
pm.environment.set('current_timestamp', Math.floor(Date.now() / 1000));
// uuid is built in
const { v4: uuidv4 } = require('uuid');
pm.environment.set('request_id', uuidv4());
// or use the built-in dynamic variables
pm.environment.set('random_email', pm.variables.replaceIn('{{$randomEmail}}'));
pm.environment.set('random_name', pm.variables.replaceIn('{{$randomFullName}}'));
Avoid Math.random().toString(36).substring(7). The length is not stable and it occasionally produces an empty string—a source of intermittent failures.
Dynamic variables such as {{$guid}} work directly in a request body, but inside a script they must go through pm.variables.replaceIn().
3. HMAC signatures
Some APIs require a signed request. Since require('crypto') is unavailable, load an external package.
const CryptoJS = pm.require('npm:crypto-js@4.2.0');
const secret = pm.environment.get('api_secret');
const timestamp = Math.floor(Date.now() / 1000);
const method = pm.request.method;
const path = pm.request.url.getPath();
const message = `${method}\n${path}\n${timestamp}`;
const signature = CryptoJS.HmacSHA256(message, secret).toString(CryptoJS.enc.Hex);
pm.environment.set('timestamp', timestamp);
pm.environment.set('signature', signature);
Building a JWT works the same way. There is no convenient jwt.sign(), so you base64url-encode the header and payload, join them, and compute the HMAC.
const CryptoJS = pm.require('npm:crypto-js@4.2.0');
function base64url(source) {
return CryptoJS.enc.Base64.stringify(source)
.replace(/=+$/, '')
.replace(/\+/g, '-')
.replace(/\//g, '_');
}
const header = { alg: 'HS256', typ: 'JWT' };
const payload = {
sub: 'user@example.com',
name: 'John Doe',
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600,
};
const encodedHeader = base64url(CryptoJS.enc.Utf8.parse(JSON.stringify(header)));
const encodedPayload = base64url(CryptoJS.enc.Utf8.parse(JSON.stringify(payload)));
const secret = pm.environment.get('jwt_secret');
const signature = base64url(
CryptoJS.HmacSHA256(`${encodedHeader}.${encodedPayload}`, secret)
);
pm.environment.set('jwt_token', `${encodedHeader}.${encodedPayload}.${signature}`);
Never hard-code a secret in a script. Put it in an environment variable of type Secret—otherwise sharing the collection leaks the key.
Post-response in practice
1. Basic assertions
pm.test('status is 200', () => {
pm.response.to.have.status(200);
});
pm.test('responds within one second', () => {
pm.expect(pm.response.responseTime).to.be.below(1000);
});
pm.test('Content-Type is JSON', () => {
pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');
});
Do not set the response-time threshold too tight. A test that fails on network jitter is one people stop reading. One to two seconds is more useful than 500ms.
2. Checking the payload
pm.test('required fields are present', () => {
const data = pm.response.json();
pm.expect(data).to.have.property('user_id');
pm.expect(data.user_id).to.be.a('number');
pm.expect(data).to.have.property('email');
pm.expect(data.email).to.match(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
});
Check types, not just presence. A user_id that arrives as a string instead of a number passes to.have.property() unnoticed.
3. Validate against a JSON schema
With many fields, a schema is easier to maintain than individual assertions. ajv is built in.
const Ajv = require('ajv');
const ajv = new Ajv();
const schema = {
type: 'object',
required: ['user_id', 'email', 'created_at'],
properties: {
user_id: { type: 'integer' },
email: { type: 'string', format: 'email' },
created_at: { type: 'string' },
tags: { type: 'array', items: { type: 'string' } },
},
};
pm.test('response matches the schema', () => {
const validate = ajv.compile(schema);
const valid = validate(pm.response.json());
if (!valid) console.error(validate.errors);
pm.expect(valid, JSON.stringify(validate.errors)).to.be.true;
});
Include validate.errors in the failure message. “Does not match the schema” alone tells you nothing about which field.
4. Pass values to the next request
pm.test('got an ID', () => {
const data = pm.response.json();
pm.expect(data.user_id).to.exist;
// usable as {{user_id}} later
pm.collectionVariables.set('user_id', data.user_id);
});
Choose the right scope. Putting run-scoped values in globals pollutes every other collection.
| Scope | Use for |
|---|---|
pm.globals |
Avoid. It leaks everywhere |
pm.environment |
Per-environment settings: hosts, accounts |
pm.collectionVariables |
Values passed between requests in a run |
pm.variables |
Temporary, within one request |
Put shared logic at the collection level
Pasting the same script into every request means editing every request when it changes. Scripts on a collection or folder apply to everything beneath.
The execution order is:
- Collection Pre-request
- Folder Pre-request
- Request Pre-request
- (request is sent)
- Collection Post-response
- Folder Post-response
- Request Post-response
// collection-level Post-response
pm.test('no server error', () => {
pm.expect(pm.response.code).to.be.below(500);
});
// log slow requests
if (pm.response.responseTime > 2000) {
console.warn(`Slow: ${pm.info.requestName} (${pm.response.responseTime}ms)`);
}
You may see the trick of storing functions as strings in a collection variable and eval-ing them. It hurts readability and debuggability—collection-level scripts are the cleaner answer.
Running it in CI
Tests that only run in the Postman UI have limited value. Newman runs them from CI.
npm install -g newman
newman run collection.json \
-e environment.json \
--reporters cli,junit \
--reporter-junit-export results.xml
Do not commit an environment file containing secrets. Pass them from CI.
newman run collection.json \
-e environment.json \
--env-var "client_secret=$CLIENT_SECRET"
Jenkins integration is covered in test automation with Selenium, Appium and Jenkins. For writing API tests in Python, see 5 ways to make QA work faster with Python.
Summary
jsonwebtokenandcryptoare not built in. Loadpm.require('npm:crypto-js@4.2.0')or similaruuid,lodash,moment,ajvandcheerioare available viarequire()- Store token expiry and refresh only near expiration
- Check both
errandres.codeafterpm.sendRequest - Dynamic variables need
pm.variables.replaceIn('{{$guid}}')inside scripts Math.random().toString(36).substring(7)can return an empty string- Assert on types as well as presence; use
ajvwhen there are many fields - Pass values with
pm.collectionVariables. Avoidpm.globals - Shared logic at collection level; run it with Newman in CI
Above all, do not hard-code secrets. Sharing the collection exposes them immediately. Secret-type environment variables are the minimum precaution.