Every rule has exactly one type, which decides what happens once it matches a request:
| Type | What it does | When to use it |
|---|---|---|
| Mock response | The request never reaches the network. You fabricate the status, headers and body yourself. | The backend endpoint doesn't exist yet, or you need a specific error/edge case (500, empty list, malformed data) that's hard to trigger for real. |
| Modify request body | The request still goes out for real, but with a different body than the page sent. | Testing how the backend reacts to a payload the UI wouldn't normally send. |
| Modify headers | Add, override or remove request and/or response headers. The request still goes out for real. | Forcing a feature flag header, testing a missing/extra CORS header, faking an auth header locally. |
| Redirect | Sends the request to a different URL entirely. | Pointing a call at a local server or a different environment without touching the app's config. |
| Cancel | The request fails outright — fetch rejects, XHR fires an error event. |
Testing what your UI does when a call fails or a script fails to load. |
| Modify query params | Add or remove query string parameters. The request still goes out for real. | Forcing a query param the UI doesn't expose (a debug flag, a page size). |
| Replace in URL | Regex-replaces part of the URL, keeping the rest intact. The request still goes out for real. | Swapping staging for production in a URL without a full redirect rule. |
| Delay | Holds the real request back by a fixed number of milliseconds before letting it through. | Checking your loading states and spinners actually show up. |
| Response body | The request goes out for real; the real response body gets find/replaced or has one JSON field overwritten before your code sees it. | Flipping one field in a real API response (e.g. a status) without hand-writing a full mock. |
This is the single most common point of confusion, so it's worth spelling out on its own:
Response
(or fakes the XHR properties) and hands it straight back. The real server never even sees the request.
A practical way to tell them apart while debugging: open DevTools' Network tab. A mocked request never shows up there at all (it never reached the network); a modified-and-passed-through one does, with the rewritten URL/body/headers.
Define a name/value pair once (the "{{ }} Variables" button in the options page) and reuse it as
{{name}} anywhere you'd otherwise retype the same string: a rule's body, a header's value, a
redirect URL, a query param's value, or the replacement text of a "Replace in URL" rule.
apiHost = api.staging.example.com, then use https://{{apiHost}}/v2/users
as a redirect target. Change the environment once, in one place, instead of hunting down every rule that
hardcoded the old host.
A name that doesn't match any defined variable is left exactly as written — {{typo}} shows up
literally in the request or response, rather than silently disappearing, so a mistake is obvious instead
of a mystery. Variables are not substituted into a rule's matching fields (the URL
pattern, or a header/body/initiator condition) — those compare against the real, live request, so a
variable there wouldn't mean anything.
An environment (the "🌐 Environments" button, next to Variables) is a named set of overrides on top of your variables — Local, Staging, Prod, whatever you need. It only has to override what actually changes between environments; anything it doesn't mention keeps the variable's own base value.
apiHost = local.test as the base value, a "Prod" environment can override just
apiHost = api.example.com and leave every other variable alone.
Pick the active environment from the star button next to it in the options page, or from the dropdown in the popup (only shown once at least one environment exists). With no active environment, every variable simply uses its own base value — exactly like before environments existed.
Check these in order — they cover every reason a rule can silently fail to apply:
send(body) — this only works for the "mock", "cancel",
"delay", "modify body" and "modify headers" rule types on XHR (it works for every rule type on
fetch). A body condition also only ever matches plain text (JSON, form-encoded, plain
strings) — FormData/Blob/binary bodies are never read for it.
fetch or XMLHttpRequest. Net Tinker can only intercept
those two — it cannot affect <img>, <script src>, CSS, fonts, or
any other resource type when it comes to applying a rule (the DevTools capture panel can
still see all of those, it just can't act on them).
Net Tinker works by patching fetch/XMLHttpRequest inside the page's own JavaScript.
That approach has a few unavoidable side effects:
interceptor.js for every request while
any rule is active on the page, because every request passes through Net Tinker's patched
fetch/XMLHttpRequest before reaching (or not reaching) the network. It doesn't
affect behavior, only attribution. If it bothers you, add interceptor.js to DevTools' own
Ignore List (Settings → Ignore List) — DevTools then skips those frames when computing the initiator.
Open DevTools on any page and look for the "Net Tinker" panel (next to Elements, Console, Network, etc.). It lists every request the page makes while the panel is open — not just fetch/XHR, every resource type — so you can find the exact call you want to work with.
curl
command or a fetch() snippet, for pasting into a terminal or the console.
Captures shown in this panel live only in memory while DevTools is open — closing DevTools clears them, and nothing is written to storage until you explicitly turn a captured request into a rule.
When exporting rules to share a setup, the "sanitize" option scans header names, query parameter names,
and JSON body field names (recursively) for anything that looks like a credential — names
containing words like auth, token, secret, password,
apikey, credential, signature, cookie or
session — and replaces their value with __REDACTED__.
It deliberately does not touch:
value or data would not be caught, because the check is name-based, not
content-based.
"true"/"false", even under a sensitive-looking
name — access-control-allow-credentials: true is not a secret, it's a fixed CORS flag, and
redacting it would just produce a broken rule.
Treat sanitize-on-export as a safety net against the most common accidental leak, not as a guarantee that an exported file contains no secrets — review what you're sharing before you send it.