Skip to content

JS API

ConventionalChangelog is a chainable builder: you configure where commits, tags, the version and repository come from, then generate the changelog with write() or writeStream(). It powers the CLI and is what you reach for when building your own release tooling.

import { ConventionalChangelog } from 'conventional-changelog'

Read the version from package.json, load a preset, and print the changelog:

import { ConventionalChangelog } from 'conventional-changelog'
const generator = new ConventionalChangelog()
.readPackage()
.loadPreset('conventionalcommits')
for await (const chunk of generator.write()) {
process.stdout.write(chunk)
}

This prints the same Markdown as the CLI — see the CLI examples for what the output looks like.

new ConventionalChangelog(cwdOrGitClient?: string | ConventionalGitClient)

Pass a working directory (defaults to process.cwd()), or a ConventionalGitClient instance to reuse an existing client or point at another repository:

import { ConventionalChangelog } from 'conventional-changelog'
import { ConventionalGitClient } from '@conventional-changelog/git-client'
const generator = new ConventionalChangelog(new ConventionalGitClient('/path/to/repo'))

All methods return this for chaining, except write() and writeStream(), which generate the output.

MethodPurpose
loadPreset(preset, loader?)Load parsing and formatting rules from a preset. preset is a name string, or { name, ...options } to configure it.
config(config)Set the preset config object ({ tags, commits, parser, writer }) directly.
readPackage(path?, transform?)Find and read package.json for the version and repository (falls back to git remote origin for the repository).
package(pkg)Set package data directly.
readRepository()Read the repository URL from git remote origin.
repository(infoOrGitUrl)Set repository info or a git URL directly.
tags(params)Configure how semver tags are read.
commits(params, parserOptions?)Configure the commit range and parser options.
options(options)Generator options — release count, append, loggers, etc.
context(context)Extra changelog writer context template variables.
writer(params)Changelog Writer options — templates, grouping, sorting.
write(includeDetails?)Generate the changelog (async generator).
writeStream(includeDetails?)Generate the changelog as a Node.js stream.

loadPreset(preset, loader?) resolves a preset by name (conventionalcommits, angular, …) and imports it. Pass a string for the defaults, or an object { name, ...options } to forward options to the preset. The optional second argument is a custom module loader — pass one when a bundler needs an explicit import:

generator.loadPreset('conventionalcommits')
// Configure the preset by passing its options alongside the name
generator.loadPreset({ name: 'conventionalcommits', preMajor: true })
// With a custom loader (e.g. inside a bundled tool)
generator.loadPreset('conventionalcommits', name => import(name))

Use config() to skip the package lookup and provide the preset config yourself:

generator.config({
writer: {
// writer options…
}
})

readPackage() provides both the version and the repository. It reads the nearest package.json (walking up from the working directory, or from an explicit path), taking the version from its version field and the commit/version links from its repository field. When package.json has no repository, it falls back to the git remote origin URL:

generator.readPackage() // nearest package.json
generator.readPackage('./package.json') // or an explicit path

Use readRepository() or repository() only when you set the package data yourself (which skips that fallback), or want to force reading the URL from git:

generator
.package({ version: '1.2.0' }) // no repository field
.readRepository() // read it from `git remote origin`
generator.repository('https://github.com/acme/app.git') // or set it directly

tags() controls which git tags count as releases; commits() controls the commit range and parsing:

generator
.tags({
prefix: 'v', // only tags like v1.2.0
skipUnstable: true // ignore x.x.x-rc.1, etc.
})
.commits({
path: 'packages/api', // only commits touching this directory
from: 'v1.0.0', // range start (tag or SHA)
to: 'HEAD' // range end
})

See GetSemverTagsParams and GetCommitsParams for all fields.

Passed to options():

OptionTypeDefaultDescription
resetbooleanfalseReset the changelog instead of extending it.
appendbooleanfalseAppend newer releases below older ones.
releaseCountnumber1How many releases to generate, counting from the latest. 0 generates all.
outputUnreleasedbooleanautoEmit unreleased commits; the version becomes Unreleased.
transformCommit(commit, params) => Partial<Commit> | null | Promise<…>Transform each parsed commit before it is written.
warn(source, messages) => voidLogger for warnings.
debug(source, messages) => voidLogger for debug messages.
formatDate(date) => stringCustom date formatter for release headings.
generator.options({
releaseCount: 0,
formatDate: date => new Date(date).toISOString().slice(0, 10)
})

write() returns an async generator that yields one chunk per release; writeStream() wraps the same output in a Node.js Readable:

import { createWriteStream } from 'node:fs'
// (1) Async iteration
for await (const section of generator.write()) {
console.log(section)
}
// (2) Node.js stream
generator
.writeStream()
.pipe(createWriteStream('CHANGELOG.md'))

Generating per-package release notes in a monorepo — the pattern used by release tooling such as simple-release:

import { ConventionalChangelog } from 'conventional-changelog'
import { ConventionalGitClient } from '@conventional-changelog/git-client'
const gitClient = new ConventionalGitClient(projectPath)
const changelog = new ConventionalChangelog(gitClient)
.loadPreset('conventionalcommits')
.commits({ path: projectPath }) // only commits under this package
.tags({ prefix: tagPrefix }) // e.g. "my-pkg-v"
.readRepository() // link commits and versions to the host
.context({ version: nextVersion }) // the version about to be released
.writer({ preamblePartial }) // optional preamble for the release section
for await (const section of changelog.write()) {
// append `section` to the package's CHANGELOG.md
}

For monorepos that tag releases as name@1.2.0, build the prefix with the exported packagePrefix helper:

import { ConventionalChangelog, packagePrefix } from 'conventional-changelog'
const generator = new ConventionalChangelog()
.readPackage()
.loadPreset('conventionalcommits')
.tags({
prefix: packagePrefix('my-package') // matches my-package@1.2.0 tags
})

Passing true to write() (or writeStream()) yields structured objects instead of Markdown strings — useful for building custom output:

FieldTypeDescription
logstringRendered Markdown for one release.
keyCommitCommit | nullThe commit identifying the release, or null.
for await (const { log, keyCommit } of generator.write(true)) {
console.log(keyCommit?.version, log.length)
}

Alongside the class, the package exports a few utilities:

ExportSignatureDescription
packagePrefix(name: string) => RegExpBuild a tag prefix matcher for Lerna-style name@version tags.
guessNextTag(previousTag: string, version?: string) => stringGuess the next tag name, preserving a v prefix.
isUnreleasedVersion(semverTags: string[], version?: string) => booleanWhether the given version matches the latest tag (i.e. nothing new is released).
defaultCommitTransform(commit, params) => Partial<Commit>The default commit transform (formats dates, extracts the version from tags).