cheat sheet
diff2html-cli
Convert unified diffs and git diff output into styled HTML side-by-side or line-by-line diff views from the command line.
diff2html-cli — Beautiful HTML Diffs from the Terminal
What it is
diff2html-cli is an npm command-line wrapper around the diff2html JavaScript library that converts unified diff text into styled HTML reports. It reads a diff from stdin or generates one from git diff internally, and outputs a self-contained HTML file with syntax highlighting, side-by-side or line-by-line layout, and a file summary header. The library is maintained as part of the rtfpessoa/diff2html project. The primary alternative is git diff --stat + a browser-based viewer, but diff2html-cli produces a shareable, self-contained HTML artifact with no server needed.
Reach for it when you need a polished diff you can email, attach to a ticket, or post as a CI artifact — anywhere a Markdown code block or a screenshot of git diff would be inadequate. For interactive terminal viewing, delta (Rust, syntax-highlighted pager) is a better fit; for structural source-level diffs (refactor-aware), difft wins. diff2html-cli sits in the niche of "shareable, self-contained, no-server HTML."
Install
# Global install (recommended for CLI use)
npm install -g diff2html-cli
# Local install (project-scoped)
npm install --save-dev diff2html-cli
npx diff2html --version
Output:
5.x.x
Verify the binary
which diff2html
diff2html --help | head -5
Output:
/usr/local/bin/diff2html
Usage: diff2html [options] -- [extra diff args]
Options:
-V, --version output the version number
Run via npx (no global install)
npx --yes diff2html-cli -i command -s side -o preview -- HEAD~1
Output: (none — browser opens with the rendered diff)
Syntax
diff2html reads a unified diff from stdin and writes HTML to stdout (or a file). When invoked with -s git it calls git diff internally and pipes the result through automatically.
diff2html [OPTIONS] -- [EXTRA GIT DIFF ARGS]
Output: (none — exits 0 on success, writes HTML to stdout or a file)
Essential options
| Option | Short | Description |
|---|---|---|
--input stdin | -i stdin | Read diff from stdin (default) |
--input file | -i file | Read diff from a file (-f path) |
--input command | -i command | Read diff from a shell command (-c "cmd") |
--output stdout | -o stdout | Write HTML to stdout (default) |
--output preview | -o preview | Open in the default browser |
--output file | -o file | Write to a file (-f path) |
--style line | -s line | Line-by-line layout (default) |
--style side | -s side | Side-by-side layout |
--no-merges | Exclude merge commits | |
--summary closed | Show file summary collapsed | |
--summary open | Show file summary expanded (default) | |
--summary hidden | Hide file summary | |
--title <text> | -t | Set the HTML page title |
--highlightCode | Enable syntax highlighting | |
--synchronisedScroll | Sync scroll between side-by-side panes | |
--matching lines | Align changed lines in side-by-side view | |
--matchWordsThreshold <n> | Word-level diff threshold (0.0–1.0, default 0.25) | |
--maxLineSizeInBlockForComparison <n> | Skip line-level diff for long lines | |
--colorScheme github | Color scheme (github or vs) | |
--version | -v | Print version |
--help | -h | Show help |
Line-by-line vs side-by-side output
The default line-by-line view stacks the old and new versions vertically, one change per row. Side-by-side (-s side) places the old version on the left pane and the new version on the right pane, making structural differences easier to read at a glance.
# Default: line-by-line
git diff HEAD~1 | diff2html -i stdin -o stdout > report.html
# Side-by-side
git diff HEAD~1 | diff2html -i stdin -s side -o stdout > report.html
Output: (none — HTML written to report.html)
# Open immediately in the browser
git diff HEAD~1 | diff2html -s side -o preview
Output: (none — browser opens with the diff rendered)
Reading from git
When using -i command, diff2html runs git diff internally. You can pass extra git flags after -- to control what gets diffed.
# Diff against the previous commit
diff2html -i command -- HEAD~1
# Diff a specific file
diff2html -i command -- HEAD~1 -- src/auth.js
# Diff two branches
diff2html -i command -- main..feature/login
# Staged changes only
diff2html -i command -- --cached
# Diff with context lines
diff2html -i command -s side -- HEAD~1 -U10
Output: (none — HTML written to stdout or browser)
# Full git log diff for a commit range, written to a file
diff2html -i command -o file -f changes.html -- main..HEAD
Output:
(changes.html created with all commits between main and HEAD)
File output and embedding
Writing to a file produces a self-contained HTML document with inline CSS and JavaScript that can be attached to tickets, emailed, or archived. Use --highlightCode to add language-aware syntax coloring.
# Styled, self-contained report
git diff HEAD~3 | diff2html \
-i stdin \
-s side \
-o file \
-f diff-report.html \
--highlightCode \
--title "Release diff v1.2 → v1.3"
Output:
diff-report.html written (self-contained, no external deps)
# Embed as a fragment (stdout, no <html> wrapper)
# diff2html does not have a native fragment mode;
# pipe the output through sed to strip the outer shell if needed.
git diff HEAD~1 | diff2html -i stdin -o stdout | \
sed -n '/<div id="diff">/,/<\/body>/p' > fragment.html
Output: (none — fragment written to fragment.html)
Syntax highlighting and matching
--highlightCode enables per-language syntax highlighting using highlight.js bundled inside diff2html. --matching lines aligns changed lines within each hunk so that moved code is visually paired across the side-by-side panes.
git diff HEAD~1 | diff2html \
-s side \
--highlightCode \
--matching lines \
--synchronisedScroll \
-o preview
Output: (none — browser opens with syntax-highlighted, scrollsync diff)
# Adjust word-matching sensitivity (default 0.25 = 25% words must match)
git diff HEAD~1 | diff2html -s side --matchWordsThreshold 0.5 -o preview
Output: (none — browser opens with stricter word-level alignment)
Color schemes
diff2html-cli ships with github (light) and vs (dark) color schemes. Both produce self-contained HTML; there is no runtime theme toggle — choose one at generation time.
# GitHub light scheme (default)
git diff HEAD~1 | diff2html -s side --colorScheme github -o file -f light.html
# VS dark scheme
git diff HEAD~1 | diff2html -s side --colorScheme vs -o file -f dark.html
Output: (none — HTML files written)
Output formats: HTML, JSON, and JSON-as-data
The CLI's primary output is HTML, but the underlying diff2html library produces a JSON intermediate representation. You can capture that JSON for programmatic post-processing (counting added/removed lines per file, feeding a custom renderer, etc.) by using the library directly rather than the CLI:
# The CLI only emits HTML or HTML-with-stripped-shell.
# For structured JSON, drop down to the library:
npm install diff2html
Output:
added 1 package in 1s
// diff-to-json.js
import { parse } from "diff2html";
import { readFileSync } from "node:fs";
const rawDiff = readFileSync(0, "utf8"); // stdin
const json = parse(rawDiff); // array of FileDiff objects
console.log(JSON.stringify(json, null, 2));
git diff HEAD~1 | node diff-to-json.js > diff.json
cat diff.json | jq '.[].newName' | head -3
Output:
"src/auth.js"
"src/router.js"
"src/index.js"
The JSON shape contains per-file metadata (oldName, newName, isBinary, addedLines, deletedLines) and per-hunk line-by-line entries — useful for building custom dashboards, reviewer notes, or LLM-friendly summaries.
Parsing existing diff files
Any unified diff produced by git, diff -u, hg diff, or a tool that emits the same format can be piped in. There's nothing git-specific about the input handler beyond the optional -i command shortcut.
# Parse a saved patch file
diff2html -i file -f my-patch.diff -s side -o preview
Output: (none — browser opens with the rendered patch)
# Apply a remote PR locally and render
gh pr diff 123 > pr-123.diff
diff2html -i file -f pr-123.diff -s side --highlightCode -o file -f pr-123.html
Output:
pr-123.html written
# Patch from `patch -p1`-style workflows
diff -u file.old file.new | diff2html -i stdin -o preview
Output: (none — browser opens with the file comparison)
Programmatic library use (no CLI)
For build pipelines that already run Node, skipping the CLI saves a subprocess and gives you full control over the HTML template.
npm install diff2html diff2html-cli
Output:
added 2 packages in 2s
// render-diff.js
import { html as diffHtml } from "diff2html";
import { execSync } from "node:child_process";
import { writeFileSync } from "node:fs";
const rawDiff = execSync("git diff HEAD~1", { encoding: "utf8" });
const body = diffHtml(rawDiff, {
drawFileList: true,
matching: "lines",
outputFormat: "side-by-side", // or 'line-by-line'
renderNothingWhenEmpty: false,
});
const page = `<!doctype html>
<html><head>
<meta charset="utf-8" />
<title>Diff report</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" />
</head><body>${body}</body></html>`;
writeFileSync("custom-diff.html", page);
console.log("custom-diff.html written");
Output:
custom-diff.html written
diff2html-cli vs git diff, delta, and difft
Four very different tools that all visualise diffs — the right pick depends on whether you want a terminal pager, a refactor-aware diff, or a shareable artifact.
| Tool | Output | Strength | Weakness |
|---|---|---|---|
git diff (built-in) | ANSI terminal | Always available; scriptable | No syntax highlight; flat colors |
delta (dandavison) | ANSI terminal | Syntax-highlighted, side-by-side in TTY | Terminal only; no shareable output |
difft (Wilfred) | ANSI terminal | Tree-aware, understands moved blocks | Per-language parsers; slower; not a drop-in for git diff |
diff2html-cli | Self-contained HTML | Email/ticket/CI-attachable; pretty | Heavyweight (browser needed); not interactive |
A common pattern: use delta for daily local review, then run diff2html-cli only when you need to attach the diff to a ticket or share it with a non-developer.
# Configure delta as git's default pager
git config --global core.pager delta
git config --global interactive.diffFilter "delta --color-only"
Output: (none — exits 0 on success)
Common pitfalls
- No diff input → empty report — if
git diffproduces no output (e.g. working tree is clean),diff2htmlwrites an HTML file with "No changes" — this is expected, not an error. -i commandrequires git in$PATH— the tool callsgitinternally; ensuregitis available in the environment where the command runs (relevant in Docker containers).- Binary files —
diff2htmlcannot render binary file diffs; git outputsBinary files a/… and b/… differwhich is passed through as plain text. - Large diffs — very large diffs (thousands of files, tens of thousands of lines) produce multi-megabyte HTML that is slow to open in a browser; use
-- <path>to scope the diff to relevant files. -fwithout-o file— the-f <filename>flag only takes effect when combined with-o file; without it,-fis silently ignored.--no-mergesonly with-i command—--no-mergesis passed togit log/git diffinternally; it has no effect when reading from stdin.- CRLF line endings — diffs generated on Windows with CRLF may render the line-ending change as a noisy whole-file diff. Configure
git config core.autocrlfor strip\rbefore piping in:git diff | tr -d '\r' | diff2html …. - Whitespace-only diffs render as huge changes — pass
-w/--ignore-all-spacethrough to git:diff2html -i command -- HEAD~1 -w. - Output path not auto-created —
-fwrites to the exact path you specify. If the parent directory doesn't exist the command fails withENOENT. Wrap withmkdir -pin scripts.
Integrating with npm scripts
Add diff2html to package.json scripts so team members can generate reports with a consistent invocation.
{
"scripts": {
"diff:preview": "git diff HEAD~1 | diff2html -s side -o preview",
"diff:report": "git diff main..HEAD | diff2html -s side --highlightCode -o file -f dist/diff-report.html"
}
}
Output: (none — JSON config; run with npm run diff:preview)
npm run diff:report
Output:
> diff2html -s side --highlightCode -o file -f dist/diff-report.html
dist/diff-report.html written
Real-world recipes
PR diff report for code review
Generate a side-by-side diff report for a GitHub pull request branch to attach to a review.
git fetch origin feature/login
git diff origin/main...origin/feature/login | diff2html \
-s side \
--highlightCode \
--title "PR: feature/login vs main" \
-o file \
-f pr-feature-login.html
Output:
pr-feature-login.html written
CI artifact: diff since last tag
Produce a self-contained HTML diff artifact on every release build.
LAST_TAG=$(git describe --tags --abbrev=0 HEAD~1)
git diff "$LAST_TAG"..HEAD | diff2html \
-s side \
--highlightCode \
--title "Changes since $LAST_TAG" \
-o file \
-f artifacts/release-diff.html
echo "Diff report: artifacts/release-diff.html"
Output:
Diff report: artifacts/release-diff.html
Quick preview of staged changes
Before committing, visually inspect what is staged in a side-by-side browser view.
git diff --cached | diff2html -s side -o preview
Output: (none — browser opens immediately)
Diff two arbitrary files
Use the Unix diff command to generate a unified diff, then pipe to diff2html.
diff -u config.old.json config.new.json | diff2html -i stdin -s side -o preview
Output: (none — browser opens with the file comparison)
Blameworthy commit visualizer
Show the diff that a specific commit introduced, with the commit SHA and message in the title.
SHA=$(git rev-parse HEAD~3)
SUBJECT=$(git log -1 --pretty=%s "$SHA")
git show --no-merges "$SHA" | diff2html \
-i stdin \
-s side \
--highlightCode \
--title "$SHA: $SUBJECT" \
-o file \
-f "diffs/$SHA.html"
echo "Wrote diffs/$SHA.html"
Output:
Wrote diffs/c93883e.html
Multi-commit batch report
Iterate over the last N commits and produce one HTML file per commit, ready to drop into an index.html listing.
mkdir -p commit-diffs
for SHA in $(git log -10 --no-merges --pretty=%H); do
git show "$SHA" | diff2html \
-i stdin -s side --highlightCode \
--title "$SHA" -o file -f "commit-diffs/$SHA.html"
done
ls commit-diffs/ | head -3
Output:
0a1b2c3.html
1d2e3f4.html
2f3a4b5.html
CI workflow (GitHub Actions)
Upload the diff report as a build artifact every time a PR pushes.
# .github/workflows/diff-report.yml
name: Diff report
on:
pull_request:
branches: [main]
jobs:
diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: npm install -g diff2html-cli
- name: Generate report
run: |
mkdir -p artifacts
git diff origin/main...HEAD | diff2html \
-s side --highlightCode \
--title "PR ${{ github.event.pull_request.number }}" \
-o file -f artifacts/pr-diff.html
- uses: actions/upload-artifact@v4
with:
name: pr-diff
path: artifacts/pr-diff.html
Output: (none — workflow file; commits and pushes trigger the workflow)
Server hot-reload of staged changes
Inside a chokidar watcher, regenerate the report each time the index changes. Useful in pre-commit review tools.
// watch-diff.js
import chokidar from "chokidar";
import { execSync } from "node:child_process";
function rebuild() {
execSync(
"git diff --cached | diff2html -s side --highlightCode -o file -f .diff-preview.html",
{ stdio: "inherit" }
);
console.log("Updated .diff-preview.html");
}
chokidar.watch(".git/index").on("change", rebuild);
rebuild();
Output:
Updated .diff-preview.html
Strip the outer shell for embedding
When embedding the diff inside an existing page (e.g. a Markdown-based PR review tool), use the library and only render the body fragment.
import { html as diffHtml } from "diff2html";
import { execSync } from "node:child_process";
const fragment = diffHtml(execSync("git diff HEAD~1", { encoding: "utf8" }), {
outputFormat: "side-by-side",
drawFileList: false,
});
// `fragment` is just <div class="d2h-wrapper">…</div> — drop it anywhere
console.log(fragment.length, "chars of HTML");
Output:
27432 chars of HTML