Markdig is a fast, extensible Markdown parser for .NET. It's CommonMark-compliant, supports a wide range of extensions out of the box, and provides an AST (abstract syntax tree) you can traverse and transform. Whether you're rendering blog posts, processing documentation, or building a CMS, Markdig handles the parsing so you can focus on presentation.
Setup
dotnet add package Markdig
Basic Usage
Convert Markdown to HTML in one line:
var markdown = "# Hello World\n\nThis is **bold** and *italic*.";
var html = Markdown.ToHtml(markdown);
// <h1>Hello World</h1>
// <p>This is <strong>bold</strong> and <em>italic</em>.</p>
Extensions
Markdig's power comes from its extension pipeline. Enable extensions via MarkdownPipelineBuilder:
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
var html = Markdown.ToHtml(markdown, pipeline);
UseAdvancedExtensions() enables a broad set of extensions. For finer control, enable them individually:
var pipeline = new MarkdownPipelineBuilder()
.UsePipeTables()
.UseTaskLists()
.UseAutoLinks()
.UseEmphasisExtras()
.UseFootnotes()
.Build();
Tables
With UsePipeTables():
var markdown = """
| Name | Age |
|---------|-----|
| Alice | 30 |
| Bob | 25 |
""";
var html = Markdown.ToHtml(markdown, pipeline);
// Produces a proper <table> with <thead> and <tbody>
Task Lists
With UseTaskLists():
var markdown = """
- [x] Write article
- [ ] Review article
- [ ] Publish
""";
var html = Markdown.ToHtml(markdown, pipeline);
// Produces checkboxes in the rendered HTML
Syntax-Highlighted Code Blocks
Markdig parses fenced code blocks with language identifiers. The rendering is up to you — Markdig produces the <code> element with a language-* CSS class:
var markdown = """
```csharp
var x = 42;
```
""";
var html = Markdown.ToHtml(markdown, pipeline);
// <pre><code class="language-csharp">var x = 42;</code></pre>
You can then apply client-side highlighting with Prism.js or highlight.js, or write a custom renderer for server-side highlighting.
Working with the AST
Markdig parses Markdown into an AST you can inspect and modify:
var document = Markdown.Parse(markdown, pipeline);
foreach (var node in document.Descendants())
{
if (node is HeadingBlock heading)
{
Console.WriteLine($"H{heading.Level}: {heading.Inline?.FirstChild}");
}
if (node is LinkInline link)
{
Console.WriteLine($"Link: {link.Url}");
}
}
This is useful for extracting metadata — table of contents generation, link validation, or image URL rewriting.
Extracting All Links
public static List<string> ExtractLinks(string markdown)
{
var pipeline = new MarkdownPipelineBuilder()
.UseAutoLinks()
.Build();
var document = Markdown.Parse(markdown, pipeline);
return document.Descendants<LinkInline>()
.Where(link => link.Url is not null)
.Select(link => link.Url!)
.ToList();
}
Generating a Table of Contents
public static List<TocEntry> GenerateTableOfContents(string markdown)
{
var document = Markdown.Parse(markdown);
return document.Descendants<HeadingBlock>()
.Select(heading =>
{
var text = heading.Inline?.FirstChild?.ToString() ?? string.Empty;
var slug = text.ToLowerInvariant()
.Replace(" ", "-")
.Replace("[^a-z0-9-]", "");
return new TocEntry(heading.Level, text, slug);
})
.ToList();
}
public record TocEntry(int Level, string Text, string Slug);
Custom Renderers
Override how specific elements render by extending the HTML renderer:
public class ExternalLinkRenderer : HtmlObjectRenderer<LinkInline>
{
protected override void Write(HtmlRenderer renderer, LinkInline link)
{
renderer.Write("<a href=\"").Write(link.Url ?? "").Write("\"");
if (link.Url?.StartsWith("http") == true)
{
renderer.Write(" target=\"_blank\" rel=\"noopener noreferrer\"");
}
renderer.Write(">");
renderer.WriteChildren(link);
renderer.Write("</a>");
}
}
Register the custom renderer:
var pipeline = new MarkdownPipelineBuilder().Build();
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
renderer.ObjectRenderers.Replace<LinkInline>(new ExternalLinkRenderer());
pipeline.Setup(renderer);
var document = Markdown.Parse(markdown, pipeline);
renderer.Render(document);
var html = writer.ToString();
This adds target="_blank" and rel="noopener noreferrer" to all external links automatically.
Converting HTML Back to Markdown
Markdig focuses on Markdown-to-HTML. For the reverse direction, you would need a separate library like ReverseMarkdown. However, because Markdig gives you the AST, you can write custom renderers that output any format — plain text, LaTeX, or even another Markdown dialect.
Performance
Markdig is one of the fastest Markdown parsers available in .NET. It processes input in a single pass and minimises allocations. For high-throughput scenarios, reuse the MarkdownPipeline instance — it's thread-safe and designed to be created once:
// Create once, reuse for all conversions
private static readonly MarkdownPipeline Pipeline =
new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
public string RenderArticle(string markdown)
=> Markdown.ToHtml(markdown, Pipeline);
Markdig is a solid foundation for any .NET application that needs to process Markdown, from simple content rendering to full CMS pipelines with custom transformations.