How to Use IronWord with Claude Code
Each November, an insurer sends renewal letters to its policyholders. The compliance team manages a single Word template for the letter's wording, while names, dates, premiums, and coverage details are sourced from the policy system. A merged letter that looks complete does not prove the merge worked. In the probes behind this guide, merges that left a blank coverage row, dropped a reference number, or reused the first customer's data all finished without an exception. Claude Code, the agentic coding tool from Anthropic, reads a codebase, edits files, and runs commands from a terminal, VS Code, a JetBrains IDE, the desktop app, or a browser.
IronWord populates MERGEFIELD placeholders in DOCX templates using dictionaries, data rows, or data sets, and supports repeating regions for table rows. It does not require Microsoft Office. The merge itself takes a few lines of code. The order of the calls and the default options determine whether the output is correct, and some mistakes produce wrong output without raising an exception. The four tested prompts below set up Claude Code to produce a batch of renewal letters, and each one is followed by the IronWord behavior it relies on.
QuickstartStart claude in the project folder and run /model opusplan once, before any prompt. An @ before a path includes that file's content in the request.
Add the IronWord NuGet package, version 2026.10.4, to RenewalLetters.csproj.
Then write one renewal letter in Program.cs.
Merge the first customer row of @Data/customers.csv into @Templates/renewal-letter.docx.
Save the letter to out/letters/NM-104233.docx.
Create that folder first, and pass the path to the save call.
Don't change the template file.
Next, open the saved letter in a new WordDocument and print these in order:
1. The body text.
2. The text of every table cell, row by row, with each cell cast to TableCell to read it.
3. The names of any merge fields still in the document, or "none".
My license key is in the IRONWORD_LICENSE_KEY environment variable.
Have the program read that variable itself and apply the key before the first IronWord call.
Don't print, echo or save the key anywhere.
Don't list environment variables to look for it either.
Tell me whether it's set and nothing more.
Review the table output before the body text. The coverage rows are in a repeating region, which only the region call populates.
Minimal Workflow (5 steps)
- Download the C# Word library from NuGet
- Open Claude Code from the project folder and set the model with /model
- Record IronWord's merge rules in CLAUDE.md so that every session loads them
- Request one letter per data row, with the region rows populated before the other fields
- Reopen each saved letter, verify its text and table cells against the source data, and inspect its header and footer parts
What does Claude Code do in a .NET Word document project?
Claude Code writes the merge service and produces one letter per policy from the two CSV files. It then reopens each saved letter and checks it. When a check fails, it revises the merge code and reruns the batch. The developer decides what a correct letter contains and reviews the audit file at the end.
Name the IronWord package and its version in the first prompt. Attackers register malicious packages under the package names that language models hallucinate, a technique called slopsquatting.
How do you set up Claude Code?
Anthropic's quickstart covers installation and sign-in. Setup is complete once claude opens in a terminal and responds.
How do you set up a project Claude Code can work in?
Put real inputs in the repository first. The fixture used in this guide consists of a .NET 9 console application that compiles, a Templates folder containing the renewal letter with its merge fields, and a Data folder with one CSV of customers and one of coverage rows keyed by policy number.
Claude Code loads CLAUDE.md into each new session, so its rules apply to all later prompts. After the Quickstart installs the package, a second prompt creates that file and a reviewer for the merge code.
Create CLAUDE.md in the repository root and give it a section called Mail merge.
Every later session should find these rules in it:
- IronWord is the only DOCX library this project may use.
- The NuGet package is IronWord, at the version RenewalLetters.csproj references now.
- Look up IronWord member names in docs/IronWord.xml.
- Templates/ is read-only, and each letter starts from a new load of the template file.
- The license key rules from my first message, written out in full.
- A letter isn't finished until the saved file has been reopened and checked.
Copy docs/IronWord.xml now from the ironword folder of the local NuGet cache.
If the file isn't there, stop and say so.
Then add a subagent at .claude/agents/merge-auditor.md.
Give it the Read, Grep and Glob tools and no others.
Its job is to list every IronWord type and member used under Services/.
It should name each one that docs/IronWord.xml doesn't document.
IronSoftware.* types such as ITableCell are documented in other packages.
Have it list those separately, so it doesn't call them missing.
Claude Code watches .claude/agents for new subagent files, but the watcher covers only folders present at launch, so create that folder before starting the session. For documentation beyond the member list, agents can start from IronWord's llms.txt, which links the how-to guides and troubleshooting pages.
Address licensing before the first save. Without a key, every save call throws a LicensingException that reads "Production License Required". This applies to Save with or without a path or stream, and to SaveAs. The IronWord License Keys page covers trial and paid keys and how to set one in code or in a configuration file.
Which IronWord mail merge rules belong in CLAUDE.md?
A probe on the fixture template checked each line against IronWord 2026.10.4, with a license key for the last three. Add the block under the Mail merge section.
IronWord 2026.10.4 behaves this way in a mail merge:
- MailMerge.Execute(DataTable) merges the first row only.
- Merge each row into its own WordDocument, loaded fresh from the template file.
- A merge removes the fields it fills, so a second Execute on the same document changes nothing.
- Call ExecuteWithRegions before Execute.
- Execute removes any region it has no table for, because RemoveUnusedRegions defaults to true.
- That region's row then stays in the table with empty cells.
- A region table with no rows removes the region row and keeps the rest of the table.
- RemoveUnusedFields defaults to true, so a field with no matching key is deleted with no error.
- Compare GetFieldNames() with the data columns before merging.
- Field names match without regard to case unless CaseInsensitiveFieldNames is set to false.
- NullValueReplacement replaces DBNull values. An empty string merges as an empty string.
- WordDocument.ExtractText() returns body paragraphs only, joined with no separator.
- ExtractText() skips table cells, the header and the footer.
- Rows[i].Cells holds ITableCell values, which have no ExtractText().
- Cast each cell to TableCell before calling ExtractText() on it.
- GetFields() and GetFieldNames() don't see merge fields in the header.
- Save each letter with Save(outputPath).
- One troubleshooting page reports a header that SaveAs(outputPath) dropped and Save kept.
- Never call Save() with no argument. It overwrites the file the document was loaded from.
The unused field rule produced the least visible failure in the probes. The template contains "Reference" followed by a reference-code field with no matching column in the customer file, and the default merge changed the sentence to "Reference . Quote this number when you call us." without raising an exception.
How do you merge a Word template with repeating table rows?
Mark the repeating row in the template with a start field and an end field that share a region name. In the fixture template, the coverage row begins with TableStart:Coverages and ends with TableEnd:Coverages. The region call accepts a data table whose name matches the region and copies the row once for each data row. The value call then populates every field outside the region from a dictionary or a single data row.
The mail merge guide documents each overload and merge option.
How do you prompt for IronWord code that compiles?
The merger and its audit go in one prompt, sent in plan mode so that the design is settled before any file changes. Cycle with Shift+Tab to plan mode and send the prompt once the status bar shows it. With opusplan, Opus handles the plan and Sonnet writes the code after approval.
Build the batch in Services/LetterMerger.cs and Services/LetterAudit.cs.
Run both from Program.cs after the Quickstart letter.
LetterMerger reads @Data/customers.csv and @Data/coverages.csv.
For each customer it does this:
1. Loads Templates/renewal-letter.docx again.
2. Fills the Coverages region with that policy's rows, if any.
3. Fills the other fields from the customer row.
4. Saves out/letters/<PolicyNumber>.docx.
Before the first merge, it prints each template merge field with no column in either CSV.
LetterAudit opens each saved letter from disk and passes it when these all hold:
- Every customer value appears in the body text.
- The coverage table holds only that policy's rows, in CSV order.
- word/document.xml, read as a zip entry, has no merge field or MERGEFIELD instruction left.
For an empty customer value, check its whole sentence.
One case is the agent sentence with no phone number.
Two more columns don't count toward a pass:
- Header, from word/header1.xml: shows the policy number, still has the merge field, or is missing.
- Footer, from word/footer1.xml: matches the template's footer, changed, or is missing.
Write out/letter-audit.csv with one row per letter.
Print how many letters pass.
After the batch, run git status on Templates/ and tell me about any change.
Don't use a docx skill or rewrite any package XML by hand.
The audit can read parts as zip entries.
Keep fixing the merger until every letter passes.
If a header keeps its field, tell me whether any IronWord call reached it.
Then have the merge-auditor subagent review Services/.
Two parts of the prompt depend on how IronWord and Claude Code behave.
- The audit reads the saved package: the header and footer are not included in the extracted text or the field list. Their columns are excluded from the pass count because no IronWord merge call populates the header, so counting it would lead the agent to the XML workaround that the prompt prohibits.
- A read-only reviewer: custom subagents load CLAUDE.md, as do all built-in agents except Explore and Plan, so the merge-auditor reviews Services/ against the Mail merge rules.
Can IronWord merge code be tested without a license key?
Yes, as long as the test stops before the save. Loading the template, merging it, and reading the text back all run unlicensed, so a build agent with no key can still run the merge tests.
Add a RenewalLetters.Tests xUnit project that tests LetterMerger without writing any file.
Merge NM-104267 and NM-104288 in memory.
Assert the body text and every coverage table cell against the CSV rows.
Cover the policy with no coverage rows and the agent sentence with an empty phone number.
A few things about the setup:
- Split the merge from the save in LetterMerger if a test needs that.
- Leave the license key unset when the variable is missing.
- Put the test project in its own folder, with no solution file at the repository root.
- Keep RenewalLetters.csproj from compiling the files in that folder.
- Have the test project copy Templates/ and Data/ to its output folder.
Run dotnet test on the test project once, in a child process with no IRONWORD_LICENSE_KEY variable.
Show me the tests passing without a key.
What should you verify before trusting generated IronWord code?
Compare each value with a contains test on the extracted body text, because its paragraphs are returned without line breaks between them.
Add three checks to the review.
- The build:
dotnet buildfails on any member name that IronWord lacks. The shipped IronWord.xml also lists members that are not public, so a name found there can still fail with CS1061, as two WordDocument members did in the probe. - The template: compare it with its committed copy after the batch. In our licensed probe, a single save with no argument replaced the template with the first customer's merged letter, so every subsequent load of that file contained no fields to fill.
- The header: the header troubleshooting page tells readers to open their own output and check the header. The same probe found that every save call preserved the header and footer byte for byte.
The AI coding assistants guide holds a complete IronWord task whose pass criteria are fixed, so any reader can rerun it.
What did the prompts produce in a real run?
Claude Code ran the four prompts in one session, in order. The Mail merge block went into CLAUDE.md before prompt 3. No code was edited by hand. The data held five customers and ten coverage rows. A clean Release rebuild produced the same audit file. Our package inspection matched every row.
- The Quickstart letter, merged with values only, kept the header row and one empty row.
- All 5 batch letters passed the first audit.
- The letters held 3, 1, 4, 0, and 2 coverage rows, as in the CSV.
- The preflight reported one field with no data column, ReferenceCode.
- The policy number field stayed in all 5 headers.
- Each footer matched the template.
- 4 of 4 tests passed with IRONWORD_LICENSE_KEY absent from the process.
- The subagent found no IronWord member missing from IronWord.xml.
- The template's hash was unchanged after the batch.
- Every prompt ended with a clean Release build.
- The license key was absent from every transcript, session file, and repository file of this run.
- In prompts 1 and 4, a shell test printed only whether the key was set.
- Some steps ran differently from the page. Because the session ran headless, the operator approved the prompt 3 plan with a typed reply. Prompt 3 changed the Quickstart's SaveAs call to Save to follow CLAUDE.md.
In the Cursor guide, Cursor also plans before it edits any file, and it writes the plan to an editable file. The ChatGPT Codex guide stores its standing instructions in AGENTS.md, the Codex counterpart of CLAUDE.md.
The shared run record gives this run's Claude Code release, models, duration, and test machine.
Troubleshooting
- Mail Merge Word Documents if a region or field does not fill
- Header Disappears After SaveAs if a merged letter loses its header
- IronWord License in web.config if the key in configuration is ignored
Questions?
If you have any questions, reach out to support@ironsoftware.com
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.