mrkeyoor.com_
Sat 08 Aug 17:42 UTC
npmUtilsupdated 08 Aug 2026

turndown

Turndown is a JavaScript converter that walks HTML and emits Markdown. It accepts an HTML string or an existing Element, Document, or DocumentFragment, applies built-in CommonMark-oriented rules, and lets you tune heading, list, fence, emphasis, and link styles. Custom rules can replace selected elements, keep them as raw HTML, or remove them with their contents. GitHub-flavored tables, strikethrough, and task-list behavior live in the separate turndown-plugin-gfm package. It is a one-way structural conversion utility, not an HTML sanitizer, browser scraper, Markdown renderer, or round-trip document format.

Verdict

Turndown is still the straightforward choice for controllable, mostly conventional HTML-to-Markdown conversion. Do not mistake it for a sanitizer or a fidelity-preserving document converter, and budget for the GFM and TypeScript companion packages when needed.

API stability5/5The constructor, turndown method, option names, addRule, keep, remove, and use plugin contract have stayed compact and recognizable across the 7.x line. Rule filters and replacement callbacks are plain JavaScript rather than a shifting class hierarchy. The old project name to-markdown is clearly historical. Custom prototype overrides such as escape reach below the normal extension API and carry more upgrade risk, but ordinary conversion code is very stable.
Docs4/5The README specifies every output option and default, accepts both HTML and DOM examples, explains plugins, shows custom filters and replacements, and documents the exact rule-precedence order. It is candid that escaping can be aggressive. The weak spots are operational: there is no dedicated security section, little malformed-HTML guidance, no TypeScript setup, and few realistic nested-list or table edge cases beyond the external GFM plugin.
Maintenance4/5The repository is not archived, was pushed on 2026-06-23, and has 11,379 stars with 149 open issues and pull requests. npm identifies 7.2.4 as current and its Node requirement has moved to 18, indicating modern runtime maintenance. The sizable issue queue reflects the long tail of HTML conversion cases, but the core surface is narrow and recent repository activity lowers the risk of adopting an abandoned converter.
Ecosystem4/5Turndown recorded 6,909,465 npm downloads for the week ending 2026-08-06, runs in Node and browsers, and has an official-style companion plugin for GitHub-flavored Markdown features. CommonJS and distributed browser builds cover older integration shapes. Its ecosystem is purposefully smaller than unified's syntax-tree pipeline, and TypeScript types live outside the package, but the simple rule/plugin model has broad practical adoption.

Use it if

  • You need predictable HTML-to-Markdown conversion in Node or a browser
  • You want to add element-specific conversion rules without building a full syntax-tree pipeline
  • You need to preserve selected unknown elements as inline HTML while converting the rest
  • You want configurable CommonMark-style output and can add the GFM plugin for tables or strikethrough
Skip it if

Setup reality

Version 7.2.4 requires Node 18 or newer and has one declared runtime dependency, @mixmark-io/domino, which supplies DOM behavior for HTML strings in Node. In a browser you can pass a live DOM node or use the distributed browser build, but converting a string and converting the browser's parsed DOM can differ around malformed markup because an HTML parser repairs it first. The default output is not the flavor many repositories expect: headings use setext style, bullet lists use *, code blocks are indented, and links are inline. Configure atx headings and fenced code explicitly if that is your house style. Core Turndown does not include GitHub-flavored tables, strikethrough, or task-list handling; install turndown-plugin-gfm and call use(gfm). The package does not advertise bundled TypeScript definitions, so strict TypeScript projects need @types/turndown or a local declaration. Treat conversion as lossy. Classes, ids, data attributes, layout containers, and unrecognized markup usually disappear while text survives. keep preserves chosen elements as raw HTML, while remove deletes both the element and its contents. Rule precedence is easy to misread: blank nodes win first, then added rules, built-in CommonMark rules, keep rules, remove rules, and the default fallback. That means keep('a') does not override the standard link rule; add an explicit rule if you need that. Input HTML is not made safe just because the output is Markdown. Sanitize untrusted HTML before conversion, and sanitize or constrain any later Markdown-to-HTML rendering too. Finally, snapshot-test real documents. Whitespace collapsing, nested lists, mixed inline markup, malformed tables, and the documented aggressive escaping are where apparently simple migrations change content.

Patterns

Convert an HTML stringconvert-html-string

const TurndownService = require('turndown');

const service = new TurndownService();
const markdown = service.turndown('<h1>Hello</h1><p>Welcome.</p>');

The conversion is structural and lossy. HTML attributes and layout containers generally do not survive unless a rule preserves them.

Convert an existing browser nodeconvert-dom-node

import TurndownService from 'turndown';

const article = document.querySelector('article');
const markdown = new TurndownService().turndown(article);

The input may be an Element, Document, or DocumentFragment. Check for a null query result before calling turndown.

Emit repository-friendly Markdownconfigure-output-style

const service = new TurndownService({
  headingStyle: 'atx',
  bulletListMarker: '-',
  codeBlockStyle: 'fenced',
  fence: '~~~',
  emDelimiter: '*',
});

const markdown = service.turndown(html);

Defaults use setext headings, * bullets, indented code blocks, and _ emphasis, so set team conventions explicitly.

Add GitHub-flavored Markdown rulesenable-gfm

const TurndownService = require('turndown');
const { gfm } = require('turndown-plugin-gfm');

const service = new TurndownService();
service.use(gfm);
const markdown = service.turndown(html);

turndown-plugin-gfm is a separate install. Core Turndown does not convert GFM tables and strikethrough by itself.

Convert highlighted text with a custom ruleadd-custom-rule

service.addRule('highlight', {
  filter: 'mark',
  replacement(content) {
    return '==' + content + '==';
  },
});

Added rules take precedence over built-in CommonMark, keep, remove, and default rules, but the special blank rule still wins.

Match elements using attributesfilter-by-attribute

service.addRule('mention', {
  filter(node) {
    return node.nodeName === 'A' && node.hasAttribute('data-user');
  },
  replacement(content, node) {
    return '@' + node.getAttribute('data-user');
  },
});

Function filters receive DOM nodes. Validate attribute content if generated Markdown is consumed by another trusted system.

Preserve selected tags as HTMLkeep-raw-html

service.keep(['sub', 'sup']);
const markdown = service.turndown('<p>H<sub>2</sub>O and x<sup>2</sup></p>');

Built-in CommonMark and added rules outrank keep filters. Keeping a normally handled tag requires an explicit added rule.

Drop unwanted elements and contentsremove-elements

service.remove(['script', 'style', 'nav']);
const markdown = service.turndown(html);

remove deletes the element and all of its contents. It is not a substitute for HTML sanitization because higher-priority rules and later rendering still matter.

Emit referenced linksuse-reference-links

const service = new TurndownService({
  linkStyle: 'referenced',
  linkReferenceStyle: 'collapsed',
});

const markdown = service.turndown(html);

Reference definitions are appended to the output. Snapshot repeated and nested links because reference labels depend on conversion order.

Mark unrecognized blocks explicitlycustomize-unknown-elements

const service = new TurndownService({
  defaultReplacement(content, node) {
    const block = /^(DIV|SECTION|ASIDE)$/.test(node.nodeName);
    return block ? '\n\n' + content + '\n\n' : content;
  },
});

The default rule only runs after added, built-in, keep, and remove rules. It is useful for diagnostics but cannot restore attributes already discarded.

Package house rules as a plugincompose-plugin

function editorialRules(service) {
  service.remove(['script', 'style']);
  service.addRule('line-break', {
    filter: 'br',
    replacement: () => '  \n',
  });
}

service.use(editorialRules);

A plugin is a function called with the service. Pass an array to use when ordering several plugin bundles matters.

Alternatives

PackageRegistryPick it when
node-html-markdownnpmYou want a newer converter focused on speed, TypeScript support, and batch Node workloads
rehype-remarknpmYou already use unified syntax trees and need plugins to inspect or transform structure between HTML and Markdown
html-to-mdnpmYou want a smaller direct converter and its narrower option set matches your documents