Fill Word merge fields from data to produce personalized DOCX letters.
dotnet add package RecursiveAI.OneDocument.DocumentsRequires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.
using RecursiveAI.OneDocument.Documents; using System.Text.Json; using var data = JsonDocument.Parse( """[{"Name":"Ada"},{"Name":"Lin"}]"""); using var doc = Document.Open("letter.docx"); using var result = doc.MailMerge(data); for (int i = 0; i < result.Documents.Count; i++) result.Documents[i].SaveAs($"letter-{i+1}.docx");
Writes letter-1.docx and letter-2.docx for Ada and Lin; the snippet prints nothing. The input file is in the zip.
Unzip the example, then dotnet run.
Full program
letter.docx is included in the example. It contains Dear «Name», where «Name» is a real Word MERGEFIELD Name field, not typed angle brackets. In Word, insert the field through Insert › Quick Parts › Field › MergeField, then enter Name as the field name. The JSON records are shown in the snippet.
using RecursiveAI.OneDocument.Documents; using RecursiveAI.OneDocument.Documents.MailMerge; using System.Text.Json; using var data = JsonDocument.Parse( """[{"Name":"Ada"},{"Name":"Lin"}]"""); using var doc = Document.Open("letter.docx"); using var result = doc.MailMerge(data); for (int i = 0; i < result.Documents.Count; i++) { string path = $"letter-{i + 1}.docx"; result.Documents[i].SaveAs(path); Console.WriteLine(path); Console.WriteLine(result.Documents[i].ExtractText().Trim()); } Console.WriteLine($"Warnings: {result.Warnings.Count}");
dotnet run prints:
letter-1.docx Dear Ada, letter-2.docx Dear Lin, Warnings: 0
A missing merge field becomes empty and adds mailmerge.field-missing to result.Warnings; unsupported merge fields stay in place with a warning.
Variations
Combine the letters
Keep the full program through Document.Open, then replace the rest with:
using var result = doc.MailMerge(data, new MailMergeOptions { Mode = MailMergeMode.SingleDocument }); result.Documents[0].SaveAs("letters.docx"); Console.WriteLine("Wrote letters.docx"); Console.WriteLine($"Documents: {result.Documents.Count}");
Output:
Wrote letters.docx Documents: 1
Related recipes
- Find and replace text in a Word document in C#
- Convert PowerPoint to PDF in C#
- Fill a Word template in C#
- Extract text from DOCX, XLSX and PPTX in C#
The short answer
Open a DOCX main document containing real Word merge fields, call doc.MailMerge(data) and save each document in the result. The default is one document per record. The complete example passes two JSON records and saves personalized letters for Ada and Lin.
A merge field is document markup, not plain text that happens to look like a placeholder. The input in this example contains a MERGEFIELD Name instruction whose displayed result is Name. Typing the characters «Name» into an ordinary paragraph does not create that field. For plain tag-based placeholders, use Word templates instead.
How do I mail merge a Word document in C#?
Prepare the main document with Word merge fields and provide a sequence of records. OneDocument accepts the same data forms as its template engine, including JSON, dictionaries and generated template models. The JSON array above makes the record boundary visible: each object supplies a Name value for one pass through the letter.
Use a file-backed main document, as in Document.Open("letter.docx"). Mail merge saves and reopens the main document once per pass. A document created only over an in-memory package is not a usable main document for this operation and raises OfficeFormatException; save it and reopen the saved file first.
The result owns its output documents, so keep it alive while saving them and dispose it afterward. The source template is not changed. An empty data sequence produces no output documents; check the count before indexing the result in code that may receive no records. The simple example has two records, so it can safely save two files.
Field names are case-insensitive. Simple and complex fields are both read, including instructions split across runs. A missing name merges as empty and adds a located mailmerge.field-missing warning. Don’t judge success only from whether files exist: inspect result.Warnings when preparing a batch for delivery. See the mail-merge reference for field switches and defect codes.
How do I split a mail merge into individual Word documents?
Keep the default PerRecord mode and save each entry in result.Documents to a distinct path, as in the complete program. This produces separate documents directly; there is no combined file to split afterward.
For the opposite task, set MailMergeOptions.Mode to MailMergeMode.SingleDocument, as in the variation. That produces one document containing every emitted pass, separated by next-page section breaks. The variation saves letters.docx and reports one output document even though the data still has two records. Output-document count and record count mean different things in this mode.
Headers and footers come from the first record’s pass. Drawing, bookmark, comment and revision identifiers are renumbered so they remain unique in the combined file. If headers need a different recipient name for every letter, this first-pass rule is a reason to prefer per-record outputs rather than assuming single-document mode duplicates every story independently.
The engine understands more than simple name fields: conditional fields, record navigation and numeric or date pictures have documented rules. NEXT can consume another record in the same pass, and SKIPIF can omit a pass. This is why one pass is not always exactly one record in a sophisticated mailing-label document.
Some Word merge fields are deliberately not expanded, including ADDRESSBLOCK, GREETINGLINE and DATABASE. They remain as written with an unsupported-field warning. Other fields such as PAGE and DATE are untouched. Use explicit supported merge fields for addresses and greetings when you need predictable output, rather than assuming the merge operation recalculates every field in the document.