All sections

Core features

Scripting

On this page

Tetiva runs JavaScript at two points of a request: before it is sent (Pre-request) and after the response arrives (Post-response). A pre-request script injects a header, computes a signature, or writes an environment variable; a post-response script reads the body, stores a token, and asserts the result with pm.test. Scripts live on the Scripts tab of a request or a collection, run in a sandbox with no file or network access, and everything they print shows up on the Tests tab next to the response.

Where scripts live#

Every request and every collection has a Scripts tab (for collections, open it through Open Details in the context menu). Inside are two buttons — Pre-request and Post-response — each with its own editor, and switching between them keeps the undo history.

A WebSocket request has a single button, Pre-connect: the script runs before the handshake, and there is no post-response phase — see WebSocket.

The editor completes pm. as you type, and {{var}} placeholders resolve the same way as in a request body: hover one to see the value from the active environment, with secrets masked.

Tetiva
v1.3.1
The Scripts tab in Tetiva showing a post-response script with pm.test and the resulting test listThe Scripts tab in Tetiva showing a post-response script with pm.test and the resulting test list
A post-response script stores a token and asserts the reply — results land on the Tests tab

Inheritance: request → collection → parent#

A script on the request replaces the collection script entirely. When the request field is empty or holds only blank lines, Tetiva walks up the collection tree and takes the first non-empty script it finds (the walk stops after 50 levels). Pre and post inherit independently, so a post-response script can come from a collection while the pre-request script is defined on the request itself.

A shared auth script therefore belongs on the root collection: it applies to everything underneath until a request defines its own.

The pm.* surface#

Available in both phases:

CallWhat it does
console.log(...)Prints into the Console block on the Tests tab; an object or an array prints as JSON. warn, error and info write to the same stream — there are no levels
pm.environment.get(key)Variable value, or undefined
pm.environment.set(key, value)Sets a variable; the value is coerced to a string
pm.environment.unset(key)Drops a variable from this run
pm.collectionVariables.get/set/unsetSame as pm.environment: works on the active environment's variables
pm.variables.get/set/unset/has, replaceIn(text), toObject()Variables of this run: a value set in a pre-request script fills the request's {{variables}} and reaches its post-response script, but is never saved to the environment. get falls back to the environment
btoa(text), atob(base64)Base64, as in a browser. atob also takes base64url and a value without padding, so JSON.parse(atob(jwt.split('.')[1])) reads a JWT payload
pm.expect(value)A chai 4.5 assertion, as in Postman. Outside pm.test, a failed assertion is a script error
pm.request.methodRequest method
pm.request.urlURL after variable substitution
pm.request.protocolhttp, grpc, graphql or websocket
pm.request.headers.upsert({ key, value })Adds a header or replaces an existing one; the name matches in any case, so content-type replaces Content-Type
pm.request.headers.add({ key, value })Adds a header, as a second value when one with that name exists
pm.request.headers.remove(key)Removes a header, whatever the case of its name

pm.request exists in both phases, but headers.add, upsert and remove do nothing in a post-response script: the request is already on the wire and the modified copy is never read.

gRPC adds pm.request.service, pm.request.grpcMethod, pm.request.message (the JSON body) and pm.request.metadata with get(key), set(key, value), remove(key) and toObject(). Metadata reaches the script as a copy: set changes what goes on the wire, while the stored request stays untouched. See gRPC.

GraphQL adds pm.request.graphqlVariables and pm.request.graphqlOperation. See GraphQL.

Post-response only:

CallWhat it does
pm.response.codeResponse code as a number
pm.response.statusThe reason phrase, "OK", as in Postman; the status name for gRPC
pm.response.text()Response body as a string
pm.response.json()Body parsed as JSON; invalid JSON yields undefined instead of throwing
pm.response.responseTimeResponse time in milliseconds
pm.response.headers.get(name), pm.response.headers.has(name)A response header's value (name matched case-insensitively) or undefined; whether the header is there
pm.response.to.have.status(...), .header(...), .body(...), .jsonBody(...), pm.response.to.be.jsonResponse checks, as in Postman; status takes a code or a reason phrase
pm.response.to.be.ok, success, clientError, serverError, notFound and othersStatus class; for gRPC ok and success mean status 0
pm.test(name, fn)A test: fn returning normally is PASS. A failed assertion is FAIL with its message, as in Postman (AssertionError: expected 200 to equal 201); any other error, a TypeError say, is FAIL with the line it broke on

For gRPC there are two extras: pm.response.statusText (the status name) and pm.response.metadata, a plain object holding the first value of each key.

Assertions use chai 4.5, as in Postman: pm.expect(x).to.equal(y), eql, include, property, lengthOf and the rest. A plain throw still fails a test too.

What happens to variables#

Values written with pm.environment.set land in the active environment once the request finishes: existing variables are updated, missing ones created as regular (non-secret) variables. With no active environment, the changes live only inside that run. pm.variables.set never reaches the environment: the value wins over an environment variable of the same name for this request only. Resolution order is covered in Environments and variables.

Headers the script sets are substituted as well, in every protocol. A {{variable}} written by a script used to travel to the server verbatim; now it is expanded before the request goes out like everything else.

Sandbox limits#

  • No filesystem, no network: fetch, XMLHttpRequest, WebSocket, require, process, module, Buffer and __dirname are undefined.
  • No timers: setTimeout and setInterval do not exist. Promise is there, but nothing can defer work.
  • Standard JSON and Math are available, and so are btoa and atob.
  • pm.globals and pm.sendRequest exist only to fail with a clear message: pm.globals is not supported in Tetiva; use pm.environment and pm.sendRequest is not supported in Tetiva: scripts cannot make network calls.
  • No state survives a run — each script gets a fresh VM, so globals from the previous run are gone.
  • Five seconds per phase. A script that ignores the interrupt — a backtracking regular expression is the classic case — is left running in the background instead of holding the request.
  • Memory is not capped. Worth remembering before you run someone else's collection.

When a script fails#

A pre-request script that throws or runs out of time stops the send, as in Postman. The request does not go out, nothing lands in History and the environment keeps its values. The response pane says Request not sent: The pre-request script of this request failed: (or of collection “Payments” when the script came from a collection), then the error with its line and column, where to fix it, and the Console lines the script printed before it failed.

A post-response script that fails leaves the response in place. Tests that passed before the failure stay on the Tests tab along with the console output, and Script Errors reads Post-response: with the message and (line 5, column 6). The environment changes that script made are not saved.

Tests and console#

The Tests tab holds three blocks: Test Results with PASS/FAIL per pm.test, Console with output from both phases (lines tagged [pre] and [post]), and Script Errors with the phase and message. The tab badge shows passed/total, or a dot when there is only console output. More on the response pane in Response viewer.

Examples#

Pre-request: a trace header and a guard.

pm.request.headers.upsert({ key: "X-Request-Id", value: String(Date.now()) })

var token = pm.environment.get("access_token")
if (!token) {
  console.warn("access_token is empty — run the login request first")
}

Post-response: store the token, assert the reply.

var data = pm.response.json()

pm.test("status is 200", function () {
  pm.response.to.have.status(200)
})

pm.test("token returned", function () {
  pm.expect(data).to.have.property("access_token")
})

if (data && data.access_token) {
  pm.environment.set("access_token", data.access_token)
}

Copy as cURL and scripts#

Copy as cURL on an HTTP request runs the pre-request script as a dry run: header and variable changes show up in the command, but nothing is written to the database and no history entry is created. If the script fails, nothing is copied: the toast names the error, and its Generate code… button copies the command without running the script. The command never goes to the network for an OAuth 2.0 token either — with none cached it goes out without Authorization and carries a warning. The other Copy as entries, Copy as cURL on a GraphQL request and the Generate code dialog do not run the script and warn that it was skipped. See Import and export.

Scripts written for Postman may need edits: only the pm.* subset listed above is implemented, so pm.sendRequest, pm.globals or require have to go: a script that calls one of them fails on that line, and in a pre-request script that stops the send. What survives a migration is covered in Migrate from Postman.

updated