Bumpy is configured via .bumpy/_config.json, created by bumpy init. Per-package config can also be set in each package.json under the "bumpy" key.
Tip: The config file supports JSONC — you can use
//line comments,/* */block comments, and trailing commas.
| Option | Type | Default | Description |
|---|---|---|---|
baseBranch |
string |
"main" |
Branch used for release comparisons |
access |
"public" | "restricted" |
"public" |
Default npm publish access level |
changelog |
false | string | [string, options] |
"default" |
Changelog formatter — "default", "github", path to a custom formatter, or false to disable |
fixed |
string[][] |
[] |
Package groups that always bump together to the same version |
linked |
string[][] |
[] |
Package groups that share the highest bump level |
ignore |
string[] |
[] |
Package name globs to exclude from versioning |
include |
string[] |
[] |
Package name globs to explicitly include (overrides ignore and privatePackages) |
privatePackages |
{ version, tag } |
{ version: false, tag: false } |
Whether to version and/or create git tags for private packages |
updateInternalDependencies |
"patch" | "minor" | "out-of-range" |
"out-of-range" |
When to update internal dependency version ranges |
dependencyBumpRules |
object |
see below | Controls how bumps propagate through dependency types |
versionCommitMessage |
string |
— | Customize the version commit message (see below) |
changedFilePatterns |
string[] |
["**"] |
Glob patterns to filter which changed files count toward marking a package as changed |
ignoredPackageJsonFields |
string[] |
["devDependencies"] |
package.json fields whose change alone doesn't require a bump file (see below) |
publish |
object |
see below | Publishing pipeline config |
gitUser |
{ name, email } |
bumpy-bot | Git identity for CI commits |
versionPr |
{ title, branch, preamble } |
see below | Customize the version PR |
allowCustomCommands |
boolean | string[] |
false |
Allow per-package custom commands from package.json (see below) |
packages |
object |
{} |
Per-package config overrides (keyed by package name) |
channels |
object |
{} |
Prerelease channels, keyed by channel name (see below) |
A package is "changed" (and so needs a bump file) when a changed file inside it matches changedFilePatterns. package.json is a special case: editing it shouldn't always demand a release — a devDependencies bump from Dependabot, for example, doesn't affect what consumers install.
So when package.json is the only changed file in a package, bumpy diffs it against the base branch and only flags the package if a field outside ignoredPackageJsonFields changed. The default ignore list is ["devDependencies"], meaning dev-only dependency updates don't require a bump file. Every other field — dependencies, exports, bin, files, description, scripts, etc. — still counts.
One exception keeps this safe: a changed devDependencies entry that matches the package's releaseTriggeringDevDeps does flag the package, since such a dep affects the published output.
To relax additional fields (e.g. treat scripts changes as non-releasing too), extend the list:
{
"ignoredPackageJsonFields": ["devDependencies", "scripts"]
}bumpy errs toward requiring a bump file whenever it can't compare cleanly — a brand-new package.json, or one it can't parse.
Controls how a version bump in one package propagates to packages that depend on it. Set globally in dependencyBumpRules or per-package.
Each rule has:
trigger— minimum bump level that triggers propagation (major,minor, orpatch)bumpAs— what level to bump the dependent (major,minor,patch, ormatchto mirror the triggering level)
Set a dependency type to false to disable propagation entirely.
Defaults:
| Dependency type | Trigger | Bump as |
|---|---|---|
dependencies |
patch |
patch |
peerDependencies |
major |
match |
devDependencies |
— | disabled |
optionalDependencies |
minor |
patch |
See version-propagation.md for the full propagation algorithm.
Customize the commit message used when versioning — both by bumpy version --commit and CI commands. Omit to use the default ("Version packages" + list of releases).
"My release"— static commit message string"./scripts/commit-msg.ts"— path to a module (starts with./or../) that exports a function receiving the release plan and returning a message string
To auto-commit locally, pass the --commit flag: bumpy version --commit. CI commands always commit and push automatically.
The publish object controls how packages are packed and published:
| Option | Type | Default | Description |
|---|---|---|---|
packManager |
string |
"auto" |
Which package manager packs tarballs ("auto" detects from lockfile) |
publishManager |
string |
"npm" |
Which tool runs publish (npm supports OIDC/provenance) |
publishArgs |
string[] |
[] |
Extra args passed to the publish command |
protocolResolution |
"pack" | "in-place" |
"pack" |
How workspace: and catalog: protocols are resolved |
provenance |
boolean |
false |
Attach provenance attestation via npm (requires OIDC CI environment) |
npmStaged |
boolean |
false |
Use npm stage publish — requires 2FA approval on npmjs.com |
When npmStaged is enabled, bumpy uses npm stage publish instead of npm publish. This stages packages on npmjs.com, where they must be manually approved with 2FA before going live. This adds an extra security gate to your release process — even if CI credentials are compromised, packages can't be published without maintainer approval.
Requirements:
publishManagermust be"npm"(the default)- npm >= 11.15.0
- The package must already exist on the npm registry (first publish cannot be staged)
{
"publish": {
"provenance": true,
"npmStaged": true
}
}The versionPr object customizes the PR that bumpy ci release creates:
| Option | Type | Default | Description |
|---|---|---|---|
title |
string |
"🐸 Versioned release" |
PR title |
branch |
string |
"bumpy/version-packages" |
Branch name for the version PR |
preamble |
string |
— | HTML content prepended to the PR body |
The channels object maps long-lived branches to prerelease lines. See prereleases.md for the full workflow.
Channel names become .bumpy/<name>/ subdirectories (holding bump files that shipped on the channel), so they must be filesystem-safe and can't start with _ or collide with reserved entries.
Per-package settings can be defined in two places:
- In
.bumpy/_config.jsonunder thepackageskey (keyed by package name) - In each package's
package.jsonunder the"bumpy"key
package.json settings take precedence over global config.
| Option | Type | Description |
|---|---|---|
managed |
boolean |
Opt this package in or out of versioning |
access |
"public" | "restricted" |
Override the global access level |
publishCommand |
string | string[] |
Custom command(s) to publish this package (replaces npm publish) |
buildCommand |
string |
Command to run before publishing |
registry |
string |
Custom npm registry URL |
skipNpmPublish |
boolean |
Don't publish to npm (still creates git tags) |
checkPublished |
string |
Custom command that outputs the currently published version |
changedFilePatterns |
string[] |
Glob patterns for changed-file detection (replaces root setting, not merged) |
dependencyBumpRules |
object |
Per-package override for dependency propagation rules |
cascadeTo |
object |
Explicit cascade targets — glob pattern mapped to { trigger, bumpAs } |
cascadeFrom |
object |
Explicit cascade sources — glob pattern mapped to { trigger, bumpAs } |
releaseTriggeringDevDeps |
string[] |
devDependencies that affect published output — a change requires a release (see below) |
The publishCommand, buildCommand, and checkPublished fields run shell commands during publishing. Because these execute with CI credentials, bumpy distinguishes between two trust levels:
- Root config (
.bumpy/_config.json→packages): always trusted — repo admins control this file. - Per-package config (
package.json→"bumpy"): requires opt-in viaallowCustomCommandsin the root config.
By default, custom commands defined in package.json are ignored with a warning. To enable them, set allowCustomCommands in .bumpy/_config.json:
{
"allowCustomCommands": true
}Or restrict to specific packages/globs:
{
"allowCustomCommands": ["@myorg/vscode-extension", "@myorg/deploy-*"]
}This prevents a contributor from introducing arbitrary shell commands via a package's package.json without the root config explicitly allowing it.
In .bumpy/_config.json (recommended — no allowCustomCommands needed):
{
"packages": {
"my-vscode-extension": {
"publishCommand": "vsce publish",
"skipNpmPublish": true
}
}
}Or in the package's package.json (requires allowCustomCommands):
{
"name": "my-vscode-extension",
"bumpy": {
"publishCommand": "vsce publish",
"skipNpmPublish": true
}
}{
"name": "@myorg/core",
"bumpy": {
"cascadeTo": ["@myorg/plugin-*", "@myorg/cli"]
}
}Or with custom trigger/bumpAs:
{
"name": "@myorg/core",
"bumpy": {
"cascadeTo": {
"@myorg/plugin-*": { "trigger": "minor", "bumpAs": "patch" }
}
}
}By default a devDependencies change doesn't require a release — it's usually dev tooling (a linter, a type package, a test runner). But sometimes a dependency that affects your published output lives under devDependencies. releaseTriggeringDevDeps marks those, so a change to one requires a release (and, for internal workspace deps, its own releases cascade to you):
{
"name": "@myorg/astro-integration",
"bumpy": {
"releaseTriggeringDevDeps": ["@myorg/vite-integration", "nanoid"]
}
}The usual reason is bundling. A build step (tsup, tsdown, esbuild, rolldown/rollup, Vite, bun build, webpack, …) inlines imports into dist/, so consumers get a self-contained artifact. A bundled dependency isn't installed from the registry at consume time — its code is copied into your output — so it's conventionally declared under devDependencies (you don't want consumers to also install it). Taken to the extreme, a fully-bundled package can have no runtime dependencies at all; every library it imports sits in devDependencies. (bumpy itself is built this way with tsdown.) Other cases that fit: a dependency whose output you commit and ship (codegen), or a re-exported types-only package.
releaseTriggeringDevDeps declares intent — "a change to this dep changes what I publish" — and that drives two behaviors:
- Propagation — when the dep gets its own release, this package is cascaded a patch bump (shorthand for a
cascadeFromrule of{ "trigger": "patch", "bumpAs": "patch" }). This only applies to internal workspace deps, since bumpy only releases packages in your workspace. - Change detection — when the dep's version is edited in this package's
package.json(e.g. a Dependabot PR, or a manual bump), this package is flagged as changed and needs a bump file — even thoughdevDependenciesedits are normally ignored (see Change detection). This applies to any listed dep, internal or external.
So for an internal workspace dep, both paths fire; for an external dep (e.g. a published npm package you inline), only change detection applies — listing it is still useful, and is a harmless no-op for propagation.
This is exactly the knob for "which devDependencies affect my published output." An internal workspace package listed under devDependencies is, by default, treated as dev-only — its releases don't cascade (dependencyBumpRules.devDependencies is false) and bumping its range doesn't flag you. Add it to releaseTriggeringDevDeps to flip both: now its releases republish you, and editing its range flags you. Leave it out and it stays dev-only. No global setting is involved — it's per-dependency, per-consumer.
If you re-export the dependency's API and want proportional bumps (a minor in the dep → a minor here), use cascadeFrom directly instead — an explicit cascadeFrom rule for the same source takes precedence over the releaseTriggeringDevDeps patch default:
{
"name": "@myorg/astro-integration",
"bumpy": {
"cascadeFrom": { "@myorg/vite-integration": { "trigger": "patch", "bumpAs": "match" } }
}
}Set changelog in config to control how changelog entries are generated. Built-in options are "default" and "github", or you can provide a path to a custom formatter module. Set to false to disable changelog generation entirely.
See the Changelog Formatters docs for full details and examples.
{
"baseBranch": "main",
"access": "public",
"changelog": "github",
"fixed": [["@myorg/core", "@myorg/types"]],
"ignore": ["@myorg/internal-tools"],
"privatePackages": { "version": true, "tag": false },
"dependencyBumpRules": {
"peerDependencies": { "trigger": "minor", "bumpAs": "match" }
},
"publish": {
"provenance": true,
"npmStaged": true
},
"packages": {
"@myorg/vscode-extension": {
"publishCommand": "vsce publish",
"skipNpmPublish": true
}
},
"allowCustomCommands": ["@myorg/deploy-*"]
}
{ "channels": { "next": { "branch": "next", // required — branch that triggers this channel "preid": "rc", // version suffix (default: channel name) "tag": "next", // npm dist-tag (default: channel name) "versionPr": { "title": "🐸 Versioned release (next)", // default: "<base title> (<name>)" "branch": "bumpy/version-packages-next", // default: "<base branch>-<name>" "automerge": false, // enable auto-merge on the release PR }, }, }, }