Skip to main content

Installation

Install Turndown from npm:
npm install turndown

Basic Usage

Require Turndown and create an instance:
var TurndownService = require('turndown')

var turndownService = new TurndownService()
var markdown = turndownService.turndown('<h1>Hello world!</h1>')

console.log(markdown) // # Hello world!

CommonJS vs ES Modules

Turndown provides different module formats to suit your project:
// Default CommonJS entry point
var TurndownService = require('turndown')

var turndownService = new TurndownService()
var markdown = turndownService.turndown('<p>Hello world</p>')
The main entry point is lib/turndown.cjs.js.

Server-Side HTML Parsing with Domino

Turndown includes @mixmark-io/domino as a dependency for server-side DOM manipulation. This allows you to parse HTML strings and work with DOM nodes in Node.js:
var TurndownService = require('turndown')

var turndownService = new TurndownService()

// Parse HTML string
var html = `
  <article>
    <h1>Article Title</h1>
    <p>This is the <strong>article content</strong>.</p>
    <ul>
      <li>Point one</li>
      <li>Point two</li>
    </ul>
  </article>
`

var markdown = turndownService.turndown(html)
console.log(markdown)
Output:
Article Title
=============

This is the **article content**.

*   Point one
*   Point two
The domino dependency is automatically excluded in browser builds via the browser field in package.json.

Integration Patterns

Express.js Middleware

Create a middleware to convert HTML responses to Markdown:
const express = require('express')
const TurndownService = require('turndown')

const app = express()
const turndownService = new TurndownService()

app.get('/api/markdown', (req, res) => {
  const html = req.query.html
  
  if (!html) {
    return res.status(400).json({ error: 'HTML parameter required' })
  }
  
  try {
    const markdown = turndownService.turndown(html)
    res.json({ markdown })
  } catch (error) {
    res.status(500).json({ error: error.message })
  }
})

app.listen(3000)

Processing Files

Convert HTML files to Markdown:
const fs = require('fs')
const TurndownService = require('turndown')

const turndownService = new TurndownService({
  headingStyle: 'atx',
  codeBlockStyle: 'fenced'
})

// Read HTML file
const html = fs.readFileSync('input.html', 'utf8')

// Convert to Markdown
const markdown = turndownService.turndown(html)

// Write Markdown file
fs.writeFileSync('output.md', markdown)

console.log('Conversion complete!')

Batch Processing

Process multiple HTML files:
const fs = require('fs').promises
const path = require('path')
const TurndownService = require('turndown')

const turndownService = new TurndownService()

async function convertDirectory(inputDir, outputDir) {
  const files = await fs.readdir(inputDir)
  
  for (const file of files) {
    if (path.extname(file) === '.html') {
      const inputPath = path.join(inputDir, file)
      const outputPath = path.join(outputDir, file.replace('.html', '.md'))
      
      const html = await fs.readFile(inputPath, 'utf8')
      const markdown = turndownService.turndown(html)
      
      await fs.writeFile(outputPath, markdown)
      console.log(`Converted: ${file}`)
    }
  }
}

convertDirectory('./html', './markdown')
  .then(() => console.log('All files converted'))
  .catch(error => console.error('Error:', error))

Streaming Large Files

For very large HTML files, you might want to process them in chunks:
const fs = require('fs')
const TurndownService = require('turndown')

const turndownService = new TurndownService()

function convertLargeFile(inputPath, outputPath) {
  return new Promise((resolve, reject) => {
    let html = ''
    
    const readStream = fs.createReadStream(inputPath, { encoding: 'utf8' })
    
    readStream.on('data', chunk => {
      html += chunk
    })
    
    readStream.on('end', () => {
      try {
        const markdown = turndownService.turndown(html)
        fs.writeFileSync(outputPath, markdown)
        resolve()
      } catch (error) {
        reject(error)
      }
    })
    
    readStream.on('error', reject)
  })
}

convertLargeFile('large-input.html', 'output.md')
  .then(() => console.log('Large file converted'))
  .catch(error => console.error('Error:', error))

Configuration Options

Customize Turndown behavior when instantiating:
var turndownService = new TurndownService({
  headingStyle: 'atx',           // Use ### style headings
  hr: '---',                      // Horizontal rule style
  bulletListMarker: '-',          // Use - for bullet lists
  codeBlockStyle: 'fenced',       // Use ``` for code blocks
  fence: '```',                   // Fence style
  emDelimiter: '*',               // Use * for emphasis
  strongDelimiter: '**',          // Use ** for strong
  linkStyle: 'inlined',           // Inline links
  linkReferenceStyle: 'full',     // Reference link style
  preformattedCode: false         // Don't preserve formatting
})

Error Handling

Turndown validates input and throws a TypeError for invalid input:
var turndownService = new TurndownService()

try {
  // This will throw a TypeError
  var markdown = turndownService.turndown(null)
} catch (error) {
  console.error(error.message)
  // "null is not a string, or an element/document/fragment node."
}
Always validate input before passing it to Turndown, especially when processing user-generated content.

Next Steps

Custom Rules

Extend Turndown with custom conversion rules for specialized HTML elements

GFM Plugin

Add GitHub Flavored Markdown features like tables and strikethrough

Build docs developers (and LLMs) love