2.4.0
Open requests
- Compose keeps up to five requests open at once, as plain chips above the URL. Opening a captured request (from the log or a past session), a Recent send, or New request (in ··· More, or the + after the chips) while another request is open puts it in a chip of its own instead of over the one you were writing; something already open shows its chip, with your edits. The strip only appears once a second request is open.
- Each request keeps its own form, status line and answer. A send still out in a request you left finishes there; its answer is under it when you come back, not under the one on screen.
- A dot after a chip's name says it has changes that are not saved; closing it asks first. × or a middle-click closes a chip. On a chip, Delete closes it and Esc closes the one on screen (Esc in a field still closes the panel). Ctrl/Cmd+W and Ctrl/Cmd+Tab stay the browser's: it reserves them, so the panel never takes them.
- A saved request opens in Compose with a click on its name in its collection, tied to the collection (Send uses its settings). One already open, from there or from Recent, is shown instead of opened twice.
- At 300 px the chips scroll sideways with no scrollbar (a mouse wheel over them moves them along), and an edge with more chips past it fades. A long name is cut with … inside its chip, and its tooltip has the whole request.
- The strip is a tablist for the keyboard and screen readers: Tab reaches the chip on screen, the arrow keys (right to left in Arabic) and Home/End move along it and show the request reached, Delete closes the chip. A chip says its name and whether its changes are saved; closing one from the keyboard says which request is now on screen.
- "Save to collection…" makes the request that saved request: named like it and tied to it. A saved request with changes gets Save changes in ··· More, which writes them into it (its name, notes, examples and checks stay).
- Open requests come back when the panel opens again, the one on screen too, with their changes. A typed secret (a token, a password, an API key: the same rule as Recent) is never written to disk; a request opened again says which ones to type in again. A saved request with no changes comes back as it is saved in its collection now. A text over 100,000 characters is not kept either, and its request says so.
- Fixed (Firefox): after the panel was reloaded, only the request on screen came back, and the others were lost. Firefox puts what was typed back in a reloaded page's fields, so Compose looked already in use and the kept requests were not brought back. Compose now starts empty and every kept request comes back.
- A sixth request asks first: it names the request you looked at longest ago, and closes it only if you say so (saying when its changes would be lost).
- Fixed: a request loaded into Compose kept the switched-off query parameters of the request shown before it.
Rule profiles that stay on (RP-1, RP-2)
- Header and URL rules used to work only while recording. Settings → Header & URL rules → Profiles that stay on (closed until you open it) keeps named profiles: a name, the sites it covers and its rules. Switch a profile on and the browser applies its rules to those sites in every tab, with nothing recording, until you switch it off; they survive a browser restart. The rules are the ones you know: set or remove a request header, set or remove a response header, rewrite part of a URL; each can be limited to URLs containing some text. A profile with no site cannot be switched on, so a profile never applies to every site.
- One quiet chip under the top bar names the profile that is on ("Rules on: Staging flags", or "2 rule profiles on"); its cross turns it off in one click, and the name opens the list. The chip shows only rules that really apply: a profile whose site access was taken away, or one the browser refused a rule of, is marked in the list ("Needs site access", "The browser refused a rule") and is not in the chip.
- No new permission: the manifests already declare declarativeNetRequest and the optional host access. Switching a profile on asks the browser for just that profile's sites (or answers at once if you already allowed them at Start Recording). The privacy policy, the store text and the docs say so.
- Import and Export share profiles as one JSON file (format in docs/RULE-PROFILES-FORMAT.md). It reads a ModHeader-style profile too. Header values that look like secrets (Authorization, Cookie, API keys, tokens) are left out of an export and come back switched off; an import never turns a profile on, and says what needs a look (no site yet, values left out, rules it could not read).
- The recording-only rules are unchanged; a recording's own rules and a profile's rules are separate lists and never disturb each other.
Suite run options (RN-1)
- A suite card has a quiet Run options… (closed until you open it): wait between steps (0 to 60000 ms, for an API that rate-limits), run the suite N times (1 to 100), and stop at the first failure. They are saved with the suite, shown as one line on the card while any is set, and used by Run and by Run once per row. A run that stopped at its first failure says so ("Stopped at the first failure — steps run: 2 of 3, passed: 1"), is never green, and the line is kept with the suite's last run. Stop also cuts a pause short.
- Export as CI config writes
delayMs,bailanditerationsinto the config (only the ones that are set), and a suite in a linked folder carries them torequestfox-ci run. - The CLI's
assertandruntake--delay-ms N,--bailand--iterations N, which override the config.--bailnames how many requests were not run and lists them as skipped in a JUnit report; each repeat starts from the config's variables. A wrong value stops the run with exit code 2 before a request is sent.
The log as a table
- A table button in the search bar shows the log as one line per call: method, path, status, time and size, plus host and flags when the panel is wider. Cards stay the default; the choice is remembered.
- Click a heading to sort: the slowest, biggest, worst status or most flagged first, then the other way, then newest first again. The sorted heading carries aria-sort, and the line above the table says the order in words. A new call lands in its sorted place.
- The Flags sort counts a stream's own flags (Stream error, Ended early, Paused) at their badges' weights, so a stream that errored or paused is no longer sorted as if it had no flag.
- Drag a heading onto another to move its column there: a thin line shows where it will go, Escape cancels, and a short press still sorts. The Columns dialog lists the columns in their order with ↑ ↓ beside each (keyboard and a single click, no dragging needed), and Default order puts them back. The order is saved for the site, with the rest of its columns; taking an answer column away moves no other.
- A row opens the call's full card under it, with every card action (Replay, Details, More…). Arrows, Home/End and Page Up/Down move between rows; Enter opens and closes; Escape closes. A jump to a call (the Timeline, a notification, a Top Issues chip) opens its row.
- Built for long sessions: only the rows on screen are drawn, so a 5,000-call log renders and sorts within the Timeline's 300 ms budget (checked in real Chrome and Firefox by npm run test:browsers). In-flight calls and JS errors stay cards.
- Add column above the table reads one value from each JSON answer by a path (
data.items.length,$.user.id,items[*].id), offering the paths of an answer in the list as suggestions. A row with nothing there shows a quiet dash that says why. Sort by it like any column. - Columns (the same button) shows or hides the table's own columns (the path always shows) and takes up to four answer columns, side by side ("Add another"). The choice is saved for the site the session was recorded on; a site with none of its own starts with the answer column you chose last. A column that does not fit the panel's width waits until it is wider: your answer columns get the room before size, flags and host.
- Group by host above the table puts a heading row over each host's calls: the host, its total, and its errors, repeats and slow calls when there are any (the tiles' own words). A click, Enter or Space on a heading folds its calls away and back. The sort still works, inside each group; the host with the newest (or slowest, or biggest…) call comes first. A jump to a call in a folded host unfolds it. Grouping is remembered; a 5,000-call log still draws only the rows on screen.
Session overview
- A new button in the search bar opens the session's Overview: calls, errors (with the share), slow calls, repeats, what came back and the time span; the status code families; the five slowest calls; the findings by kind, in the card badges' words, each with the finding codes its calls have (RF-FAILED-5XX, RF-SLOW…; also in the Markdown); the hosts, most calls first.
- Every line leads into the log: a slow call opens its card; a host or a finding shows only those calls (the search box's own
host:,error:true,has:drift… filters). - Copy as Markdown puts the same in a bug report, in plain English like Copy bug report. Calls are named by host and path only: no query string (where tokens live), no headers, no bodies. It says whether the clipboard took it.
- Calls over time: a small bar chart of how many calls started in each step of the session (a round step — 250 ms, 1 s, 10 s, 1 min… — at most 20 of them), its first and last times under it, and the same in words below ("120 calls over 240 s. Busiest: 18 in 10 s, from 12:03:10."). The words are its text alternative (the chart itself is hidden from screen readers), it has one calm colour (nothing is said by colour) and time runs left to right in every language. The Markdown names the busiest stretch.
- A Paused finding kind: streams that stopped sending for a long time (the card's Paused badge), with their code
RF-STREAM-PAUSED; a click opens the first one. - Fixed: what the panel says to a screen reader after you act ("Overview copied") could be replaced, before it was read, by a batch of new problems found while recording. A problems batch now waits until the panel's status line has been quiet for 5 seconds, and words still on their way never land over newer ones.
An answer's links and bytes
- A web address in an answer's text (a card's Response, Compose's answer) is a link: a click opens it as a new GET request in Compose, in an open request of its own. On the same site, the call's headers come along (the browser's own and anything redaction hid stay out, and the status line says which to add back); to another site, none do. Ctrl/⌘- or middle-click is the browser's own.
- In the JSON tree, a value that is an address, or a path from the site's root (
/v1/items?page=2, read against the call's own address), shows a dotted line and offers Open as request first in its menu. - Hex (in a card beside Tree/Table, or Save for an image or protobuf kept as its bytes; in Compose beside Headers and Timing) shows any answer as bytes: an offset, sixteen bytes in hex and their ASCII per line, left to right in every language; where a line of sixteen does not fit (a 300 px panel), eight a line, so nothing scrolls sideways. A text answer is the UTF-8 of the text kept (it says so); bytes kept as base64 are shown as they are. The first 32 KB are drawn, and a longer answer says so. Copy hex copies what is drawn, Copy as base64 the whole answer. Hex is per answer, not remembered.
A list answer as a table
- A JSON answer that is a list of objects (or holds one under a key, such as
itemsordata.results) offers Table beside Tree, in a log card's Response and in Compose. No script: the list's place and row count, the first six columns (a nested object's keys are columns of their own), a column picker, 100 rows at a time. - A heading sorts (A to Z, Z to A, back to the answer's order). Copy as CSV copies every row with the columns shown, in the order shown; a text a spreadsheet would run as a formula is written as text. It says whether the clipboard took it.
- Text, Tree or Table is one remembered choice. An answer that only looked like a list says so on the button.
Moving requests to and from other API apps
- Compose's Import & export is two closed lists, Import and Export, each with its commonest action first (a collection, then curl; the workspace, then another app), instead of ten buttons in one row.
- Export for another app: a collection's ··· menu, and Compose's Export list for every collection, save a file that Bruno (OpenCollection 1.0.0 YAML), Insomnia (v5 YAML) or Hoppscotch (JSON) opens. Folders, headers, off rows, query parameters, bodies, auth, collection settings and environments come along. Passwords, tokens and keys never do (there is no "export everything" here): each becomes a named variable, listed empty in the other app to fill in. The summary counts what those apps cannot hold (scripts, checks, saved responses, OAuth 1.0a / JWT / HMAC auth) and names the one next step in the other app.
- Import reads Bruno 3+ OpenCollection files (one
.yml), keeping the collection's headers, auth and variables as the collection's settings. Insomnia and Hoppscotch imports now pass a folder's or collection's headers and auth on to its requests, keep{{variables}}in query values and url-encoded bodies as written (they were percent-encoded), read Hoppscotch's url-encoded body text, and leave a{{variable}}alone when the base environment holds it empty. Bruno's older JSON export no longer doubles its query parameters. - The YAML writer quotes words other readers take for something else (
yes,0x1F,1.0.0, dates) and writes U+0085, U+2028 and U+2029 escaped.
Import from a URL
- Compose's Import list has Import from URL…: one field, one Import button. The address is read once from the extension (no cookies, no sign-in) after the browser asks for access to that one site, and goes through the same importer a file does: OpenAPI / Swagger (and the contract link), Postman, Insomnia, Bruno, Hoppscotch or HAR. A GitHub or GitLab page link becomes the raw file's address. Every failure stays in the dialog as one sentence with its next step: nothing at that address (404), a sign-in wall (401/403), a web page instead of the file, a file over 8 MB, a format RequestFox does not read, a site that did not answer in 20 s, or a site it was not allowed to read (CORS). Cancel stops the download.
- Several addresses at once (NA4-2). The field takes one address per line (paste a list; Shift+Enter starts a line, Enter still imports), up to 20, with a repeated address read once. A line that is not a web address is named by its number before anything is fetched. The browser asks once for access to all their sites, inside the click; each address is then read in turn (with its own 20 s and "Loading 2 of 5 from …"), and ONE summary at the end, the same as Import a folder's, names each address with the collection it became or the reason it was not imported. Cancel stops the address being read and the rest. One address works exactly as before.
Import more
- A WSDL split over files.
xsd:import/xsd:include(schemaLocation) andwsdl:import(location) are now followed, up to 6 levels and 30 files. From Import from URL… a relative location is read from the WSDL's own folder; another site is asked for only when it is needed, with the same site-access prompt, and a refusal leaves that file out. From files, choose the.wsdltogether with its.xsdfiles (or import their folder) and they are matched by path, else by file name. A file that could not be had is named in one sentence at the end of the summary, and its types stay comments in the envelope as before. Onlyhttp(s)addresses and plain relative names are followed. - Environments inherit. An environment can have a Parent environment (in its editor, folded away; absent while there is only one environment). It gets the parent's variables and secrets; its own values win. A parent that would make a loop is never offered, and deleting a parent frees its children. Sends, scripts, Copy as code, the suite's CI export and the CLI's
--envuse the inherited values; a workspace export and the folder files keep the parent link. Secret values stay in the vault under the environment that holds them. - Import a
.envfile. Import .env… in the environment editor (or a.envamong the files of an import) makes an environment named from the file (.env.productionis "production"). Values whose names read like credentials (token, secret, password, key, auth, cookie, session…) or whose address holds a password go to the vault; they are never written as plain values. With the vault locked or not set up they are left out and the summary says how many and what to do.${OTHER}references are kept as written, not expanded. - Batch import. The Import a collection button takes several files at once, and Import a folder… takes a whole folder: specs, collections, WSDL and
.envfiles are sorted by what they are and imported, schema files are read with the WSDL that uses them, and ONE summary names each file with what happened to it and the reason for any that were not imported. Other files in a folder (andnode_modules,.git) are counted, not listed. A same-named collection is kept alongside as "(2)", never replaced. Up to 200 files, 8 MB each.
A default environment per collection (NA4-1)
- Collection settings… and Folder settings… have Default environment. Sending a saved request from it (its row's Send, or Send in Compose when the request was opened from it) or opening it in Compose switches the environment list to that environment, and Compose's "From …" line names it. An environment you pick by hand while working there is kept, also when you come back from another collection (until the panel closes). A collection inside a folder takes the folder's unless it names its own; a pin whose environment was deleted counts as none. With no environments yet the list is off and says how to make one. A suite run keeps the environment it was started with. The workspace export and the folder files keep the pin (
defaultEnvId).
OAuth 2.0 device code
- A new sign-in method for OAuth profiles (RFC 8628): Get token shows the provider's page and a short code (Open page, Copy code, the time left), polls once per the provider's interval, adds 5 s on slow_down, and stops on access_denied or expired_token with one sentence and Start again. The token is stored encrypted in the vault; renewal uses the refresh token the provider returned. Discovery fills the device authorization URL.
requestfox-ci device-logindoes the same in a terminal or pipeline: page and code on stderr, the access token on stdout, or--outfor a JSON file.
The QUERY method
- QUERY (RFC 10008, June 2026: safe, repeatable, carries a body) is in every method list: Compose, Edit & replay, the breakpoint editor and fault rules. Compose opens the Body tab for it, and the body is sent. Replay and "Replay all" treat it like GET: nothing to confirm, since it cannot change data.
- "Copy as code" writes QUERY in every language. PowerShell puts it in
-CustomMethod(QUERY is not one of the values Invoke-WebRequest's-Methodtakes; the snippet says-CustomMethodneeds PowerShell 7). JMeter's HTTP Request stops with "Unexpected method" on QUERY, so a .jmx keeps the sampler but switched off, with a note. - The CLI sends QUERY with its body (
assert,diff,loadtest,run). OpenAPI 3.2queryoperations are imported, mocked (mock --spec), contract-checked (conform) and probed (QUERY counts as a safe method, like GET).
Live streams
- Event streams a page reads with fetch (server-sent events, NDJSON, a language model's token stream) are captured for as long as they stay open, not only their first 64 KB or 15 seconds. They are copied as the page reads them: nothing extra is read, and when the page closes a stream, the connection closes at once.
- The newest whole events within the body limit are kept; older ones are trimmed and counted. Stop keeps the events that came so far and logs the stream straight away.
- If the page goes away before its stream ends, the newest part seen live is kept.
- A finished stream's card counts every event that came, says how many were trimmed (and the setting that keeps more), and why it ends there: Stop, the page closing it, a network error, or the page going away.
- A stream's events are judged: an error event inside a 200 answer is flagged Stream error (with its message), a language-model stream that closes without its end marker is flagged Ended early, and a long pause between events is flagged Paused. A stream is Slow when its first event is late, not because it stays open. The verdict stays with the call in History, the HAR (a
_streamfield), the HTML report and copied text, and a HAR opened in History keeps it. - A language model's token stream reads as "Token stream · Events: N" with its text joined up, on its card (live and finished), in the HAR and in the copied text. This is parsing only: no model runs, and nothing is counted as tokens or cost.
- While a stream is open, its card in the log counts the events so far, and Show events opens the newest ones as rows as they arrive, with a token stream's text joined up. The finished card replaces it when the stream ends.
- A linked OpenAPI spec checks a stream item by item when its answer has an OpenAPI 3.2
itemSchema(server-sent events, JSON Lines / NDJSON, JSON text sequences), in the panel and inconform. An event is checked as{ data, event, id, retry }, its JSONdataagainstcontentSchema, and a problem names its place in the stream (response body $[2].data). Trimmed events are listed as not checked; before, such an answer passed with nothing checked. - Streams a page reads with XMLHttpRequest or EventSource get the same live rows, kept events and verdicts as fetch streams, in History and the HAR too. An XHR stream is read as its text grows (one read as an arraybuffer, once it ends). An EventSource stream's rows are the events the page gets: every message, a server's
event: error, and each named event the page listens for; each reconnect is a call of its own, and close() keeps what came.
API docs
- Compose's Export list has a new button,
Export docs (HTML): your saved collections as one web page. Each request is there with its notes, headers, auth, body and saved example answers, in the folders and collections you made, with a contents list. The page is one file with no script and no outside link, so it opens from disk, goes on a ticket or sits in git. Nothing to choose: passwords, tokens and keys are always replaced by {{variables}} (the same scrub as Export workspace; a secret it took out elsewhere is hidden in the notes too), and scripts, environment values and recorded traffic are never written. With nothing saved, a dialog says to save a request first. The same page, byte for byte, for the same collections: there is no date on it. requestfox-ci docs --workspace <folder or workspace.json> --out docs.htmlwrites that page from the folder RequestFox keeps for git (or an Export workspace file), with--titlefor the heading. No request is sent and no server is needed. It also ships inside the one-file CLI.
CLI
--http2forassert,diff,loadtest,runandprobe: every request goes over HTTP/2 with Node's ownnode:http2. On an https address the server must agree to HTTP/2 in the TLS handshake; on an http address it is h2c with prior knowledge. There is no quiet fall-back to HTTP/1.1: a server that does not speak HTTP/2 is an error that says so. A run's requests share one connection per server. HTTP/3 is not supported.- The protocol each request really came over (
h2,h2corhttp/1.1) is in the JUnit report (aprotocolproperty on the request's first case), inloadtest --json(every result) and inprobe --json(every case). A run on Node's plain fetch does not say, so nothing is written for it. - Fixed: with the CLI's own transport (
--cert,--ca,--proxy,--insecure), a HEAD or 204 answer that names a content encoding failed with "unexpected end of file"; an empty body is no longer decoded. - The MCP server over HTTP (NA4-4).
requestfox-mcp --http <port>serves the same seven tools over MCP's Streamable HTTP athttp://127.0.0.1:<port>/mcp(--http 0picks a free port; the address is printed on stderr, stdout stays empty). It listens on 127.0.0.1 only. A Host that is not that address (DNS rebinding) and an Origin that is not a local page are 403;--allow-origin <origin>lets one more in, and the refusal says so.--token <value>orREQUESTFOX_MCP_TOKEN(16 characters or more) makes every request carryAuthorization: Bearer <token>, checked in constant time; else 401. One JSON-RPC message per POST, a JSON answer (202 for a notification); GET and DELETE are 405 (no stream, no session); an MCP-Protocol-Version it does not speak is 400 with the versions it does, so RequestFox's own MCP tester (and other 2026-07-28 clients) fall back to initialize. Tool calls run one at a time. No new tools.
Capture
- Each captured WebSocket and SSE message in the WebSocket list says its size after its protocol: a text message's UTF-8 bytes (the whole message, also when only its first 64 KB is kept), a binary one's exact bytes. A line under the count adds up the listed messages ("Messages: 18.4 KB (12.1 KB received, 6.3 KB sent)"), and says "at least" when a message kept before 2.4.0 had no size. The WebSocket client's own text messages say their size beside their time.
- Fixed: a stream from another site that the page read and then cancelled itself was called "Most likely blocked by CORS" in Chrome (which ends it with the same generic error). A page that read the answer's status was not blocked by CORS, so the card no longer says so; it keeps what came.
- Record a site in every window (opt-in): with two or more browser windows open, Start offers Also record this site in my other windows. Ticked, a tab in another window is recorded while it is on that site (the host of the tab you started on) and stops being recorded when it leaves; other sites in those windows never are. The recording bar says Every window: <site>, with the number of recorded tabs on hover. The shortcut's Start remembers the box too.
- Show page files (Settings → Noise filters, off by default): while you record, the page's own scripts, styles, images and fonts are listed too, each with a badge of its kind (Script, Style, Image, Font). They never raise an alert and are never flagged as duplicate or slow; one that fails (no answer, or 4xx/5xx) counts as an error. Switched off, the ones already listed step aside, and nothing extra listens to the browser.
Mocks & rules
- Mock sets that stay on (MS-1): Settings → Mock sets that stay on (right under Mock or break an endpoint, closed until you open it) keeps named sets: a name, the sites whose pages it works on, and the rules it picks from Mock or break an endpoint (Mock, Fail, Slow, Network error and Cycle; the others need a recording). Switch a set on and those rules answer matching
fetch()and XMLHttpRequest calls on those pages, in every tab, in Chrome and Firefox, with nothing recording, until you switch it off. A set names its rules rather than copying them: you edit a rule in one place, and a rule switched off there is off in the set too. A page's first calls wait (briefly) for the set, so a call made as the page starts is answered too. Pages already open get a set after a reload; switching it off or editing a rule reaches them at once. In a recorded tab a set's rules come after the recording's own and are logged like any mock. - The same quiet chip as rule profiles, beside it: "Mocks on: Checkout offline" (or "2 mock sets on"); its cross turns them off, its name opens the list. A set whose sites the browser was not allowed to reach, or whose page scripts the browser refused, is marked in the list and is not in the chip. A set with no site, or with no rule that is switched on, cannot be switched on; its form says which.
- Fixed (Firefox): a mock set switched on showed "on" but answered nothing. Firefox's background never loaded the mock sets' code, so the set's page scripts were never registered. It does now. The "same answer as last time" checks had the same gap in Firefox: their code is loaded there now too.
- No new permission: switching a set on asks for just its sites (the optional host access). The privacy policy and the store text say what a set does there. Import and Export share sets as one JSON file (docs/MOCK-SETS-FORMAT.md) with copies of their rules; values that look like secrets (tokens in a mocked body, credential headers,
?api_key=) are left out and counted; an import adds the rules to Mock or break an endpoint under the set's name and never turns a set on. - Rewrite the reply with a script (Chrome): a new mode in Mock or break an endpoint. The real call goes out; its reply goes to your own script before the page sees it, and the page gets what the script leaves in
context.response(status, headers, body). The script runs in the same sandbox as pre/post-response scripts (no extension access, no network;rf.sendRequestis off here) while the RequestFox panel is open, and the page is only told the rule exists, never its code. The card says Rewritten (its body is what the page got; status and headers are the server's) or Not rewritten with why (the script failed, or no RequestFox panel answered — then the page gets the real reply: at once with no panel open, after 20 s at most otherwise). Bothfetch()and XMLHttpRequest (axios, jQuery) replies are covered, for text bodies (an XHR's text, json, arraybuffer or blob reply); a synchronous XHR can't be held and goes through unchanged. Firefox can't run code typed into an extension, so there the mode isn't offered and the editor points to Mock and Header & URL rules. - Rewrite the request with a script (MS-2, Chrome): the same, before the call is sent. A matching
fetch()or XMLHttpRequest call waits for your script and goes out with what it leaves incontext.request(method, url, headers, body): a signed header, a field added to every body, one call sent to another host or with another method. A relative URL is read against the call's own; a GET or HEAD can't be given a body; a body that is not text (FormData, a file, a stream) reaches the script asnulland is kept unless the script sets one. The card of the call that went out says Request rewritten, or Not rewritten with why (a bad method or URL, the script failed, or no panel answered — then the call goes as the page made it, after 20 s at most). Same sandbox, same limits; the page is only told the rule exists. An XHR the page aborts while it waits is never sent; a synchronous XHR can't wait and goes out unchanged. Firefox: not offered; the note there now says Header & URL rules change a request's headers and address. - The reply mode is now called Rewrite the reply with a script, so the two read apart in the Mode list.
- WebSocket message rules (MS-3): a new mode in Mock or break an endpoint, WebSocket message. For a socket whose address contains the rule's text, it acts on text messages from the server (before the page's listeners and
onmessagesee them), to the server (insend()), or both, while recording. Only messages that contain: text, or a JSON field ($.type = price;$.typealone: the field is there); empty: every text message. Then: drop it; change it (replace every copy of a text, set a JSON field such as$.priceto-1, or with nothing to find, replace the whole message); or, on Chrome, change it with a script (context.message.data, orcontext.message.drop = true) in the same sandbox as the rewrite scripts, with 3 s to answer — then the message goes on as it was. A socket's later messages wait behind one that is with a script, so the order never changes. Works in Firefox too (drop and change); there the script action isn't offered. The WebSocket list marks a message a rule touched Dropped or Changed (the text shown is what went on); a message injected from the panel is sent as typed; binary messages pass untouched. A path the rule can't read is named when you save it. The Method field steps aside for this mode (a socket has none).
gRPC-Web and Connect with your .proto files
- Load .proto files… (under any protobuf answer, and on Compose's gRPC line) reads them with RequestFox's own parser: proto3, proto2 labels and editions, nested types, enums, maps, oneofs and services. Imports are found among the files you load; the well-known google.protobuf types are built in. The files are kept in this browser; a line that can't be read, or an import that isn't loaded, is named with its file.
- A page's own gRPC-Web, Connect and protobuf answers are captured as their bytes (by fetch and by XHR) instead of "binary response — body not captured". Their gRPC status is the call's status: anything but OK is an error, as for a send.
- An answer whose path is a loaded method reads as proto3 JSON with field and enum names (64-bit numbers as strings, bytes as base64, well-known types in their JSON forms); a field the files don't know keeps its number and wire type.
- Compose has a gRPC-Web / Connect body type: pick the method and the protocol, write the message as JSON (an example is filled in), and Send encodes it with the .proto and sends it from the extension. A message that doesn't fit says which field and why. Server-streaming answers are read when the server ends them; client and bidirectional streams say plainly that a browser can't send them. Saved and Recent requests keep the method and protocol, Open in Compose opens a captured gRPC call of a loaded method in this body type, and Replay sends the same bytes again.
- A page's gRPC-Web or Connect server stream is copied as the page reads it, for as long as it runs, and the page's own cancel closes the connection at once. Its newest whole messages within the body limit are kept and older ones trimmed and counted; its card says "Streaming…" while it is open, then "Messages: N", with a note when messages were trimmed or Stop ended it.
- While a page's gRPC-Web or Connect server stream is open, its card says "Streaming… · Messages: N", and a quiet Show messages link opens its newest messages as rows as they arrive, the way an event stream's Show events does: up to 200, each read as fields (by name when your .proto files have the method) with its size in bytes. A message sent compressed is decompressed in the page first; one in an encoding the browser can't decompress says so, with the encoding's name.
- Messages sent compressed with gzip or deflate (the
grpc-encodingorconnect-content-encodingheader, or data that starts like either when the header can't be read) are decompressed with the browser's own DecompressionStream, in a page's calls and in Compose's sends (when the answer fits the body limit), so they read as fields instead of "compressed". - Messages sent compressed with brotli (
br, named by the header: brotli data does not start with bytes that show what it is) are decompressed the same way where the browser's own DecompressionStream reads brotli. Where it doesn't, the card says "These gRPC frames are compressed with brotli, so they can't be read here", and an open stream's rows say "Compressed with brotli: not decoded here". Any other named encoding (snappy, zstd) is named the same way. RequestFox has no brotli decoder of its own. - Fixed: a grpc-web-text answer written in several pieces (padding between them) could not be read at all.
Testing MCP servers (MC-1)
- Compose has an MCP server body type: a tester for MCP (Model Context Protocol) servers over Streamable HTTP. It sends the plain JSON-RPC 2.0 requests you pick and shows the answers. RequestFox runs no AI model; it only tests and inspects the server.
- Connect (Send, in this mode) speaks the current MCP revision, 2026-07-28:
server/discover, the protocol version in every request, and theMCP-Protocol-Version,Mcp-Method,Mcp-NameandMcp-Param-*headers (a tool whosex-mcp-headerbreaks the spec's rules is left out, and Messages says why). A 2025-11-25, 2025-06-18 or 2025-03-26 server is reached throughinitializeinstead; its session id stays in the panel's memory only, and Disconnect ends the session with DELETE. - It lists the server's tools, resources and prompts. A tool's arguments are a form built from its inputSchema (text, numbers, true/false, a fixed list, JSON for the rest), or JSON; a prompt's are text. Call tool, Read and Get prompt show the answer: text (JSON indented), images, the structured content, the tool's own error, or the server's error and its code. Messages lists every JSON-RPC message sent and received as it happens, progress and log notifications too, with secret values masked.
- The URL, Auth (Bearer, Basic, API key, AWS, HMAC, OAuth 1.0a, JWT, an OAuth profile's token), Headers and environment apply as for any send. Digest is not used for MCP.
- A server that asks for something only an AI client gives (sampling, a form, roots) is told no, and Messages says so; its ping is answered.
- Save to collection… keeps the call: what it calls and its arguments. Opening it picks them once the server answers. An MCP call is not a suite step; the CLI and the Postman, Bruno, Insomnia and Hoppscotch exports leave it out, and the API docs page names it.
- Each problem says what to do next: sign in (401), a local server that refuses the browser's Origin header (403), no MCP endpoint at the address (404), a session the server ended. stdio servers (a program a browser can't start) and the old HTTP+SSE transport (2024-11-05) are out of reach, and the panel says so.
- MCP calls are not logged: they don't go into the log, Recent or History.
The LLM-call view (MC-2)
- A call whose answer is shaped like an OpenAI-style API (Chat Completions, the Responses API, Completions; any OpenAI-compatible server), Anthropic Messages or Gemini generateContent, whole or streamed, gets an LLM call section in its card's Details, and an LLM call view under Compose's answer. It shows the API's shape, the model the answer names, how many messages the request sent, the token counts the answer itself gives (input, output, total, cached, written to cache, reasoning: each with the field it came from), the stop reason, how long the call took and, for a captured stream, when its first event came.
- Only what the answer says: no counting, no cost (prices change). When the answer has no usage numbers the view says so, and for an OpenAI-style stream says how to ask for them (
stream_options.include_usage). - Any other answer has no such section. RequestFox runs no AI model; this only reads answers.
SOAP
- Import a WSDL. Import a collection takes a
.wsdl(or.xml) file: each operation of each SOAP 1.1 or 1.2 service port becomes a POST request to the port's address, withSOAPAction(SOAP 1.1) or the action inContent-Type(SOAP 1.2) and an envelope with sample values from the schema in the file. A WSDL 2.0 file, XML that is not a WSDL, and broken XML each say what is wrong. Schemas in other files are read when they are given: see Import more below. - XPath checks on XML answers. A new check kind, XML (XPath), in Settings → Pass/fail checks, in a saved request's own checks, in the CLI's
assertconfig ("type": "xpath") and in the background's judging of captured calls. A small XPath 1.0 reader of our own (no library); a name without a prefix matches any namespace, so//Priceworks on a SOAP answer. A check on an answer that is not XML, or with an XPath that cannot run, fails and says why. The supported subset is written out in docs/xpath-subset.md. - XML tree. An XML answer (SOAP, RSS) has the same Tree button as JSON, in a log card and in Compose. Click a row for its XPath and three actions: Copy XPath, Copy value, Check this value (adds an XPath check). Arrow keys, Home, End and Enter work as in the JSON tree; big elements show 100 children at a time.
No AI in RequestFox
- Removed: the two helpers that used Chrome's built-in on-device model, Ask in plain English (the sparkle button by the search bar) and Suggest assertions (in a card's More menu), with their Settings group (AI) and its on-switch. RequestFox now contains no AI model and no AI code, and the privacy policy (section 5) says so.
- Instead of Ask in plain English: the filter syntax. The funnel button (Filter syntax help) next to the search bar lists every token with click-to-add examples; "slow POST requests over 2 seconds" is
method:POST latency:>2000. - Instead of Suggest assertions: Check this value in the JSON tree (one click adds a pass/fail check), the Pass/fail checks editor in Settings, and Convert for a script that only checks simple things.
GraphQL answers checked against the schema (GQ-1)
- A captured GraphQL call to an endpoint with a kept schema now has its answer checked too, not only its operation: a value of the wrong type, null in a non-null field, a field the query did not ask for, and an asked field that is missing (one under @skip, @include or @defer may be). Unions and interfaces are followed by
__typename. The card's Schema mismatch and Details name the place in the answer (data.product.price: expected Int!, got string), never the value; one problem in every item of a list is said once, with how many places. - Details has an Answer line: matches, does not match, or why it was not checked (cut at the body size limit, not JSON, an HTTP error, a request that already breaks the schema, a subscription).
- The CLI:
conform --graphql-schema <schema.graphql | introspection.json>checks a session's GraphQL calls and their answers, with or without--spec(--graphql-endpointpicks one endpoint when there are several);assertandruntake--graphql-schemafor their own sends. The README's "answers are not validated" limit is gone. - Custom scalars by what they declare (NA4-3). A scalar's
@specifiedBy(url:)(SDL) orspecifiedByURL(introspection; graphql-js 15'sspecifiedByUrltoo) is kept with the schema and shown in Docs as a link. An answer value, and a literal in the operation, is then checked by it: an RFC 3339 date-time, date or time (the whole RFC 3339 is read with the scalar's name), a UUID (RFC 4122 / 9562), an absolute URL (RFC 3986 or the WHATWG URL standard), an email address (RFC 5321 / 5322); JSON takes anything. A scalar with no specifiedBy, or an address not recognised, takes any value as before. The place is named, never the value. Load asks for specifiedByURL and, when a server older than GraphQL's October 2021 edition refuses that field, asks once more without it. - Subscriptions in Compose (NA4-3). Send on a
subscriptionopens the browser's WebSocket to the endpoint (https → wss, http → ws; a ws(s):// address as typed), offering graphql-transport-ws and the legacy graphql-ws; the server picks. Events come in live under Send, newest first (up to 200 kept on screen), each with its number, time and what the kept schema says of it ("matches the schema", or the first problem by place); a screen reader hears them at most once every 2 s, like the WebSocket client's replies. Send reads Stop meanwhile; Stop sendscomplete(legacy:stopandconnection_terminate) and closes. Pings get their pong. The headers and auth go inconnection_init's payload, by name and underheaders(a browser WebSocket carries no headers). Connecting, waiting for the first event, the server's errors, a refused connection, 4401 / 4403 / 4406 / 4408 and other closes, no answer within 10 s, offline and a non-web address each say what happened and the next step. Nothing is recorded or kept.
Finding codes and CI baselines (GQ-2, GQ-3)
- Every finding has a stable code, such as RF-FAILED-5XX, RF-SLOW, RF-CHECK-STATUS or RF-GQL-ANSWER-NULL (63 in all, listed in docs/finding-codes.md). The CLI prints it at the end of each failure line (assert, run, conform, probe, diff, and loadtest's broken limits), writes it as the
findingproperty of each failing JUnit case and ascodeinprobe --json. The HAR export has_findingson each flagged entry, and the session JSON exportfindingson each flagged request. - In the panel, a flagged call's Details starts with Flags: each code and what it found, and one Why is this flagged? button. Its dialog explains each code (why, and what to do) in the panel's language, offline, and names the CLI command that says the same. The HTML report shows each call's codes under its flags.
requestfox-ci explain <code>says why a finding is flagged, what to do and how to turn it off; with no code it lists them all. A typo names the nearest code.--baseline <file>on assert, run, conform and probe: the run fails only on findings that are not in the file.--update-baselinewrites the run's findings to it. The file is sorted JSON, one finding per line, with codes and places only (no answer data, query values or secrets); known findings are skipped JUnit cases that say so, and ones no longer seen are named.
Cookie jar (CJ-1 to CJ-4)
- A cookie jar for the requests RequestFox sends itself (Compose, saved requests and suites with nothing recording). Turn it on for a site in Settings → Mocks, rules & checks → Cookie jar, or from Compose's ··· More → Cookies…: the cookies that site sets come back on your next sends to it, so a sign-in carries to the next call. The browser's own cookies are never sent or changed by it.
- The rules are the browser's (RFC 6265): host-only or domain cookies, path, Secure (https, or this computer), Max-Age over Expires, the __Host- and __Secure- prefixes, at most 400 days. A deleted cookie (Max-Age=0) leaves the jar.
- Compose's sent line says what the jar did in one sentence ("Sent from the extension → 200 (12 ms), with the jar's cookies: 1 sent, 1 kept"), or that the browser sent the request without them; nothing when the jar did nothing.
- Each site's cookies: name, path, how long and HTTPS only (never the value); add, edit and delete them in one form, the value masked with Show beside it; Forget this site (it asks first); Import from this browser copies the browser's own cookies for that site. That button is the only thing that asks for the new optional
cookiespermission, in its click, with that site's access. - Session cookies are kept until the browser closes (session storage), the rest until they expire (local storage). A line says what is kept, in counts only; Clear the jar empties it. Values never go into an export, a linked folder, code, History, Recent, a report or the log.
- How it works: one declarativeNetRequest session rule per send sets the Cookie header on the extension's own request to that origin and is removed when the send ends; the answer's Set-Cookie comes from webRequest (fetch() never shows it).
- CLI:
assertandruntake--cookie-jar <file>(Netscape cookies.txt or JSON with Playwright's cookie fields). Its cookies go with each request to their site, each answer's Set-Cookie updates them (redirects are then followed hop by hop, so a sign-in's 302 keeps its cookie), and the file is written back when the run ends. A file that is not a jar is exit 2 before any request; values are never printed.
Slow down the whole tab (PT-1 to PT-3)
- Chrome: Settings → Mocks, rules & checks → Slow down the whole tab. The tab in front loads as on Slow 3G (2 s, 400 kbit/s), Fast 3G (0.56 s, 1.4 Mbit/s), 4G (0.17 s, 8.1 Mbit/s), offline, or your own delay and speeds — the page and everything it loads, through Chrome's debugger (Network.enable, Network.emulateNetworkConditions and Network.disable, nothing else: Chrome applies network conditions only with the Network domain enabled; the events this produces are not listened to, read or kept).
- The first time, one plain dialog says that Chrome then shows its own "started debugging this browser" bar, which can't be hidden; its Continue is what asks Chrome for the new optional
debuggerpermission. - While it is on, a chip under the top bar says so; its × turns it off. Stop recording, closing the tab, closing the last panel or closing Chrome's bar turn it off too, and the line says which.
- Firefox has no debugger API: the group says so in one line and opens a mock rule's Slow and Throttle modes instead.
Final logic and wording fixes
- With "Show page files" switched off, the count tiles and the Overview no longer count the page files a recording kept; the Total matches the list.
- When Chrome refuses the whole-tab slow-down because DevTools or another extension is already debugging that tab, the status line says so and what to do, in your language, instead of Chrome's English sentence.
- Import .env with the vault locked now asks first: Unlock the vault, or Import without them. Esc, or a click beside the question, imports nothing (it is not an answer). Importing the same file again after unlocking fills the environment that has the same plain values in, instead of making "name (2)".
- The environment editor lists the names a parent environment gives and which environment each comes from (names only, never values); a plain name starts your own line to override it.
- The HTML report marks page files with a quiet Script, Style, Image or Font flag, and the CSV has a findings column with the finding codes.
- A session file opened from disk keeps a call's stream summary, gRPC status and page-file kind only in the shape a recording gives them.
- The privacy policy (all four copies) now names Import from a URL, a WSDL's linked schema files and the MCP server tester, and the Firefox reviewer notes list the device code sign-in's requests.
- A GraphQL subscription belongs to the open request that started it. Showing another request (its chip, or New request) or picking another body type stops it, and that request's status line says so; its events come back with it. Send on any other request sends, instead of reading Stop. While Send reads Stop its tooltip says "Stop (Ctrl+Enter)", and a plain Send after a subscription clears that subscription's event list.
- Sizes in the panel are in its language ("74 B" is "74 بايت" in Arabic, "74 o" in French), and Arabic milliseconds read "م.ث", so a duration or a size in Arabic no longer shows its unit on the wrong side. The HTML report and the command line keep B, KB and MB.
- The .env and batch import summaries put a count after its word ("variables: 2, secrets kept in the vault: 1"), so one secret no longer reads "1 secrets".
- The environment editor says above the inherited names what pressing one does: "Press a name to add it to your variables and set your own value."
- The Columns dialog keeps ↑ before ↓ in Arabic.
- Import from URL wraps a long address and grows to show it whole, instead of running it past the edge of the field.
- The MCP server (
bin/requestfox-mcp.js) uses only files in the folder it was started in, or below it: a session export, a folder of them, a spec, a data file or a baseline outside it is refused before it is looked at. Over--httpwithout a token, the line it prints when it starts says that any program or local page on this computer can call the tools. - The privacy policy (all four copies) names the GraphQL subscription's WebSocket, and says cookies you add to the jar by hand or import from the browser are kept the same way; README's promise lists every kind of request RequestFox makes when you ask; the Chrome listing and the Firefox reviewer notes name Import from a URL and the MCP server where the policy does, and the reviewer notes describe Import from a URL's fetch and the subscription WebSocket.
- Fixed: plain words in the log's search box now match the method and the status code as well as the URL, so "post" finds the POST calls and "500" the 500s. Before, only the URL was searched: "get" seemed to work because many addresses contain "get", while "post", "put" or "delete" found nothing. The search help says so in every language. MCP control's list_calls filter and the MCP server's search_requests (
bin/requestfox-mcp.js, on a session file) match the same way.
MCP control
- An AI agent you run on this computer can drive RequestFox through its own MCP server:
node bin/requestfox-mcp.js --http 3333 --control. RequestFox runs no AI model. Off by default: Settings → Mocks, rules & checks → MCP control, Let a local MCP client control RequestFox, then the port and the one-time pairing code the server printed, and Connect. The browser asks for access tohttp://127.0.0.1only; the manifests are unchanged. - 21 more tools while connected: recording (start, stop, list calls with the panel's search syntax, one call's details, replay),
send_request(opens in Compose and sends from the extension), mock and fail rules (create, change, delete), rule profiles and mock sets on or off, saved requests and suites, HAR / report / docs exports, the whole-tab throttle (Chrome), clear the log, delete a collection. - Safe by default: the extension connects out to 127.0.0.1 (long-poll); the code is 8 letters and digits, compared in constant time, good once and for 10 minutes, one try a second, replaced after 5 wrong ones; the session key lives only in memory and an ended session's key is refused; a DNS-rebinding Host and every web page are refused (with
--control,/mcptoo). Each action is listed under MCP actions; answers are redacted as exports are (always) and scrubbed of every secret value the background knows; no tool reads the cookie jar, the vault, secret variables, OAuth tokens, or any Settings beyond the mock and fail rules it manages; a request with{{variables}}is refused and never carries the cookie jar; deleting, clearing, changing a rule you made, every request the agent writes or replays (to any address: the host is shown in bold) and a saved request or suite that changes data wait for your click; a request to the server's own port is refused; a rule made from MCP matches the address's path only; nothing asks for a permission; at most 120 calls a minute. While connected, a chip under the top bar says Controlled by a local MCP client, with Disconnect. Closing the panel or quitting the browser ends the session. requestfox-mcpalso speaks MCP 2026-07-28 (server/discover, the version in each request's_meta,resultType; a mismatchedMcp-Methodheader is -32020), and still answersinitialize.
2.3.0
Shortcuts moved
- Shortcuts are now Alt+Shift+S (start or stop recording) and Alt+Shift+M (switch between Full, Background and Mini) — Option+Shift on a Mac — so they no longer take Chrome's own Reload key. Help → Keyboard shortcuts lists them.
- Alt+Shift+S, not Alt+Shift+R: Chrome keeps Alt+Shift+R for itself, so on a new install it left the recording shortcut "Not set" and the key did nothing until you set it by hand in chrome://extensions/shortcuts.
- Alt+Shift+S opens the panel again: it used to wait before opening it, and the browser only lets a shortcut open the panel straight away, so nothing happened. The same press starts recording too, panel open or not; before site access is ever allowed, the panel says in one line to press Start once. Start on a browser page (chrome://extensions, a new tab, the Web Store) says to open the site first, instead of recording a page that can never show a call. Alt+Shift+M pressed right after the browser woke the extension no longer resets the other settings.
Getting around
- The RequestFox logo and name in the top bar are Home: a click closes the open panel, leaves a past session and shows the newest request. Esc closes the open panel too, even from a field inside it, and focus goes back to the button that opened it, with the log where you left it.
- The top bar shows which panel is open: its icon is highlighted, or More when that icon is folded into More. A panel's name, ? and × stay at its top while it scrolls.
- Enter in Compose's address field sends. Enter in a session name, a new suite's name or the Mini popup's session name presses its button.
- The Mini popup's logo opens the full panel, and its dot shows only while recording.
- Help names each panel's walkthrough as a tour ("History tour"), so it is no longer mistaken for the panel itself, and the idle screen stops offering the tour once you have seen it.
- A jump from the Timeline or an alert lands on its request card, with focus on it.
- Cancel on the site-access explanation just cancels, with no second dialog.
Auth: more ways to sign in
- OAuth profiles have a Sign-in method: browser sign-in (as before), Client credentials (no sign-in, the token is asked for by itself and again before it runs out), Refresh token, and Password (marked legacy). A server that says no gets a plain sentence with one next step (invalid_client, invalid_grant).
- Paste an OpenID Connect issuer and "Fill from issuer" fills the endpoints; "ID token claims" shows what is inside an id_token (never its signature).
- Compose's Auth type has AWS Signature v4 (region and service read from the host; keys as secret variables) and an HMAC signature preset. Saved requests, suites, the CLI and the CI export sign the same way; the keys leave the export as environment variables.
- A GET or HEAD with a body typed says it sends no body.
- A token or authorization address that starts with http:// is now refused (the secret would travel unprotected): use https. Only localhost may stay on http. Profiles saved before this with an http address show the plain message at Get token; edit the address to fix it.
- Two sends that need a token renewed at the same moment now share one renewal, so a rotating refresh token is not spent twice.
- Params, Headers and Form fields rows have a small checkbox: untick it and the row is dimmed and not sent (it stays in saved requests, left out of code and the CLI).
- Compose's Auth type (and a collection's or folder's auth) has OAuth 1.0a: HMAC-SHA1, HMAC-SHA256, HMAC-SHA512, RSA-SHA1, RSA-SHA256, RSA-SHA512, and PLAINTEXT (sent only over https or to this computer). The request is signed as it is sent, after {{variables}}; realm, callback, verifier and "send the signature in the query string" are under More.
- JWT Bearer makes and signs a new token on every send: HS256, HS384, HS512, RS256, RS384, RS512, ES256 and ES384, with claims, Expires in, iat, a key ID and a base64 secret. Another algorithm, or "none", is refused with a plain sentence. A 401 that looks like a clock difference says to check the clock.
- OAuth 1.0a and JWT keys are secret fields: kept out of Export workspace, folder files and Copy as code. Suites, the CLI and the CI export sign the same way (the keys leave the export as environment variables), and a Postman import or export keeps both, naming what it could not bring over.
Recording
- A page's very first calls, made before the recording has fully started, are captured too, and Firefox now loads the capture before the page's own scripts, so its startup calls are no longer missed there.
- A call shows in the log the moment it starts, as a quiet "…" card that turns SLOW live; its finished card replaces it. Switching tabs no longer drops these cards, and the last one expiring brings the empty list's next step back.
- Streams (server-sent events, NDJSON, token streams) are captured: their first 64 KB is kept. A stream that stays open says "Streaming…" instead of turning SLOW, and keeps that first part until it closes — even if the browser restarts the extension's background worker in between.
- Failed calls say what went wrong in plain words (the raw code stays in the tooltip and in exports), and a successful call with no body says why. The in-page alert and the desktop notification use the same words now, never "net::ERR_FAILED", and the panel's own sends (Compose, a saved request, a suite step, Replay) raise neither: the panel reports them.
- WebSocket frames are no longer cut short, duplicates are counted per tab, and a multipart form is replayed as form fields.
- Numbers and units follow the panel's language and read right in Arabic; the timeline waterfall has a ruler.
- Search takes host:api.example.com, and a "-" before any word or token leaves those requests out (-host:cdn.example.com, -status:200), as in DevTools. The search box's tooltip shows how.
- Words separated by spaces each have to match, in any order: "users orders" finds /users/7/orders.
- Calls to the same endpoint are counted as one: /users/1 and /users/2 are /users/:id (numbers, UUIDs, hashes, ids like ord_445566, dates), and GraphQL calls are told apart by their operation, JSON-RPC calls by their method. Performance, the Timeline, Heaviest endpoints, the test plan, rate limits, Top Issues, N+1, History's compare, the report and the exports all group the same way. A click on such a row filters the log with path:/users/:id, which you can also type in the search box.
- A changed answer (SCHEMA CHANGED) is found up to 5 levels deep and inside lists, and stays quiet about fields that are optional or can be null. Your own sends (Compose, saved requests, suites, Replay from the extension) are checked against their own earlier answers too.
- N+1 for an operation with no id in its path needs 5 calls within a second, so search-as-you-type is no longer flagged.
- A card flagged as a repeat has "Stop flagging repeats" in its ··· More menu: repeats of that endpoint (every id) are no longer flagged, counted or alerted. The dialog that says so has Undo, and Settings → Noise filters lists the endpoints, each with "Flag again".
- Captured GraphQL calls to an endpoint with a schema kept are checked against it: SCHEMA MISMATCH and the reason on the card, every problem under Details. A persisted query that sends only its hash shows its text once a call has sent the two together.
- A call's security findings are listed under Details, not only in the SECURITY badge's tooltip, and a finding that is only advice gets a calmer badge. New passive checks: a token in a URL query, an answer with a login that shared caches may keep, a JWT with alg "none", and card numbers or many emails or phone numbers in an answer. Masked or redacted values and ids never count.
JSON answers
- A JSON answer can be read as a tree: press Tree, in a log card or in Compose (the choice is remembered). It opens at most 8 levels by itself, shows long lists a page at a time, and has a filter once there are more than 25 values.
- Click any value (or right-click it) for Copy value, Copy path, Check this value (a pass/fail check on later answers) and Save as variable (an environment variable for {{name}}). The first tree you open says so in one line. A value hidden by "Redact sensitive data" is never saved as a variable; its check only asks that it is there.
- Numbers keep every digit as sent: a 64-bit id like 12345678901234567890 is not rounded, and Copy value copies it exactly.
- Compose's body editor says where and why the JSON breaks while you type, in the panel's language ("Not valid JSON: line 4, column 12 — a comma is missing here"), with "Go to line 4". GraphQL variables are checked too. It never blocks Send.
Replay and breakpoints
- Replay keeps the real Authorization and Cookie values when the browser restarts the background worker, and refuses with a clear reason rather than sending "*REDACTED*".
- A breakpoint nobody answers no longer hangs the page: the call goes out unedited and its card says BREAKPOINT SKIPPED. Closing the panel with a breakpoint dialog still open now does the same at once, instead of holding the page's call for up to ten minutes. With the side panel and the DevTools panel both open, closing one leaves the other's dialog in charge: your edit is no longer lost.
- Replay refuses, with a reason, a request whose stored body had a value hidden by "Redact sensitive data" or was cut at the capture limit, instead of sending "*REDACTED*" or half a body; Edit & Replay still sends.
- Replay of a call to another site that the page's CORS rules refuse (for example an analytics beacon that allows any site but no cookies) is sent again from the extension by itself, without the page's cookies, and says so under the card; its new card is marked FROM EXTENSION. Any Replay failure now says what went wrong in plain words, with a next step, instead of "TypeError: Failed to fetch".
- Replay sequence says how many of its calls change data (POST, PUT, PATCH or DELETE) and asks in the red danger style before it sends them again.
History and past sessions
- Export file names are short and say what they are: the site (or the name you typed) and when the recording started, once, in your local time — requestfox-github.com-2026-10-23-2108.har, not the session name with its date plus a second, ISO timestamp. The same for the report, CSV, JSON, Postman, OpenAPI, test scripts, settings and workspace files.
- The Performance panel fills in live while recording (at most once a second) instead of only after Stop, and with nothing captured yet it says "Recording — stats appear as requests come in." No panel tells you to start a recording while one is on any more: the empty log, History's "Compare to current" and a suite's send error say what to do instead, and with a past session open during a recording, Compose and Suites still know it is recording.
- Open a past session in the log, read-only, and go back with "Back to live" or Esc. The recording keeps going meanwhile, and a suite step, a post-response script or the Compose preview waiting on a live response still finishes while the past session is on screen. Switching tabs no longer closes it; only a new recording does, and a screen reader hears it. Deleting the session you are looking at takes you back to the live log. Going back to live, opening a past session and starting a new recording show the newest request at the top, instead of keeping the place you had scrolled to.
- A past or opened session remembers the page it was recorded on, so its bug report and HTML report name it; session JSON and HAR files carry it.
- Bug reports start with the page the recording started on, the time, the browser and the session, and GitHub and Linear issue links stay short.
- A past session can be saved as JSON or OpenAPI, and a RequestFox session file opens again with Open file…; a failed call in a HAR stays failed.
- Delete asks first, History search results work from the keyboard, compare lists say how many rows they leave out, and a session that cannot be saved to History now raises an alert.
- Pinned sessions come first in History, and every card's time uses one clock, in the panel's language.
- Past sessions keep every body as it was captured, and their screenshots (before: the first 20,000 characters of each body and no screenshots). Opening one, and every export from History, uses the full copy. History keeps up to 50 unpinned sessions within 512 MB (or half of what the browser allows, if that is less); pinned sessions always stay. If the browser refuses the full copy, History keeps a shorter one and says so.
- One line under History says how much space it uses on this device, with Clear old sessions: it says how many sessions go and keeps the pinned ones unless you tick "Also delete pinned sessions". It replaces Clear history, which deleted pinned sessions too.
- History's compare boxes show only when there are two sessions to compare, under one line that says what they do, and a session's name looks like a link, since a click on it opens the session.
- Open file… in History opens a session JSON (Export → JSON; .json or .requestfox), like a HAR, with its checks, issues and screenshots, for Report, CSV and Compare.
Compose and collections
- Compose no longer needs a recording. With nothing recording, Send (in Compose or on a saved request) goes from the extension itself, without any page's cookies: the browser asks once for that one site (no prompt if you already allowed it at Start), and the answer shows in Compose. These sends stay out of the log, History and exports. While recording, Compose still sends through the page you test, as before.
- Test suites no longer need a recording either. With nothing recording, Run sends every step from the extension, without any page's cookies: the Run click asks once for the sites the steps call, and a declined prompt says so under the suite instead of failing each step.
- A calmer Compose: fewer controls when it opens, room in a 300 px sidebar (the environment list moves under the title), real labels, and a readable response — indented, with "Headers (1)" and a note when it is cut.
- Slow sends are not cut off at 20 s and can be cancelled; a screen reader hears "Sending…" once, not every second.
- Imports are saved as collections; collections and folders can be renamed, moved and deleted; one "Save to collection" dialog; Copy as code for a composed or saved request. Extra actions sit behind one "··· More" button in Compose, on saved requests and on collections.
- {{variables}} in auth fields are filled in first, pasted curl commands read more flags, Postman imports keep inherited auth, and saving an environment never overwrites another one.
- When the page can't reach an API on another origin (often CORS), Compose offers Send from the extension instead: RequestFox sends the same request itself, using the site access you allowed at Start, without the page's cookies, and marks it FROM EXTENSION. Replay on that card sends it the same way, the host is remembered until the browser closes, and the card names any typed header the browser does not let an extension send (Cookie, Origin…).
- Secret variables: an environment can keep a value encrypted in the vault, masked, and never in an environment export or Copy as code.
- Postman-style path variables (/users/:id) get their own rows under Params.
- Importing a collection you already have asks: Replace or Keep both.
- Copy as code refuses a {{variable}} the environment doesn't set, the way Send does.
- Choose file… is a quiet button with the file's name beside it.
- Compose shows the answer to your own send as it came; one line says the saved copy and exports hide values that look like secrets. A path variable's value is encoded as one path segment.
- Recent: Compose keeps your last 100 sends from Compose and saved requests, recording or not, with their answers — on this device and redacted. The quiet Recent link lists them: filter them, reopen one with its whole form and answer, or Clear them. A saved request's send reopens tied to it ("From <collection> — Send uses its settings · Unlink").
- Collection and folder settings (Collection settings… or Folder settings… in the ··· menu): variables, a base URL, headers and auth that every request inside uses. A folder's settings win over its collection's, a request's own auth wins over both, and the active environment wins over collection variables. Send, Copy as code, suites and the CI export all use them. Moving a request or deleting a folder says first how many requests it changes, and saving "No auth" into a collection with auth asks which you mean.
- Postman import keeps {{variables}} as written, the collection's variables and auth (a folder's auth too), and Postman's secret variables stay secret. Simple pm.test checks (status, time, a header, a JSON field) become pass/fail checks on that request, and pm.environment.set from the answer becomes a chaining row. Other scripts are listed in a short import summary and kept as they were. Export to Postman… (a collection's ··· menu) gives the collection back with its variables, auth, folders and scripts.
- Chaining can take a value from a response header, a cookie or a regex, not only from the JSON body. A value that is an object or a list is saved as JSON, not "[object Object]".
- Sends from Compose, a saved request, a suite or a script run in the panel itself and are no longer cut off after 30 seconds: a slow answer is waited for, and Cancel stops it. Replay of a card sent from the extension goes the same way. If the panel closes before the answer comes, Recent keeps the send and says the request may still have reached the server.
- A streaming answer (server-sent events, NDJSON, a language model's token stream) reads as one row per event, live, with "Events: N" and Stop, which keeps the events so far. A token stream also shows its text joined up, and a stream's card in the log uses the same rows.
- A send from the extension says where its time went (DNS, connect, TLS, wait, download) behind a quiet Timing link, or says plainly that timing isn't available for it.
- An image, PDF or protobuf answer to a send from the extension is kept as its bytes: an image shows as a preview, and Save response saves the real file under the server's file name.
- gRPC-web, Connect and protobuf answers read as fields without a schema, with the gRPC status as a badge ("gRPC NOT_FOUND" is an error).
- The WebSocket client has an optional subprotocol, "Reconnect if it drops" (five tries, then it stops), saved messages ("Forget saved messages" asks first) and a filter once its log grows long.
- Socket.IO: the WebSocket/SSE panel reads a page's own Socket.IO traffic (v2 to v4) as events: event(args) lines, acks, namespaces and binary attachments, with pings hidden and counted and a Raw frames switch.
- The WebSocket client has a Socket.IO mode that speaks v3 and v4 over the browser's WebSocket, without long-polling (a v2 server is named as such): path, namespace and a JSON auth payload; emit an event with JSON arguments and an optional ack, shown with its time; show only some events; pings answered and counted. Every failure says what happened and what to do. A Socket.IO request can be saved to a collection (its auth payload is a secret field) and opened again; it is not a suite step.
- In Firefox the panel reaches your own ws:// servers and plain http:// servers on your network; Firefox had turned them into wss:// and https://.
- After a send, one quiet line may offer the next step, from what just happened: save a token from a sign-in answer as {{token}}, add auth after a 401 (or a 403 that asks you to sign in), use a {{variable}} for a host you keep sending to, edit a check that failed, or move a secret header kept in a saved request into a secret variable. After a suite run, steps that pass on any answer below 400 get "Check each one's status". One click, with Undo; × is "Not now", a second × turns that kind off, and Settings shows turned-off suggestions again. Only one such line shows in the panel at a time.
- Typing {{ in Headers, Params, form fields and the Auth fields lists your variables.
- Checks on this request (Compose → Advanced): a saved request's own checks in plain words, with Edit and Delete (with Undo).
- GraphQL that knows the schema: with the GraphQL body type, one line says "Schema: not loaded · Load". Load asks the endpoint (introspection), and Paste SDL… takes a schema file or an introspection answer; it is kept in this browser, per endpoint, with Reload and Remove (with Undo). The query box then suggests fields, arguments and enum values (Ctrl+Space or Alt+Down; Enter inserts), names a problem under it ("Did you mean price?") with Go to line, and Docs searches the schema. When a load fails, the line says why and the one next step (sign in, paste the SDL instead).
- Link a folder… (Import & export, in Chrome) keeps your saved requests, environments, suites, checks and rules as plain files in a folder you pick, one readable file each, so git can track them. Changes are saved there as you work. After a git pull the bar says the folder changed, and Reload from folder brings the changes in, keeping the edits you made here. Passwords, tokens and keys RequestFox recognises stay on this device as {{placeholders}} in the files. A pull's header rules and URL rewrites arrive switched off, and its scripts are asked about first. A file RequestFox cannot read is left in the folder as it is, and a folder with other people's files is not linked over them.
- Save as example under Compose's answer keeps that answer (status, headers, body) on the saved request; Examples (n)… in its ··· menu lists them, shows one, compares it with the latest answer (status, content type, JSON shape, headers that came or went), renames it, and deletes it with Undo. Cookies are never kept in an example.
- Notes… on a saved request, a collection or a folder keeps plain text for a teammate; it shows as one quiet line under the name, and search finds it.
- Examples and notes go with the request in exports and folder files (the secret scrub covers example bodies), and to and from Postman as saved responses and descriptions.
Settings, checks and suites
- Pass/fail checks and header/URL rules are lists, each with an on/off switch and a remove button; the text box is a quiet "Edit checks as text" or "Edit rules as text" link.
- Settings names say what they hold; mock and fault rules open with one line and short labels; Arabic keeps URL patterns the right way round.
- Suites show translated errors and say why a step failed. The command-line tool refuses bad flags, answers --help and times out; rf.log() output shows under Compose.
- Edit a saved check or header rule in place, and the comparison menu speaks words ("contains", "does not exist").
- A suite step sends the saved request's Auth tab, as its own Send does, and a step to a host you chose Send from the extension instead for goes that way too.
- The command-line tool says when checks are switched off or match nothing, stops before any request when a {{env:NAME}} it needs isn't set (and names it), and a diff with no baseline exits 2.
- The security probe is called an OPTIONS check, and a clean result no longer claims CORS is fine.
- Performance is calmer: Rate limits and Heaviest endpoints are closed rows you open when you need them, and a 300 px sidebar shows four tiles.
- Checks and chaining read one path language: data.items[-1].id, items[*].price, filters such as items[?(@.price > 10)], .. and length(). A check on a list holds only when every item passes, and an empty list fails it.
- The JSON Schema check understands much more of JSON Schema ($ref, allOf/anyOf/oneOf, if/then/else, formats and more) and never passes what it did not check: a keyword it does not support is listed as "not checked" and the check fails.
- A check that cannot run marks that request's checks as failed, instead of losing the request.
- Link an OpenAPI spec as the contract: an OpenAPI import links it, and Settings → Pass/fail checks → Contract changes it or turns it off. While a spec is linked, a call that breaks it shows SPEC MISMATCH with one line of reason, a call the spec does not have shows NOT IN SPEC, and Details lists every problem and what could not be checked. Calls to other sites (analytics, fonts) are never judged, and what RequestFox cannot check is "not checked", never a pass. With no spec linked nothing new shows.
- Top Issues shows "Spec coverage: 34 of 52 operations seen"; a click lists the operations not called yet. The HTML report gets a Contract section, and Copy bug report a Contract line.
- When a spec names no host and calls to a host on another site match several of its operations, one line asks "Check calls to <host> against this spec?": Check them (with Undo) or Not now, which is remembered. Settings lists the hosts added this way, each with "Stop checking".
- A card can carry a quiet Advice badge, only for the site's own calls and local servers: a JSON or text answer sent without compression (with what gzip measured it would save, worked out in the browser), or the very same answer downloaded again with nothing a cache could use (no ETag, Last-Modified or max-age), with what a cache would have saved. Details has the fix in one line. It is advice, never an error.
- A new check, "Same answer as last time" (Settings → Pass/fail checks, or one switch on a test suite), compares each answer with the last one for the same request. Clock times, random ids, tokens, nonces and the order of lists of records are left out on their own; you can name more paths to ignore. A changed card lists each changed value, was and now, and has "Use this answer from now on". Kept answers never hold a token, key, password, cookie or session id RequestFox recognises.
- In the CLI the same check keeps each answer in requestfox-snapshots/ (--snapshots <dir> to choose); --update-snapshots takes the new answers, and in CI (the CI variable, or --ci) a missing saved answer fails.
Scripts
- Pre-request and post-response scripts get rf.test("name", fn) and rf.expect(…) (Jest and Chai words), can await, and have rf.json(path), rf.getVariable, rf.crypto (SHA, HMAC, base64, UUID) and rf.sendRequest (up to 20 per run, each one logged). Their tests show on the request's card, in a suite's result and in Compose ("Script checks: 2 of 3 passed."). A script that runs too long is stopped, and the panel stays usable. Scripts run in Chrome; Firefox still refuses them, and chaining rows and checks do the same work there without code.
- A pre-request script's changes to the request are sent; in Chrome they were lost.
- Importing a collection with scripts warns that a script can also send requests of its own to any server. A regex longer than 200 characters is refused, with the reason.
- The script boxes have Snippets: ready-made scripts with a comment that says why (save a token, refresh it when it runs out, check status, time and a field, sign with HMAC, add a request id and time). Typing rf. lists what scripts can use, each with a one-line note.
- Problems show while you type, with their line and column: a text, bracket or comment never closed, a missing await, {{name}} typed inside a script, a secret typed into it, context.response in a pre-request script, and console.log. A Postman pm.* line gets its rf.* twin with Fix, or Fix all Postman lines at once.
- A script that only saves values or checks the answer can become code-free Chaining rows and checks with Convert (with Undo); they run in Firefox too, where scripts can't. "Good habits for scripts" sums up the rest. Post-response scripts can read context.response.duration, in the panel and the CLI.
Command-line tool and CI
- A suite's ··· menu offers Export as CI config again. Its dialog has Download the CLI: requestfox-ci.mjs, one file that ships inside the extension and runs with Node 18 or newer, with nothing to install (node requestfox-ci.mjs assert --config requestfox.config.json).
- The CLI runs a suite the way the panel does: chaining (body, header, cookie, regex), saved auth, path variables, collection and folder settings, checks and scripts. A step whose {{token}} was never saved is not sent, and the run names the step that failed.
- The export keeps typed-in secrets out of the file: a secret-looking value in a body, a query or a header becomes {{env:NAME}}, and the run stops before any request when that variable is not set. The dialog counts the requests, and the pass/fail checks when there are any.
- conform --spec openapi.yaml --session session.json checks a recorded session (the extension's JSON export or a HAR file) against an OpenAPI 3 or Swagger 2 spec with the panel's own check: a line per call, the coverage and the operations not called yet; exit 1 on a mismatch. It has --reporter junit, --min-coverage and --strict, and --spec on assert and diff checks the CLI's own sends the same way.
- "securityHeuristics": true in a CI config adds the side panel's newer security checks; off by default, so a suite that passed before still passes.
- run <folder> runs the suites of a linked workspace folder (or a workspace export) with --suite, --env and --var; a folder with scripts needs --allow-scripts, as they are code. mock --spec api.yaml or mock --session captured.json starts a local mock server on 127.0.0.1.
- mock --workspace <folder or file> answers each saved request's path with its first 2xx example; Prefer: example=<name>, Prefer: code=404 or Postman's x-mock-response-name / x-mock-response-code pick another. conform --spec api.yaml --workspace <folder> checks every example against the spec.
- assert, diff, loadtest and run take --cert and --key (a client certificate), --ca (a private CA), --proxy and --insecure (staging only).
- JUnit results carry each request's time and count errors apart from failures; a step that did not run is marked skipped.
requestfox-ci probe --spec api.yaml --base http://localhost:3000makes test cases from an OpenAPI or Swagger spec and sends them: wrong types, missing required fields, values out of range or outside the enum, extra properties, malformed and oversize bodies must be refused with a 4xx (not accepted, not a 5xx), and an operation that needs sign-in must refuse a call with no or a wrong credential. Valid calls built from the examples must not fail and must match the spec. Each failure shows the request, a curl line and why, with every--headervalue shown as***;--junitand--jsonfor CI. Careful by default: this computer only unless--allow-remote, 5 requests a second, GET, HEAD and OPTIONS only unless--include-unsafe, at most 200 cases, the same cases for the same--seed,--dry-runto see them first.mock --spec:Prefer: dynamic=trueand the new--dynamicnow make a different, schema-valid answer on each call (it used to be the same placeholder every time), and--seedrepeats them.Prefer: example=namewith a name the spec does not have answers a 500 that lists the names it has, as in Prism, instead of quietly sending the default.- YAML specs: a list item written as a flow mapping (
- { name: limit, in: query }) is read as one mapping. It used to become a key named{ name, so the parameters of a spec written that way were lost in the panel's import,conform,mockandprobe.
Look
- A new fox: our own one-colour fox mark, with "Request" and "Fox" in two colours, in the top bar, the Mini popup, the first-run splash, the empty log and the HTML report. The toolbar, DevTools and store icons are the same fox, with a red dot while recording.
- An open card's views (Details, Request, Response) look like tabs; Replay and ··· More are plain actions at the end of the row.
- Form fields have a visible edge and keep their focus ring, and a rule form keeps each field's name above it, filled in or not (the example stays in the empty field).
- A copy says it worked, or that it didn't, where you are looking: the Copy button reads "Copied!", and a refused copy says "Couldn't copy".
- The filter help beside the search is a pick-list: a click on a token adds it to the search.
- Small text is 12 px (it was 11.5 px); tile labels stay at 11 px and may take two lines in a narrow sidebar.
- In dark mode a tile takes its colour only when it has a count or is picked, Total never glows, and Stop has no glow.
- The bell that mutes a top issue and the search box's × are bigger to hit.
- A switched-off rule stays readable; scrollbars are a quiet grey; the advanced Settings groups sit one step in; Performance scrolls as one page, with plain numbers and the same short hints in every language.
- Compose opens on Params, like other API clients.
- A first run has no empty band at the bottom of the panel.
- The strip of top issues wraps instead of scrolling sideways: two chips, then a "+N more" button that shows them all (and "Show less").
- Pill and badge text is a little larger (11 px instead of 10.5 px).
- Arabic uses one arrow, ←, for "then" and "becomes".
- Compare requests wraps long addresses between their parts in a narrow sidebar, instead of cutting a word in two.
- One chevron for every row that opens; icons come in two sizes and spacing follows one scale.
- A binary response is one plain line, not an empty code box.
- The Mini popup says what went wrong in red and the next step in grey, and its clock matches the side panel's.
- The first-run splash is just the fox and the name; the mock and fault form asks for the URL first.
- One delete style: filled red only for Stop and a confirm dialog's delete; every other delete is a quiet red link.
- A suite step has a quiet × to remove it, and its ↑/↓ say which step they move; an empty suite says the one thing to do next.
- A mock rule opens for editing when you click its row, like checks and header rules.
- The collections inside a folder are indented, with a thin guide line, and the environment editor ends with its own edge before the request row.
- Copy as code says in one line when a secret variable stayed as {{name}}.
- A check or header rule is deleted from its open form, like a mock rule, with a quiet "Delete this …" that asks first; the row × is gone. Path variables sit a full step below the query table. The Replay button shows its time like every other duration ("1.2 s").
- The HTML report follows a dark system theme on screen; a printout stays light.
- Empty lists say the one next step, with an example: mock rules, checks, header rules, History and saved searches, the WebSocket filter, OAuth profiles, the Timeline and the endpoint table.
- Badges are sentence case at one weight ("Slow", "Schema changed", "Token expired"), with one icon rule, in every language.
- One corner scale (4, 6, 10 and 14 px, and round) and one spacing scale across the panel, the Mini popup and the HTML report: cards are 10 px, buttons and fields 6 px, dialogs 14 px.
- Compose's answer reads status · time · size on its first line, with the Text/Tree switch at its end; Headers, Timing and View in log wrap as their own row under it.
- The counts above the log are one slim row of segments; when it does not fit (a narrow sidebar, a long language) it wraps three over two.
- Dates ("Yesterday", month names) follow the panel's language.
- Menus and popovers grow from where they open, with one easing curve. Reduced motion still turns all of it off.
- The Auth type list reads as a setting, not a heading; the header dot shows only while viewing a past session (the recording bar has its own).
- The HTML report keeps a readable width on a wide screen, shows times in seconds past 1000 ms, and breaks long URLs between their parts.
Accessibility
- The panel says its language, so a screen reader reads Arabic with an Arabic voice. Panel and dialog names, Settings groups and Performance sections are real headings, and the log is the panel's main area, with a heading.
- A request card's top row is one open/close button, one Tab stop per card, and a jump to a request focuses it. A Top Issues chip is two real buttons; the top-bar buttons folded into More are its menu items; Performance rows work from the keyboard.
- Dialogs: Enter on Cancel cancels and Enter on the confirm answers once; a delete confirm opens with focus on Cancel; a prompt's field is named by its title. Start, Stop, Cancel on a slow send and the tour hint leave focus where you were.
- What changes is said, not only shown: "Copied!" and "Couldn't copy", "Saved." (once), the search's match count, a removed row, the vault unlocking, a Compose tab's mark, and which session History's Report and Compare act on. The JSON check waits until you stop typing, and a field in error says so, tied to the reason.
- New in Settings: "Announce new problems to screen readers" says new failed, slow or flagged calls in one short line, a few at a time (on by default), and "Press / to jump to the search box" can be turned off.
- Arrowing through the environment or language list no longer acts on each item: Enter, or leaving the list, applies it.
- Decorative icons are hidden from screen readers, a timeline bar's colour is said in words too, and top-bar tooltips can be hovered and closed with Esc.
- Windows High Contrast keeps the picked tile, tab and panel icon, the edge of filled buttons, and the focus ring in the JSON tree.
- The Timeline is one Tab stop: Up, Down, Home and End move between its rows, and a redraw while recording keeps the row you are on.
- Text, placeholders, links and badges reach AA contrast: light mode's links get their own deeper blue, the green badge is soft like the others, a muted Top Issues chip is no longer faded, and the edges of fields in Params, Headers, Chaining and the Mini popup reach 3:1.
- Small controls are at least 24 px to hit: JSON tree rows, a body's Tree and Copy, "This tab only", the search box, the tour hint's ×, a card's row and a Top Issues filter.
- Compose's Basic, API Key and Digest fields have visible labels.
- RequestFox's alert on the page is read out by screen readers, opens its request from the keyboard, and its × is named in your language.
- In the HTML report, screenshots open from the keyboard and are described, the page has landmarks, and on a narrow screen each table scrolls on its own instead of the whole page.
- The recording dot pulses three times, then stays still (not at all when the system asks for less motion).
- History's Pin keeps its name and shows a ✓ when the session is pinned; a card's row is read with exactly the words it shows; a recording started from the shortcut or the Mini popup no longer moves focus in the panel.
Small fixes
- Request chaining and post-response scripts read your send's answer as it came, so a token chained out of a login works with "Redact sensitive data" on (it chained "*REDACTED*"); what is stored and exported stays redacted.
- "Rate it" opens RequestFox's own Chrome Web Store page on Chrome; it opened the Firefox page, and on a copy loaded by hand or installed from the Edge store, a "This item is not available" page.
- A panel opened in a language you picked earlier draws its lists in that language straight away; Compose's "No saved requests yet" line stayed English until something redrew it.
- When site access is refused, the message says to choose Allow "when your browser asks" (it said Chrome, on Firefox too), in every language.
- Shortcut descriptions in chrome://extensions/shortcuts follow your language.
- The HTML report says "Recording started on:", like the bug report, not "Target page:".
- A script's rf.sendRequest that the page cannot make fails at once with the reason, instead of after 30 seconds.
- Switching the panel's language redraws Compose's answer too.
- For contributors: the plugin's files now live in one folder, extension/ (Load unpacked → choose it); the sample CI config and test site are in examples/.
- Compose's hint for {{$guid}}, {{$timestamp}} and the other built-in variables showed them with letters missing in Chrome ({{uid}}).
- In Firefox, the note that scripts can't run, and their switch-off, sit on the script boxes again, not on the environment's secrets box.
Privacy
- The page a recording starts on is redacted like every request address: a token in its query or #fragment (a magic link, a reset link, an OAuth callback) no longer reaches a report, a HAR, a bug report or an issue link.
- Exporting settings or a workspace leaves secrets out (the token vault, client secrets, saved credentials), and importing cannot replace them.
- Scripts run in a sandbox that can no longer navigate away.
- One privacy policy, the same in every copy, that matches what the code does. RequestFox asks for one permission fewer (the unused activeTab).
- The OAuth vault hint says what really happens: the passphrase is asked for again after the extension has been idle for a while.
- Send from the extension instead goes only after your click, never with the page's cookies, and its answer is redacted like any captured body; the privacy policy says so.
- A secret variable's value is redacted wherever it shows up in what a send captured: a URL path, any header, a body.
- Redaction also catches S3 presigned signatures, Google API keys (key=), sig=, otp= and an OAuth code= in addresses and form bodies.
- A field that is just a web address is no longer hidden because of its name: GitHub's "authorizations_url" or a sign-in server's "token_endpoint" show as they are. An address that carries a secret (a password, a secret-named query key, a token in its path) is still hidden.
- Redaction also hides a PKCE code_verifier and a private_key, and no longer hides "author". The policy says a site chosen for Send from the extension stays chosen until the browser closes, and the Firefox reviewer notes explain the extension's own send.
- Recent keeps the last 100 sends with their answers on this device, redacted, until you Clear them; a token or password typed in the Auth tab is never kept. The privacy policy, the FAQ and the Firefox reviewer notes say so.
- Past sessions keep their full bodies and screenshots in the browser's own storage on this device; Delete and Clear old sessions remove them.
- The CI export keeps typed-in secrets out of the file, the CLI runs scripts without your computer's environment variables, and a script's test results are stored without the values redaction hid. An export without credentials also leaves out the secrets in collection and folder settings.
- Saved WebSocket messages stay on this device until you forget them; the privacy policy lists them.
2.2.1
- The Help menu's About line shows the version ("RequestFox 2.2.1") in place of the personal signature, which no longer ships in any language.