Markdown to HTML
Write or paste Markdown below, see a live rendered preview plus the generated HTML source, instantly.
About the Markdown to HTML Converter
This is a hand-rolled parser, not a full CommonMark implementation, so it covers headers, bold, italic, inline code, fenced code blocks, ordered and unordered lists, links, images, blockquotes and horizontal rules, but skips edge cases like nested lists, tables or reference-style links.
Any raw HTML you paste, including a stray <script> tag, gets escaped to plain text before formatting runs, so it shows up as visible text in the preview instead of executing.
Link and image URLs are sanitized against javascript: and vbscript: schemes
Every URL that comes out of a [text](url) or  pattern passes through a dedicated sanitizeUrl() step before it's placed inside an href or src attribute. That function first strips any control character in the URL (byte values 0 through 32, which includes tabs and newlines), because attackers commonly hide a malicious scheme from naive filters by inserting whitespace inside it, like java script:, which browsers still interpret as a working scheme after ignoring the embedded whitespace. Once the URL is stripped of those hidden characters, anything starting with javascript: or vbscript: is replaced outright with a harmless #, so a markdown-formatted link can't be turned into a click-to-execute script even though it never contains an actual <script> tag. Any quote character remaining in the URL is also escaped, which stops it from breaking out of the attribute and injecting a new one like onerror= or onclick=.
Images are matched before links, because their syntax overlaps
Markdown image syntax is just link syntax with a leading exclamation mark —  versus [text](url) — which means a naive parser that checks for links first would match the [alt](url) portion of an image reference as if it were a plain link, leaving a stray, unconverted ! sitting in front of the resulting <a> tag. This parser's applyInline() function runs the image regex before the link regex specifically to avoid that, the same "match the more specific pattern first" principle that governs the ampersand-before-other-characters order in this site's HTML Encoder.
Underscore italics won't fire inside snake_case identifiers
Asterisk-based italics (*text*) use a plain regex, but underscore-based italics use a more careful one: (^|[^\w])_([^_]+)_(?!\w). The (^|[^\w]) at the front requires the opening underscore to sit at the very start of the text or right after a non-word character, and the (?!\w) at the end requires the closing underscore not be immediately followed by another word character. That pair of checks is what stops a variable name like my_variable_name from being misread as "my" followed by italicized "variable" followed by "name" — a well-known failure mode in simpler Markdown implementations that treat every underscore pair as emphasis regardless of context.
What "skips nested lists" means in practice
Both the unordered and ordered list patterns match leading whitespace with \s* but don't use how much whitespace was matched for anything — an indented list item is recognized as a list item, but its indentation is discarded rather than used to build a nested sub-list. So a markdown source with a two-space-indented sub-bullet renders as a flat, single-level list item at the same depth as everything around it, rather than throwing an error or silently dropping the line.
Blockquotes collapse every consecutive line into one paragraph
Consecutive lines starting with > are gathered up and joined with spaces into a single <p> inside one <blockquote>, which correctly handles the common case of a quote spanning a few wrapped lines, but doesn't support multiple separate paragraphs inside a single blockquote or a blockquote nested inside another one — both collapse into that same flat single paragraph.
Fenced code blocks never run through inline formatting
Text between a pair of triple-backtick fences is escaped with the same escapeHtml() used for raw pasted HTML, but it deliberately skips applyInline() entirely — so an underscore, asterisk, or bracket pair sitting inside a code sample is shown exactly as typed, rather than being misread as bold, italic, or link syntax the way it would be in an ordinary paragraph.
Frequently Asked Questions
Can a link written in Markdown execute JavaScript when clicked?
No. Every link and image URL is checked against javascript: and vbscript: schemes after stripping hidden control characters attackers sometimes use to sneak past naive filters, and any match is replaced with a harmless # instead of the original URL.
Does this parser support nested (indented) lists?
Not really. Indentation on a list item is matched but not used to determine nesting depth, so an indented sub-item still renders as a flat list item at the same level as the rest of the list rather than as a true nested sub-list.
Will a variable name like my_variable_name get accidentally italicized?
No. The underscore-italics pattern specifically requires the underscore to sit at a word boundary on both sides, which is what CommonMark-style parsers also do, so underscores inside an identifier are left alone instead of being read as emphasis markers.
Does formatting like bold or links apply inside code blocks?
No. Content inside triple-backtick fenced code blocks is only escaped for safe display, never passed through the inline formatter, so markdown-looking characters inside a code sample are shown literally instead of being converted.
Is it safe to paste Markdown containing raw HTML tags?
Yes. Any text is escaped for &, <, and > before any markdown syntax is applied, so a pasted <script> tag or other raw HTML shows up as visible, inert text in the preview instead of being parsed as real markup.