How to Use IronWord with Cursor

Curtis Chau
Curtis Chau
Updated: October 19, 2026

The sample repository for this guide reads supplier forms with OpenXML SDK code that iterates over body elements and reads InnerText. Moving that reader to IronWord by parsing every labeled value from one block of extracted text with a regular expression fails. In calibration, that version read 3 of 12 forms correctly, joining five registration numbers to the following label and returning no registration number for the four table forms.

Cursor is built on the VS Code codebase and provides an agent alongside the editor. Two of its features, the documentation links the agent reads and the project rules committed in .cursor/rules, determine whether it produces IronWord code or code that follows the OpenXML reader pattern. Both features appear below in a supplier onboarding service that reads the DOCX forms returned by suppliers. The service extracts the supplier name, the registration number, the price list table and the embedded logo and signature images, and its reader moves from the OpenXML SDK to IronWord.

For details on IronWord's reading methods, see the text extraction guide and the image extraction guide.

Quickstart
NuGetInstall with NuGet

PM > Install-Package IronWord

Install IronWord by running the command above in the NuGet Package Manager Console, or search for the package in the NuGet Package Manager.

With the supplier intake repository open, paste this prompt into a new Agent chat.

@src/SupplierIntake/ @TestData/forms/

Don't write anything until you've fetched https://ironsoftware.com/word/csharp/llms.txt.
Read the extract text and extract images pages it links as well.

Then give Program.cs a dump command that opens one form with IronWord:
`dotnet run --project src/SupplierIntake -- dump <file.docx>`

Have the dump print what IronWord returns for that form:
- The ExtractText result, in quotes.
- Every entry of Paragraphs, with its index.
- Every table cell, with its table, row and column numbers.
- Each extracted image's Width and Height, and the pixel size of the AnyBitmap behind it.

The program should read IRONWORD_LICENSE_KEY itself when it's set.
At startup, assign the key to License.LicenseKey.
Never print the key, and keep environment variables out of the output.
Leave Forms/FormReader.cs and its DocumentFormat.OpenXml reference as they are.

Run the dump on TestData/forms/form-03.docx and show me the output.
Text

Do not enter the license key in a chat. The agent edits files and runs shell commands, so a pasted key is exposed to every file, commit message and build log it writes. In our tests, IronWord opened and read every form without a key and threw a LicensingException only when saving a document. The license key guide shows how to set the key in code, in Web.config or in appsettings.json.



Which Cursor model should you use for IronWord work?

Composer is Cursor's own agentic model. The model picker also offers SpaceXAI's Grok models, which Cursor co-developed, and models from OpenAI, Anthropic, Google and other labs. The test run below used Grok 4.7.

How do you give Cursor the IronWord documentation?

Include IronWord's documentation link in the request, or store it in a rule shared by the whole team. IronWord's llms.txt lists each guide and the API reference, and llms-full.txt condenses 46 documentation pages into a single file. As of October 2026, Cursor's @ menu attaches files, folders, terminals, chats, Git diffs and the browser, so IronWord's documentation goes to the agent as a link.

The Quickstart dump shows two behaviors that the extract text guide does not state. For form-03, ExtractText returned the body paragraph text concatenated into one string and omitted all text in the form's two tables.

@src/SupplierIntake/Forms/RegistrationCheck.cs

Answer five questions about IronWord, without editing any files:
1. Does WordDocument.ExtractText include text from table cells?
2. What does it put between paragraphs?
3. Does the Paragraphs collection list the paragraphs inside table cells?
4. What does ExtractImages return, and what element type does the list hold?
5. What unit do the Width and Height of each result use?

Use the documentation at https://ironsoftware.com/word/csharp/llms.txt as your source.
The project rule doesn't count as one.
Check your answers against the form-03 dump from the last turn, where it shows them.
Cite the page each answer comes from.
Where no page states it, say the answer is measured, not documented.
Text

How do you write project rules that encode your IronWord conventions?

A project rule is an .mdc file stored in .cursor/rules and versioned with the repository, so every clone includes it. Cursor ignores plain .md files in that folder because, without frontmatter, it cannot determine when to load them.

The rule below defines globs and sets alwaysApply to false. Cursor loads it whenever a request includes a form reader or a test file, and omits it otherwise. Save it as .cursor/rules/ironword-forms.mdc.

---
description: IronWord conventions for reading supplier forms
globs: src/SupplierIntake/Forms/**/*.cs, tests/**/*.cs
alwaysApply: false
---

Read and write DOCX files with IronWord only, under these package rules:
- The NuGet package is IronWord.
- Its types come from two namespaces, IronWord and IronWord.Models.
- AnyBitmap comes from IronSoftware.Drawing.
- Don't add DocX, NPOI or any other DOCX package.
- Add no new DocumentFormat.OpenXml code.
- The documentation index is https://ironsoftware.com/word/csharp/llms.txt

Keep license keys out of the code, and handle the key this way:
- Read it at run time from an environment variable named IRONWORD_LICENSE_KEY.
- Never print it.

WordDocument.ExtractText leaves out table cells and runs paragraphs together with no separator.
Use the paragraph and table collections instead:
- Read labeled values one paragraph at a time from doc.Paragraphs.
- Read tables through doc.Tables, Rows and Cells.
- doc.Paragraphs also returns the paragraphs inside table cells.

Table.Rows returns ITableRow, and a row's Cells returns ITableCell.
Two casts are needed:
- Cast a row to TableRow before you pass it to a method that takes a TableRow.
- Cast a cell to TableCell before you call ExtractText on it.

Don't invent IronWord members.
When you're unsure of one, say so and name the page you checked.
Markdown
Please note: Cursor also reads AGENTS.md, at the root or in any subfolder. That file has no frontmatter and no file patterns, so the IronWord rule, which depends on its globs, belongs in .cursor/rules.

Cursor's rules page states that rules do not affect Tab and that user rules are not applied to inline edits, so Tab and inline edit suggestions need a manual check against the rule.

How do you map OpenXML SDK calls to IronWord?

Instructing the model to use IronWord does not tell it which IronWord call replaces each OpenXML SDK call. A table of equivalents provides that mapping.

OpenXML SDKIronWord
WordprocessingDocument.OpenThe WordDocument constructor, from a path, bytes or a stream
Body paragraphs and their inner textParagraphs, with ExtractText on each
Body tables, rows and cellsTables, Rows and Cells
Blip elements and image part streamsExtractImages, saved through AnyBitmap

Two rows behave differently from the SDK code they replace. The paragraph list holds the paragraphs inside table cells as well as the body paragraphs, so form-03 returns 15 entries where the OpenXML SDK reader iterated over 5 body paragraphs. The cells are returned as ITableCell, which has no text method, so a call without the cast fails to build with CS1061.

IronWord does not reference the DocumentFormat.OpenXml package, and a project that references both IronWord 2026.10.4 and DocumentFormat.OpenXml 3.5.1 builds and runs. The migration can therefore proceed one reader at a time. The OpenXML conflict page covers IronWord builds older than 2025.7.13.

Adding these lines to the end of the rule puts the table in every request that loads the rule.

Translate OpenXML SDK code instead of porting it, with these equivalents:
- WordprocessingDocument.Open becomes new WordDocument(path).
- Body.Elements<Table>() becomes doc.Tables.
- Counting Blip elements becomes doc.ExtractImages().Count.

Save extracted images with ((AnyBitmap)image.Image).SaveAs(path).
Markdown

How do you plan and run a migration to IronWord?

Press Shift+Tab in the chat input until Plan Mode is selected, then send the request below. Cursor writes its plan to an editable file.

@src/SupplierIntake/Forms/ @TestData/forms/expected.csv

Plan the move of FormReader to IronWord so the project can drop DocumentFormat.OpenXml.
Write no code until I approve.
FormReader still reads supplier forms with the OpenXML SDK.
RegistrationCheck already uses IronWord, and the team wants a single DOCX library.

The plan has to cover these points:
- Every OpenXML SDK call, with its IronWord replacement and the file where the call lives.
- How the reader will find the price table.
- How it will find a registration number inside a table.

Keep SupplierForm and the public Read and SaveImages signatures unchanged.
Where something is unclear, state your assumption instead of asking.
Show me the plan when it's ready.
Text

Correct any errors in the plan. Then either click Build or switch back to Agent and send this request.

@ironword-forms

Carry out the approved plan.
Check the forms output against TestData/forms/expected.csv before and after the move.
A printed row counts as a match when its supplier, registration, items and images columns agree.

Work through it in this order:
1. Run `dotnet run --project src/SupplierIntake -- forms TestData/forms` and count the matches.
2. Migrate FormReader to IronWord.
3. Remove the DocumentFormat.OpenXml package reference.
4. Run dotnet build, then run the forms command again.

Tell me the match count for the OpenXML version and for the IronWord version.
Then show me the diff.
Text

The @ironword-forms mention applies the rule by name, whether or not Cursor has already attached it through its globs.

Which Cursor feature fits each change?

Tab completes an edit that is already visible in the editor, and inline edit (Ctrl+K) rewrites a selection from a one-sentence instruction. Changes across several files, or changes that need a build, go to Agent. Use Plan Mode when an incorrect change takes more effort to undo than a plan takes to write. A Cloud Agent suits unattended work.

A new supplier returns form-13, whose price table merges one item cell across two price tiers. Highlight the loop that iterates over the price rows in FormReader, press Ctrl+Shift+L (Cmd+Shift+L on macOS) to add the highlighted code to the chat input, and send this request in Agent.

Make FormReader.Read handle merged cells in the price rows:
- An Item cell that Word merged with the cell above comes back empty.
- For that row, reuse the item name from the previous row.
- Before you read a cell by its column position, check that the row has that many cells.

Then rerun the forms command over TestData/forms.
Tell me how many rows agree with expected.csv, which now lists 13 forms.
Text

The row length check addresses merges in the other direction. In a separate probe file, a cell merged across columns was returned as one cell, so that row was shorter than the others.

How do you debug values missing from extracted text?

The repository's pre-check reads the registration number with a regular expression over the result of ExtractText.

@src/SupplierIntake/Forms/RegistrationCheck.cs @TestData/forms/

Find out why the pre-check flags 9 of the 13 forms as missing a registration number.
FormReader finds one in all 13, and every number is visible in Word.

Before you edit anything, do these three things:
1. Run the check command on TestData/forms and on TestData/edge, and note what it flags.
2. Run the dump command on one flagged form.
3. Explain what the check reads that FormReader doesn't.

Then fix RegistrationCheck and keep it on IronWord.
Rerun the check on both folders.
Tell me how many forms it flags in TestData/forms and in TestData/edge, before and after the fix.
Text

The nine failures have two causes. In five forms, a contact email paragraph follows the number, so the expression captures GB-0012345Contact and the format check rejects it. The other four store the number in a company details table, where the label cell reads Registration number without a colon and the value is in the next cell. Reading the paragraphs individually and taking the value from the cell next to the label resolves both issues.

Which parts of a DOCX does IronWord leave out?

Our probes against IronWord 2026.10.4 identified content that the library does not return to a form reader.

  • Headers: no read returns header text, and ExtractImages omits header images. The page setup exposes the header's height but none of its content.
  • Content controls and text boxes: their text is absent from both the paragraphs and the extracted text.
  • Tracked changes: the reads leave out text inserted while tracking was enabled.
  • Legacy files: a Word 97-2003 .doc file throws "File contains corrupted data" on load.
  • Inline pictures without spacing attributes: a DOCX whose inline pictures omit distT, distB, distL and distR fails to load with an InvalidOperationException. python-docx omits these attributes.

Floating pictures and pictures inside table cells are extracted, in document order. If suppliers complete forms built with content controls, or place the registration number in a header, test those forms before relying on the reader.

How do you write regression tests for document reading?

Tests that compare extracted values with a known list check the intake service's output, regardless of the reader's internal implementation. A Cloud Agent can write them in its own isolated VM. Store IRONWORD_LICENSE_KEY under secrets in the Cloud Agents dashboard first.

@tests/SupplierIntake.Tests/ @TestData/

Write xUnit tests for FormReader and RegistrationCheck.

Each of these needs a test:
- Every row of TestData/forms/expected.csv matches.
- In form-13, both price tiers keep the item name.
- SaveImages writes one PNG per picture, with the pixel sizes listed in the image_sizes column.
- RegistrationCheck passes every form and flags TestData/edge/no-registration.docx.
- Reading TestData/edge/legacy.doc throws a FormatException or a type derived from it.

Copy TestData into the test output and run dotnet test.
Tell me the passed and failed counts.
Text

The legacy file assertion cannot reference IronWord's exception type because it is internal. It derives from FormatException, so an assertion that accepts derived types, such as Assert.ThrowsAny<FormatException> in xUnit, compiles and still fails when a different error is thrown.

The AI coding assistants guide includes one IronWord task run end to end against written pass criteria.

What did the prompts produce in a real run?

The seven prompts were sent to Cursor in order in one chat. The sample repository held an OpenXML SDK reader, an IronWord pre-check, an empty xUnit project, twelve forms with known values, and two edge files.

  • The OpenXML SDK reader and the IronWord reader that replaced it each matched all 12 forms.
  • IronWord was the only DOCX package left in the app and its tests.
  • With form-13 added, the reader matched 12 of 13 and dropped the item name from the second price tier.
  • After the merged-cell change, the reader matched 13 of 13.
  • The pre-check flagged 9 of the 13 forms before the fix and none after it. It flagged the edge form with no registration number both times.
  • All 21 saved images had their listed pixel sizes.
  • The form-03 dump listed 10 cells in two tables and one image of 108 by 36 points and 144 by 48 pixels.
  • Asked what the documentation states, Cursor answered without changing any files.
  • All 42 cases from six test methods passed, with and without a license key.
  • In copies of the final code, removing the merged-cell handling failed 2 cases. Restoring the old pre-check failed 9, and skipping one image per form failed 13.
  • The legacy file test accepted any exception and passed with the file deleted. Narrowed to FormatException, it passed with the real file and failed without it.
  • Cursor's first IronWord reader lacked the row cast and failed to build with CS1503. Cursor added the cast before its next build.
  • Every Release build had 0 warnings and 0 errors, with no manual edits.
  • Some steps ran differently from the page. The rule lacked the row cast. The merged-cell prompt did not request a row length check, and Cursor added none. The test prompt required only that an exception be thrown. Prompts, @ mentions included, were pasted into the desktop app as text. The plan was approved in Agent, not with the Build button. The Cloud Agent prompt ran locally in Agent mode, without a VM.

The Claude Code guide adds a read-only subagent that checks every IronWord member in the generated code against IronWord.xml. The ChatGPT Codex guide covers the writing side and measures which parts of a Word document a text replacement changes.

The run record lists the IronWord package, model, timing and test machine for these results.


Troubleshooting


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.

Read More

Ready to Get Started?

Nuget Downloads 58,016Version:2026.10just released