# chrome-har-capturer: turning a headless Chrome run into a HAR file

> chrome-har-capturer is an MIT licensed Node module that drives a headless Chrome through the DevTools protocol and emits HAR files, either from a list of URLs on the command line or from raw protocol events. It is a good fit for reproducible web performance baselines, and a poor fit if you need browser support beyond Chromium.

**cyrus-and/chrome-har-capturer** — Capture HAR files from a Chrome instance

- Repository: https://github.com/cyrus-and/chrome-har-capturer
- Stars: 571 · Forks: 91
- Language: JavaScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/cyrus-and-chrome-har-capturer

## A HAR file assembled from DevTools protocol events

chrome-har-capturer does one thing: it takes a browser that is loading pages and writes down what the browser did, in the HTTP Archive format. The format is the same one you get from the network panel in Chrome DevTools, which means the output can be opened in a HAR viewer or fed to a HAR analyzer.

The mechanism underneath is chrome-remote-interface, a client for the Chrome DevTools Protocol. The module connects to a Chrome that has been started with a remote debugging port, subscribes to the network and page events, and reassembles them into HAR pages, entries, and timings. The README points to the HAR 1.2 specification, a viewer, and an analyzer as the resources for reading the result.

That dependency choice explains the module's shape. It does not reimplement a browser or a network stack. It is a protocol client plus a format converter, and it leaves the browser to do the loading. The cost of that design is that the module is tied to Chromium and to the debugging port. The benefit is that what you capture is what Chrome actually did, timings included, rather than a simulation.

## Installing the module and starting Chrome with a debugging port

The package is published on NPM under the same name as the repository.

```bash
npm install chrome-har-capturer
```

The browser side needs a Chrome that is listening for DevTools connections. The README gives the invocation directly.

```bash
google-chrome --remote-debugging-port=9222 --headless
```

The port matters, because 9222 is the default the library assumes. The API documentation lists host defaulting to localhost and port defaulting to 9222, and the command line utility exposes the same pair as the --host and --port options. If you start Chrome on another port you must tell the tool about it.

The repository layout is small: a bin directory holding the command line entry point, a lib directory, index.js at the root, and a test directory. The npm scripts run eslint as a pretest and then mocha with a ten second timeout, and the dev dependencies include har-validator, which suggests the generated HAR output is checked against the specification by the test suite.

## The command line utility works from a list of URLs

The command line utility generates HAR files from a list of URLs, and the README documents its options as a plain list. The capture-related ones are the interesting ones:

```bash
    -o, --output <file>      write to file instead of stdout
    -c, --content            also capture the requests body
    -k, --cache              allow caching
    -b, --block <URL>        URL pattern (*) to block (can be repeated)
    -H, --header <header>    Additional headers (can be repeated)
    -i, --insecure           ignore certificate errors
    -g, --grace <ms>         time to wait after the load event
    -u, --timeout <ms>       time to wait before giving up with a URL
    -d, --post-data <bytes>  maximum POST data size to be returned
```

Two of these deserve a note. The --block option takes a URL pattern with a wildcard and can be repeated, which is how you keep third party analytics out of a measurement. The --insecure switch ignores certificate errors, which is convenient against staging hosts with self signed certificates and dangerous as a default, since it also hides real certificate problems.

Caching is off unless you pass --cache, which matters if you want the second load of a page to look like a first load. The --grace and --timeout options cover the two different kinds of waiting: a fixed pause after the load event, and a ceiling on a single URL before it is abandoned.

## Retries, parallel loading, and the warning attached to parallel mode

Failure handling is configurable in three dimensions, and the interaction between them is where the interesting behaviour lives.

```bash
    -r, --retry <number>     number of retries on page load failure
    -e, --retry-delay <ms>   time to wait before starting a new attempt
    -f, --abort-on-failure   stop after the first failure (incompatible with parallel mode)
    -l, --parallel <n>       load <n> URLs in parallel
```

A retry count with a retry delay is straightforward. The abort on failure flag is not, and the README states plainly that it is incompatible with parallel mode. That is a design consequence rather than a limitation of implementation: in parallel mode several URLs are in flight at once, so there is no single first failure to abort on.

The API documentation carries a warning on the parallel option that says it may spoil time-based metrics. Read literally, that means the HAR you get from a parallel run has timings that do not reflect a real user loading those pages one after another, since the browser was fetching them all at once and competing for the same connections. Parallel mode is for throughput of the capture itself, not for measurement of load time.

There is one more subtlety in the API. The parallel option is described as a boolean that loads the URLs in parallel, while the command line takes a number n. Treat the two as different shapes for the same idea, and check the exact option name you are using.

## run() returns an event emitter with load, done, fail, and har

For custom work there is a small API, and the README points at the command line source as a working example. The entry point is run(urls, options), which starts loading a batch of URLs and returns an event emitter.

The four events describe the life of a capture. The load event fires when Chrome is about to load a URL and carries the URL, its index in the input array, and the array itself. The done event fires when Chrome finished loading a URL. The fail event carries the URL, an Error object with the failure reason, the index, and the array, and the README states that failed URLs will not appear in the resulting HAR object. The har event fires once every URL has been processed and hands over the finished HAR object. If all the URLs fail, the module still returns a valid empty HAR object rather than nothing.

```js
function (url, index, urls) {}
```

Two hooks let you act around each page load. preHook runs before a page loads and postHook runs after the page load event, and both receive the url, the CDP client instance, the index, and the urls array. If postHook resolves to a value, that value is written into the resulting HAR as the _user key of that URL's page object, which is a neat place to stash a correlation id or a build number next to the measurements.

## fromLog() rebuilds a HAR from a raw event log you already have

The second half of the API is more interesting than a wrapper would suggest. fromLog(url, log, options) takes an array of raw Chrome DevTools Protocol events and returns a Promise for a single page HAR, and it works on events you collected yourself, for example from chrome-remote-interface.

```js
{
    method: '...',
    params: {...}
}
```

The list of required events is long and specific: Page.domContentEventFired, Page.loadEventFired, Network.requestWillBeSent, Network.dataReceived, Network.responseReceived, Network.resourceChangedPriority, Network.loadingFinished, and Network.loadingFailed. WebSocket traffic adds five more, covering the handshake, the close, and frames in both directions.

Request bodies need extra care, and the README explains why. Chrome does not deliver body content through events, so you have to request it yourself with the Network.getResponseBody method and append the reply to the log.

```js
{
    method: 'Network.getResponseBody',
    params: {
        requestId: '...',
        body: '...',
        base64Encoded: true/false
    }
}
```

The content option must be set to true for those synthetic events to be expected. This is the difference between using the module as a command line tool and using it as a format library: in the tool case the module asks Chrome for the bodies itself, while in the fromLog case the caller has already gathered them.

## Version 0.14.4 on npm with no GitHub releases

The package metadata records version 0.14.4 and names Andrea Cardaci as the author, with MIT as the licence. The MIT licence is permissive, so using the module inside a closed source product carries no disclosure obligation beyond keeping the licence text with the distributed code.

The default branch is master, and the last push to the repository was on 2026-10-19. The repository has no GitHub releases at all, which means there is no published changelog on the releases page and no tag to point a user at a known good state. The version number in package.json is the only version marker available.

The dependency set is worth noting for upgrade cost. The module depends on chrome-remote-interface at ^0.31.2, commander at 2.x.x, and chalk at 1.x.x. Commander is pinned to the 2.x line and chalk to the 1.x line, both of which are old major versions, so a consumer on a modern toolchain inherits whatever those majors do about argument parsing and terminal colours. On the test side, har-validator 5.x.x checks the produced HAR against the specification and http2 3.x.x suggests the suite exercises HTTP/2 behaviour, which is where HAR timing fields get interesting.

## Conclusion

chrome-har-capturer suits anyone who needs repeatable HAR captures from a Chromium instance, especially for performance baselines where the same URLs are replayed over time, and it fits a scripted pipeline that already has a Chrome binary and can expose a debugging port. It is the wrong choice for a project that needs to capture from a browser the user is already using, or that must record request bodies without the extra Network.getResponseBody work. Before installing it, run google-chrome --remote-debugging-port=9222 --headless, confirm the module connects to that port, and note that the package is at version 0.14.4 with no GitHub releases published, MIT licensed, and last pushed on 2026-10-19.

## FAQ

### How do I capture a HAR file with chrome-har-capturer?

Install the module with npm install chrome-har-capturer, start Chrome with google-chrome --remote-debugging-port=9222 --headless, then use the command line utility to load your URLs. The --output option writes the HAR to a file instead of stdout.

### Does chrome-har-capturer capture request and response bodies?

Yes, but only when the content option is enabled, with --content on the command line or content: true in the API. Bodies are not delivered by Chrome as events, so the module requests them with the Network.getResponseBody method, and in fromLog() you must supply those replies yourself.

### Is it safe to use the parallel option for performance measurements?

The README warns that the parallel option may spoil time-based metrics, because several URLs load at the same time. Use it for capture throughput, and load URLs one at a time when the HAR timings are the thing you care about.

## Sources

- [cyrus-and/chrome-har-capturer on GitHub](https://github.com/cyrus-and/chrome-har-capturer)
- [Issues](https://github.com/cyrus-and/chrome-har-capturer/issues)
- [License: MIT](https://github.com/cyrus-and/chrome-har-capturer/blob/master/LICENSE)
- [README](https://github.com/cyrus-and/chrome-har-capturer/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/cyrus-and-chrome-har-capturer
