Browse by section

QA 日本語

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. jsonwebtoken is not among Postman’s built-in modules
  • require('crypto') does not work either. The available Node.js modules are limited to path, buffer, util, url, querystring, stream and a few others
  • For the record, require('uuid') does work—it is built in

Sponsored

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

Sponsored

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 err and res.code. pm.sendRequest only sets err when the transport fails; a 401 leaves err as null

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

Sponsored

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

  • jsonwebtoken and crypto are not built in. Load pm.require('npm:crypto-js@4.2.0') or similar
  • uuid, lodash, moment, ajv and cheerio are available via require()
  • Store token expiry and refresh only near expiration
  • Check both err and res.code after pm.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 ajv when there are many fields
  • Pass values with pm.collectionVariables. Avoid pm.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.