Back to Blog
Pre & Post Request Scripts Beyond the JavaScript Sandbox: Real Python and Shell in Voiden
N

Nikolas Dimitroulakis

06/07/2026

Pre & Post Request Scripts Beyond the JavaScript Sandbox: Real Python and Shell in Voiden

Every API client runs pre- and post-request scripts in a JavaScript sandbox — and there's an architectural reason why. Voiden is the only one that runs them on real interpreters: JavaScript, Python, and shell.

The Problem

You know this scene. You need to sign a request with HMAC before it goes out. Or chain a JWT login: hit the auth endpoint, grab the token, inject it into the next call. Or loop through a batch of negative tests with slightly different payloads.

So you open the Pre-request tab, and you start writing pm.* code — pm being Postman's global scripting object, the API your code has to go through for everything: pm.environment.get(), pm.request.headers.add(), pm.response.json(). Your script doesn't run as normal code. It runs as calls into the tool's dialect.

It works, at first. Then you need your company's actual signing logic — the one that already exists in a Python module your backend team maintains. You can't import it. You rewrite it in sandbox JavaScript, from memory, hoping you got the salt ordering right. Six months later the Python module changes, the sandbox copy doesn't, and you spend an afternoon debugging a 401 that isn't a bug in the API at all.

This isn't a Postman problem specifically. Almost every API client made the same choice: a JavaScript sandbox, sometimes with a Postman-compatible API bolted on for easy migration. Bruno, Insomnia, Requestly — different tools, same shape. A small, controlled JS runtime where your scripts live in a parallel universe, cut off from your actual stack.

To be fair: the sandbox is a reasonable design for small tests. It's predictable, it's portable, it can't accidentally rm -rf anything. Postman has even added npm package imports through its package library. But the fundamental constraint doesn't move. Your script runs in the tool's environment, not yours.

New to scripting? A 60-second primer

If you've never touched the scripting tab in an API client, here's the whole concept:

Pre-request scripts run before the request is sent. They prepare things the request needs but that can't be hardcoded: generate a fresh auth token, compute a signature, set a timestamp header, inject a value that changes on every run.

Post-response scripts run after the response comes back. They act on the result: validate it, extract a value (like a token or an ID), store it in a variable, or kick off the logic for the next request in a chain.

Put together, they turn a static request into a small workflow. The classic example: request one logs in and its post-response script saves the token; request two's pre-request script reads that token and sets the Authorization header. No copy-pasting tokens by hand, ever again.

One distinction worth knowing: scripts are not the same as assertions. Assertions are simple declarative checks — "status is 200," "body.id exists" — and for basic validation they're the right tool. Scripts are for when you need actual logic: conditions, loops, computation, chaining. If assertions are a checklist, scripts are code.

That's the concept. Now, back to the part where every tool implements it the same way.

Why every API client scripts in JavaScript

It's worth asking honestly: if JS-only scripting frustrates so many backend engineers, why does every tool do it? It's not laziness. It's architecture and history — four overlapping reasons, and each one is rational on its own.

The engine is already there. Nearly every modern API client is built on web technology, Electron or the browser itself. That means a JavaScript engine is already running inside the process. Exposing it as a scripting sandbox is close to free: spin up an isolated context, hand it a pm-shaped API, done. Supporting Python means either bundling an interpreter or depending on whatever version happens to be on the user's machine. One of these is an afternoon of work. The other is a commitment.

The sandbox is deterministic. The same sandboxed script behaves identically on every OS, every machine, every teammate's laptop. Real runtimes inherit the mess of real machines: interpreter versions, missing packages, PATH weirdness. For a vendor, that mess translates directly into support tickets. A sandbox is the cheaper thing to promise.

Cloud-first tools genuinely can't do it. This is the deepest reason. If your scripts sync to a cloud workspace and execute in hosted monitors and runners, then the vendor is running your code on their infrastructure. Letting users execute arbitrary Python with arbitrary package imports on someone else's servers is a non-starter. So the sandbox isn't really a scripting decision. It's a downstream consequence of the cloud decision.

And then there's Postman gravity. Postman didn't just build a scripting sandbox; it defined what "scripting" means in an API client. Millions of developers have pm.* muscle memory and collections full of scripts written against it. For every tool that came after, the cheapest path to adoption was obvious: offer a pm-shaped API so those scripts migrate with minimal rewriting. So the pattern got cloned — Bruno has bru.*, Requestly has rq.*, each a slightly different accent of the same dialect. Every clone made the pattern look more like a law of nature. At some point, nobody was choosing the JS sandbox anymore. They were inheriting it.

Once you see that, the whole landscape makes sense. And it also explains why Voiden could take a different path without heroics: we're offline-first. Your scripts run on your machine, where you already run Python, bash, and everything else. There is no vendor server to protect. The same architecture that removed accounts and cloud sync also removed the reason for the sandbox.

Meanwhile, the cost of JS-only lands on the developer. Backend teams live in Python, Go, Rust, and JVM stacks. Their auth libraries, signature generators, and compliance utilities already exist, tested, in those languages. Forcing that through a JS sandbox means either rewriting the logic (and letting it drift from the source of truth) or handling it manually outside the tool (and losing the automation entirely). We kept seeing both: token rotations done by hand, little helper scripts scattered across repos, doing what the API client should have done.

Rewriting working production logic into a tool's dialect is pure waste.

Scripting is not a helper feature. It's a runtime layer.

That's the design position we took in Voiden, and it's the whole difference.

In Voiden, pre-request and post-response scripts run on real interpreters, in your local environment — JavaScript, Python, and shell. Not a sandbox emulating a language. The actual runtime, with access to the packages you already have installed.

As far as we can tell, Voiden is the only API client today that doesn't restrict scripting to JavaScript. Postman, Insomnia, Bruno, Hoppscotch, Requestly: all of them script in a JS sandbox, and only a JS sandbox. If we've missed one, we'd honestly like to know.

Concretely, that means:

  • Real languages, first-class. JavaScript, Python, and shell (bash) — and each script runs in a full Node.js, Python, or bash process, not an emulation. The three languages follow the same conventions, so switching between them isn't a context switch. We added shell after JS and Python because people kept asking for it — sometimes the right pre-request script is just three lines of bash.
  • Package imports. Your Python script can import the same modules your services use. No re-implementation, no drift.
  • Stateful workflows. voiden.variables.set("authToken", voiden.response.body.token) in one request's post-script; voiden.variables.get("authToken") in the next one's pre-script. A login response becomes the bearer token for everything after it.
  • Orchestration, not just validation. Multi-step flows, token rotation, dependent API calls — including conditionally killing a request before it's sent with voiden.cancel().

Here's a real one — a pre-request script signing a request with HMAC, in actual Python. Type /pre_script in a .void file, hit Enter, and write:

import hashlib
import hmac
import time

# the same signing logic your backend already uses —
# imported or copied verbatim, not re-implemented in sandbox JS
timestamp = str(int(time.time()))
payload = f"{timestamp}:POST:/v1/orders"

signature = hmac.new(
   SECRET_KEY.encode(),
   payload.encode(),
   hashlib.sha256
).hexdigest()

voiden.request.headers.append({"key": "X-Signature", "value": signature})
voiden.request.headers.append({"key": "X-Timestamp", "value": timestamp})

The voiden object is the entire integration surface, and it's deliberately small. In a pre-script, voiden.request is fully writable: url, method, headers, body, query params, path params. In a post-script, voiden.response is read-only: status, headers, body, response time, size. Then voiden.variables.set() and get() carry values between requests, voiden.assert() checks them, voiden.log() writes to the script logs, and voiden.cancel() stops a request from being sent at all — useful when a pre-script decides the conditions aren't right. Everything else is just your language. That's the point: the tool-specific API handles the handoff, and the logic stays portable.

The full reference for all three languages lives in the docs: Pre Script, Post Script, and the complete Voiden Scripting API reference.

And because scripts live inside .void files, which are plain Markdown in your Git repo, the script that signs your requests goes through the same pull request review as the code it tests. Your teammate can read it in GitHub without Voiden installed.

The honest tradeoff

Real runtimes mean real consequences. A sandboxed script can't touch your filesystem; a Voiden script is a full Node.js, Python, or bash process running on your machine, with your installed packages. That's power you're responsible for. Don't run a .void file from a stranger without reading the script block first — the same rule you already apply to any shell script or package.json you didn't write.

This is also exactly why the Git-native part matters. Scripts as reviewable plain text in a repo means the safety mechanism is one you already trust: code review. A sandbox protects the tool. A repo protects the team.

Getting Started

  1. Download Voiden — no account, no signup.
  2. Create a .void file, add a request, then type /pre_script or /post_script and hit Enter. Pick JavaScript, Python, or shell.
  3. Commit it to Git, and let the script go through review like the code it belongs with.

If you'd rather watch this than read it, there's a short video walking through pre- and post-request scripting: Introduction to Scripting: Pre and Post Requests in Voiden.

And if your stack scripts in a language we don't support yet, that's exactly the feedback that got shell added — tell us in GitHub Discussions. The whole runtime layer is open to read at github.com/VoidenHQ/voiden, or grab the app at voiden.md/download.

Related Posts