Net Tinker Help

Leer en español

On this page

I want to… — find the right tool

Every name in bold below is written exactly as it appears in the extension, so you can also find it with Ctrl+F here or search for it in the interface.

…change what a request or a response does

All of these are a rule. Pick the rule type from the Rule type dropdown when you create it.

I want to… Use this
Make an endpoint return a 500, a 404, a 401 — an error I can't easily trigger for real Rule type Mock the response (API), then set Status code. See Your first rule.
Return test data for an endpoint that doesn't exist yet Rule type Mock the response (API), write the JSON in Response body. See Your first rule.
Start a mock from what the server really returned, instead of typing a fixture The Mock button in the DevTools panel — see Building rules from real traffic.
Change one field of a real API response and leave the rest alone Rule type Modify the real response body, Mode = Overwrite a JSON field, e.g. Field path user.status.
Swap a piece of text inside a real response Rule type Modify the real response body, Mode = Find and replace.
Add, override or remove a header Rule type Modify headers — see the syntax and the Presets below.
Send an Authorization header the app doesn't send Rule type Modify headers, preset Auth headers. It writes Authorization: Bearer {{token}}, so define a token variable to go with it.
Stop the browser serving from cache so my rule gets a chance to run One-off: ↻ Reload without cache in the popup. For every reload: rule type Modify headers, preset Cache bypass.
Get past a CORS complaint while developing Rule type Modify headers, preset Development CORS — but read Things that look like bugs but aren't first: it does not lift a real browser block.
Point a call at localhost or at another environment Rule type Redirect the request, fill in Redirect to.
Replace staging with local in every URL, keeping the rest Rule type Replace text in the URL (Find / Replace with).
Force a query parameter the interface doesn't expose (a debug flag, a page size) Rule type Query parameter.
Make a call fail, to see what my error handling does Rule type Block the request.
Test slow network, spinners and loading states Set Latency (ms) in the rule's Network simulation block. It works on any rule type — including a rule that does nothing else.
See what my UI does if this endpoint returns a 500, but with the real payload and without writing a mock Set Override the real status in the rule's Network simulation block. The request still goes out, the real body still comes back, and only the status code changes. See Overriding the status of a real response.
Send the backend a payload the interface would never send Rule type Modify the request body.
Mock an endpoint and make it slow — or redirect a call and add a header to it Two rules matching the same URL, one for each half: they all apply. See When several rules match the same request.
Affect a single GraphQL operation, not the whole endpoint Any rule type, plus a Body condition such as "operationName":"GetUser". The DevTools panel fills this in for you.
Limit a rule to one tab so it doesn't affect everything I have open The 📌 button next to the rule in the popup — see extra conditions.

…find my way around the extension

I want to… Use this
Work out why my rule isn't doing anything Why a rule might not be firing — the full checklist, in order.
Check my pattern matches a URL before saving the rule Test URL (optional), right under the pattern in the rule form. See matching.
Know which of my rules wins when two of them match the same URL When several rules match the same request — short answer: they all apply, and where they clash the one lower in the list wins.
See which requests a rule actually caught Record this tab in the popup, then 📡 Captures in the options page. See Seeing what actually happened.
Switch a host between Local, Staging and Prod without editing every rule Variables plus Environments.
Turn several rules on or off together Put them in a Group (the Groups panel at the top of the options page) and toggle the group. Or tick several rules in the list and use the bar that appears.
Find one rule among many The search box and the type filter next to it. The box says "name, URL or group", but it actually also looks inside the Description and the Tags.
Share my setup with a teammate, or move it to another machine Export / Import in the options page. Read what "sanitize on export" covers before sending the file.
Hand someone a trace of what the page did Export HAR in the DevTools panel — see Building rules from real traffic.
Copy a request into a terminal or the console Copy as… in the DevTools panel (cURL or fetch()).
Reuse my rules in my Playwright or Cypress tests Copy as… on the rule's own row (just that one), or Export → the mock buttons in the dialog (all of them, or the selected ones). Read the comments in the snippet before you commit it: they say what was redacted and what could not be translated.
Call the endpoint a rule intercepts, to compare against the real thing The same Copy as… on the row, options cURL and fetch().
Make one rule win over another that also matches Move it with and : the one lower in the list wins. See When several rules match the same request.
Keep the extension off everything but my own sites Scope in the options page: everywhere, only localhost, or only the domains you list.
Turn everything off for a moment without deleting anything The master toggle: On in the popup, Extension enabled in the options page.

Your first rule: which fields actually matter

Press + New rule (in the popup or in the options page). The form opens on Mock the response (API), which is the most common case: answer a request from the browser instead of letting it reach the server. What the form shows up front is short — the rest is folded into four collapsible blocks, described below — and most of it is optional. Here is what a working JSON mock actually needs:

  1. URL / pattern — the only field the extension refuses to save without. By default it is read as Contains, so /api/users is enough to catch every URL with that fragment. See Choosing which requests a rule matches.
  2. Status code — starts at 200. The field is a plain number box with a list of suggestions attached (200, 201, 204, 301, 400, 401, 403, 404, 429, 500, 502, 503…): click it to pick one, or just type any code you want.
  3. Headers — a new mock rule opens with Content-Type: application/json already written in the box, as a real value you can edit or delete: what is in the box is what gets saved. Delete it and the mock comes back as plain text — a legitimate choice, and nothing writes the header back when you save. Opening a rule that already exists never adds it, even when its headers are empty.
  4. Response body — what the caller gets. The { } Format and { } Minify buttons above the box tidy up JSON, and a note underneath tells you whether what you typed parses as JSON at all.

Everything else narrows the rule down or helps you find it later, and lives in four collapsible blocks: Organization (description, tags, group), Advanced matching (source, match type, method), Additional conditions (optional) (header, body and initiator conditions) and Network simulation (latency and status code, which apply to every rule type). A new rule opens with the four of them closed; editing an existing one opens each block that rule actually uses, so a folded block never hides something you set. Two things worth doing before you close the form:

Rule types

Every rule has exactly one type, which decides what happens once it matches a request. The name in bold is the one in the Rule type dropdown, where the nine of them are grouped by what they do to the request — Mock, Modified passthrough, Redirect and Block, the split described in Mock vs. modify & passthrough. The shorter name in brackets is how the same type is labelled in the rule list, the type filter and the captures list. One rule does one of these things — but a request can be governed by several rules at once, so combining two types is a matter of writing two rules. See When several rules match the same request.

Type What it does When to use it
Mock the response (API)
[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 the request body
[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
[Headers]
Add, override or remove request and/or response headers. The request still goes out for real. One per line: Key: Value overwrites or adds, +Key: Value appends to what is already there, -Key removes. The Presets dropdown fills the boxes in for you with three common sets — Cache bypass, Development CORS and Auth headers — adding to whatever you already wrote and only replacing lines for the same header. Forcing a feature flag header, testing a missing/extra CORS header, faking an auth header locally.
Redirect the request
[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.
Block the request
[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.
Query parameter
[Query param]
Add or remove query string parameters. The request still goes out for real. One per line: +key=value adds or overwrites, -key removes. Forcing a query param the UI doesn't expose (a debug flag, a page size).
Replace text in the URL
[Replace]
Replaces every occurrence of a piece of text in the URL, keeping the rest intact. The request still goes out for real. The text you type in Find is matched literally, character for character — it is not a regular expression, so a . only ever matches a real dot. Swapping staging for production in a URL without a full redirect rule.
No change (delay only)
[No change]
Does nothing to the request by itself: it goes out exactly as the page built it. It exists so that a rule can carry only a modifier — today, a latency — without also having to change something. Slowing an endpoint down without altering it in any other way.
Modify the real response body
[Response body]
The request goes out completely unchanged; only the body that comes back is transformed before your code sees it. Two modes: Find and replace (literal text, every occurrence) or Overwrite a JSON field (a dot path such as user.status, leaving the rest of the JSON alone). It has limits of its own — see Things that look like bugs but aren't. Flipping one field in a real API response (e.g. a status) without hand-writing a full mock.

Latency is not a rule type

Delaying a request used to be a type of its own, and it no longer is. Latency (ms) lives in the Network simulation block of the rule form and applies to every type: a mock arrives late, a redirect leaves late, a blocked request fails late. Leave the field empty and the rule adds no latency at all; 0 is not the same thing as empty — it is a real, if tiny, wait.

If all you want is to slow an endpoint down, use the No change (delay only) type and give it a latency. Rules that used to be of the old Delay type were converted to exactly that, keeping their milliseconds, the first time you ran this version.

Overriding the status of a real response

There are two fields in the form that hold an HTTP status code, and they are not the same thing. It is the easiest confusion to fall into here, so it goes first:

Field What it is Where it lives
Status code The status of a response the extension builds. The request never goes out, so there is no real response anywhere. Only on the Mock the response (API) type, next to the mock's body and headers.
Override the real status Replaces the status of the response that did come back from the server. The real body, the real headers and everything else arrive untouched. In the Network simulation block, on any rule type.

The case it exists for: you want to see what your error handling does with a 500 from that endpoint, but with the real data. With a mock you would have to hand-write the whole body — and then you would no longer be testing against the real response, which is exactly what you wanted to keep.

And like any modifier, it combines: a single rule can override the status and transform the response body, and change a header on it, and arrive late.

Mock vs. modify & passthrough

This is the single most common point of confusion, so it's worth spelling out on its own:

Mock — the request never leaves the browser. Net Tinker builds a fake Response (or fakes the XHR properties) and hands it straight back. The real server never even sees the request.
Modify & passthrough (request body, headers, redirect, query params, replace-in-URL, no change) — the request really goes out over the network, just not quite the way (or not quite when — any rule can carry a latency) the page originally built it. The real server does see it, and its real response comes back (possibly with response headers further modified on the way back, for the "headers" rule type).
Modify the real response body — the request goes out completely unchanged, and only the body coming back gets transformed (find/replace, or overwriting one JSON field) before your code ever reads it. Non-JSON bodies can't be inspected for the "overwrite a field" mode, so they're left untouched rather than silently doing nothing you'd notice.

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. The captures list says the same thing in words, per request: "reached the network" or "never reached the network".

This is a property of the request, not of each rule taken alone: as soon as one matching rule mocks it, the request never leaves the browser, whatever the other matching rules do. A mock with a latency is a delayed answer that still never reaches the server — see When several rules match the same request.

Choosing which requests a rule matches

Whatever its type, a rule always starts from one pattern: the Source dropdown decides whether that pattern is compared against the full URL or only the Host, and the Match type dropdown decides how. Both live inside the form's Advanced matching block, which is folded away until you open it:

Match type How the pattern is read
Contains (the default) Matches if the pattern appears anywhere in the URL/host. /api/users catches every URL with that fragment in it.
Equals Matches only if the URL/host is exactly the pattern, character for character.
Regex The pattern is a JavaScript regular expression, tested against the URL/host. It doesn't have to match the whole thing unless you anchor it with ^/$.
Wildcard (* ?) A middle ground between "Contains" and "Regex": * stands for any run of characters (including none) and ? for exactly one character. Everything else is literal, so you don't have to escape anything — a . is a real dot, not "any character". Like Regex it isn't anchored either: the pattern only has to occur somewhere in the URL/host.
https://*.example.com/api/v?/users in Wildcard mode matches https://api.example.com/api/v2/users and https://staging.example.com/api/v3/users, but not https://example.com/api/v10/users? is exactly one character.

Rather than guessing, paste a real URL into Test URL (optional), the box right under the pattern. It answers ✓ Matches or ✗ Doesn't match as you type, tells you what it compared against (the whole URL or just the host), and warns you if a Regex pattern doesn't compile. Nothing is saved and no request is made. It only checks the pattern, the match type, the source and the exclusions — not the method or the header/body/initiator conditions below.

Everything under /api/ except the health check

A pattern that is broad enough to be useful is usually broad enough to catch something you didn't want: the health check your dashboard polls every five seconds, the login call, the one request that already carries a real token. That's what Except URLs containing (one per line) is for — it sits just under the three dropdowns in Advanced matching. Write one pattern per line; if any of them appears in the request's URL, the rule leaves that request alone.

Pattern /api/ with /api/health and /api/auth/ as exclusions: every API call is mocked except the health check and anything under auth.

Two things about this box are deliberate, and worth knowing before you fight it:

The Test URL box knows about exclusions: an excluded URL answers ✗ Excluded by “…” and names the line responsible, instead of claiming a match that would never happen.

On top of the pattern, a rule can carry extra conditions, under Additional conditions (optional) in the form — except Method, which sits with the other two dropdowns under Advanced matching. They are all optional, and all of them have to hold at once (AND, never OR):

When several rules match the same request

Nothing stops two rules from matching the same request, and when that happens all of them apply — not just the first one. That is what lets you redirect a call and add a header to it, without trying to cram both into a single rule.

They are applied in list order, from top to bottom, and each rule only has a say in the part of the request its own type is about: a headers rule contributes header changes, a mock contributes the answer, and any of them can contribute a latency on top. When two rules speak about the same thing, the one lower in the list wins. So the order of the rule list is the priority — use the and buttons on a rule to move it above or below another one, and what your request ends up doing really does change.

Two matching rules both… What the request ends up doing
…mock the response The lower one answers: its status, its headers, its body. The upper one is replaced outright, not blended into it.
…rewrite the URL (redirect, query parameter, replace text) Both, chained: each one rewrites the URL the previous one produced. "Redirect to localhost" plus "add ?debug=1" gives you a localhost URL with the parameter on it, not one or the other.
…modify headers Both sets of lines, run one after the other in list order — so for the same header name the later line wins, exactly as two lines inside a single rule would.
…modify the real response body Both, in order: the lower rule transforms whatever the upper one produced.
…set a latency, or modify the request body The lower one's value. A latency of 0 ms and an empty body are real answers, not "no opinion".
One rule that mocks /api/users with a 500 and carries a 2000 ms latency gives you a 500 that takes two seconds to arrive. Two separate rules — one mocking, one only adding the latency — give you exactly the same thing, which is handy when you want to switch the slowness off on its own.

Two combinations are settled by a rule of their own rather than by list order:

To see which rules governed a given request rather than reasoning it out, record the tab and open the captures list: a request that matched three rules gets a ⚡ line for each of them, and the toolbar badge counts rules applied, not requests.

Using variables

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, the replacement text of a "Replace text in the URL" rule, and both the replacement text and the new JSON field value of a "Modify the real response body" rule.

Define 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.

Environments

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.

With 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 Environment 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.

Why a rule might not be firing

First, a quick check that saves a lot of time: the extension's toolbar icon carries a badge with the number of rules applied on the current tab. If it's empty after reloading, nothing matched at all and the list below is where to look. If it's counting, the rule is firing and the surprise is in what it does, not in whether it runs.

Check these in order — they cover every reason a rule can silently fail to apply:

  1. The master toggle (On at the top of the popup, Extension enabled in the options page) is off. Nothing applies at all while it is.
  2. The rule's own enable/disable toggle is off.
  3. The rule belongs to a group that's disabled. A disabled group silently disables every rule inside it — in the rule list, those rules show a ⚠ next to the group's name.
  4. The rule is pinned to a specific tab (📌) and you're testing from a different one.
  5. This site is outside the configured scope. This is the most misleading one, because it has nothing to do with the rule: if Scope (options page) is set to "Only localhost" or "Only these domains", then on every other site no rule applies at all. The toolbar icon says so — hover it and you'll read "out of scope here" — and in that mode the popup warns too. Watch out for the empty-list case: "Only these domains" with no domain listed allows nothing, anywhere.
  6. The request's HTTP method doesn't match the rule's configured Method (if it's not set to "Any").
  7. The URL/host pattern doesn't actually match — check the match type as well as the pattern itself, and paste the real URL into Test URL (optional) to settle it. If the match type is Regex, a typo that makes the pattern invalid means the rule matches nothing rather than throwing an error — the test box flags that case explicitly. Another common one: writing * or ? in a pattern still set to "Contains" or "Equals", where they are just literal characters — those only mean anything in Wildcard mode.
  8. The rule has an exclusion pattern that matches this URL. It's the easiest one to overlook, because the positive pattern does match and everything looks right: check Except URLs containing inside Advanced matching — the block opens by itself when the rule has any, and the folded summary says how many. Pasting the URL into Test URL (optional) settles it instantly: it names the line that excludes it.
  9. There's a Header condition, Body condition or Initiator domain attached to the rule, and the current request doesn't satisfy it. For header and body conditions on XHR requests specifically: a redirect/query-param/ replace-in-URL rule can never fire based on either, because the URL is decided before the page has set its headers or called send(body) — this only works for the "mock", "block", "no change", "modify request 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.
  10. The request isn't 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).
  11. The request never happened, because the browser answered it from its own cache. There is nothing to intercept, and it looks exactly like a rule that doesn't work. Use ↻ Reload without cache in the popup to reload that tab ignoring the cache, or add a Modify headers rule with the Cache bypass preset so it happens on every reload.
  12. Another matching rule is overruling it. Rules don't shadow each other — they all apply — but where two of them speak about the same thing, the one lower in the list wins, and a Block the request rule beats everything. So the rule can be firing perfectly and still leave no trace: reorder the list, or narrow the other rule's pattern. See When several rules match the same request.
  13. The rule has a type this version doesn't know how to apply. It can happen after importing a file written by a newer version, or if an update left a rule half-migrated. That rule alone is skipped, exactly as if it hadn't matched — never answered with an empty mock, and any other rule that matched the same request still applies — and the row is flagged in the captures list with ⚠ plus the unrecognised type name, so the situation is visible instead of looking like ordinary traffic. Reopen the rule and pick a type again.
  14. A Modify the real response body rule matched, but the response was a live stream it can't buffer, so it was deliberately left alone. See Things that look like bugs but aren't; the captures list says "matched but not applied — streaming response" for exactly this.
  15. You have two copies of Net Tinker installed at once — typically the Web Store one plus an unpacked development build. Both patch the same page, and whichever answers first takes the request, so the other copy sees nothing: no badge, no line in the captures list, and the rule you are looking at appears to do nothing even though a rule did run. The giveaway is that the request clearly was intercepted while the extension you're watching shows no activity at all. Disable one of the two.

Seeing what actually happened

Guessing whether a rule ran is the slow way. There are three places that tell you, each answering a different question:

The captures list is off until you ask for it. Open the popup on the tab you want to watch and switch on Record this tab — only that tab's requests are captured. Then open the list with 📡 Captures in the options page (or View captures in the popup), which shows the Captured requests panel.

Each row tells you, in this order: the page, the status, the time, and then what Net Tinker did with it.

Unlike the DevTools panel, these captures are stored, so they're still there after you close the tab or the browser. The list keeps the 200 most recent and Clear empties it. Recording stops on its own if you close the tab being recorded.

Things that look like bugs but aren't

Net Tinker works by patching fetch/XMLHttpRequest inside the page's own JavaScript. That approach has a few unavoidable side effects:

Building rules from real traffic

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. The header shows Recording, and clicking it switches to Paused if you'd rather freeze the list; tick Preserve log to keep entries across navigations, and Clear empties it.

  1. Each row has a "+ Rule…" dropdown plus a dedicated Mock button. Picking a type opens the rule editor pre-filled with that request's real URL (as an Equals match, so it only affects that exact call) and its method. The dropdown offers every rule type except the mock, which has its own button.
  2. A rule built this way arrives disabled unless it's a mock or a block — those two are complete the moment they're created, while the rest are still empty skeletons, and enabling an empty "redirect" or "response body" rule would break the very endpoint you're debugging. Fill it in, then switch it on. The panel says which of the two happened each time.
  3. Mock goes further: it also copies the request's real status code, response headers and response body into the new rule, so you start from what the server actually returned instead of typing a fixture by hand.
  4. A ⚡ mark next to a row means one of your currently-enabled rules would intercept that exact request — evaluated live against your current rules, not against whatever was active when the request actually happened.
  5. Each row also has a "Copy as…" option to copy the request as a cURL command or a fetch() snippet, for pasting into a terminal or the console.
  6. Export HAR saves the requests currently visible in the list as a standard .har file, for handing to someone else or loading into another tool. The dialog lists the sensitive values it found and offers Redact credentials (always on when the dialog opens) and Include response bodies (always off, capped at 1 MB each, because bodies are what makes the file heavy). Both boxes are reset every time it opens, so the safe option is never left off from a previous export. A HAR carries real request headers, Authorization included, plus cookies — treat the file like a credential.
  7. For a GraphQL request (detected automatically and shown as its operation name next to the URL), creating a rule from it also fills in a Body condition requiring that operation's name — because a GraphQL endpoint serves every operation at the same URL, a rule matched on URL alone would otherwise apply to all of them. You can see and edit this in the opened rule (it's just the same body condition described above, pre-filled for you). The panel's filter box also searches by operation name.
  8. The panel lists every resource type, but rules can only be applied to fetch/XHR. Build one from an image, a script or a stylesheet row and the panel warns you: the rule is created, but it will never fire. The "fetch/XHR only" checkbox hides those rows if you'd rather not see them.

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 or export a HAR. That is the difference from the captures list in the options page, which is stored on purpose.

What "sanitize on export" does (and doesn't) cover

Export writes your rules and groups to a JSON file, together with your variables and environments. Exporting everything includes every variable you have; exporting a selection of rules only includes the variables those rules actually reference by name, and prunes each environment down to the same set. Which environment is currently active is a local preference and is never part of the file.

Because that file gets shared, the export goes through a dialog first. It lists what it found that looks like a credential, and ticks Replace credentials with __REDACTED__ for you when there is anything to hide. Sanitizing looks for names that suggest a credential — anything containing auth, token, secret, password, passwd, apikey, api_key, api-key, credential, signature, cookie or session — and replaces the matching value with __REDACTED__. It checks:

It deliberately does not touch:

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.

On the way back in, Import lets you pick what to bring over and warns you when a rule already exists (same type, method and pattern), offering to import it as a copy, skip it or overwrite the existing one — with the same choice, separately, for variables whose value differs from yours.