Skip to main content
Rules define how HTML elements are converted to Markdown. Turndown includes built-in CommonMark rules and allows you to add custom rules to handle additional elements or override default behavior.

What Are Rules?

A rule is a JavaScript object with two properties:
  • filter - Selects which HTML elements the rule applies to
  • replacement - A function that converts the element to Markdown

Basic Rule Structure

{
  filter: 'p',
  replacement: function (content) {
    return '\n\n' + content + '\n\n'
  }
}
This rule selects <p> elements and wraps their content with blank lines.

Adding Custom Rules

Use the addRule() method to add a custom rule:
turndownService.addRule('strikethrough', {
  filter: ['del', 's', 'strike'],
  replacement: function (content) {
    return '~' + content + '~'
  }
})
key
string
required
A unique identifier for the rule. Used for reference and debugging.
rule
object
required
An object with filter and replacement properties.
addRule() returns the TurndownService instance, allowing you to chain multiple calls.

The Filter Property

The filter determines which elements the rule applies to. It can be a string, array, or function.

String Filter

Matches elements by tag name (case-insensitive):
filter: 'p'  // Matches <p> elements

Array Filter

Matches any of the specified tag names:
filter: ['em', 'i']  // Matches <em> or <i> elements

Function Filter

Provides complete control over element matching:
filter: function (node, options) {
  return (
    options.linkStyle === 'inlined' &&
    node.nodeName === 'A' &&
    node.getAttribute('href')
  )
}
The function receives:
  • node - The DOM node being evaluated
  • options - The TurndownService options object
Return true to apply the rule to the node.
Tag names in the filter must be lowercase, even if the HTML uses uppercase tags.

The Replacement Function

The replacement function converts the matched element to Markdown. Function signature:
function (content, node, options)
Parameters:
  • content - The Markdown content of the element’s children
  • node - The DOM node being converted
  • options - The TurndownService options object
Returns: A Markdown string

Replacement Examples

Here are real examples from Turndown’s built-in CommonMark rules:
{
  filter: ['em', 'i'],
  
  replacement: function (content, node, options) {
    if (!content.trim()) return ''
    return options.emDelimiter + content + options.emDelimiter
  }
}
Converts <em>text</em> to _text_ (or *text* depending on options).
{
  filter: ['strong', 'b'],
  
  replacement: function (content, node, options) {
    if (!content.trim()) return ''
    return options.strongDelimiter + content + options.strongDelimiter
  }
}
Converts <strong>text</strong> to **text**.
{
  filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'],
  
  replacement: function (content, node, options) {
    var hLevel = Number(node.nodeName.charAt(1))
    
    if (options.headingStyle === 'setext' && hLevel < 3) {
      var underline = repeat((hLevel === 1 ? '=' : '-'), content.length)
      return (
        '\n\n' + content + '\n' + underline + '\n\n'
      )
    } else {
      return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n'
    }
  }
}
Converts headings to either setext or atx style based on options.
{
  filter: 'blockquote',
  
  replacement: function (content) {
    content = trimNewlines(content).replace(/^/gm, '> ')
    return '\n\n' + content + '\n\n'
  }
}
Prefixes each line with > to create a blockquote.
{
  filter: function (node) {
    var hasSiblings = node.previousSibling || node.nextSibling
    var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings
    
    return node.nodeName === 'CODE' && !isCodeBlock
  },
  
  replacement: function (content) {
    if (!content) return ''
    content = content.replace(/\r?\n|\r/g, ' ')
    
    var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? ' ' : ''
    var delimiter = '`'
    var matches = content.match(/`+/gm) || []
    while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`'
    
    return delimiter + extraSpace + content + extraSpace + delimiter
  }
}
Handles backtick escaping and edge cases for inline code.

Rule Precedence

When converting an element, Turndown selects the first matching rule in this order:
  1. Blank rule - Elements containing only whitespace
  2. Added rules - Custom rules added with addRule()
  3. CommonMark rules - Built-in rules for standard Markdown elements
  4. Keep rules - Elements marked with keep()
  5. Remove rules - Elements marked with remove()
  6. Default rule - Fallback for unrecognized elements
Rules added with addRule() take precedence over CommonMark rules, allowing you to override default behavior.

Special Rules

Turndown has several special built-in rules:

Blank Rule

Handles elements that contain only whitespace (except <a>, <td>, <th>, and void elements). Customize with the blankReplacement option:
var turndownService = new TurndownService({
  blankReplacement: function (content, node) {
    return node.isBlock ? '\n\n' : ''
  }
})

Keep Rules

Elements marked with keep() are rendered as HTML in the Markdown output:
turndownService.keep(['del', 'ins'])
turndownService.turndown('<p>Hello <del>world</del><ins>World</ins></p>')
// Result: 'Hello <del>world</del><ins>World</ins>'
Customize with the keepReplacement option:
var turndownService = new TurndownService({
  keepReplacement: function (content, node) {
    return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML
  }
})

Remove Rules

Elements marked with remove() are completely removed from output:
turndownService.remove('del')
turndownService.turndown('<p>Hello <del>world</del><ins>World</ins></p>')
// Result: 'Hello World'

Default Rule

Handles elements not matched by any other rule. Outputs the element’s text content. Customize with the defaultReplacement option:
var turndownService = new TurndownService({
  defaultReplacement: function (content, node) {
    return node.isBlock ? '\n\n' + content + '\n\n' : content
  }
})

Advanced Examples

Handling Custom HTML Elements

turndownService.addRule('customAlert', {
  filter: function (node) {
    return node.nodeName === 'DIV' && 
           node.classList.contains('alert')
  },
  replacement: function (content, node) {
    var type = node.getAttribute('data-type') || 'info'
    return '\n\n> [!' + type.toUpperCase() + ']\n> ' + 
           content.replace(/\n/g, '\n> ') + '\n\n'
  }
})

Preserving Data Attributes

turndownService.addRule('abbr', {
  filter: 'abbr',
  replacement: function (content, node) {
    var title = node.getAttribute('title')
    return title ? content + ' (' + title + ')' : content
  }
})

Conditional Conversion

turndownService.addRule('spoiler', {
  filter: function (node) {
    return node.classList && node.classList.contains('spoiler')
  },
  replacement: function (content) {
    return '||' + content + '||'
  }
})

Build docs developers (and LLMs) love