Introduction
conventional-changelog-writer renders already-parsed commits into changelog text. It’s the final stage of the pipeline — parser → filter → writer — and the engine behind conventional-changelog. What the output looks like is controlled entirely by the options you pass; presets are ready-made option sets.
Quick start
Section titled “Quick start”-
Install
conventional-changelog-writer:pnpm add conventional-changelog-writernpm i conventional-changelog-writeryarn add conventional-changelog-writer -
Provide parsed commits. The writer consumes parsed commit objects, like those produced by
conventional-commits-parser:const commits = [{type: 'feat',scope: 'api',subject: 'add async write() generator',header: 'feat(api): add async write() generator',hash: '0f7e2c1a9d3e4b5c6f7089abcdef0123456789ab',notes: [],references: []},{type: 'fix',scope: 'cli',subject: 'resolve config path relative to cwd',header: 'fix(cli): resolve config path relative to cwd',hash: 'a3b9d8472e1f0c9b8a7d6e5f4c3b2a1908f7e6d5',notes: [],references: []}] -
Render the changelog.
writeChangelogString(commits, context?, options?)returns it as a string; thecontextsupplies the version and repository:import { writeChangelogString } from 'conventional-changelog-writer'const changelog = await writeChangelogString(commits, {version: '1.2.0',repoUrl: 'https://github.com/acme/app'})console.log(changelog)Out of the box the writer groups commits by their raw type and prints each commit’s full header — deliberately minimal, because formatting is meant to come from options:
CHANGELOG.md ## 1.2.0 (2026-07-01)### feat* feat(api): add async write() generator ([0f7e2c1](https://github.com/acme/app/commits/0f7e2c1))### fix* fix(cli): resolve config path relative to cwd ([a3b9d84](https://github.com/acme/app/commits/a3b9d84)) -
Format it with a preset. A preset’s
writeroptions map types to sections, format links, and choose a template. Pass them as the third argument:import { writeChangelogString } from 'conventional-changelog-writer'import createPreset from 'conventional-changelog-conventionalcommits'const preset = createPreset()const changelog = await writeChangelogString(commits, {version: '1.2.0',repoUrl: 'https://github.com/acme/app'}, preset.writer)CHANGELOG.md ## 1.2.0 (2026-07-01)### Features* **api:** add async write() generator ([0f7e2c1](https://github.com/acme/app/commit/0f7e2c1a9d3e4b5c6f7089abcdef0123456789ab))### Bug Fixes* **cli:** resolve config path relative to cwd ([a3b9d84](https://github.com/acme/app/commit/a3b9d8472e1f0c9b8a7d6e5f4c3b2a1908f7e6d5))
See the JS API for every option — template, groupBy, transform, sorting, and more — when you want to shape the output yourself.
Streaming
Section titled “Streaming”For large histories you can stream commits instead of buffering them. writeChangelog returns an async-generator function, and writeChangelogStream a Node.js Transform:
import { writeChangelog, writeChangelogStream } from 'conventional-changelog-writer'import { pipeline } from 'node:stream/promises'
// Async iterableawait pipeline( commitsStream, writeChangelog(context, options), async function* (changelog) { for await (const chunk of changelog) { process.stdout.write(chunk) } })
// Node.js streamcommitsStream .pipe(writeChangelogStream(context, options)) .pipe(process.stdout)The CLI wraps these to render a changelog from a stream of commits on the command line.