To display Markdown in HTML, you convert it to HTML first, because browsers don't render Markdown on their own. You can convert it ahead of time with a tool like Pandoc, or in the page with a JavaScript library such as marked or markdown-it. If the Markdown can come from anyone but you, sanitize the HTML before you put it on the page.
Why can't a browser display Markdown in HTML directly?
A browser only renders HTML, so Markdown inside a page or a .md file shows up as plain text with the asterisks and hash signs intact. Something has to turn # Title into <h1>Title</h1> before the browser sees it. That step can happen on your machine, on your server, or in the visitor's browser.
Where you convert decides how fast the page loads, whether search engines see the text, and how much security work you take on.
| Approach | When the conversion runs | Good for | Watch out for |
|---|---|---|---|
| Pandoc or a site generator | Before you publish | Docs, blogs, README pages | You rebuild after every edit |
| marked or markdown-it in the page | When the page loads | Comments, previews, dashboards | Untrusted input needs sanitizing |
| Online previewer | When you paste | One-off checks of a file | Not a way to publish |
Option 1: convert Markdown to HTML before you publish
Converting ahead of time means the HTML file you publish already contains headings, lists and links, so no JavaScript is needed to read it. The Pandoc getting started guide shows the basic command:
pandoc test1.md -f markdown -t html -s -o test1.html
The -s flag matters. Pandoc's guide says it creates a "standalone" file, "with a header and footer, not just a fragment." Without it, you get only the body markup, which is what you want if you paste the result into an existing template. You can also drop -f markdown and -t html, since Markdown to HTML is the default.
I'd pick this route for anything that rarely changes, like documentation or a changelog. The page is plain HTML, it works with JavaScript turned off, and you never parse untrusted text at runtime. Static site generators do the same job across a whole folder of .md files.
Option 2: render Markdown in the browser with JavaScript
Rendering in the browser means you ship the Markdown text plus a small library, and the library builds the HTML when the page loads. Use it when the Markdown changes often or comes from users, such as a comment box with a live preview.
The marked documentation gives a browser example that loads the library from a CDN and writes the result into an element:
<div id="content"></div>
<script src="https://cdn.jsdelivr.net/npm/marked/lib/marked.umd.js"></script>
<script>
document.getElementById('content').innerHTML =
marked.parse('# Marked in browser\n\nRendered by **marked**.');
</script>
To show a .md file instead of a string, fetch it and pass the text to marked.parse:
const text = await fetch('/notes/readme.md').then((r) => r.text());
document.getElementById('content').innerHTML = marked.parse(text);
In Node, run npm install marked and call marked.parse the same way.
The alternative, markdown-it, "Follows the CommonMark spec" and adds extras like URL autolinking. Create a parser with new MarkdownIt() and call md.render(text). Its README says it loads in the browser from a CDN such as unpkg.com or esm.sh.
Sanitize the HTML before you insert it
Sanitizing means running the converted HTML through a filter that removes anything able to run script, before it reaches innerHTML. Skip it and one comment can run JavaScript in every visitor's browser.
The marked docs say so outright: "Marked does not sanitize the output HTML." They list DOMPurify as the recommended filter, along with js-xss, sanitize-html and insane.
In CommonMark, the spec's raw HTML section says text that looks like an HTML tag "will be rendered in HTML without escaping." So a user can type this into your Markdown comment box:
Nice post! <img src='x' onerror='alert(1)'>
The MDN page on innerHTML uses that exact img payload as its example and notes that assigning it to innerHTML shows the alert. MDN also points out that innerHTML blocks injected <script> elements but is open to "many other ways" of running code, which is why removing only script tags isn't enough.
The fix is one extra call. DOMPurify describes itself as "a DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG":
const dirty = marked.parse(text);
document.getElementById('content').innerHTML = DOMPurify.sanitize(dirty);
The img tag survives. Its onerror handler doesn't.
markdown-it lists "Safe by default" among its features. I'd add DOMPurify to either library anyway. It costs one line and still protects you after someone changes the parser options.
How to show Markdown source as plain text
Showing the raw Markdown is a different job: you want the asterisks visible. MDN says Node.textContent "should be used when you know that the user-provided content should be plain text." Put the text in a <pre> element and set textContent, and the browser escapes every character for you.
document.querySelector('pre').textContent = text;
Use it for any input you aren't sure about. For log files and config snippets, my notes on opening a JSON file cover viewing raw text without accidental rendering.
Mixing HTML and Markdown in one file
Mixing works because CommonMark passes raw HTML tags through, so you can drop a <details> block or a <table> with custom attributes into a .md file. It's the same feature that made the img example dangerous, so keep it to trusted authors.
Check the output quickly
Paste your Markdown into a previewer and compare the HTML with what you expected. The OnlineViewer Dev Markdown preview renders as you type and runs in the page, so the text isn't sent anywhere. One limitation: it uses a small built-in renderer for headings, emphasis, code, links, images, blockquotes, lists and rules. It is not a full CommonMark or GFM parser, so tables and edge cases can render differently from marked or Pandoc. Test with your real library before you ship.
If you bundle the renderer into your own front end, minifying your JavaScript keeps the extra library from slowing the page. And if your Markdown lives next to YAML front matter, my comparison of JSON vs YAML for config files covers the format rules that trip people up.
FAQ
Can a browser display a .md file as formatted text?
No. A browser shows Markdown as plain text. You have to convert it to HTML first, either ahead of time with a tool like Pandoc or in the page with a JavaScript library such as marked or markdown-it.
Does marked sanitize its HTML output?
No. The marked documentation says it does not sanitize the output HTML and recommends filtering it, with DOMPurify as the recommended option, before you insert it into the page.
Can I put raw HTML inside a Markdown file?
Yes, in CommonMark. Text between < and > that looks like an HTML tag is passed through to the output without escaping. That is handy for your own content and a risk for content from users.
Should I use textContent or innerHTML for Markdown output?
Use innerHTML only with sanitized HTML, because the converted Markdown is HTML you want rendered. Use textContent when you want to show the Markdown source itself as plain text.