Configuration reference
Set these options in a config file, such as bundlewatch.config.mjs, and run npx bundlewatch. The command line requires files; every other option has a default. Configuration under the bundlewatch key in package.json is also supported.
Your configuration is merged with the defaults below. Arrays, such as files and ci.trackBranches, replace the default value instead of being merged with it.
| Option | Default | Description |
|---|---|---|
files | None | The files to check and their limits, see files |
prefix | Unset | Application namespace for GitHub statuses, baselines and results, see prefix |
defaultCompression | 'gzip' | Compression for files that don't set their own, see defaultCompression |
normalizeFilenames | null | Removes hashes from file names, see normalizeFilenames |
bundlewatchServiceHost | 'https://service.bundlewatch.io' | The service that stores baselines; null disables it, see bundlewatchServiceHost |
ci | Detected from your CI environment | Token, repository, commit and branches, see ci |
prefix
Requires BundleWatch 1.1.0 or later, for both the CLI and Node API. Use a dependency such as "bundlewatch": "^1.1.0" and ensure CI runs that installed version. Older versions silently ignore prefix: applications share the unprefixed baseline and the bundlewatch GitHub status, so one application's results can overwrite another's. Do not downgrade a prefix-enabled application below 1.1.0.
Use a top-level prefix to run BundleWatch independently for multiple applications in one GitHub repository. It works in config files, package.json, and the Node API (await bundlewatch({ prefix: 'storefront', files: [...] })).
A prefix must be 1–64 lowercase ASCII letters, digits, hyphens or underscores, starting with a letter or digit. For example, storefront, admin, and web_v2 are valid. Empty strings, null, uppercase letters, spaces, slashes, and non-string values are rejected by both the client and service. Values are never trimmed or converted. Omit the option to use the existing bundlewatch status and baselines.
For a monorepo with two frontends, give each application its own config:
// apps/storefront/bundlewatch.config.mjs
export default {
prefix: 'storefront',
files: [{ path: 'dist/assets/*.js', maxSize: '200 kB' }],
}// apps/admin/bundlewatch.config.mjs
export default {
prefix: 'admin',
files: [{ path: 'dist/assets/*.js', maxSize: '200 kB' }],
}Build and run BundleWatch from each application's directory. Paths are relative to the working directory, so both applications can measure dist/assets/app.js without sharing a baseline:
(cd apps/storefront && npm run build && npx bundlewatch)
(cd apps/admin && npm run build && npx bundlewatch)The overall GitHub contexts are storefront/bundlewatch and admin/bundlewatch. Per-file failures use the same prefix, such as storefront/bundlewatch dist/assets/app.js; path shortening, collision suffixes and status limits stay the same. Set branch protection to require the two prefixed contexts if both applications must pass. Results pages display the application name.
Baselines are identified by repository + prefix + branch. Pull requests compare against their own application's base-branch baseline and never save baselines, even when the head branch is tracked. Neither application reads or replaces an unprefixed baseline. Changing or removing a prefix selects a different namespace; it does not move data. Build each application's tracked base branch once to establish its baseline.
Service rollout
Deploy the updated service to every instance first, then upgrade clients and enable prefix. Existing clients and unprefixed configurations keep their current requests, storage keys, and links; no migration is needed.
Before a prefixed client reads or saves a baseline, it sends POST /capabilities and requires { "baselinePrefix": 1 }. Failure, an unreachable endpoint, or a different response stops the run with instructions to upgrade or select a compatible bundlewatchServiceHost. The client never falls back to unprefixed baselines. With the service disabled (bundlewatchServiceHost: null), prefixes still work for GitHub statuses without a capability check.
Prefixed clients use /store/prefixed/lookup and /store/prefixed, and check that responses identify the requested prefix. These dedicated routes prevent an old instance from interpreting a prefixed request as a legacy baseline write during a partial rollout or rollback. Keep the capable service deployed while prefixed clients run; rolling it back can fail their checks. Do not remove a prefix just to bypass the compatibility error, since that selects the legacy namespace.
files
Each entry in files checks the files that match its path:
| Field | Description |
|---|---|
path | Required. A glob, relative to the directory you run BundleWatch in, not to the config file. *, ** and {a,b} work. A path that matches no files fails the run, and so does one that matches a directory: ./dist/* fails as soon as dist contains a folder, so write ./dist/*.js or ./dist/**/*.js. |
maxSize | Optional. The limit for each matching file, not for their total: a file larger than its maxSize fails. Write a string with a unit, such as '100kB', or a number of bytes, such as 102400. |
compression | Optional: 'gzip', 'brotli' or 'none'. Defaults to defaultCompression. Any other non-empty value isn't rejected: the file is then measured uncompressed. |
maxSize values:
- Units are
B,KB,MB,GB,TBandPB. They are case-insensitive and use multiples of 1024, so'100kB'is 102400 bytes. - A string with any other unit is read as a number of bytes:
'100k'and'100KiB'both mean 100 bytes. - When
maxSizeis missing,0(a number, or a string such as'0'or'0kB'), or doesn't start with a number, the file has no size limit.
Growth compared with the baseline never fails the check: only maxSize does. If two entries match the same path, such as ./dist/*.js and ./dist/app.js, the maxSize and compression of the last one apply: put catch-all globs first.
defaultCompression
The compression used to measure files that don't set their own compression: gzip (default), brotli or none. gzip runs at level 9, and brotli at quality 11. Any other value stops the run with [FATAL] config.compression must be a valid type.
Brotli uses Node's built-in zlib; no extra package is needed.
normalizeFilenames
Removes the part of each file name that changes between builds, such as a content hash, so that each file can be compared with its baseline. It changes the names BundleWatch reports and saves, not your files.
Set it to a RegExp, or to a string that BundleWatch turns into one. Write a pattern that matches the whole path, from ^ to $, and capture the part to remove: BundleWatch removes the captured text. A path that the pattern doesn't match keeps its name.
| Path | Pattern | Becomes |
|---|---|---|
./dist/main.3f2a1b9c.js | /^.*(\.[a-f0-9]+)\.\w+$/ | ./dist/main.js |
./dist/assets/index-BdF3x9Qa.js | /^.*?(-[\w-]{8})\.\w+$/ | ./dist/assets/index.js |
In JSON, write the pattern as a string without the slashes, and double each backslash: "normalizeFilenames": "^.*(\\.[a-f0-9]+)\\.\\w+$".
WARNING
Check the result against your real file names. The second pattern also turns ./dist/app-polyfill.js into ./dist/app.js. /^.+?(\..+?)\.\w+$/, an older example, turns ./.next/static/chunks/main-abc123.js into ./.js.
When two files end up with the same name, only the last one is checked and saved, and nothing is logged.
How the pattern is applied
BundleWatch splits the path with the pattern. That gives the text before the match, the text after it, and each captured group. BundleWatch then removes the first occurrence of each of these pieces from the path.
With a pattern that matches the whole path, that removes the captured text. A pattern that matches only part of the path also removes everything outside the match: /-[a-f0-9]+/ turns ./dist/main-abc123.js into -abc123.
bundlewatchServiceHost
The service that stores baselines, creates short results links and renders results pages. Defaults to 'https://service.bundlewatch.io'.
Set it to your own service's URL to use a custom server. Give the full URL, starting with https://, without a trailing slash: BundleWatch appends paths such as /store to it. It can include Basic auth credentials, such as https://user:password@bundlewatch.example.com: BundleWatch sends them in an Authorization header, and leaves them out of results links and log lines.
Set it to null to disable the service: BundleWatch then only checks maxSize, without comparing with or saving a baseline. It prints no results link, the GitHub status has no details link, and the Node.js API returns url: undefined.
ci
The ci options let BundleWatch post GitHub statuses, and compare with and save baselines. Most defaults are detected from your CI environment; set an option only to override it.
| Option | Default |
|---|---|
githubAccessToken | BUNDLEWATCH_GITHUB_TOKEN, then GITHUB_ACCESS_TOKEN |
repoOwner | Detected, e.g. facebook in github.com/facebook/react |
repoName | Detected, e.g. react in github.com/facebook/react |
repoCurrentBranch | Detected |
repoBranchBase | Detected: the branch a pull request is being merged into, or on GitHub Actions the repository's default branch. Falls back to 'master'. |
commitSha | Detected |
trackBranches | ['master', 'main', 'develop'], plus the repository's default branch when it can be detected |
isPullRequest | Detected. Pull request builds compare with a baseline but never save one; see which builds count |
Builds on a branch listed in trackBranches save their results as the baseline for that branch, unless they are pull request builds. See Base and tracked branches.
Environment variables
BundleWatch reads these environment variables. Values in your configuration come first. CI_REPO_OWNER, CI_REPO_NAME, CI_REPO_SLUG, CI_COMMIT_SHA, GIT_COMMIT, CI_BRANCH, GIT_BRANCH and CI_BRANCH_BASE take precedence over what BundleWatch detects from your CI provider. GIT_URL is used only when nothing else gives the repository, and CI_EVENT and CI_TARGET_BRANCH are read for generic CI: when CI is set and no supported provider is detected.
| Variable | Sets |
|---|---|
BUNDLEWATCH_GITHUB_TOKEN, then GITHUB_ACCESS_TOKEN | ci.githubAccessToken |
CI_REPO_OWNER, CI_REPO_NAME | ci.repoOwner, ci.repoName |
CI_REPO_SLUG (owner/name) | ci.repoOwner and ci.repoName, if the variables above are not set |
CI_COMMIT_SHA, then GIT_COMMIT | ci.commitSha |
CI_BRANCH, then GIT_BRANCH | ci.repoCurrentBranch |
CI_BRANCH_BASE | ci.repoBranchBase |
CI_TARGET_BRANCH | ci.repoBranchBase with generic CI, when CI_BRANCH_BASE isn't set |
GITHUB_API_URL | The GitHub API URL that commit statuses are posted to. Defaults to https://api.github.com; GitHub Actions sets it automatically. GitHub Enterprise also needs your own service, see GitHub Enterprise. |
CI_EVENT | pull_request marks a pull request build with generic CI |
GIT_URL | ci.repoOwner and ci.repoName, from a github.com remote such as https://github.com/owner/repo.git or git@github.com:owner/repo.git, if nothing else sets them |
DEBUG (any non-empty value) | Logs the details of failed requests to the BundleWatch service |
NO_COLOR, NODE_DISABLE_COLORS, FORCE_COLOR | Output is colored only in a terminal. NO_COLOR or NODE_DISABLE_COLORS turns colors off. FORCE_COLOR turns them on, for example in a CI log viewer that supports them, unless it is 0 or false; it wins over the other two. |
BundleWatch detects the other values on GitHub Actions, GitLab CI, Travis CI, Drone, CircleCI and several other CI providers. See Supported CI providers.