Recursive AI

Convert Markdown to Word in C#

OneDocument is a free, open-source Word, Excel and PowerPoint document library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

Turn Markdown headings, paragraphs and lists into an editable Word DOCX document.

Terminal
dotnet add package RecursiveAI.OneDocument.Documents

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using RecursiveAI.OneDocument.Documents;
 
using var doc =
    Document.OpenMarkdown("notes.md");
doc.SaveAs("notes.docx");
Console.WriteLine("Wrote notes.docx");
Text
Wrote notes.docx

Unzip the example, then dotnet run.

notes.md is in the zip:

MARKDOWN
# Meeting notes
 
A **bold** decision.
 
- Ship the report
- Archive the draft

The full program

The download’s Program.cs uses the supplied samples:

C#
using RecursiveAI.OneDocument.Documents;
 
using Document doc = Document.OpenMarkdown("notes.md");
doc.SaveAs("notes.docx");
Console.WriteLine("Wrote notes.docx");
foreach (Paragraph paragraph in doc.Paragraphs)
    Console.WriteLine($"[{paragraph.Style}] {paragraph.Text}");

dotnet run prints:

Text
Wrote notes.docx
[Heading 1] Meeting notes
[Normal] A bold decision.
[List Paragraph] Ship the report
[List Paragraph] Archive the draft

Malformed Markdown can raise OfficeFormatException; resource limits raise OfficeLimitException.

Variations

Convert a Markdown string

C#
using RecursiveAI.OneDocument.Documents;
 
using var doc = Document.ParseMarkdown("# Hello\n\nA **bold** note.");
doc.SaveAs("inline.docx");
Console.WriteLine("Wrote inline.docx");

Output:

Text
Wrote inline.docx

Read the import losses

Check ImportLoss.Items for content the importer could not carry over. The supplied notes.md has no import losses.

C#
using RecursiveAI.OneDocument.Documents;
 
using var doc = Document.OpenMarkdown("notes.md");
if (doc.ImportLoss is { } losses)
{
    Console.WriteLine($"Import losses: {losses.Items.Count}");
    foreach (var loss in losses.Items)
        Console.WriteLine($"{loss.Code}: {loss.Message}");
}

Output:

Text
Import losses: 0

Embed an image with a resolver

square.png is an 8 × 8 red square included in the zip. This example’s Markdown input is ![Red square](square.png). The resolver supplies bytes only for that exact address; it does not fetch URLs or open arbitrary paths.

C#
using RecursiveAI.OneDocument.Documents;
 
var options = new MarkdownImportOptions
{
    ImageResolver = address => address == "square.png"
        ? File.ReadAllBytes("square.png") : null
};
using var doc = Document.ParseMarkdown(
    "![Red square](square.png)", options);
doc.SaveAs("illustrated.docx");
Console.WriteLine("Wrote illustrated.docx");
Console.WriteLine($"Import losses: {doc.ImportLoss!.Items.Count}");

Output:

Text
Wrote illustrated.docx
Import losses: 0

The short answer

Use Document.OpenMarkdown for a Markdown file, then save the document as .docx. For a string already in memory, use Document.ParseMarkdown, as shown in the variation. The resulting file contains editable Word paragraphs, runs and lists rather than a screenshot of the Markdown source.

OneDocument imports CommonMark with GitHub Flavored Markdown extensions. Its Markdown documentation lists the mapping between source elements and the document model, including the cases reported as import losses.

Can I convert a Markdown file to Word?

Yes. The example imports the heading as a heading paragraph, bold text as formatted runs, and the bullet list as a Word bullet list. The printed paragraph text does not include the ** markers: those control formatting in the DOCX. Likewise, a bullet is represented by document numbering, not necessarily by a literal bullet character in the paragraph text.

The importer handles headings, emphasis, strike-through, code spans and blocks, quotes, ordered and unordered lists, task-list marks, tables, links and footnotes. It is a document conversion rather than a reproduction of a website’s CSS. Code blocks become paragraphs in a source-code style, and their language information is dropped with a reported loss.

Markdown files must be named as Markdown explicitly. OpenMarkdown does that in the snippet; ParseMarkdown does it for in-memory text. A generic content sniffer does not recognize Markdown merely because a plain-text file happens to contain heading markers. Choosing the right entry point avoids treating Markdown syntax as literal document text.

How do I insert Markdown into Word?

Generate a DOCX from the Markdown with the full program, then open that output as a Word document. This approach converts markup to document structure before the user opens it. It is not an instruction to paste raw Markdown into an existing Word paragraph and expect that paragraph to interpret the syntax.

Images need an explicit resource policy. By default the importer does not read image paths or fetch image URLs. It embeds supported image bytes from a data URI or from a caller-supplied image resolver; otherwise it keeps the image description as text and records a loss. For a document that relies on diagrams or photos, review these losses before distributing the result.

Raw HTML is read through a sanitized fragment importer. Supported text and inline formatting can become document content, while scripts, styles, forms and unsupported structure are dropped and reported. This is why a Markdown document with HTML tables or custom styling may differ from its browser rendering.

The source sample on this page deliberately uses only a heading, a bold phrase and two list items, so its entire input is visible before you run the project. Start with that example, then inspect the import report and the output when adding images, HTML or other richer content. Review the features in the source, not just its .md extension.

All onedocument recipes