Stop Hardcoding URLs: Insomnia Environment Variables Done Right
A practical walkthrough of Insomnia's {{variable}} templating: one request definition, three environments, and the secret-handling trade-offs.
14 Oct 2025, 22:39 UTC

Every API collection hits the find-and-replace wall
Sooner or later, any non-trivial API collection runs into the same moment: the staging host moves, a token rotates, or you need to point thirty requests at a new gateway. If URLs and credentials are typed directly into each request, that means a manual sweep through every field — and one missed request quietly sends traffic to the wrong box.
Insomnia's environment variables exist to end that. The thesis here is simple: write each request once as a template, keep the changing parts in named environments, and switch between dev, staging, and prod with a dropdown. This post covers how the templating works, a setup you can build in a few minutes, and the trade-off you accept when convenience meets secrets.
How {{variable}} templating actually works
Every text field in an Insomnia request — URL, headers, query parameters, body — accepts {{variable_name}} references. At send time, Insomnia resolves each reference from the active environment, which is a key-value store attached to the workspace.
Scoping has two layers. Each workspace has a base environment that always applies, plus any number of sub-environments (dev, staging, prod) that you toggle on and off. Values in an active sub-environment override same-named keys from the base. If you enable several sub-environments simultaneously, keep key overlap minimal — otherwise it gets hard to say which value wins without checking.
Two naming rules matter: variable names can't contain spaces (they break the {{}} syntax), and avoid names that collide with request keywords such as url or method.
A three-environment setup in ten minutes
This post describes Insomnia's environment system as it has behaved across recent releases; menu labels shift between versions, so treat exact UI paths as approximate and confirm in your installed build. Everything below runs inside the Insomnia desktop app — no shell access or special permissions needed.
- Create a workspace for the API (for example, "Orders API").
- Open the environment manager and add shared keys to the base environment — things that rarely change per host, like
API_PATH. - Add sub-environments
dev,staging, andprod, each definingBASE_URLandTOKEN. - Build a request:
GET {{BASE_URL}}{{API_PATH}}/healthwith a headerAuthorization: Bearer {{TOKEN}}. - Use the environment dropdown to toggle
devon, send the request, then switch tostagingand send again. The URL and header should change with no edits to the request itself.
The environment values look like this behind the scenes:
// dev (sub-environment)
{
"BASE_URL": "https://api.dev.example.com",
"TOKEN": "dev-token-here"
}
// base environment (shared across all sub-environments)
{
"API_PATH": "/v1"
}
| Key | Where it lives | dev value | prod value |
|---|---|---|---|
BASE_URL |
Each sub-environment | https://api.dev.example.com |
https://api.example.com |
API_PATH |
Base environment | /v1 |
/v1 |
TOKEN |
Each sub-environment | plain text | secret-type variable |
How to verify it worked: hover a {{BASE_URL}} reference in the editor to see the resolved value, and check the request timeline pane to confirm exactly what was sent. To undo the whole setup, delete the sub-environments — request definitions revert to whatever literal text they contain, though any request still referencing a deleted variable will fail to resolve until you fix it.
Dynamic values without editing requests
Some values must change on every send: a timestamp header, a nonce, a freshly minted token. Insomnia's pre-request scripts handle this — short JavaScript snippets that run in a sandbox inside the app just before the request leaves it. Open the request's pre-request script tab and add:
pm.environment.set('sent_at', Date.now());
Then reference {{sent_at}} in a header such as X-Sent-At. Send the request twice and confirm the header value changes — that's your check that the script ran. Note that script API support varies by version: newer builds accept the Postman-style pm.* calls, while the native insomnia.environment.* API also exists. The autocomplete in the script editor tells you which is available in yours. For simple cases, Insomnia's built-in template tags (timestamp, UUID, random values) do the same job with no scripting at all.
The trade-off: convenience versus secret hygiene
Environment variables concentrate your credentials in one place, which is exactly why they deserve scrutiny:
- Plain text on disk. In the free tier, environment values are stored unencrypted in Insomnia's local data. The paid Insomnia Vault encrypts secret-type variables at rest; without it, treat anything in an environment as readable by anything that can read your disk.
- Exports leak. Exporting an environment produces a plain JSON file containing every value, including tokens. If you version-control environments for your team — a genuinely good practice for URLs and non-sensitive config — keep secrets out of the exported file, and never push exports to a public repository.
- Secret masking is UI-level. Marking a variable as secret hides its value in the interface, but on the free tier that's presentation, not encryption.
- Type changes don't propagate automatically. Flipping an existing variable to secret type may not update requests that already reference it until they're re-saved — worth verifying in your version before assuming a token is protected.
The pragmatic rule: environments are excellent for base URLs, paths, and per-environment flags; they're acceptable for dev tokens; and production credentials belong in the Vault tier or injected at runtime rather than sitting in a synced JSON file.
Try it this week
Pick your most-used collection and do the smallest useful version of this: move BASE_URL and TOKEN into a base environment plus two sub-environments, convert one request to {{BASE_URL}} references, and flip the dropdown to confirm the resolved URL changes in the preview. Once that request works, convert the rest — the find-and-replace wall never gets shorter on its own.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.