mrkeyoor.com_
Thu 06 Aug 00:57 UTC
npmUtilsupdated 05 Aug 2026

diff

jsdiff (published on npm as diff) is a JavaScript implementation of the Myers diff algorithm. Give it two strings and it returns an array of change objects saying what was added, removed, or kept, tokenized by character, word, line, sentence, CSS token, or pretty-printed JSON. It also speaks unified diff format: it can create patches, parse them (including Git-style patches with renames and mode changes), apply them to text with configurable fuzz, and reverse them. Zero dependencies, works in Node and the browser, and it is the diff engine behind the red/green output in a lot of test runners and CLI tools.

Verdict

The default answer for text diffing and unified patches in JavaScript, with an API that has earned its 137M weekly downloads. Adopt it freely in apps, but pin the major version and read the changelog before upgrading, because recent majors have not been shy about breaking changes.

API stability3/5Four breaking majors between September 2024 and April 2026 (6, 7, 8, 9) changed tokenization, types, and supported runtimes; the core function names are stable but behavior details move between majors.
Docs4/5One long README documents every function, option, and change-object field with real examples, including patch header edge cases; there is no dedicated docs site or searchable reference.
Maintenance4/5Pushed June 2026 with the 9.0.0 release in April 2026 and only 15 open issues; active but effectively a small-team project without corporate backing.
Ecosystem5/5137.2M weekly downloads and 9.2K stars; it sits under test runners, linters, and CLI tools across the ecosystem, and the change-object format is a de facto standard other tools consume.

Use it if

  • You are building a text comparison UI (code review, document history, admin audit views); change objects map one-to-one to colored spans
  • You need to generate, parse, or apply unified diff patches in JavaScript, including Git patches with rename, copy, and file mode headers
  • You are diffing structured data: diffJson normalizes key order before diffing, and diffArrays takes a custom comparator for object tokens
  • You need to diff on a server without blocking the event loop: callback mode plus the maxEditLength and timeout options let you bound or abort expensive diffs
Skip it if

Setup reality

npm install diff and you are running; zero dependencies, ESM and CommonJS both work, plus a dist/diff.js UMD build exposing a global Diff for script tags. Two traps: since v8 types are bundled, so uninstall @types/diff or the stale types will fight the real ones; and the TypeScript overloads for async/abortable modes only resolve when your options object is statically analyzable, so building options dynamically (options: any = {}) produces confusing type errors the README itself documents.

Patterns

Diff two strings character by characterdiff-characters

import { diffChars } from 'diff';

const changes = diffChars('beep boop', 'beep boob blah');
for (const part of changes) {
  if (part.added) console.log('+', part.value);
  else if (part.removed) console.log('-', part.value);
  else console.log(' ', part.value);
}

Change objects where added and removed are both false are common text. Characters means Unicode code points, so surrogate pairs are not split.

Diff by words, optionally case-insensitivediff-words

import { diffWords } from 'diff';

diffWords('The quick fox', 'the slow fox', { ignoreCase: true });

// better word splitting for non-English text:
const seg = new Intl.Segmenter('zh', { granularity: 'word' });
diffWords(oldZh, newZh, { intlSegmenter: seg });

Whitespace is ignored when computing the diff but preserved in output. The default regex tokenizer is weak for CJK text; pass an Intl.Segmenter with granularity 'word' for those languages.

Diff line by linediff-lines

import { diffLines } from 'diff';

diffLines(oldText, newText, {
  ignoreWhitespace: true,   // trim before comparing lines
  stripTrailingCr: true,    // Windows vs Unix line endings
});

newlineIsToken: true reads better for humans but is worse for patch output; leave it off when feeding results into patch creation. Combining it with ignoreWhitespace gives surprising results on blank lines.

Diff two JSON-serializable objectsdiff-json-objects

import { diffJson } from 'diff';

const changes = diffJson(
  { name: 'Ana', role: 'admin' },
  { role: 'viewer', name: 'Ana' }
);
// only the role line shows as changed

Objects are serialized with alphabetically ordered keys first, so property order never produces false diffs. Use undefinedReplacement to control how undefined values serialize.

Diff arrays of objects with a custom comparatordiff-arrays-custom-equality

import { diffArrays } from 'diff';

const result = diffArrays(oldUsers, newUsers, {
  comparator: (left, right) => left.id === right.id,
});

Default equality is ===, which never matches two distinct objects; without a comparator every object counts as removed plus added.

Create a unified diff patch (like diff -u)create-unified-patch

import { createTwoFilesPatch } from 'diff';

const patch = createTwoFilesPatch(
  'config.old.json', 'config.new.json',
  oldStr, newStr,
  undefined, undefined,
  { context: 3 }
);

Set context to Infinity to include the whole file in one hunk. The headerOptions option controls the Index/underline/filename header lines if a consumer tool is picky about them.

Apply a unified diff patch to textapply-patch

import { applyPatch } from 'diff';

const patched = applyPatch(sourceText, patchString, { fuzzFactor: 2 });
if (patched === false) {
  throw new Error('patch did not apply');
}

Check === false, not falsy: a valid result can be an empty string. fuzzFactor allows mismatched context lines, but deleted lines must exist in the source regardless.

Parse a Git patch and detect renamesparse-git-patch

import { parsePatch } from 'diff';

const patches = parsePatch(gitDiffOutput);
for (const p of patches) {
  if (p.isRename) console.log(`${p.oldFileName} -> ${p.newFileName}`);
  if (p.isBinary) console.log('binary change, no hunks to apply');
}

isGit, isRename, isCopy, isCreate, isDelete, oldMode, and newMode only appear on Git-style patches. Binary patches carry no hunks, so you cannot apply them from the patch text alone.

Give up early on huge or unrelated inputsbound-expensive-diffs

import { diffLines } from 'diff';

const result = diffLines(bigOld, bigNew, {
  maxEditLength: 1000, // or: timeout: 200 (ms)
});
if (result === undefined) {
  // too different; fall back to 'file changed' UI
}

Both options make the function return undefined instead of a diff when the limit is hit; handle that case or you will crash on .forEach of undefined.

Compute a diff without blocking the event loopasync-diff-callback

import { diffLines } from 'diff';

diffLines(oldText, newText, {
  callback: (changes) => {
    render(changes);
  },
});

In callback mode the function returns undefined and delivers the result asynchronously. TypeScript picks the right overload only if the callback is declared inline in the options literal.

Build an undo patchreverse-patch

import { structuredPatch, reversePatch, formatPatch } from 'diff';

const patch = structuredPatch('a.txt', 'a.txt', oldStr, newStr);
const undo = reversePatch(patch);
const undoText = formatPatch(undo);

Reversing Git patches with copy from/copy to headers cannot be done correctly from the patch alone; the output for those will usually be rejected by git apply.

Render a diff as colored HTMLrender-diff-html

import { diffWords } from 'diff';

const html = diffWords(oldStr, newStr)
  .map((part) => {
    const cls = part.added ? 'ins' : part.removed ? 'del' : 'same';
    return `<span class="${cls}">${escapeHtml(part.value)}</span>`;
  })
  .join('');

Escape part.value yourself; jsdiff returns raw text and building innerHTML from user content without escaping is an XSS bug.

Alternatives

PackageRegistryPick it when
diff-match-patchnpmYou want semantic cleanup of noisy character diffs or Google-style fuzzy patch application
fast-diffnpmYou only need raw character-level diffs as fast as possible and no patch tooling
diff-sequencesnpmYou want the bare Myers algorithm over arbitrary sequences (it is what Jest uses) and will build your own output layer
microdiffnpmYou are diffing object and array state, not text