NAME
Markdown::Simple - Markdown to HTML
VERSION
Version 0.22
SYNOPSIS
use Markdown::Simple;
# Functional interface. GFM by default (tables, strikethrough,
# task lists, autolinks, disallow-raw-html). Output is
# CommonMark/GFM-conformant HTML.
my $html = markdown_to_html($markdown);
# Strict CommonMark.
my $cm = markdown_to_html($markdown, { gfm => 0 });
# Opt-out an individual extension.
my $no_tables = markdown_to_html($markdown, { tables => 0 });
# Extras off by default.
my $hb = markdown_to_html($markdown, { hard_breaks => 1 });
my $plain = strip_markdown($markdown);
# Object interface — keeps the parser's arena warm between
# render() calls. Use this when converting many documents in a
# loop with the same options.
my $md = Markdown::Simple->new({ gfm => 1 });
for my $doc (@docs) {
print $md->render($doc);
}
DESCRIPTION
Markdown::Simple is a Perl XS module that converts Markdown text to HTML. The default rendering mode is GitHub Flavored Markdown (GFM); options let you switch to strict CommonMark or toggle individual extensions.
Two entry points are provided: the exported procedural function "markdown_to_html", and the object-oriented "new" / "render" pair, which is preferable when you intend to render many documents in the same process because it reuses the underlying parser arena.
FUNCTIONS
markdown_to_html
markdown_to_html($markdown);
markdown_to_html($markdown, \%options);
Converts the given Markdown text to HTML. The optional second argument is a hash reference of feature flags.
The available options are:
gfm - preset switch. Defaults to enabled.
gfm => 0selects strict CommonMark (turns tables, strikethrough, task lists, autolink, and disallow-raw-html off). Individual feature toggles below still override.tables - GFM tables (default: on in GFM mode)
strikethrough - GFM
~~strike~~rendering (default: on in GFM mode)tasklist - GFM
- [ ]task list items (default: on in GFM mode)autolink - GFM bare-URL autolink scanner (default: on in GFM mode)
disallow_raw_html - escape disallowed raw HTML tags (default: on in GFM mode)
hard_breaks - emit
<br />for soft line breaks (default: off)unsafe - allow
javascript:,vbscript:,data:URLs and other otherwise-stripped dangerous content (default: off)no_simd - disable SIMD fast paths (default: off; useful for debugging)
strict_utf8 - reject input that is not well-formed UTF-8 with a fatal
croakinstead of silently producing best-effort output (default: off)highlight - syntax-highlight fenced code blocks that carry a language tag (e.g.
```perl). Recognised tokens are wrapped in<span class="esh-X">elements; the content remains fully HTML-safe. Requires Eshu to be installed. Blocks with no language tag are unaffected. (default: off)heading_ids - give every heading an anchor, as
<h2 id="the-slug">. The slug follows GitHub's rule so that existing cross-document links keep working: the heading's plain text lowercased, ASCII punctuation dropped, runs of whitespace collapsed to a single hyphen. Inline markup does not reach the slug, so## The **bold** bitanchors asthe-bold-bit. Bytes above ASCII pass through unchanged rather than being mangled by a partial case fold. Slugs are unique within a document; a repeat gets-1,-2and so on. (default: off)
Per-syntax disables
Pass any of the following with a false value (feature => 0) to make the corresponding Markdown construct fall through as literal text instead of being recognised. All default to enabled.
headers - ATX (
#) and Setext (===/---) headingsthematic_break -
---,***,___horizontal rulesfenced_code -
```/~~~fenced code blocksindented_code - 4-space indented code blocks
blockquote -
>quoted blocksordered_lists -
1.,2.ordered listsunordered_lists -
-,*,+bullet listshtml - raw HTML blocks and inline HTML
references - link reference definitions (
[id]: url)bold -
**strong**/__strong__italic -
*em*/_em_code - inline
`code`spanslinks -
[text](url)and reference linksimages -

strip_markdown
strip_markdown($markdown);
Removes all Markdown formatting from the given text, returning plain text. List markers, table pipes, and table separator rows are preserved so that the output stays scan-readable; bold/italic/strike/code/link/image delimiters are stripped while their textual content is retained.
OBJECT INTERFACE
For workloads that render many documents in succession, the object interface keeps the parser's internal arena and scratch buffers warm between calls, eliminating the per-render malloc/free traffic incurred by the procedural entry point.
new
my $md = Markdown::Simple->new(\%options);
my $md = Markdown::Simple->new; # GFM defaults
Constructs a persistent renderer. \%options is the same hash reference accepted by "markdown_to_html"; flags are decoded once at construction and reused on every render call.
render
my $html = $md->render($markdown);
Converts $markdown to HTML using the options bound at construction time. The arena's head page is reset (not freed) between calls so that allocations within a typical document complete without touching the system allocator.
render_with_toc
my ($html, $headings) = $md->render_with_toc($markdown);
Renders as "render" does, and additionally returns the document's headings as an arrayref of hashrefs, in document order:
[ { level => 1, text => 'Title', id => 'title' },
{ level => 2, text => 'First bit', id => 'first-bit' } ]
text is the heading's plain text with inline markup stripped, and id is the anchor that was emitted on the <hN> tag, so a table of contents built from this list links into the document without re-deriving anything.
Asking for the list turns "heading_ids" on for that render whatever the session was constructed with, since a list of anchors that were never emitted would be of no use.
DESTROY
Releases the C-side arena and scratch buffers. Called automatically by Perl when the object goes out of scope; you do not need to invoke it explicitly.
EXAMPLES
use Markdown::Simple qw(markdown_to_html);
my $markdown = "This is **bold** text and this is *italic* text.";
my $html = markdown_to_html($markdown);
print $html; # <p>This is <strong>bold</strong> text and this is <em>italic</em> text.</p>
# Switch to strict CommonMark (no GFM extensions).
my $cm = markdown_to_html("see http://x\n", { gfm => 0 });
# Reuse a single parser for many documents.
my $md = Markdown::Simple->new;
print $md->render($_) for @docs;
# Anchors and a table of contents to link into them.
my ($html, $toc) = Markdown::Simple->new->render_with_toc($markdown);
print qq{<a href="#$_->{id}">$_->{text}</a>\n} for @$toc;
C ABI
An XS module can render through the parser without a Perl frame in between. include/mds_abi.h declares a function-pointer table with five entries:
flags_from_hv- decode an options hashref into theMDS_FLAG_*bitmask, applying the same defaults "markdown_to_html" does. ANULLhash gives the GFM preset.session_of- the opaque session behind a blessed Markdown::Simple object. ReturnsNULLfor anything that is not one rather than croaking, so it is safe to probe with on a fall-back path.session_render- render through a session, reusing its warm arena and scratch buffers. Takes an optionalAV *which, when supplied, collects one{ level, text, id }hashref per heading and turns anchors on for that render.render- the same with no session: a local arena, allocated and freed around the call.strip- "strip_markdown", for search indexing and result snippets, where indexing the HTML would mean indexing the tags.
The table is resolved at runtime through Markdown::Simple::_abi_ptr and gated on its abi_version, so there is no link-time coupling and the two distributions upgrade independently; entries are only ever appended. Reach the header with ExtUtils::Depends:
my $pkg = ExtUtils::Depends->new('My::Module', 'Markdown::Simple');
Nothing in the table croaks. Errors arrive through an SV **err out-parameter, so a consumer can turn a bad document into its own error response rather than an exception it has to catch.
Hold a reference to the object and call session_of on each render rather than caching the session pointer, so an object that was replaced or cloned cannot leave you with a stale handle. The lookup is a walk of the object's magic chain, which is cheap enough to mean that.
Every entry deals in raw bytes and none of them sets the UTF-8 flag on anything. The parser is byte-oriented and the caller is the one who knows whether its input was characters or octets, so the flag is the caller's to set. For a consumer writing an HTTP response body this is the behaviour you want, since the body must be bytes and Content-Length counts bytes.
Note that a template engine which encodes its own output will double encode anything you hand it from here. Decode on the way in and let it do the single encode.
CONCURRENCY
The renderer keeps a file-scope image-nesting stack, so two interpreters must not render concurrently. One session per thread or worker is the supported shape, and is what the object interface is for.
AUTHOR
LNATION, <email at lnation.org>
BUGS
Please report any bugs or feature requests to bug-markdown-simple at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Markdown-Simple. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Markdown::Simple
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
Search CPAN
LICENSE AND COPYRIGHT
This software is Copyright (c) 2025 by LNATION.
This is free software, licensed under:
The Artistic License 2.0 (GPL Compatible)