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:

Example.cs
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:

Example.cs
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:

Example.cs
var pipeline = new MarkdownPipelineBuilder()
    .UsePipeTables()
    .UseTaskLists()
    .UseAutoLinks()
    .UseEmphasisExtras()
    .UseFootnotes()
    .Build();

Tables

With UsePipeTables():

Example.cs
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():

Example.cs
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:

Example.cs
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:

Example.cs
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.

Example.cs
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

Example.cs
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:

Example.cs
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:

Example.cs
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:

Example.cs
// 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.