DEV Community

楊東霖
楊東霖

Posted on • Originally published at devplaybook.cc

Markdown to HTML: Best Free Tools and How They Work

Markdown is the writing format of choice for developers. README files, documentation, blog posts, technical wikis — nearly all developer-facing content starts in Markdown. At some point, that Markdown needs to become HTML.

This guide explains how Markdown-to-HTML conversion works, covers the best free tools for different use cases, and shows how to do the conversion programmatically when you need it in a pipeline or application.


How Markdown to HTML Conversion Works

Markdown is a lightweight markup language with a plain-text syntax designed to be converted to HTML. John Gruber created it in 2004, and the core spec maps a set of text conventions to HTML elements:

Markdown HTML
# Heading <h1>Heading</h1>
## Subheading <h2>Subheading</h2>
**bold** <strong>bold</strong>
*italic* <em>italic</em>
[link](url) <a href="url">link</a>
![alt](src) <img alt="alt" src="src">
`code` <code>code</code>
> quote <blockquote>quote</blockquote>
--- <hr>

A Markdown parser processes the input text and applies these transformations, producing a valid HTML fragment. Most parsers work in two stages:

  1. Tokenization/Lexing — scan the Markdown text and identify tokens (heading, paragraph, code block, etc.)
  2. Rendering — convert each token to its HTML equivalent

The complexity comes from edge cases: nested emphasis, inline code within links, raw HTML pass-through, escaped characters, and the many spec variations between Markdown dialects.

Markdown Flavors

There is no single "official" Markdown spec. The original spec by Gruber left many edge cases undefined, leading to different implementations handling them differently. Major flavors:

  • CommonMark — community-driven spec that resolves most ambiguities; becoming the standard baseline
  • GitHub Flavored Markdown (GFM) — extends CommonMark with tables, task lists, strikethrough, autolinks
  • MultiMarkdown — adds footnotes, tables, citations, cross-references
  • Pandoc Markdown — extends GFM further with academic features

For most developer documentation, GFM compatibility is what you need.


Best Free Markdown to HTML Tools

1. DevPlaybook Markdown Preview

DevPlaybook's Markdown Preview is designed for developers who need a quick, clean conversion without installing anything.

Key features:

  • Real-time preview as you type
  • GFM support (tables, task lists, strikethrough)
  • Syntax-highlighted code blocks
  • Copy HTML output button
  • Side-by-side editor and preview
  • Export the rendered view

Best for: Quick conversion of README snippets, documentation drafts, checking how your Markdown will look when rendered, and extracting the HTML output for templates.

It's completely browser-based — your content never leaves your machine.


2. Pandoc (Command Line)

Pandoc is the gold standard for document format conversion. It handles not just Markdown to HTML but 40+ format combinations — Word to PDF, HTML to Markdown, RST to LaTeX.

Install:

# macOS
brew install pandoc

# Ubuntu/Debian
sudo apt install pandoc

# Windows
winget install JohnMacFarlane.Pandoc
Enter fullscreen mode Exit fullscreen mode

Basic Markdown to HTML:

# Convert file to HTML
pandoc input.md -o output.html

# Convert with a standalone HTML page (includes DOCTYPE, head, body)
pandoc -s input.md -o output.html

# Convert with syntax highlighting
pandoc --highlight-style=github input.md -o output.html

# Convert using GFM flavor
pandoc -f gfm input.md -o output.html

# Add CSS
pandoc -s --css style.css input.md -o output.html

# Convert from stdin
echo "# Hello\n\nThis is a paragraph" | pandoc -f markdown -t html
Enter fullscreen mode Exit fullscreen mode

Pandoc's Markdown supports footnotes, definition lists, and citations — useful for technical writing and academic documentation that goes beyond GFM.


3. marked.js (JavaScript Library)

marked.js is the most widely deployed Markdown-to-HTML library in JavaScript. It's used by Ghost, Docusaurus, and many other content platforms.

Install:

npm install marked
Enter fullscreen mode Exit fullscreen mode

Basic usage:

import { marked } from 'marked';

const markdown = `
# Hello World

This is a paragraph with **bold** and *italic* text.

\`\`\`javascript
const greeting = 'hello';
console.log(greeting);
\`\`\`
`;

const html = marked.parse(markdown);
console.log(html);
Enter fullscreen mode Exit fullscreen mode

Output:

<h1>Hello World</h1>
<p>This is a paragraph with <strong>bold</strong> and <em>italic</em> text.</p>
<pre><code class="language-javascript">const greeting = 'hello';
console.log(greeting);
</code></pre>
Enter fullscreen mode Exit fullscreen mode

Configuration options:

import { marked } from 'marked';

marked.setOptions({
  gfm: true,            // GitHub Flavored Markdown
  breaks: false,        // GFM line breaks (true = newline becomes <br>)
  pedantic: false,      // Conform to original markdown.pl spec
});

// Custom renderer — change how elements are rendered
const renderer = new marked.Renderer();

renderer.link = (href, title, text) => {
  // Open external links in new tab
  const isExternal = href.startsWith('http');
  const target = isExternal ? ' target="_blank" rel="noopener noreferrer"' : '';
  return `<a href="${href}"${title ? ` title="${title}"` : ''}${target}>${text}</a>`;
};

marked.use({ renderer });

const html = marked.parse(markdown);
Enter fullscreen mode Exit fullscreen mode

With syntax highlighting (highlight.js):

import { marked } from 'marked';
import hljs from 'highlight.js';
import 'highlight.js/styles/github.css';

marked.use({
  highlight(code, lang) {
    if (lang && hljs.getLanguage(lang)) {
      return hljs.highlight(code, { language: lang }).value;
    }
    return hljs.highlightAuto(code).value;
  }
});

const html = marked.parse(markdown);
Enter fullscreen mode Exit fullscreen mode

4. markdown-it (JavaScript Library)

markdown-it is a CommonMark-compliant parser that's more extensible than marked.js. It supports a plugin architecture for adding features.

Install:

npm install markdown-it
Enter fullscreen mode Exit fullscreen mode

Basic usage:

import MarkdownIt from 'markdown-it';

const md = new MarkdownIt({
  html: true,         // Allow HTML tags in source
  linkify: true,      // Convert URL-like text to links
  typographer: true,  // Smart quotes and dashes
  highlight: function(str, lang) {
    // Add syntax highlighting here
    return '';
  }
});

const html = md.render('# Hello\n\nParagraph with [link](https://example.com).');
Enter fullscreen mode Exit fullscreen mode

With plugins:

import MarkdownIt from 'markdown-it';
import taskLists from 'markdown-it-task-lists';
import footnote from 'markdown-it-footnote';

const md = new MarkdownIt()
  .use(taskLists)
  .use(footnote);

const input = `
- [x] Completed task
- [ ] Pending task

Here is a footnote[^1].

`;

const html = md.render(input);
Enter fullscreen mode Exit fullscreen mode

5. Python-Markdown

For Python pipelines and backends, markdown is the standard library:

pip install markdown
Enter fullscreen mode Exit fullscreen mode
import markdown

# Basic conversion
text = """
# Hello World

This is a paragraph with **bold** text and a [link](https://example.com).

Enter fullscreen mode Exit fullscreen mode


python
print("Hello, World!")

"""

html = markdown.markdown(text)
print(html)

# With extensions
html = markdown.markdown(
    text,
    extensions=[
        'fenced_code',      # ```

code blocks

Enter fullscreen mode Exit fullscreen mode
    'tables',           # GFM-style tables
    'toc',              # Auto-generated table of contents
    'codehilite',       # Syntax highlighting via Pygments
    'nl2br',            # Newline to <br>
    'meta',             # Metadata at top of file
]
Enter fullscreen mode Exit fullscreen mode

)

Using the preprocessed metadata

md = markdown.Markdown(extensions=['meta'])
html = md.convert(text)
print(md.Meta) # Access front matter


javascript

---

## Markdown to HTML in Different Contexts

### Static Site Generators

Most static site generators handle Markdown-to-HTML conversion internally. You write Markdown files, and the generator produces HTML pages:

- **Astro** — processes `.md` and `.mdx` files using remark + rehype
- **Next.js** — MDX support via `@next/mdx` or `next-mdx-remote`
- **Hugo** — built-in Goldmark parser (CommonMark + GFM)
- **Jekyll** — uses kramdown by default

For these, you don't need a separate library — the framework handles conversion. The key is understanding the Markdown flavor your generator uses.

### Content Management Systems

When building a CMS or editor:



```javascript
// React editor with live preview
import { useState } from 'react';
import { marked } from 'marked';

function MarkdownEditor() {
  const [markdown, setMarkdown] = useState('# Hello\n\nStart typing...');

  return (
    <div style={{ display: 'flex', gap: '20px' }}>
      <textarea
        value={markdown}
        onChange={(e) => setMarkdown(e.target.value)}
        style={{ flex: 1, height: '400px', fontFamily: 'monospace' }}
      />
      <div
        style={{ flex: 1 }}
        dangerouslySetInnerHTML={{ __html: marked.parse(markdown) }}
      />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Important: When rendering user-provided Markdown in the browser, always sanitize the HTML output to prevent XSS. Use DOMPurify:

import DOMPurify from 'dompurify';
import { marked } from 'marked';

function safeMarkdown(input) {
  const rawHtml = marked.parse(input);
  return DOMPurify.sanitize(rawHtml);
}

// Safe to set as innerHTML
div.innerHTML = safeMarkdown(userInput);
Enter fullscreen mode Exit fullscreen mode

Never use marked.parse() output directly in innerHTML without sanitization if the input comes from users.

Email Clients

Converting Markdown to HTML for email has extra constraints — email clients have notoriously inconsistent CSS support:

  • Inline all CSS (most email clients ignore <style> tags)
  • Avoid flexbox and grid — use tables for layout
  • Images must have width and height attributes
import { marked } from 'marked';
import juice from 'juice'; // CSS inliner

const markdown = 'Hello **World**! Check out our [sale](https://example.com).';
const html = marked.parse(markdown);

// Add inline CSS for email compatibility
const withStyles = `
  <style>
    p { font-family: Arial, sans-serif; font-size: 16px; }
    strong { color: #333; }
    a { color: #0066cc; }
  </style>
  ${html}
`;

const emailHtml = juice(withStyles); // Inlines the CSS
Enter fullscreen mode Exit fullscreen mode

Documentation Sites

For documentation that needs a full-featured Markdown pipeline:

// remark + rehype pipeline (used by Astro, Next.js, etc.)
import { unified } from 'unified';
import remarkParse from 'remark-parse';
import remarkGfm from 'remark-gfm';
import remarkRehype from 'remark-rehype';
import rehypeSanitize from 'rehype-sanitize';
import rehypeStringify from 'rehype-stringify';
import rehypeHighlight from 'rehype-highlight';

const processor = unified()
  .use(remarkParse)               // Parse Markdown AST
  .use(remarkGfm)                 // GitHub Flavored Markdown
  .use(remarkRehype)              // Convert to HTML AST
  .use(rehypeHighlight)           // Syntax highlighting
  .use(rehypeSanitize)            // Sanitize HTML
  .use(rehypeStringify);          // Stringify to HTML

const html = await processor.process(markdownContent);
console.log(String(html));
Enter fullscreen mode Exit fullscreen mode

This is the most robust approach for production documentation — each step is pluggable and testable independently.


Common Issues and Fixes

Code Blocks Not Rendering

If fenced code blocks (code) aren't converting properly, check that your library supports the dialect. marked.js supports them by default. Python's markdown library requires the fenced_code extension.

Tables Not Converting

Tables are a GFM extension, not in the original Markdown spec. Enable GFM mode in your library:

marked.setOptions({ gfm: true });        // marked.js
const md = new MarkdownIt({ /* gfm via plugin */ });  // markdown-it requires plugin
markdown.markdown(text, extensions=['tables'])         // Python
Enter fullscreen mode Exit fullscreen mode

Line Breaks Behaving Unexpectedly

Different flavors handle line breaks differently:

  • Strict Markdown: a single newline in a paragraph is ignored; two newlines create a paragraph break
  • GFM: same behavior
  • GFM with breaks: true: a single newline becomes <br>

Set breaks: false (the default) for most documentation use cases. Use breaks: true only if your content is designed for line-by-line display (like messaging apps).

Raw HTML Being Escaped

If you include raw HTML in your Markdown and it's showing as escaped text (&lt;div&gt;), enable HTML pass-through:

const md = new MarkdownIt({ html: true });
Enter fullscreen mode Exit fullscreen mode

Or in marked.js, HTML is passed through by default.


Choosing the Right Tool

Use Case Best Tool
Quick one-off conversion DevPlaybook Markdown Preview
Complex document conversion (Word, PDF) Pandoc
JavaScript web app marked.js or markdown-it
Python backend Python-markdown
Static site (Astro, Next.js, Hugo) Built-in framework support
Documentation with plugins remark + rehype pipeline

For interactive testing and preview, DevPlaybook's Markdown Preview covers most daily needs. For production applications, marked.js (simple, fast) or the remark/rehype pipeline (flexible, production-grade) are the leading choices.


Conclusion

Markdown-to-HTML conversion is a solved problem. The tools are mature, the libraries are well-maintained, and the patterns are established. The key decisions are: which Markdown flavor do you need (usually GFM), do you need sanitization (always for user input), and how complex is your rendering pipeline?

For quick previewing and one-off conversions, DevPlaybook's Markdown Preview is the fastest path. For programmatic use in JavaScript, reach for marked.js or markdown-it. For Python, use markdown with the extensions your use case requires.


Working with Markdown content that contains code blocks? The Regex Tester helps write patterns for extracting code sections. Need to format JSON in your documentation? JSON Formatter Pro makes it readable before you paste it into your Markdown.


Level Up Your Dev Workflow

Found this useful? Explore DevPlaybook — cheat sheets, tool comparisons, and hands-on guides for modern developers.

🛒 Get the DevToolkit Starter Kit on Gumroad — 40+ browser-based dev tools, source code + deployment guide included.

Top comments (0)