Markdown Preview

Paste Markdown and see it rendered live, with the generated HTML alongside it. Supports a common subset — headings, emphasis, code, links, images, lists, quotes, rules and tables — and escapes every scrap of HTML in your source, so a <script> tag in the input can only ever appear as text.

This parser covers a common subset of Markdown, not the full CommonMark or GitHub Flavored Markdown specification. Reference links, footnotes, task lists and inline HTML are not supported — raw HTML is shown as text on purpose. Nothing you paste leaves your browser.

Type some Markdown to start.
The rendered preview appears here.
The generated HTML appears here.

What this markdown preview does

Markdown is a plain-text formatting syntax invented by John Gruber in 2004 with a single goal: a document should be readable as-is, before anything converts it. Two decades later it is the format of README files, changelogs, issue trackers, static site generators, chat clients and most API documentation. A markdown preview tool answers the immediate question — what will this actually look like — and a markdown to HTML converter answers the next one: what markup does it become.

This page does both. Type on the left, see the rendered result and switch the view to read the exact HTML produced. The HTML pane is not a re-implementation; it is a serialisation of the very DOM you are looking at, so the two panes cannot disagree.

A subset, stated plainly

Markdown has no single definition. Gruber's original description was prose plus a Perl script, and it left enough ambiguity that every implementation diverged. CommonMark exists to fix that with a specification long enough to be genuinely difficult to implement, and GitHub Flavored Markdown layers tables, strikethrough, task lists and autolinking on top. This tool implements the common subset that covers the overwhelming majority of real documents, and it does not pretend otherwise.

SupportedNot supported
ATX headings # … ###### and setext underlinesReference-style links and images [a][b]
Bold, italic, strikethrough, inline codeFootnotes and definition lists
Fenced code blocks with ``` or ~~~Indented (four-space) code blocks
Ordered and unordered lists, one level of nestingTask list checkboxes
Blockquotes, including nestedRaw HTML blocks and inline HTML
Horizontal rules and pipe tablesEntity references such as &copy;
Links, images, and <https://…> autolinksColumn alignment in tables
Backslash escapes and hard line breaksCommonMark's full emphasis-flanking rules

If a document renders differently here and on GitHub, assume GitHub is correct. This is a preview and a conversion aid, not a reference implementation, and treating it as one would be the mistake.

Why rendering Markdown safely is harder than it looks

Markdown converters are a classic source of cross-site scripting, and the reason is structural. The output of a converter is markup. Anything that reaches the output unescaped becomes markup. And the input, in every interesting deployment, is written by someone other than the person running the converter — a comment, a profile bio, a pull request description, a webhook payload rendered into an admin dashboard.

There are three separate ways to get this wrong, and most vulnerable implementations get at least two.

1. Honouring raw HTML

Gruber's original Markdown deliberately allows inline HTML to pass through untouched, and many libraries keep that behaviour by default. It is a reasonable choice for a single-author blog and a catastrophic one for user-generated content, because <script>alert(1)</script> is valid Markdown that a permissive renderer will happily emit. This tool does not honour raw HTML at all. Every character of the source that is not a Markdown construct becomes a text node, so an <iframe>, an <img onerror> or a <script> shows up on screen as the characters you typed. Paste one into the box above and watch it appear as text.

2. Building output as a string

The usual architecture is to build an HTML string and hand it to innerHTML. That works, and it means the escaping is only as good as its weakest branch — one place where a heading's text, a link title or a table cell is concatenated without escaping, and the whole thing is exploitable. This parser never produces an HTML string for display. It calls document.createElement and sets textContent, so escaping is not a step that can be forgotten; it is a property of the API. The HTML pane is generated afterwards by walking that finished DOM and escaping as it goes, which means the markup you copy is a description of what was rendered rather than the thing that produced it.

3. Trusting URLs

Even a converter that escapes text perfectly will happily write your URL into an href. And [click me](javascript:alert(document.cookie)) is a well-formed Markdown link. The javascript: scheme executes on click; data:text/html loads an attacker-controlled document; data:image/svg+xml in an image source can carry script in some contexts. So before any URL is used here it is stripped of control characters and surrounding whitespace, its scheme is extracted and lowercased, and anything that is not http, https, mailto, tel or ftp is refused outright. A rejected link renders as plain text with no anchor; a rejected image renders as its alt text. Relative paths and #fragment links have no scheme and pass through untouched.

Two details in that check matter more than they look. Stripping control characters first defeats the old java script: trick, where a tab inside the scheme is ignored by the URL parser but breaks a naive prefix test. And because attributes are set with setAttribute rather than written into markup, an HTML entity such as &#106;avascript: is never decoded into anything, so it cannot re-form into a live scheme.

If you are shipping a Markdown pipeline of your own, the takeaway is: escape first, never render raw HTML from untrusted authors, sanitise URL schemes, and add a Content Security Policy as the layer that catches the bug you missed. Rendering someone else's Markdown is accepting someone else's input, and the same instincts apply as when you handle a request body — see the notes on authenticating and trusting callers and the reasons a webhook payload should never be treated as friendly.

Markdown syntax quick reference

You writeYou get
# Title###### SmallHeadings, levels one to six
**bold** or __bold__Strong emphasis
*italic* or _italic_Emphasis
~~struck~~Strikethrough
`code`Inline code
Three backticks, then lines, then three backticksA fenced code block
[text](https://example.com)A link
![alt](https://example.com/a.png)An image
- item or * itemAn unordered list
1. itemAn ordered list
> quotedA blockquote
--- on its own lineA horizontal rule
| a | b | then | --- | --- |A table
Two spaces at end of lineA hard line break
\*literal\*An escaped character

Two rules cause most of the confusion people bring to a preview tool. First, blank lines are structural: a list, a table or a code fence that follows a paragraph without a blank line between them will usually be swallowed into that paragraph. Second, a single newline is not a line break. Markdown joins consecutive lines into one paragraph, which is what makes hand-wrapped source files work. To force a break, end the line with two spaces — invisible, which is why many people prefer to leave a blank line and start a new paragraph instead.

Inline code is the escape hatch worth remembering. Anything inside backticks is taken literally, so `*not italic*` renders with its asterisks intact. When your code sample itself contains backticks, use more of them on the outside than appear on the inside — the parser matches a run of backticks with an equal run.

Where Markdown meets API work

Markdown is the connective tissue of a documented API. OpenAPI descriptions accept Markdown in nearly every description field, so the sentence you write in a schema becomes rendered prose in whatever documentation viewer your team uses — and a stray underscore in a field name can silently turn half a paragraph italic. Previewing the string before it goes into the spec is faster than regenerating a docs site to find out. If you work with specifications directly, the notes on Swagger and the OpenAPI toolchain cover where those descriptions surface.

The same applies to changelogs, pull request templates, chat notifications and error messages that end up in a Markdown-rendering surface. A release note posted to a chat webhook is Markdown in a JSON string, which means it is double-escaped and easy to break; formatting the payload with the JSON formatter first makes the backslashes visible. And when you need filler to see how a long document behaves in a template, the lorem ipsum generator will produce paragraphs already wrapped in tags.

One more practical case: reading API responses that contain Markdown. Plenty of services return description, body or content fields written in Markdown, and pasting one into this preview is the quickest way to confirm the escaping survived the round trip. Send the request with the API tester, copy the field out of the response, and drop it in above. If it renders wrong, the problem is usually one of encoding rather than syntax — check the Content-Type header and, if the body arrived percent-encoded, run it back through the URL decoder before judging the Markdown.

Frequently asked questions

Does this markdown preview support full CommonMark or GitHub Flavored Markdown?

No, and it says so in the tool as well. This is a hand-written parser covering the common subset: ATX and setext headings, bold, italic, strikethrough, inline code, fenced code blocks, links, images, ordered and unordered lists including one level of nesting, blockquotes, horizontal rules and pipe tables. It does not implement reference-style links, footnotes, definition lists, task lists, HTML blocks, entity references, autolink extensions, or the many edge cases CommonMark specifies for overlapping emphasis. If a document renders differently on GitHub, GitHub is right.

Is raw HTML inside my Markdown rendered?

No. Raw HTML is displayed as literal text. That is a deliberate security decision rather than a missing feature. Real Markdown permits inline HTML, which means any renderer that honours it is one careless input away from cross-site scripting. This tool builds its output by creating DOM nodes and setting textContent, never by assigning an HTML string, so a <script> tag, an <iframe> or an onerror attribute in the source can only ever become visible characters on the page.

What happens to a javascript: link in Markdown?

It is rejected and the link text is rendered as plain text with no anchor at all. Before any URL is used, control characters and whitespace are stripped, the scheme is extracted and lowercased, and anything outside http, https, mailto, tel and ftp is refused. The same check runs on image sources, so a data: URL cannot smuggle an SVG containing script. Relative URLs and fragment links are allowed through unchanged.

Can I use this as a markdown to HTML converter?

Yes. The HTML pane shows the exact markup produced from your Markdown, generated by walking the rendered DOM and escaping every text value, and the copy button puts it on your clipboard. It emits plain semantic tags with no classes, no inline styles and no wrapper elements, so it drops into a template or an email body cleanly.

Why does my Markdown table not render?

A table needs a header row followed immediately by a delimiter row made of dashes and pipes, such as three dashes between each pair of pipes. Without that second line the block is just a paragraph containing pipe characters. Column alignment colons are parsed but ignored here, because applying alignment would require inline styles. A blank line before the table is also required if a paragraph precedes it.

Is my document uploaded anywhere?

No. The parser is plain JavaScript in this page and there is no network request, no logging and no storage. You can paste an unpublished README, a draft changelog or an internal runbook and it stays on your machine, and the tool keeps working with the connection turned off.

Related tools and reference