9.6 KiB
title
| title |
|---|
| Usage |
Usage
This guide covers the core Markdig workflow: parsing Markdown, rendering output, and understanding how the pipeline ties everything together.
The Markdown static class
The Markdown static class is the main entry point. It provides several methods:
{.table}
| Method | Description |
|---|---|
Markdown.ToHtml(...) |
Convert Markdown to HTML |
Markdown.Parse(...) |
Parse Markdown to an AST (MarkdownDocument) |
Markdown.ToPlainText(...) |
Convert Markdown to plain text |
Markdown.Normalize(...) |
Normalize Markdown to a canonical form |
Markdown.Convert(...) |
Convert using any custom IMarkdownRenderer |
document.ToHtml(...) |
Extension method — render a parsed document to HTML |
All methods optionally accept a MarkdownPipeline and a MarkdownParserContext.
Parse + Render: the two-phase model
Markdig uses a two-phase model:
- Parse — Convert Markdown text into an Abstract Syntax Tree (AST).
- Render — Walk the AST and produce output (HTML, plain text, or any custom format).
using Markdig;
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
// Phase 1: Parse
var document = Markdown.Parse(markdownText, pipeline);
// Phase 2: Render
var html = document.ToHtml(pipeline);
The convenience method Markdown.ToHtml(string, pipeline) does both phases in a single call, but understanding the separation is important for advanced use.
Why the same pipeline must be passed to both Parse and Render
This is the most common mistake new users make. The pipeline serves two distinct purposes:
- During parsing, extensions register custom
BlockParserandInlineParserobjects that produce extension-specific AST nodes (e.g.,MathInline,TaskList,Table). - During rendering, extensions register custom
ObjectRenderersthat know how to convert those AST nodes into output (e.g.,HtmlMathInlineRenderer,HtmlTaskListRenderer).
If you parse with one pipeline but render with another (or with no pipeline), the renderer won't know how to handle the extension-specific nodes — they'll be silently skipped or produce incorrect output.
// ✅ Correct — same pipeline for parse and render
var pipeline = new MarkdownPipelineBuilder().UsePipeTables().Build();
var document = Markdown.Parse(markdownText, pipeline);
var html = document.ToHtml(pipeline);
// ❌ Wrong — pipeline mismatch
var parsePipeline = new MarkdownPipelineBuilder().UsePipeTables().Build();
var renderPipeline = new MarkdownPipelineBuilder().Build(); // missing PipeTables
var document = Markdown.Parse(markdownText, parsePipeline);
var html = document.ToHtml(renderPipeline); // Tables won't render correctly!
// ❌ Also wrong — no pipeline for render
var document = Markdown.Parse(markdownText, pipeline);
var html = document.ToHtml(); // Uses default pipeline — no extensions!
Rule of thumb: Create the pipeline once, store it, and pass the same instance everywhere. Pipelines are thread-safe and immutable after building.
The MarkdownPipeline
The MarkdownPipeline is an immutable, thread-safe object that holds:
- The collection of block parsers (identify block-level elements like paragraphs, headings, lists)
- The collection of inline parsers (identify inline elements like emphasis, links, code spans)
- The list of registered extensions (which add/modify parsers and renderers)
- Configuration flags (trivia tracking, precise source location, etc.)
You create a pipeline using the MarkdownPipelineBuilder:
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions() // Enable extensions
.UsePreciseSourceLocation() // Track precise source spans
.Build(); // Produce the immutable pipeline
Once built, the pipeline can be reused across threads and calls.
The MarkdownPipelineBuilder
The builder provides a fluent API for configuration. All extension methods return the builder for chaining:
var pipeline = new MarkdownPipelineBuilder()
.UsePipeTables()
.UseFootnotes()
.UseEmphasisExtras()
.UseAutoLinks()
.UseGenericAttributes() // Must be last (modifies other parsers)
.Build();
Configuration options
{.table}
| Method | Description |
|---|---|
.UseAdvancedExtensions() |
Enable most extensions at once |
.UsePreciseSourceLocation() |
Map AST nodes to their exact source location |
.EnableTrackTrivia() |
Track whitespace and trivia for roundtripping |
.ConfigureNewLine(string) |
Set the newline string for output |
.DisableHeadings() |
Disable ATX and Setex heading parsing |
.DisableHtml() |
Disable HTML block and inline HTML parsing |
Caution
Markdig is a Markdown processor, not an HTML sanitizer. Disabling HTML parsing reduces risk from raw HTML input, but it does not make rendering untrusted Markdown to HTML "safe" by itself. If you accept user-provided Markdown, sanitize the generated HTML and consider filtering/rewriting link and image URLs.
Extension ordering
Extensions are applied in the order they are added. Most extensions are order-independent, but a few need specific positioning:
UseGenericAttributes()should be last — it modifies other parsers to support{.class #id}syntax.- Extensions that modify the same parser (e.g., adding emphasis characters) should be aware of potential conflicts.
Dynamic configuration with strings
For scenarios where extensions are configured at runtime (e.g., from a config file), use the Configure method:
var pipeline = new MarkdownPipelineBuilder()
.Configure("common+pipetables+footnotes+figures")
.Build();
Available extension tokens: common, advanced, alerts, pipetables, gfm-pipetables, emphasisextras, listextras, hardlinebreak, footnotes, footers, citations, attributes, gridtables, abbreviations, emojis, definitionlists, customcontainers, figures, mathematics, bootstrap, medialinks, smartypants, autoidentifiers, tasklists, diagrams, nofollowlinks, noopenerlinks, noreferrerlinks, nohtml, yaml, nonascii-noescape, autolinks, globalization, cjk-friendly-emphasis.
The MarkdownParserContext
For advanced scenarios, a MarkdownParserContext lets you pass per-call state to parsers:
var context = new MarkdownParserContext();
// Extensions or custom parsers can read properties from the context
var document = Markdown.Parse(markdownText, pipeline, context);
The context is useful when custom parsers need external information (e.g., base URLs for link resolution).
Thread safety
The MarkdownPipeline is thread-safe and should be shared. Do not create a new pipeline for every call — building a pipeline has a cost (extension setup, parser allocation).
// ✅ Good — build once, use everywhere
private static readonly MarkdownPipeline Pipeline =
new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
public string RenderMarkdown(string input)
=> Markdown.ToHtml(input, Pipeline);
The Markdown.ToHtml(string, pipeline) and Markdown.Parse(string, pipeline) methods are thread-safe when given a shared pipeline.
Outputting to a TextWriter
For streaming output (e.g., directly to an HTTP response), use the TextWriter overloads:
using var writer = new StreamWriter(responseStream);
// Returns the parsed MarkdownDocument
var document = Markdown.ToHtml(markdownText, writer, pipeline);
This avoids building the complete HTML string in memory.
Rendering to other formats
Markdig's architecture separates parsing from rendering, so you can render the same AST to different formats:
// Render to HTML
var html = document.ToHtml(pipeline);
// Render to plain text
var plainText = Markdown.ToPlainText(markdownText, pipeline);
// Render to normalized Markdown
var normalized = Markdown.Normalize(markdownText, pipeline: pipeline);
// Render to a custom format
Markdown.Convert(markdownText, myCustomRenderer, pipeline);
See the Renderers guide for how to implement custom renderers.
Common patterns
Parse once, render multiple times
var document = Markdown.Parse(markdownText, pipeline);
// Render to HTML
var html = document.ToHtml(pipeline);
// Analyze the AST
var headings = document.Descendants<HeadingBlock>().ToList();
var links = document.Descendants<LinkInline>().Where(l => !l.IsImage).ToList();
Extract metadata from the AST
using Markdig;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
var pipeline = new MarkdownPipelineBuilder()
.UseAdvancedExtensions()
.Build();
var document = Markdown.Parse(markdownText, pipeline);
// Get all headings
foreach (var heading in document.Descendants<HeadingBlock>())
{
// Extract the heading text
var text = heading.Inline?.FirstChild?.ToString();
Console.WriteLine($"H{heading.Level}: {text}");
}
// Get all links
foreach (var link in document.Descendants<LinkInline>())
{
Console.WriteLine($"Link: {link.Url} (image: {link.IsImage})");
}
Modify the AST before rendering
var document = Markdown.Parse(markdownText, pipeline);
// Add a CSS class to all tables
foreach (var table in document.Descendants<Table>())
{
table.GetAttributes().AddClass("table table-striped");
}
// Render the modified document
var html = document.ToHtml(pipeline);
Next steps
- CommonMark syntax — Core Markdown syntax reference
- Extensions — All built-in extensions
- Developer guide — Create custom parsers and renderers