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.

:path=/static-assets/word/content-code-examples/how-to/mail-merge-dictionary.cs
using IronWord;
using System.Collections.Generic;

// Load a template containing MERGEFIELD placeholders
WordDocument doc = new WordDocument("template.docx");

// Replace each field whose name matches a dictionary key
doc.MailMerge.Execute(new Dictionary<string, string>
{
    { "FirstName", "Jane" },
    { "LastName",  "Smith" },
    { "Company",   "Acme Corp" }
});

doc.SaveAs("output.docx");
Imports IronWord
Imports System.Collections.Generic

' Load a template containing MERGEFIELD placeholders
Dim doc As New WordDocument("template.docx")

' Replace each field whose name matches a dictionary key
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:

:path=/static-assets/word/content-code-examples/how-to/mail-merge-parallel-sequences.cs
using IronWord;

WordDocument doc = new WordDocument("template.docx");

// Merge from two parallel sequences of field names and values
doc.MailMerge.Execute(
    new[] { "FirstName", "LastName" },
    new[] { "Jane", "Smith" });

doc.SaveAs("output.docx");
Imports IronWord

Dim doc As New WordDocument("template.docx")

' Merge from two parallel sequences of field names and values
doc.MailMerge.Execute(
    New String() { "FirstName", "LastName" },
    New String() { "Jane", "Smith" })

doc.SaveAs("output.docx")
$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.

:path=/static-assets/word/content-code-examples/how-to/mail-merge-datatable.cs
using IronWord;
using System.Data;

// Build a DataTable, for example from a database query
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");

// Merge-field names are matched to the row's column names
doc.MailMerge.Execute(row);

doc.SaveAs("output.docx");
Imports IronWord
Imports System.Data

' Build a DataTable, for example from a database query
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")

' Merge-field names are matched to the row's column names
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.

:path=/static-assets/word/content-code-examples/how-to/mail-merge-regions.cs
using IronWord;
using System.Collections.Generic;
using System.Data;

// One DataTable per repeating region, named to match the TableStart/TableEnd markers
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");

// Expand the repeating region once per row, then fill standard fields
doc.MailMerge.ExecuteWithRegions(ds);
doc.MailMerge.Execute(new Dictionary<string, string> { { "CustomerName", "Jane Smith" } });

doc.SaveAs("invoice.docx");
Imports IronWord
Imports System.Collections.Generic
Imports System.Data

' One DataTable per repeating region, named to match the TableStart/TableEnd markers
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")

' Expand the repeating region once per row, then fill standard fields
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.
:path=/static-assets/word/content-code-examples/how-to/mail-merge-inspect-fields.cs
using IronWord;
using System.Collections.Generic;

WordDocument doc = new WordDocument("template.docx");

// Discover the merge fields and regions before merging
IReadOnlyList<string> fieldNames = doc.MailMerge.GetFieldNames();
IReadOnlyList<string> regionNames = doc.MailMerge.GetRegionNames();

// fieldNames:  ["FirstName", "LastName", "Company"]
// regionNames: ["Orders", "LineItems"]
Imports IronWord
Imports System.Collections.Generic

Dim doc As New WordDocument("template.docx")

' Discover the merge fields and regions before merging
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.
:path=/static-assets/word/content-code-examples/how-to/mail-merge-options.cs
using IronWord;
using System.Collections.Generic;

WordDocument doc = new WordDocument("template.docx");

// Keep unmatched fields in the output instead of removing them
doc.MailMerge.Options.RemoveUnusedFields = false;
doc.MailMerge.Execute(new Dictionary<string, string> { { "FirstName", "Jane" } });

doc.SaveAs("partial-output.docx");
Imports IronWord
Imports System.Collections.Generic

Dim doc As New WordDocument("template.docx")

' Keep unmatched fields in the output instead of removing them
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 49,216 | 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.