pm Scripting API Compatibility¶
Vayu exposes a Postman-compatible pm scripting API for pre-request and test
scripts. Scripts run in the engine's QuickJS runtime
(engine/src/runtime/script_engine.cpp); the global pm object is bound there. The
intent is that the most common Postman scripts paste in and run unchanged.
"Postman" is a trademark of Postman, Inc. Vayu is not affiliated with or endorsed by Postman. Vayu re-implements a compatible API surface (method names and behaviour); it does not ship Postman's code or documentation. References to Postman in code and docs are nominative ("compatible with…") only.
Supported surface¶
| Group | API |
|---|---|
| Core | pm, pm.test(name, fn), pm.expect(value) |
| Response | pm.response.code, .status, .responseTime, .headers, .json(), .text() |
| Response assertions | pm.response.to.have.status(code), .header(name), .jsonBody() |
| Request | pm.request.url, .method, .headers, .body |
| Environment | pm.environment.get(name), pm.environment.set(name, value) |
| Globals | pm.globals.get(name), pm.globals.set(name, value) |
| Collection vars | pm.collectionVariables.get(name), pm.collectionVariables.set(name, value) |
| Console | console.log/info/warn/error |
Variable writes persist to the scope they target (environment / collection / globals) and
participate in variable resolution. Calling set(name, value)
on a variable that already exists updates only its value - the existing secret flag,
enabled flag and type are preserved. A set on a new name creates it with the defaults
(not secret, enabled, type: "string").
Assertion chains (pm.expect / pm.response.to)¶
Chai-style chains, implemented in the QuickJS runtime:
.to.equal(v) .to.eql(v) .to.exist
.to.be.true .to.be.false .to.be.null .to.be.undefined
.to.be.ok .to.be.empty
.to.be.above(n) .to.be.below(n) .to.be.at.least(n) .to.be.at.most(n)
.to.have.property(name) .to.have.length(n) .to.have.lengthOf(n)
.to.include(v) .to.contain(v)
.to.be.a(type) .to.be.an(type) .to.match(/regex/)
.to.not … (negates the chain)
Not (yet) supported¶
These Postman APIs are not implemented - scripts that rely on them will fail:
pm.sendRequest(...)- sending auxiliary requests from a scriptpm.variables.*- the merged/resolved variable accessor (use the scopedpm.environment/pm.collectionVariables/pm.globalsinstead)pm.iterationData.*- data-file driven runspm.cookies.*- Request mutation -
pm.request.headers.add/upsert/remove(...), and editing the URL, method, or body from a script (see below) pm.info,pm.execution,pm.visualizer- The
tests["name"] = boollegacy assertion style (usepm.test)
Request mutation & URL variables¶
A pre-request script cannot change the outgoing request in Vayu today.
pm.requestis a read-only snapshot. Itsurl/method/headers/bodyare copied out of the C++Requestinto a plain JS object (script_engine.cpp,setup_pm_request); there are no setters and the object is never read back after the script runs (onlytestsand console output are). Assigningpm.request.url = …mutates the throwaway JS object and is discarded.- Setting a variable that the URL references (
pm.environment.set("host", …)with a{{host}}in the URL) also has no effect on the current request:{{…}}placeholders are resolved app-side, before the payload reaches the engine (app/src/modules/request-builder/index.tsx,resolveString(request.url)), whereas the pre-request script runs later, in the engine. The variable write only affects subsequent runs.
This matches Postman's behaviour for the URL/method/body (its docs mark the body immutable
and provide no URL mutators) but diverges on headers - Postman does support
pm.request.headers.add/upsert/remove, which Vayu does not yet implement.
TODO (future)¶
To make the full Postman pattern work - "set a variable in a pre-request script and have it
change the outgoing URL/headers" - variable resolution has to move (or be duplicated) into
the engine and run after the pre-request script, instead of entirely app-side beforehand.
That means sending the unresolved URL/headers plus the variable maps to the engine and
interpolating {{…}} in C++ post-script (and applying the same to the load-test path). This
is a deliberate architectural change in resolution ownership, deferred for now. A smaller
intermediate step is mutable headers (pm.request.headers.*) with write-back from the JS
request object to the C++ Request before client.send().
Editor autocomplete¶
The full completion set (labels, snippets, signatures, docs) is generated server-side by
the engine and served at GET /scripting/completions
(engine/src/http/routes/scripting.cpp). This endpoint is the single source of truth
for what the editor advertises, and it is fetched once on startup and cached
(app/src/queries/script-completions.ts).
The script editors are mounted via the shared CodeEditor
(app/src/components/ui/code-editor.tsx) with language="javascript".
useScriptCompletionProvider (app/src/hooks/useScriptCompletionProvider.ts, called once
in App) registers the cached completions with Monaco's JavaScript language via
registerCompletionItemProvider (trigger character .), so typing pm. surfaces the
pm.* entries and snippets alongside Monaco's built-in JavaScript IntelliSense. The
registration is global per language, so one call covers every script editor instance.
The
kindfield on each completion usesmonaco.languages.CompletionItemKindnumeric values (Function = 1, Field = 3, Variable = 4, Snippet = 28); changing the engine constants requires an engine rebuild for new icons to take effect.
Where it lives¶
| Concern | Location |
|---|---|
pm runtime (QuickJS bindings) |
engine/src/runtime/script_engine.cpp |
| Completion metadata endpoint | engine/src/http/routes/scripting.cpp |
| Completion fetch + cache | app/src/queries/script-completions.ts |
| Monaco completion provider | app/src/hooks/useScriptCompletionProvider.ts |
| Shared editor wrapper | app/src/components/ui/code-editor.tsx |
| Script editor panels | app/src/modules/request-builder/components/RequestTabs/panels/{Pre,Test}ScriptPanel.tsx, app/src/modules/collections/CollectionDetail/ScriptTab.tsx |