Postguy
Reference

Scripting API

Reference for the script globals (pm, pg, bru, req, res, test, expect), the assertion library, the Node-style shims and the sandbox limits.

Postguy runs pre-request and post-response scripts in an embedded QuickJS engine. The scripting surface is implemented as Rust closures in crates/script. This page lists every global and member.

Scripts apply to HTTP requests and, at request level only, to gRPC requests (gRPC scripts). WebSocket requests do not run scripts. Settings › Disable scripts turns every script off.

Phases

Phasepm.info.eventNamepm.response / resRequest edits
Pre-requestprerequestnot definedallowed
Post-responsetestdefinedrefused: pm.request is read-only in post-response scripts

The request's Tests tab runs in the post-response phase, after the post-response scripts. It runs the enabled assertions in order first, then the Tests script.

  • Each script runs inside its own function. const and let names never collide between the collection, folder and request scripts. Share data through variables, not JS scope.
  • After a script finishes, queued promise callbacks and setTimeout callbacks are drained before the next script starts.
  • A thrown error is recorded as a failure with the script's origin label and line number.
  • A failing pre-request script stops the remaining pre-request scripts of that run.
  • A failing post-response script does not stop the later ones, unless a limit (time, cancel) tripped.

Globals

GlobalAvailableWhat it is
pmalwaysThe Postman-style API, described below. pm.response exists in post-response scripts only.
postmanalwaysOnly postman.setNextRequest(name).
pgalwaysEvery pm member plus Bruno-style variable and request methods, flow control, pg.assets and pg.report.
brualwaysBruno-style variables, sendRequest, setNextRequest, runner.
reqalwaysBruno-style request object.
respost-responseBruno-style callable response.
testalwaysThe same function as pm.test.
expectalwaysThe same function as pm.expect.
consolealwayslog, info, warn, error, debug.
atob, btoaalwaysBase64 helpers (Latin-1 text).
setTimeout, clearTimeoutalwaysQueued timers, run after the script body.
BufferalwaysMinimal Node-style Buffer.
requirealwaysBuilt-in fs, path, buffer, crypto shims and modules from the collection's Assets folder.
__dirname, __filenamealways/ and /script.js.

console, atob, btoa, timers

  • console.log/info/warn/error/debug(...args) joins arguments with a space. Objects are rendered as JSON. A line is cut at 16 KiB with … (truncated). Lines past the line limit are dropped.
  • btoa(text) throws InvalidCharacterError: btoa only encodes Latin-1 text for characters above U+00FF.
  • atob(text) ignores whitespace and optional padding and throws InvalidCharacterError: the string to be decoded is not correctly encoded on bad input. The result is a Latin-1 string.
  • setTimeout(fn, delay?, ...args) returns a numeric id. The delay is not waited: callbacks run after the script body, ordered by delay and then by creation. Timers scheduled from timers run in later rounds. clearTimeout(id) cancels one.

pm

Variable scopes

pm.globals, pm.collectionVariables, pm.environment, pm.variables and pm.iterationData share the same methods. Values are stored as text: strings as-is, everything else through JSON.stringify. The limit is 1 MiB.

MemberNotes
get(key)Value, or undefined when missing.
has(key)true when get would return a value.
toObject()All entries of the scope as an object.
set(key, value)Not on iterationData.
unset(key)Not on iterationData.
clear()Not on iterationData.
replaceIn(text)pm.variables only. Replaces {{var}} over every scope using the precedence below.
namepm.environment only. Name of the active environment, or null.
ScopeWritableNotes
pm.globalsyesWorkspace globals.
pm.collectionVariablesyesCollection variables.
pm.environmentyes, when an environment is selectedOtherwise set/unset/clear throw no environment is selected; choose one before setting environment variables.
pm.variablesyesset stores a local value for the run; local values are never persisted. get reads the merged chain. toObject() returns the merged view.
pm.iterationDatanoCurrent data row of a collection run. Throws pm.iterationData is read-only on write attempts (no write methods exist). Values keep their JSON type.

pm.variables.get looks in this order, and the first match wins: local values, runtime variables (pg.setVar), iteration data, environment, folder variables, collection variables, globals.

Request

pm.request is read-only after the response:

MemberDescription
name, idRequest name and id.
methodGetter/setter. The setter upper-cases the value.
urlGetter returns an object; setter accepts a string or an object with toString. Text is unresolved ({{vars}} still in place).
url.toString(), url.getHost(), url.getPath(), url.getQueryString()URL parts.
url.query.get(key), has(key), toObject()Read query rows.
url.query.add({key, value}), upsert({key, value}), remove(key)Edit query rows (pre-request only).
headers.get(name), has(name), toObject()Enabled headers; names are case-insensitive.
headers.add({key, value})Append a header.
headers.upsert({key, value})Replace the header of that name and enable it, or append.
headers.remove(name)Remove every header of that name.
body.modenone, raw, urlencoded, formdata or other (binary, GraphQL and any mode scripts cannot edit).
body.rawGetter/setter for raw text. Getter returns null unless the mode is raw. Setting switches the body to raw.
body.urlencoded, body.formdataArrays of {key, value}; empty unless the mode matches.
body.update(value)Same as setting body.raw; non-strings are stored as JSON.

A key/value object without a key throws expected an object with a key and a value.

Response

pm.response exists only in post-response scripts. It is a plain snapshot:

MemberDescription
codeNumeric status.
statusReason text, for example OK.
reason()The same text as status.
responseTimeMilliseconds.
responseSizeBody size in bytes.
headers.get(name), has(name), toObject()Case-insensitive header lookup.
text()Body as text (invalid UTF-8 replaced).
json()Parsed body; throws JSONError: <detail> when the body is not valid JSON.
toResponse assertions, listed below.

Cookies

MemberDescription
pm.cookies.get(name), has(name), toObject()Name/value pairs from the response's Set-Cookie headers plus the cookies the jar would send for the request URL. A response cookie wins over a jar cookie of the same name.
pm.cookies.jar()Throws pm.cookies.jar() is not supported yet.

Tests and requests

MemberDescription
pm.test(name, fn?)Records a passing test when fn returns, a failing one with the error message when it throws. Without fn it records a pass. If fn returns a promise, the test is the test did not finish until the promise settles; a rejection fails it with the reason.
pm.test.skip(name, fn?)Records a skipped test without running fn.
pm.expect(value)Chai-style assertion, see Assertions.
pm.sendRequest(urlOrOptions, callback?)Sends a request through the engine's pipeline (cookie jar and settings apply). Returns a promise, or calls callback(error, response).
pm.infoeventName, iteration, iterationCount, requestName, requestId.

pm.sendRequest options:

  • A URL string, or { url, method, header, body }.
  • url is a string or an object with toString.
  • method defaults to GET.
  • header is an array of {key, value} or a plain object.
  • body is { mode: "raw" | "urlencoded" | "formdata", raw | urlencoded | formdata }.

The response has the same shape as pm.response, but without to. A failed send passes an Error to the callback, or rejects the promise.

It throws these errors:

  • pm.sendRequest was called more than 10 times, after the limit.
  • the script was cancelled, when cancelled.
  • pm.sendRequest expects a URL or an options object, for any other argument.

Flow control

CallEffect
pm.execution.setNextRequest(name)Runner only: the next request to run, by name.
pm.execution.setNextRequest(null)Any non-string argument (or none) stops the run after this request.
pm.execution.skipRequest()Pre-request only: skips sending this request. In a post-response script it throws pm.execution.skipRequest() can only be called in a pre-request script.
postman.setNextRequest(name)Alias of pm.execution.setNextRequest.

See Collection runner for how the runner applies them.

// Pre-request: sign the request and skip it when a flag is off
const stamp = Date.now().toString();
pm.variables.set("stamp", stamp);
pm.request.headers.upsert({ key: "X-Stamp", value: stamp });
if (pm.environment.get("dryRun") === "true") pm.execution.skipRequest();
// Post-response: assert and chain
pm.test("created", () => pm.response.to.have.status(201));
pm.test("has id", () => pm.expect(pm.response.json()).to.have.property("id"));
pm.collectionVariables.set("userId", String(pm.response.json().id));
pm.execution.setNextRequest("Fetch user");

Assertions

pm.expect(value) (also expect(value)) returns an assertion. Failures throw an AssertionError whose message is expected <value> to <description> (or to not <description> after not). Values that are cyclic or too large fail with value is too deeply nested or too large.

Chain words

These words return the assertion unchanged: to, be, been, is, that, which, and, has, have, with, at, of, same, but, does, still, own, all, any.

Flags: not negates the next check. deep makes equal, include, property and oneOf compare structurally.

Terminals

These properties run the check when read, for example pm.expect(x).to.be.true.

TerminalPasses when
okThe value is truthy.
true / falseThe value is exactly true / false.
null / undefinedThe value is null / undefined.
NaNThe value is a number that is NaN.
existThe value is neither null nor undefined.
emptyAn empty string, array or object. Any other type fails.

Methods

Method (aliases)Checks
equal(x) (equals, eq)Strict equality. Arrays and objects compare by identity unless deep is set.
eql(x) (eqls)Deep equality.
above(n) (gt, greaterThan)Number greater than n.
below(n) (lt, lessThan)Number less than n.
least(n) (gte)Number at least n.
most(n) (lte)Number at most n.
within(lo, hi)Number between lo and hi, inclusive.
a(type), an(type)Type name, compared lower-case: undefined, null, boolean, number, string, array, object, function, symbol.
include(x) (includes, contain, contains)Substring of a string; element of an array; or an object containing every key/value of the given object (deep compare).
property(name, value?)The object has the property; with value, it also equals it. A passing property makes the property's value the new subject of the chain.
lengthOf(n) (length)String length (UTF-16 units) or array length. Other types fail with expected <value> to have a length.
keys(...names) (key)The object has exactly these keys (array or arguments).
members(array)Same elements in any order (deep compare).
oneOf(array)The value is in the array.
match(regex) (matches)A string matching the regular expression.
string(text)A string containing text.

Number comparisons on a non-number throw expected <value> to be a number. A missing argument throws the assertion needs an argument.

pm.expect(pm.response.json().items).to.be.an("array").and.have.lengthOf(2);
pm.expect({ a: 1, b: [2] }).to.eql({ a: 1, b: [2] });
pm.expect("abc").to.match(/b/);
pm.expect(5).to.be.within(1, 10);

pm.response.to

Response assertions use the same chain words, not and deep. They throw there is no response in a pre-request script when used without a response.

Terminals (status class and content type):

TerminalPasses when
okStatus is 2xx.
successStatus is 2xx.
errorStatus is 400 or above.
clientErrorStatus is 4xx.
serverErrorStatus is 5xx.
notFoundStatus is 404.
jsonA content-type header contains json.

Methods:

MethodChecks
status(code | reason)A number compares the status code; any other value compares the reason text case-insensitively.
header(name)The header is present.
header(name, value)The header has exactly this value. Name lookup is case-insensitive.
body(text)The body text equals text.
jsonBody()The body is valid JSON.
jsonBody(path)The dot path exists (object keys and array indices, for example data.items.0.id).
jsonBody(path, value)The value at the path equals value (JSON equality).
pm.test("json ok", () => {
  pm.response.to.be.ok;
  pm.response.to.be.json;
  pm.response.to.have.header("content-type", "application/json");
  pm.response.to.have.jsonBody("data.items.0.id", 1);
});

pg

pg contains every member of pm (the same values) and these additions. It does not add postman.

Variables (also on bru)

Bruno-style methods. They call the same scopes as pm:

MethodScope
getVar(key), setVar(key, value), hasVar(key), deleteVar(key)Runtime variables: kept by the engine for the session and across requests, never written to a file.
getEnvVar(key), setEnvVar(key, value), hasEnvVar(key), deleteEnvVar(key)Active environment. setEnvVar and deleteEnvVar throw the no-environment error when none is selected.
getGlobalVar(key), setGlobalVar(key, value)Globals.
getGlobalEnvVar(key), setGlobalEnvVar(key, value)Globals (alias names).
getCollectionVar(key), setCollectionVar(key, value)Collection variables.
getFolderVar(key)Merged folder variables, read-only.
getRequestVar(key)Local values set with pm.variables.set.
getEnvName()Name of the active environment, or null.
getProcessEnv(key)Walks the normal variable chain. Operating-system environment variables are never exposed.
interpolate(text)Replaces {{var}} using the same precedence as pm.variables.

set* methods accept a third options argument and ignore it.

Request methods (on pg.request and req)

MethodNotes
getUrl(), getMethod(), getName()Reads.
setUrl(url), setMethod(method)Pre-request only. The method is upper-cased.
getHeader(name), getHeaders()Enabled headers; getHeaders() returns a snapshot object.
setHeader(name, value), setHeaders(object), deleteHeader(name)Pre-request only.
getBody()Raw text parsed as JSON when it parses, otherwise the string. Form modes return an array of {key, value}. undefined for no body or other.
setBody(value)A string as-is, anything else as compact JSON. undefined throws <label>.setBody expects a string or a JSON value.

pg.request also keeps the pm.request members. After the response, setters throw <label> is read-only in post-response scripts, where the label is req, pg.request or pm.request.

Flow control

CallEffect
pg.setNextRequest(name), pg.skipRequest()Same as the pm.execution calls.
pg.stopExecution()Stops the run after this request. Valid in both phases.

pg.assets

Read-only access to the collection's Assets folder (see Collections). Paths use / and are relative to the folder root. A path that climbs above the root throws path <p> leaves the collection assets.

MethodReturns
base64(path)File contents as a base64 string.
text(path)File contents as UTF-8 text; throws asset <path> is not UTF-8 text; use pg.assets.base64.
exists(path)true for a file or folder that exists.
list(dir?)Names of the direct children of a folder (root when omitted).

Requests that are not saved in a collection throw assets are only available for requests saved in a collection.

pg.report

Adds custom columns to run reports (see Collection runner).

MethodNotes
set(key, value)Primitives are stored as JSON; objects as their JSON.stringify text; strings longer than 32 KiB are cut with …. undefined, functions, symbols and BigInts remove the key.
get(key)The stored value, or undefined.
toObject()All fields in first-set order.

Limits: keys up to 128 characters (pg.report keys may be at most 128 characters), at most 100 fields (pg.report holds at most 100 fields).

pg.report.set("tenant", pg.getEnvVar("tenant"));
pg.report.set("latencyMs", pm.response.responseTime);

Bruno compatibility

Postguy accepts the Bruno script globals, so scripts from imported Bruno folders run. There is no Bruno export.

bru members:

MemberNotes
Variable methodsAll the variable methods above.
bru.sendRequest(...)Same as pm.sendRequest.
bru.setNextRequest(name)Same as pm.execution.setNextRequest.
bru.runner.setNextRequest(name), skipRequest(), stopExecution()Flow control.

Unsupported bru calls throw <name> is not supported in Postguy scripts:

CallError text
bru.sleep(ms)bru.sleep is not supported in Postguy scripts
bru.cwd()bru.cwd is not supported in Postguy scripts
bru.runRequest(...)bru.runRequest is not supported in Postguy scripts

req

The request methods plus accessors that read and write the same values: req.url (string), req.method, req.headers (snapshot object; assigning merges each entry into the headers) and req.body (parsed JSON or string; assigning calls setBody).

res

res (post-response only) is both a function and an object.

MemberDescription
res(path)Queries a JSON body. Supports a.b, a[0], a["key"] and ..key (every key at any depth, returned as an array, depth limit 64). Returns undefined when nothing matches or the body is not JSON.
res.status, res.statusText, res.responseTimeStatus code, reason text, milliseconds.
res.headersObject with lower-cased names; repeated headers are joined with , .
res.bodyParsed JSON, or the text when the body is not JSON.
res.getStatus(), getStatusText(), getHeader(name), getHeaders(), getBody(), getResponseTime()Accessor forms. getHeader is case-insensitive.
res.getSize(){ body, headers, total } in bytes.
test("token present", () => {
  expect(res.status).to.equal(200);
  bru.setVar("token", res("data.token"));
});

Node-style shims

Postguy does not embed Node. The shims are Rust closures over the collection's Assets folder.

require

SpecifierResult
fs, path, buffer, crypto (optionally node: prefixed)Built-in shims below.
./x, ../x, /xA CommonJS module from the Assets folder, resolved relative to the requiring module. Tried in order: the path itself, .js, .json, then /index.js. .json files are parsed.
anything elseThrows require('<name>') is not available in Postguy scripts; npm packages are not supported. For axios the hint ; use pg.sendRequest is appended.

Modules receive exports, require, module, __filename and __dirname.

  • Modules are cached per run, so dependency cycles see the partial module.exports.
  • A module that throws is not cached.
  • A missing module throws Cannot find module '<spec>' in the collection assets: <detail>.
  • At most 64 modules load per run.
  • Each source is limited to 8 MiB and must be UTF-8.

fs (read-only virtual file system)

The root / is the collection's Assets folder.

FunctionNotes
fs.readFileSync(path, options?)Returns a Buffer, or a string when an encoding is given ("utf8" or { encoding }).
fs.existsSync(path)true when the file or folder exists.
fs.readdirSync(path?)Names of the direct children.

Every writing function (writeFileSync, appendFileSync, mkdirSync, unlinkSync, rmSync, rmdirSync, renameSync, copyFileSync, truncateSync, chmodSync, symlinkSync, openSync, writeSync, createWriteStream, mkdtempSync and the callback forms writeFile, appendFile, mkdir, unlink, rm, rmdir, rename, copyFile, truncate, chmod, symlink, open, write) exists and throws fs.<name> is not available in Postguy scripts (scripts can only read collection assets).

path

POSIX only: join, resolve (with / as the working directory), normalize, dirname, basename(path, ext?), extname, isAbsolute, sep (/).

crypto

Only crypto.randomUUID().

Buffer

MemberNotes
Buffer.from(value, encoding?)value is a string, an array of bytes, or another Buffer. Anything else throws Buffer.from expects a string, an array of bytes or a Buffer.
Buffer.isBuffer(value)Type check.
buffer.lengthByte length.
buffer.toString(encoding?)Encode the bytes as text.

Encodings: utf8 (utf-8), base64, base64url, hex, latin1 (binary). Others throw Buffer encoding '<name>' is not supported in Postguy scripts. Base64 decoding is lenient (both alphabets, optional padding).

Limits

These are the defaults of Limits in crates/script/src/lib.rs. The wall-clock budget covers all scripts of one run.

LimitDefaultWhen exceeded
Wall-clock time5,000 msthe script ran longer than 5000 ms
Engine heap64 MiBthe script used more than 64 MiB of memory
Native stack1 MiBThe script fails with an engine error (for example runaway recursion).
pm.sendRequest calls10 per runpm.sendRequest was called more than 10 times
Console lines kept1,000Further lines are dropped.
Console line length16 KiBCut with … (truncated).
pm.test results kept (assertions count too)1,000more than 1000 tests; the rest were ignored (once).
Variable value size1 MiBa variable value may not be larger than 1 MiB
Pending timers10,000too many pending timers
Timer rounds100setTimeout callbacks kept scheduling more callbacks
Promise jobs per drain round1,000,000too many queued promise jobs
Modules loaded by require64 per runscripts may load at most 64 modules per run
Module source size8 MiBmodule <path> is larger than 8 MiB
pg.report100 fields, 128-char keys, 32 KiB valuesSee pg.report.

A user cancel stops a running script with the script was cancelled. Scripts have no file system, process or network access, except pm.sendRequest and read-only Assets. An engine error or panic becomes a recorded failure, never a crash.

Copyright © 2026