---
title: "Convert HTML to DOCX in C#"
url: https://recursiveai.org/onedocument/cookbook/convert-html-to-docx/
description: "Convert HTML to DOCX in C# with OneDocument, a free, open-source .NET library. Runnable example, download and actual output."
version: "1.0.0"
---

# Convert HTML to DOCX 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.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneDocument.Documents) · [GitHub](https://github.com/recursive-ai-oss/onedocument-csharp) · [Docs](https://recursiveai.org/onedocument/docs/) · [Download example (.zip)](https://recursiveai.org/onedocument/cookbook/convert-html-to-docx/convert-html-to-docx.zip)

Turn HTML headings, paragraphs, links and tables into editable Word content, not a screenshot of a web page.

```shell
dotnet add package RecursiveAI.OneDocument.Documents
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using RecursiveAI.OneDocument.Documents;

using var doc = Document.FromHtml(
    "<h1>Report</h1><p>Hello.</p>");
doc.SaveAs("report.docx");
```

Writes `report.docx` containing the “Report” heading and “Hello.” paragraph. The snippet prints no console output.

Unzip [the example](https://recursiveai.org/onedocument/cookbook/convert-html-to-docx/convert-html-to-docx.zip), then `dotnet run`.

## Full program

The full program embeds all its HTML: a “Quarterly report” heading, a paragraph linking to `https://example.com/report`, a two-column table with North/1200, and a script that is not imported. No input download or network request is needed.

```csharp
using RecursiveAI.OneDocument.Documents;

const string html = """
<h1>Quarterly report</h1>
<p>Read the <a href="https://example.com/report">report</a>.</p>
<table><tr><th>Region</th><th>Revenue</th></tr>
<tr><td>North</td><td>1200</td></tr></table>
<script>alert('not imported');</script>
""";
using var doc = Document.FromHtml(html);
foreach (var loss in doc.ImportLoss!.Items)
    Console.WriteLine($"{loss.Code}: {loss.Count}");
doc.SaveAs("report.docx");
using var reopened = Document.Open("report.docx");
foreach (var paragraph in reopened.Paragraphs)
    Console.WriteLine(paragraph.Text);
Console.WriteLine($"Tables: {reopened.Tables.Count()}");
Console.WriteLine($"Links: {reopened.Paragraphs.SelectMany(p => p.Inlines().OfType<Hyperlink>()).Count()}");
```

```text
loss.html-script: 1
Quarterly report
Read the report.
Region
Revenue
North
1200

Tables: 1
Links: 1
```

The import loss report lists the dropped script. Input beyond the configured XML-depth or part-size bounds can throw `OfficeLimitException`.

## Variations

**Read an HTML file instead of a string.** This creates its own small HTML input file.

```csharp
using RecursiveAI.OneDocument.Documents;

File.WriteAllText("page.html", "<p>Saved HTML input.</p>");
using var doc = Document.Open("page.html");
doc.SaveAs("from-file.docx");
Console.WriteLine(doc.Paragraphs.First().Text);
```

```text
Saved HTML input.
```

## Related recipes

- [Create a Word document](https://recursiveai.org/onedocument/cookbook/create-word-document/)
- [Add a table to Word](https://recursiveai.org/onedocument/cookbook/add-table-to-word/)

---

## The short answer

Call `Document.FromHtml(html)` and save the result with a `.docx` extension. Read `ImportLoss.Items` before saving to see reported content that did not carry over. For a file on disk, `Document.Open("page.html")` recognizes HTML by its content.

## How to convert HTML to DOCX?

Use the string entry point for HTML you already hold in memory, or the file entry point shown in the variation. Saving as DOCX serializes the imported word-processing document. The [HTML documentation](https://recursiveai.org/onedocument/docs/html/) lists the mapping from elements and computed styles to Word structures.

Headings become heading styles. Paragraph-like blocks with inline content become paragraphs. Links become hyperlink relationships or internal anchors. Tables retain rows and cells, including documented mappings for merged cells, repeated header rows, borders, shading and widths. These are document structures you can edit after import; they are not retained as a live HTML page.

The example prints table-cell text because paragraph enumeration includes paragraphs inside tables. It then separately counts top-level tables and paragraph hyperlinks. Seeing those cell values in the text output does not mean the table was flattened: the saved document still has a table.

## How can I convert HTML code to a Word document?

Keep the HTML self-contained when you want predictable inputs. Inline `style` attributes and `style` elements participate in the computed styles read by the importer. The documented subset includes bold, italic, underline, font family and size, text and background colors, paragraph alignment, indents, spacing, borders and shading. Lists become numbering definitions, and preformatted content preserves spaces and line breaks.

Do not treat that subset as a browser layout guarantee. This path maps content and selected computed properties into DOCX. It does not execute JavaScript to generate missing page content. Forms, embedded frames, media, inline SVG and MathML have documented loss codes rather than an interactive Word equivalent. Check the report against what your application needs before accepting the output.

The report groups repeated losses by code and accumulates their counts. An empty report is useful information about reported conversion losses, but should not be used as a visual comparison of the HTML and Word layouts. Open representative output in your target document application when page appearance matters.

## Can I convert HTML to DOCX for free in C#?

The downloadable project uses the published `RecursiveAI.OneDocument.Documents` package under MIT OR Apache-2.0. It runs locally with .NET 10 and does not need a service account or conversion API key. The example includes the source and its package version so you can change the input without using a hosted converter.

External resources need an explicit policy. Import does not fetch linked style sheets or open image URLs on its own. A linked style sheet is reported as dropped. Images embedded as supported `data:` URIs can become inline pictures; an external image must be supplied through `HtmlImportOptions.ImageResolver` or is reported, with its alt text retained.

That resolver receives the source string and returns bytes. If you add one, your code decides which resources may be read; the import operation itself does not become a web crawler. The self-contained fixture avoids that extra dependency entirely. For an actual web page, supply the HTML and resources you intend to convert rather than expecting browser navigation, script execution or remote asset loading.
