Reading Network Traffic in a DevTools Panel
Show and analyse the inspected page's network requests in a custom DevTools panel: devtools.network.onRequestFinished, HAR entries, getContent for bodies, getHAR for history, filtering, and memory limits.
Table of Contents
- What chrome.devtools.network provides
- Step-by-step: a network analysis panel
- 1. Declare the DevTools page and create a panel
- 2. Listen for finished requests in the panel
- 3. Fetch response bodies on demand
- 4. Clear on navigation and reload history
- 5. Capture before the panel is opened
- 6. Bound memory
- 7. Send data to the service worker only when needed
- 8. Filter and search like the Network panel
- Common mistakes
- Cross-browser variation
- Verification
- FAQ
- Related
A team builds a DevTools extension that shows every GraphQL operation the page makes, decoded and grouped by operation name — something the built-in Network panel can’t do. In MV3 the obvious route, webRequest in the service worker, can’t read response bodies and requires host permissions for every site. The DevTools route can: a DevTools page gets chrome.devtools.network, which reports every request the inspected tab makes in HAR format, including response bodies on demand, with no host permissions at all — but only while DevTools is open on that tab. This guide builds a panel on that API. It belongs to side panel and DevTools interfaces.
What chrome.devtools.network provides
A DevTools extension declares devtools_page in the manifest; that page loads whenever DevTools opens for any tab and can create panels with chrome.devtools.panels.create. Both the DevTools page and its panels can use chrome.devtools.network: onRequestFinished fires with a HAR entry object for each completed request (URL, method, headers, status, timings, sizes), and entry.getContent(callback) retrieves the response body and its encoding. getHAR() returns all requests recorded so far in this DevTools session. onNavigated fires when the inspected page navigates. The API only sees the inspected tab, only while DevTools is open, and only what the Network panel records.
Step-by-step: a network analysis panel
1. Declare the DevTools page and create a panel
1"devtools_page": "devtools.html"
1// devtools.js (loaded by devtools.html)
2chrome.devtools.panels.create("GraphQL", "icons/16.png", "panel.html");
Execution context: the manifest and the DevTools page. The DevTools page is invisible; it exists to create panels and sidebars. No permissions are needed for devtools.network — access is implied by the user opening DevTools on the page. See building a custom DevTools panel.
2. Listen for finished requests in the panel
1// panel.js
2const rows = [];
3chrome.devtools.network.onRequestFinished.addListener((entry) => {
4 const { request, response } = entry;
5 if (request.method !== "POST" || !/\/graphql\b/.test(request.url)) return;
6 const body = request.postData?.text ?? "";
7 let op = "(anonymous)";
8 try { op = JSON.parse(body).operationName ?? op; } catch {}
9 const row = { op, url: request.url, status: response.status, ms: Math.round(entry.time), size: response.bodySize, entry };
10 rows.push(row);
11 appendRow(row);
12});
Execution context: the DevTools panel page. Each HAR entry includes the request’s postData, so the GraphQL operation name is available without fetching the response. entry.time is total duration in milliseconds; response.bodySize is the transferred size (−1 if unknown). Listening in the panel means the listener only runs once the panel has been opened; to capture from DevTools open, listen in the DevTools page and buffer (step 5).
3. Fetch response bodies on demand
1function loadBody(row) {
2 return new Promise((resolve) => {
3 row.entry.getContent((content, encoding) => {
4 const text = encoding === "base64" ? atob(content ?? "") : (content ?? "");
5 resolve(text);
6 });
7 });
8}
9
10table.addEventListener("click", async (e) => {
11 const row = rows[e.target.closest("tr")?.dataset.index];
12 if (!row) return;
13 const text = await loadBody(row);
14 detail.textContent = prettyJson(text);
15});
Execution context: the DevTools panel. getContent is callback-based and returns the body as text, or base64 for binary responses. Fetch bodies only when the user selects a row — eagerly fetching every body on a busy page wastes memory and slows DevTools. Display with textContent (or a safe JSON viewer): response bodies are untrusted page data.
4. Clear on navigation and reload history
1chrome.devtools.network.onNavigated.addListener(() => {
2 if (!preserveLog.checked) { rows.length = 0; table.tBodies[0].replaceChildren(); }
3});
4
5// When the panel opens, backfill from what DevTools already recorded
6chrome.devtools.network.getHAR((har) => {
7 for (const entry of har.entries) handleEntry(entry);
8});
Execution context: the DevTools panel. Mirroring the Network panel’s “Preserve log” checkbox gives users a familiar control. getHAR returns entries recorded before the panel was opened, so opening the panel late does not lose earlier traffic. HAR entries from getHAR also support getContent in current Chrome versions.
5. Capture before the panel is opened
1// devtools.js — runs as soon as DevTools opens
2const buffer = [];
3chrome.devtools.network.onRequestFinished.addListener((e) => { buffer.push(e); if (buffer.length > 500) buffer.shift(); });
4chrome.devtools.panels.create("GraphQL", "icons/16.png", "panel.html", (panel) => {
5 panel.onShown.addListener((win) => win.receiveBuffer?.(buffer.splice(0)));
6});
Execution context: the DevTools page. The panel’s window only exists once the user selects the panel. Buffering in the DevTools page (with a cap) and handing the buffer over in onShown captures traffic from the moment DevTools opens. The panel window object is passed to onShown, and the panel can expose a function on window to receive data.
6. Bound memory
Keep at most a few thousand rows, drop the entry reference for old rows, and never store response bodies unless the user asks. DevTools itself holds the network log; your panel should hold only what it displays.
7. Send data to the service worker only when needed
If the panel should save a capture or share it with other extension UI, send selected entries to the service worker with chrome.runtime.sendMessage or a port, identifying the tab with chrome.devtools.inspectedWindow.tabId. See connecting a DevTools panel to the service worker.
8. Filter and search like the Network panel
1const filterInput = document.querySelector("#filter");
2filterInput.addEventListener("input", () => {
3 const q = filterInput.value.trim().toLowerCase();
4 for (const tr of table.tBodies[0].rows) {
5 const row = rows[tr.dataset.index];
6 tr.hidden = Boolean(q) && !(`${row.op} ${row.url} ${row.status}`.toLowerCase().includes(q));
7 }
8});
Execution context: the DevTools panel. Developers expect a filter box that narrows the table as they type, matching operation names, URLs and status codes. Hiding rows is fine here because the table is bounded; for very long captures, filter the rows array and re-render instead. Add quick toggles for common cases — “errors only” (status ≥ 400), “slow” (over 500 ms) — which are what people look for most often.
Common mistakes
- Expecting
webRequestto read bodies. It cannot in MV3. - Fetching every body eagerly. Slows DevTools; fetch on selection.
- Listening only in the panel. Misses traffic before the panel is opened.
- Rendering bodies with
innerHTML. Page data is untrusted. - Unbounded row arrays. Memory grows on long sessions.
Cross-browser variation
- Chrome / Edge:
devtools.networkwithonRequestFinished,getHAR,getContent,onNavigated. - Firefox: supports
devtools.network.onRequestFinished,getHARandonNavigated;getContentreturns a promise of[content, mimeType]. - Safari: Web Inspector extensions support
devtools.networkin recent versions; check API coverage for your target version.
Verification
- Open DevTools on a page that uses GraphQL, open the panel, and confirm operations appear with names.
- Open DevTools, trigger requests, then open the panel — confirm earlier requests appear.
- Click a row and confirm the body renders as text.
- Navigate with Preserve log off and confirm the table clears.
FAQ
Can the panel see requests when DevTools is closed?
No. The API exists only while DevTools is open on the tab.
Can I modify requests from the panel?
No. devtools.network is read-only; use DNR rules to modify requests.
Does it see service worker or worker requests?
It sees what the Network panel shows for the inspected target, which includes requests from the page’s dedicated workers in current Chrome.
Can I export the captured requests?
Yes. Build a HAR object from the selected entries — or use getHAR for everything — and download it from the panel with an object URL. Strip cookies and authorisation headers before export, since HAR files often end up in bug reports.
Does reading traffic slow the inspected page?
Listening to onRequestFinished has negligible cost because DevTools already records the traffic. Fetching many bodies or doing heavy parsing in the panel can make DevTools itself slower, so keep work on demand.
Related
- Building a custom DevTools panel — panel basics.
- Observing network requests with webRequest in MV3 — the always-on alternative.
- Connecting a DevTools panel to the service worker — sending data out.
- Side panel and DevTools interfaces — the parent topic.