Using IronWord with AI Coding Assistants
IronWord is Iron Software's DOCX library for .NET. It allows you to create, edit, style, and build tables in Word documents, and read text without Microsoft Office. Available on NuGet since December 2023, IronWord includes public guides and code examples.
Current examples do not reflect the behavior of release 2026.10.4. In this version, text element size, color, and weight are read-only. Document-wide replace does not affect table cells, and document-wide text extraction excludes tables.
There are two types of AI coding tools.
- Assistants operate within the editor. They complete lines or rewrite methods, similar to GitHub Copilot's inline suggestions.
- Agents manage complete tasks, such as editing multiple files, running builds and programs, and returning results for review, as seen with Claude Code, Cursor, ChatGPT Codex, and Google Antigravity.
Both types need the same setup, whether the task is adding text, styling, building tables, replacing placeholders, or extracting text. Install the IronWord package, read the key from an environment variable, give the assistant IronWord's machine-readable documentation, and reopen each saved DOCX before the task counts as done.
How to Use IronWord with an AI Coding Assistant
- Get the IronWord package from NuGet
- Assign
IronWord.License.LicenseKeyfromIRONWORD_LICENSE_KEYbefore the first save - Hand the assistant IronWord's llms.txt index before it writes code
- Record the IronWord rules in the instruction file your assistant loads
- Paste the one-shot setup prompt into a new session
- Build the project, reopen each saved DOCX, and compare its text with the input
Can't My AI Assistant Handle Word Documents on Its Own?
Without documentation, an assistant writes IronWord code from training data that also covers other DOCX libraries and older IronWord releases. IronWord's machine-readable documentation gives it the current API.
A one-page letter is simple. Differences between DOCX libraries appear with templates, tables, and large-scale generation.
- A faulty document saves without an error: IronWord raises no exception when a line merges into the previous paragraph or a placeholder stays in a table cell. Only reading the saved file back reveals the fault.
- Raw OpenXML requires low-level code for every feature: runs, run properties, tables, and section settings are separate XML elements, and the OpenXML SDK has a class for each, so an assistant writes long code that you then maintain. IronWord exposes them through one object model.
- Package and namespace share one name: both are
IronWord, with the model types underIronWord.Models. Registering package names that assistants hallucinate is called slopsquatting, so the prompt should give the exact package name. - Licensing terms are defined in advance: an IronWord license is perpetual, and the licensing page gives each tier's limit on developers, locations, and projects.
Step 1: Install IronWord
Ask the assistant to install the package.
Install IronWord 2026.10.4 from NuGet in this project and restore it.
Then tell me the IronWord version the restore picked.
From a terminal, pin a release.
dotnet add package IronWord --version 2026.10.4
A single package supports Windows, Linux, and macOS, as IronWord 2026.10.4 includes native layout engines for win-x64, win-x86, linux-x64, osx-x64, and osx-arm64. The Docker setup guide recommends Ubuntu, Debian, or RHEL base images and covers installing fonts in Linux containers.
Step 2: Apply Your License Key
An unlicensed program can still create a document, load one, extract its text, and replace words in memory, but every save throws IronSoftware.Exceptions.LicensingException with the message Production License Required. This covers SaveAs and every form of Save, to a path, a stream, or bytes. A mistyped key fails the save with License Key Not Recognized. A free trial key can be requested below.
Start using IronWord in your project today with a free trial.
Set the key once at startup, before any IronWord call. Reading it from an environment variable keeps it out of the repository.
IronWord.License.LicenseKey = Environment.GetEnvironmentVariable("IRONWORD_LICENSE_KEY");
IronWord.License.IsLicensed is true when the assigned key is valid, and IronWord.License.IsValidLicense tests a key string. The exception class is not public, so catch Exception and read its message.
Step 3: Point Your Assistant at IronWord's LLM Documentation
- llms.txt has a one-line entry per page. Give it to the assistant when each session starts.
- llms-full.txt puts a summary of all 46 documentation pages in one file.
- llms-full-catalog.txt adds blog articles to the index.
- The API Reference gives each member's declaration.
- skill.md is for tools that load skill files. It covers installation, licensing, code recipes, and a table of common failures.
- The AI Product Fact Sheet names the supported .NET targets and the main use cases.
Name a specific guide when a task relies on one.
Read https://ironsoftware.com/word/csharp/llms.txt before you touch IronWord code.
Use only what those pages show.
Here's how the object model works:
- A document is a WordDocument made of paragraphs, runs, and tables.
- Styling goes on the run through a TextStyle.
- Nothing reaches disk until the document is saved.
If you need a member the pages don't mention, look it up in the API reference.
It's at https://ironsoftware.com/word/csharp/object-reference/api/.
Build once to prove the call compiles.
If it still looks doubtful, tell me so instead of guessing.
With permission to answer "not documented", the assistant has a choice other than making up a method.
Step 4: Add IronWord Context to Your Project
Instructions typed in chat are gone when the session ends, so keep the IronWord rules in the project file your tool loads. That file is CLAUDE.md for Claude Code, AGENTS.md for ChatGPT Codex, .github/copilot-instructions.md for GitHub Copilot, an .mdc file in .cursor/rules/ or AGENTS.md for Cursor, and AGENTS.md or GEMINI.md for Google Antigravity. One block of text works for all of them, though Cursor rule files also take MDC frontmatter.
All DOCX work in this project uses IronWord.
The NuGet package and its namespace are both `IronWord`.
Model types live in `IronWord.Models`.
Follow these rules for every Word document:
- Start from the docs index at https://ironsoftware.com/word/csharp/llms.txt.
- Load the skill file from https://ironsoftware.com/word/csharp/skill.md.
- Build documents from `Paragraph`, `Run`, and `Table` objects.
- Add each one with `AddParagraph` or `AddTable`.
- `WordDocument.AddText` appends to the last paragraph rather than starting a new one.
- Style a run with `Run.Style = new TextStyle { ... }`.
- Give the style a `TextFont` whenever it sets `FontSize`.
- Size, color, and weight on `TextContent` are read-only.
- `WordDocument.ReplaceText` does not reach table cells.
- Replace placeholders paragraph by paragraph across `doc.Paragraphs`.
- That list includes the paragraphs inside table cells.
- Read body text from top-level paragraphs.
- Read table text from `Tables`, `Rows`, and `Cells`, and cast each cell to `TableCell`.
- `WordDocument.ExtractText` leaves out tables and joins paragraphs without a separator.
- Build a new `WordDocument` for each file you save. A second save writes the body again.
- Set `IronWord.License.LicenseKey` from IRONWORD_LICENSE_KEY at startup.
- Never print the key or write it into a file.
- Reopen every saved document before calling a change done.
- Compare its paragraphs and cells with what was written.
- Do not add a second DOCX library. If IronWord cannot do something, say so.
The skill file disagrees with the measurements in at least seven places. Its single-file example still uses 2026.8.1, three releases before the 2026.10.4 package on NuGet. It refers to document-wide replace as a whole-document find and replace, which the measurements below contradict. Its examples serialize the document model to JSON, which caused a serialization error on every document tested in 2026.10.4. They present the parameterless Save only as a way to get the bytes, although IronWord.xml states that it also writes a loaded document back to its source file. The skill file styles text through TextContent.Style, which saved an empty run style. Step 7 covers the other two, the document's add-text call and the shipped XML.
Step 5: One-Shot Setup Prompt
The prompt below combines Steps 1 to 4 in a single opening message.
Prepare this project for Word document work with IronWord.
Then append these rules to the instruction file your future sessions load:
- Reference the IronWord NuGet package at version 2026.10.4.
- Its namespace is also IronWord.
- The paragraph, run, table, and style types live in IronWord.Models.
- Take IronWord.License.LicenseKey from the IRONWORD_LICENSE_KEY environment variable.
- Assign it before any IronWord call.
- Never print the key, and keep it out of every file you write or commit.
- Fetch https://ironsoftware.com/word/csharp/llms.txt first and code against what it documents.
- Look up any other member in the API reference.
- It lives at https://ironsoftware.com/word/csharp/object-reference/api/.
- Confirm that member in a build before you rely on it.
- Where the tool supports skill files, load https://ironsoftware.com/word/csharp/skill.md.
- Believe the compiler over the skill when they disagree.
- After any change that saves a DOCX, reopen that file with IronWord.
- Compare each paragraph and table cell with the text you meant to write.
Tell me which file you appended them to.
Step 6: Prompt Patterns That Produce Working Code
Specify the paragraphs, styling, and table.
Vague Make a Word report with IronWord.
Better Add the title as its own paragraph whose run has a TextStyle of bold 20 point Calibri, add each body paragraph with
AddParagraph, then add a two-column table with one row per metric.
State the IronWord release and target framework, since members change between releases.
Better Use IronWord 2026.10.4 on .NET 9, and set the license key from IRONWORD_LICENSE_KEY before the first save.
Ask for whole files. A snippet can leave out the IronWord.Models directive that paragraph, run, and table types require, or the line that sets the key.
Better Send back the full Program.cs, using directives and license assignment included, so it builds unchanged.
Step 7: Verify What You Get Back
Each mistake below is reproducible in 2026.10.4.
- Styling the text element: the size, color, weight, and font name on
TextContentonly report the effective style, and the size and font name on aRunare also read-only, so assigning them fails with CS0200. Apply aTextStyleto the run instead. - Building paragraphs with the document's own add-text call:
WordDocument.AddTextappends to the last paragraph instead of starting a new one. - Treating the shipped XML as the API: the skill file tells agents to search IronWord.xml for member names, but in 2026.10.4 that file documents non-public members, such as a replace-across-runs method on paragraphs and a Markdown export on the document, and omits the page-scoped text extraction, which does compile. Calls to the hidden members fail with CS1061.
- Saving one document twice: a second save of a document built in memory writes its whole body again after the first copy, so a notice with three paragraphs and a table is saved with six paragraphs and two tables. Build a new document for each file you save.
- Editing a loaded document that contains a table: when a DOCX with a table is loaded and saved, a replacement inside the table or in a later paragraph is silently lost, although the document in memory shows it. Documents built in memory, and loaded documents without a table, retain their edits.
Let the Agent Verify Its Own Work
An agent can run the same checks and report each result.
Rebuild in Release and run it.
If it's a web app, start it in the background.
Call each endpoint that saves or returns a DOCX, then stop the app.
After that, show me the evidence:
- Name each IronWord type and member the code calls, with the docs page where it appears.
- Report the value of IronWord.License.IsLicensed.
- Reopen each DOCX the program produced.
- Count the paragraphs outside tables and the table rows in each file.
For every paragraph and cell in each file, list the text written and the text found.
Say whether the two agree.
Text extraction with IronWord
In 2026.10.4, the extraction calls return different results.
- The document's text extraction joins the body paragraphs with no separator and returns no table text.
- Its paragraph list holds the body paragraphs and every table cell paragraph, in document order.
- Table rows hold cells typed as an interface, so cast each cell to
TableCellto read its text. - A cell's own text extraction joins its paragraphs with no separator.
For a report with a title, one body paragraph, and a four-row table, the paragraph list returns 10 entries, 8 of which are table cells. A cell paragraph has a TableCell parent, so a reader can separate body text from table text. The text extraction guide shows how to read a cell.
Placeholder replacement with IronWord
A loaded notice held CUSTOMER_NAME in two body paragraphs and one table cell, with the first body copy split across two runs. IronWord 2026.10.4 replaced it in memory three ways.
| Replace call | Body paragraphs | Table cell |
|---|---|---|
ReplaceText on the document | Replaced, including the copy split across runs | Unchanged |
ReplaceText on each entry of Paragraphs | Replaced | Replaced |
ReplaceText on each table cell | Unchanged | Replaced |
The split copy is replaced in a loaded file because IronWord merges adjacent plain-text runs on load. A paragraph built in memory from two runs was also replaced across the split. For MERGEFIELD templates, the mail merge guide covers the separate merge API.
A Result You Can Reproduce
The whole test is the prompt below, and the assistant writes all of the code. Open an empty folder, start a new session there, and paste the prompt. Then compare the result with the criteria under What passes.
IRONWORD_LICENSE_KEY. The assistant needs network access and permission to run terminal commands.The prompt
Build a .NET 9 minimal API that writes internal report documents as DOCX and reads them back.
Start in an empty directory. If it holds a project file or code, say so and stop.
No one can answer questions, so plan, code, build, run, and repair on your own.
Settle open points sensibly and write each choice down.
Keep every deliverable in the project directory.
Do these steps in order:
1. Install IronWord 2026.10.4 from NuGet as the only DOCX library, and write every DOCX with it.
2. Use only members in https://ironsoftware.com/word/csharp/llms.txt, its guides, or API reference.
3. At startup, assign IRONWORD_LICENSE_KEY to IronWord.License.LicenseKey.
The key follows these rules:
- Never view, print, echo, hard-code, or save it, or list the environment.
- If IronWord.License.IsLicensed is false, report that and stop.
4. Add POST /documents and POST /extract.
POST /documents takes JSON and returns a DOCX built like this:
- The title is one paragraph, bold at 20 point.
- Each body paragraph is its own paragraph.
- Optional label and value rows form a two-column table.
- An absent or empty row list gives no table.
POST /extract takes one DOCX upload and returns JSON with two fields:
- "paragraphs" lists paragraphs outside tables that hold text, in document order.
- "rows" holds one array of cell texts per table row.
5. In process, write 8 DOCX files to testset/ with the /documents code and title style.
The set holds these files:
- letter-01 to letter-03: a title and three body paragraphs.
- report-01 to report-03: a title, one body paragraph, and five label and value rows.
- notice-01 and notice-02: a title, two body paragraphs, and a two-row table.
- After replacement, no title, body paragraph, or table row repeats in the set.
- manifest.json gives each file's title, customer name, and replaced paragraphs and rows.
Build each notice this way:
- Its rows are Reference and an ID you pick, then Customer and CUSTOMER_NAME.
- Body paragraph one is "Dear CUSTOMER_NAME," as the runs "Dear CUSTOMER_" and "NAME,".
- Body paragraph two mentions CUSTOMER_NAME again.
- Extend the /documents code so it takes runs.
- Save the notice as built to pre-replacement/notice-01.docx or pre-replacement/notice-02.docx.
- Replace each CUSTOMER_NAME with its customer name through IronWord's ReplaceText.
- Only then save it to testset/.
6. Before /extract is first called, check each testset file's word/document.xml yourself.
Fix the generator whenever a check fails.
Once /extract code reads a testset file, over HTTP or in process, the testset is final.
Open it as a ZIP with only System.IO.Compression and System.Xml.Linq, and pass these checks:
- The first paragraph's run is bold at 40 half-points.
- Paragraphs outside tables that hold text match the manifest in number and text.
- So do table rows.
- CUSTOMER_NAME and CUSTOMER_ occur nowhere in it.
- Each pre-replacement notice keeps body paragraph one in exactly those two runs.
7. Make /extract read every upload with IronWord, the same way each time.
The /extract code has these limits:
- It uses no other DOCX reader, no ZIP or XML parsing, and not the manifest.
- It holds no title, paragraph, label, value, or customer name as a literal.
- It never picks a reading by comparing it with expected text.
- You may change how it reads between runs. The program may not.
8. Build in Release and launch the API.
Then make these requests in order:
- Send /documents your own title, four rows, and two body paragraphs, none from the testset.
- Save the returned DOCX as report.docx.
- POST report.docx and every testset file to /extract over HTTP.
- Save each raw response body as extract/<file name without .docx>.json.
9. Write roundtrip.md in three parts.
Part one gives each file's step 6 values, with report.docx measured against your request.
Give title size in half-points and placeholders as CUSTOMER_NAME or CUSTOMER_ occurrences.
Part two describes the /extract method and the ReplaceText calls that made the replacements.
Part three is a 50-row table, one row per expected title, body paragraph, and table row.
Its columns are File, Element, Expected text, Text read, and Match, filled this way:
- Element is title, paragraph N, or row N.
- Expected text comes from the manifest or the request.
- Text read is copied from the saved response, wrong or not, or is (missing) if absent.
- A row's text, expected or read, is its label and value joined by ": ".
- So no label, value, ID, or name contains a colon.
- Match needs exactly equal text at the same position.
- An extra or missing paragraph, row, or cell fails all of that file's rows.
10. Until all 50 rows match, study the failing responses and change the /extract method.
Each pass follows these rules:
- Rebuild and rerun after each change.
- Keep only the latest responses in extract/.
- Stop after the sixth run that still fails.
11. Finish with a short summary that gives me these things:
- The Release build result and rows matched out of 50.
- The /extract method, the step 6 values, and each assumption you relied on.
- If the sixth run fails, the failing rows, each method tried, and what you think causes them.
Output
The run's unedited round-trip table lists the measured values for all 11 files and the 50 rows. report.docx is the document that POST /documents returned.
- Each pre-replacement notice contained 3 placeholders, with the greeting split into the runs "Dear CUSTOMER_" and "NAME,".
- Each saved notice contained no placeholders, and its greeting stayed two runs with the comma in the second.
- All 11 files passed the step 6 checks, with every title bold at 40 half-points.
- All 50 rows matched on the first call to the extract endpoint, so the step 10 repair loop never ran.
- Claude Code found the font size and second save behaviors in scratch documents and built each notice twice.
- The extract endpoint dropped table cell paragraphs from the paragraph list and read the cells row by row.
- The documentation prompt changed no code and found TableCell.Paragraphs only in the API reference.
- Step 5 wrote its rules and the measured behaviors to a new CLAUDE.md.
- The self-check matched all 87 paragraphs and cells across the 11 files.
- Our own checks on the saved files passed every requirement.
- A fresh start of the Release build returned the same 9 responses.
- Some steps ran differently from the page. The prompts for Steps 1, 3, 5, and 7 came after the acceptance prompt, in that same session. During the acceptance prompt, the API that Claude Code had started blocked a rebuild, so Claude Code restarted it and matched 50 of 50 again.
What passes
- The project builds in Release with no change made by hand.
- The step 6 values and the 50 matching rows are reproduced when you measure the saved files yourself.
- The license key appears in no file of the project folder.
The run record
- One Windows 11 x64 desktop, with a Core i9-14900KF processor and 64 GB of memory, ran every test.
- Each project targets .NET 9.
- The runs used
IronWord2026.10.1. The prompts were then pinned to 2026.10.4, published on October 12, 2026, which adds a .NET 8 build and resolves CVE-2026-106110 through CVE-2026-106116 by moving SixLabors.ImageSharp to 4.1.3 on .NET 8 and later. - 2026.10.4 exposes the same public API and the same XML documentation as 2026.10.1, and 32 probe runs on 2026.10.4 under .NET 9 gave the same results as on 2026.10.1.
- The test projects in the five tool guides run on xUnit, and Antigravity resized its thumbnails with SixLabors.ImageSharp, which IronWord references through IronSoftware.System.Drawing.
- Duration adds up the logged minutes of every prompt on a guide's page. This guide's row counts the acceptance prompt alone.
- Last tested October 15, 2026, for this guide, GitHub Copilot, Claude Code, Google Antigravity and ChatGPT Codex, and October 12, 2026, for Cursor.
| Guide | Assistant and version | Model | Duration (minutes) |
|---|---|---|---|
| This guide | Claude Code 2.1.277, one session, permission prompts off | Claude Opus 5 | 16.2 |
| Claude Code | Claude Code 2.1.277, opusplan alias, prompt 3 in plan mode | Claude Sonnet 5, with Claude Opus 5 planning prompt 3 | 35.4 |
| ChatGPT Codex | Codex CLI 0.160.0, one sitting | GPT-6 Luna | 43.2 |
| GitHub Copilot | Copilot CLI 1.0.97, prompt 3 in plan mode, the review as its own call | GPT-6 Luna, picked by Auto | 12.8 |
| Cursor | Cursor 3.23.23 desktop app, prompt 3 in Plan Mode | Grok 4.7 | 23.0 |
| Google Antigravity | Antigravity CLI 1.3.5, permission prompts skipped | Gemini 3.8 Flash (High) | 43.2 |
Agent-Specific Guides
Each guide below runs these steps with one tool on one Word document task.
- Use IronWord with Claude Code for merging a batch of renewal letters from a MERGEFIELD template, with repeating regions for table rows
- Use IronWord with ChatGPT Codex for renaming a company across a folder of contracts, with a CSV row for each contract
- Use IronWord with GitHub Copilot for a statement service in a Visual Studio solution that builds styled DOCX files with data tables
- Use IronWord with Cursor for migrating a supplier form reader from the OpenXML SDK, guided by documentation links and committed project rules
- Use IronWord with Google Antigravity for a Blazor review page that lists the photos embedded in uploaded inspection reports, checked in a sandboxed browser
Troubleshooting
- IronWord License in web.config if a key set in configuration is not applied
- IronWord with DocumentFormat.OpenXml if the OpenXML SDK is also referenced directly
- Header Disappears After SaveAs when a template loses its header once its placeholders are replaced
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.