How to Mail Merge Word Documents in C#

IronWord performs mail merge in C# by populating MERGEFIELD placeholders in a Word template with your data, drawn from dictionaries, DataTable/DataRow/DataSet objects, or repeating-region tables, all without Microsoft Office Interop. Merge fields created in Microsoft Word, or any compatible tool, are detected automatically.

Mail merge is the standard way to generate personalized documents at scale: form letters, invoices, certificates, contracts, and reports that share one template but differ per recipient. Instead of editing each document by hand, you author a single .docx template with merge fields and let IronWord fill them from your data source. Every operation is reached through the WordDocument.MailMerge entry point.

Quickstart: Mail Merge a Word Document

Load a template that contains MERGEFIELD placeholders, pass a dictionary of field names and values to Execute, and save the result.

  1. Install IronWord with NuGet Package Manager

    PM > Install-Package IronWord
  2. Copy and run this code snippet.

    WordDocument doc = new WordDocument("template.docx");
    doc.MailMerge.Execute(new Dictionary<string, string> { { "FirstName", "Jane" }, { "Company", "Acme Corp" } });
    doc.SaveAs("output.docx");
  3. Deploy to test on your live environment

    Start using IronWord in your project today with a free trial

    arrow pointer


How Do I Mail Merge from a Dictionary?

The simplest data source is an IDictionary<string, string> keyed by merge-field name. Execute replaces every field whose name matches a key.

WordDocument doc = new WordDocument("template.docx");
doc.MailMerge.Execute(new Dictionary<string, string>
{
    { "FirstName", "Jane" },
    { "LastName",  "Smith" },
    { "Company",   "Acme Corp" }
});
doc.SaveAs("output.docx");
WordDocument doc = new WordDocument("template.docx");
doc.MailMerge.Execute(new Dictionary<string, string>
{
    { "FirstName", "Jane" },
    { "LastName",  "Smith" },
    { "Company",   "Acme Corp" }
});
doc.SaveAs("output.docx");
Dim doc As New WordDocument("template.docx")
doc.MailMerge.Execute(New Dictionary(Of String, String) From {
    {"FirstName", "Jane"},
    {"LastName", "Smith"},
    {"Company", "Acme Corp"}
})
doc.SaveAs("output.docx")
$vbLabelText   $csharpLabel

TipsField-name lookups are case-insensitive by default, matching Microsoft Word. Set MailMerge.Options.CaseInsensitiveFieldNames = false to require an exact-case match.

You can also supply two parallel sequences of field names and values:

doc.MailMerge.Execute(
    new[] { "FirstName", "LastName" },
    new[] { "Jane", "Smith" });
doc.MailMerge.Execute(
    new[] { "FirstName", "LastName" },
    new[] { "Jane", "Smith" });
doc.MailMerge.Execute(  
    New String() { "FirstName", "LastName" },  
    New String() { "Jane", "Smith" })
$vbLabelText   $csharpLabel

WarningExecute(fieldNames, values) throws an ArgumentException when the two sequences have different lengths.

How Do I Mail Merge from a DataTable or DataRow?

When data comes from a database or an existing DataSet, pass a DataTable or DataRow straight to Execute. Merge-field names are matched to column names. Execute(DataTable) uses the values from the table's first row, while Execute(DataRow) uses the column names of the row's parent table.

DataTable table = new DataTable();
table.Columns.Add("FirstName");
table.Columns.Add("LastName");
DataRow row = table.NewRow();
row["FirstName"] = "Jane";
row["LastName"]  = "Smith";
table.Rows.Add(row);

WordDocument doc = new WordDocument("template.docx");
doc.MailMerge.Execute(row);
doc.SaveAs("output.docx");
DataTable table = new DataTable();
table.Columns.Add("FirstName");
table.Columns.Add("LastName");
DataRow row = table.NewRow();
row["FirstName"] = "Jane";
row["LastName"]  = "Smith";
table.Rows.Add(row);

WordDocument doc = new WordDocument("template.docx");
doc.MailMerge.Execute(row);
doc.SaveAs("output.docx");
Imports System.Data

Dim table As New DataTable()
table.Columns.Add("FirstName")
table.Columns.Add("LastName")
Dim row As DataRow = table.NewRow()
row("FirstName") = "Jane"
row("LastName") = "Smith"
table.Rows.Add(row)

Dim doc As New WordDocument("template.docx")
doc.MailMerge.Execute(row)
doc.SaveAs("output.docx")
$vbLabelText   $csharpLabel

How Do I Fill Repeating Table Regions?

Repeating regions turn one template row into many, which is ideal for invoice line items or order lists. In the template, wrap the repeating content between two marker fields named TableStart:RegionName and TableEnd:RegionName. Then call ExecuteWithRegions, which repeats the region's content once per row of the matching table.

ExecuteWithRegions accepts a full DataSet (expanding every region whose name matches a table), a single DataTable (expanding the region whose name matches dataTable.TableName), or a region name together with a DataTable.

DataTable orders = new DataTable("Orders");
orders.Columns.Add("Item");
orders.Columns.Add("Qty");
orders.Rows.Add("Widget A", "3");
orders.Rows.Add("Widget B", "1");

DataSet ds = new DataSet();
ds.Tables.Add(orders);

WordDocument doc = new WordDocument("invoice-template.docx");
doc.MailMerge.ExecuteWithRegions(ds);
doc.MailMerge.Execute(new Dictionary<string, string> { { "CustomerName", "Jane Smith" } });
doc.SaveAs("invoice.docx");
DataTable orders = new DataTable("Orders");
orders.Columns.Add("Item");
orders.Columns.Add("Qty");
orders.Rows.Add("Widget A", "3");
orders.Rows.Add("Widget B", "1");

DataSet ds = new DataSet();
ds.Tables.Add(orders);

WordDocument doc = new WordDocument("invoice-template.docx");
doc.MailMerge.ExecuteWithRegions(ds);
doc.MailMerge.Execute(new Dictionary<string, string> { { "CustomerName", "Jane Smith" } });
doc.SaveAs("invoice.docx");
Imports System.Data
Imports System.Collections.Generic

Dim orders As New DataTable("Orders")
orders.Columns.Add("Item")
orders.Columns.Add("Qty")
orders.Rows.Add("Widget A", "3")
orders.Rows.Add("Widget B", "1")

Dim ds As New DataSet()
ds.Tables.Add(orders)

Dim doc As New WordDocument("invoice-template.docx")
doc.MailMerge.ExecuteWithRegions(ds)
doc.MailMerge.Execute(New Dictionary(Of String, String) From {{"CustomerName", "Jane Smith"}})
doc.SaveAs("invoice.docx")
$vbLabelText   $csharpLabel

Please noteExecuteWithRegions only populates repeating regions. To also fill standard merge fields outside the regions, call Execute(...) after expanding the regions, as shown above.

How Do I Inspect a Template's Merge Fields?

Before merging, you can discover what a template contains, which is useful for validating templates or building the data source dynamically.

  • GetFieldNames() returns the names of all value-style merge fields, in document order and deduplicated.
  • GetRegionNames() returns the names of all TableStart regions declared in the document.
  • GetFields() returns every field, including TableStart/TableEnd markers, as MergeField objects.
WordDocument doc = new WordDocument("template.docx");
IReadOnlyList<string> fieldNames = doc.MailMerge.GetFieldNames();
IReadOnlyList<string> regionNames = doc.MailMerge.GetRegionNames();
// fieldNames:  ["FirstName", "LastName", "Company"]
// regionNames: ["Orders", "LineItems"]
WordDocument doc = new WordDocument("template.docx");
IReadOnlyList<string> fieldNames = doc.MailMerge.GetFieldNames();
IReadOnlyList<string> regionNames = doc.MailMerge.GetRegionNames();
// fieldNames:  ["FirstName", "LastName", "Company"]
// regionNames: ["Orders", "LineItems"]
Dim doc As New WordDocument("template.docx")
Dim fieldNames As IReadOnlyList(Of String) = doc.MailMerge.GetFieldNames()
Dim regionNames As IReadOnlyList(Of String) = doc.MailMerge.GetRegionNames()
' fieldNames:  ["FirstName", "LastName", "Company"]
' regionNames: ["Orders", "LineItems"]
$vbLabelText   $csharpLabel

Each MergeField exposes its Name, the full Instruction text as stored in the document (for example MERGEFIELD FirstName \* MERGEFORMAT), a RegionName that is set only for region markers (for example "Orders" from "TableStart:Orders"), and a Kind that classifies the field as Value (text replaced with a data value), TableStart or TableEnd (the bounds of a repeating region), or NextRecord (a NEXT field that advances to the next data record within the same template body).

How Do I Control Unmatched Fields and Null Values?

Merge behavior is configured through MailMerge.Options:

Option Default Behavior
RemoveUnusedFields true Removes merge fields that have no matching key in the data source. Set to false to leave them in place.
RemoveUnusedRegions true Removes TableStart/TableEnd regions with no matching table in the data source.
NullValueReplacement "" Text substituted when the data source supplies a null value for a field.
CaseInsensitiveFieldNames true Ignores case when matching field names, matching Microsoft Word behavior.
WordDocument doc = new WordDocument("template.docx");
doc.MailMerge.Options.RemoveUnusedFields = false;
doc.MailMerge.Execute(new Dictionary<string, string> { { "FirstName", "Jane" } });
doc.SaveAs("partial-output.docx");
WordDocument doc = new WordDocument("template.docx");
doc.MailMerge.Options.RemoveUnusedFields = false;
doc.MailMerge.Execute(new Dictionary<string, string> { { "FirstName", "Jane" } });
doc.SaveAs("partial-output.docx");
Dim doc As New WordDocument("template.docx")
doc.MailMerge.Options.RemoveUnusedFields = False
doc.MailMerge.Execute(New Dictionary(Of String, String) From {{"FirstName", "Jane"}})
doc.SaveAs("partial-output.docx")
$vbLabelText   $csharpLabel

For straightforward value substitution without a data source, see how to replace text in a Word document.

Frequently Asked Questions

How do I mail merge a Word document in C#?

Load a Word template containing MERGEFIELD placeholders with the WordDocument constructor, then call doc.MailMerge.Execute with an IDictionary, DataTable, or DataRow to replace each field whose name matches a key or column, and save the result with SaveAs. Merge fields created in Microsoft Word are detected automatically, and no Microsoft Office installation is required.

Which data sources can IronWord mail merge use?

MailMerge.Execute accepts an IDictionary of field names and values, two parallel sequences of field names and values, a DataTable (using the first row), or a DataRow (using its parent table's columns). MailMerge.ExecuteWithRegions accepts a DataSet or DataTable to expand repeating table regions.

How do I fill repeating table regions in a Word mail merge?

Wrap the repeating content in the template between TableStart:RegionName and TableEnd:RegionName merge fields, then call MailMerge.ExecuteWithRegions with a DataSet or DataTable. The region's content repeats once per row of the matching table. Because ExecuteWithRegions only populates regions, call Execute afterward to fill any standard merge fields.

Can I inspect a template's merge fields before merging?

Yes. MailMerge.GetFieldNames returns the value-style field names in document order, GetRegionNames returns the TableStart region names, and GetFields returns MergeField objects exposing each field's Name, Instruction, Kind (Value, TableStart, TableEnd, or NextRecord), and RegionName.

What happens to merge fields with no matching data?

By default MailMergeOptions.RemoveUnusedFields is true, so fields with no matching key in the data source are removed from the output. Set it to false to leave them in place. Unmatched TableStart/TableEnd regions are removed when RemoveUnusedRegions is true, and null values are replaced with the NullValueReplacement string.

Is mail merge field matching case-sensitive?

No. Field-name lookups are case-insensitive by default, matching Microsoft Word behavior. Set MailMerge.Options.CaseInsensitiveFieldNames to false if you need an exact-case match.

Curtis Chau
Technical Writer

Curtis Chau holds a Bachelor’s degree in Computer Science (Carleton University) and specializes in front-end development with expertise in Node.js, TypeScript, JavaScript, and React. Passionate about crafting intuitive and aesthetically pleasing user interfaces, Curtis enjoys working with modern frameworks and creating well-structured, visually appealing manuals.

...

Read More
Ready to Get Started?
Nuget Downloads 47,210 | Version: 2026.7 just released
Still Scrolling Icon

Still Scrolling?

Want proof fast? PM > Install-Package IronWord
run a sample watch your data become a Word doc.