How to Use IronXL with GitHub Copilot

Curtis Chau
Curtis Chau
Updated: October 15, 2026

A .NET reporting solution can already write the right numbers into plain workbooks and still get a request about appearance. Finance wants its header colors, thousands separators, highlighted margins, a header row that stays put while you scroll, and names that other sheets can point at.

IronXL provides the styling calls for fonts, fills, number formats, conditional rules, freeze panes, column widths, tables and named ranges. Two instruction files committed to the repository record how those calls behave.

This guide styles the output of an existing monthly sales report and proves each style with tests that save the workbook, open it again and read every setting back. The cell formatting guide and the conditional formatting guide document the calls themselves.

Quickstart
NuGetInstall with NuGet

PM > Install-Package IronXL.Excel

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

Open a terminal at the solution root, start copilot, and send this prompt. Visual Studio chat takes the same two files as #report-style.md and #ReportStyler.cs.

Read @docs/report-style.md, then fill in @src/FinanceReports/Styling/ReportStyler.cs.
Give it a public static ApplyHouseStyle(WorkBook workbook) method.
It styles the workbook that MonthlySalesReport.Build returns.
IronXL is the IronXL.Excel package.
Styling types are in IronXL.Styles and number formats in IronXL.Formatting.

Keep this first pass to the header row, the data rows and the Total row of the Sales sheet.
Set their fonts, fills, borders, alignment and number formats. The other sections come later.
Find the Total row by the word Total in column A. Don't count on a fixed row number.

1. Call ApplyHouseStyle from Program.cs just before SaveAs.
2. Run the job.
3. Load out/monthly-sales-2026-09.xlsx again.
4. Print the fill and font color of A1 and the FormatString of E2, exactly as IronXL gives them.
Text

The prompt says nothing about how IronXL reports colors and formats after a reload, because the path-specific instruction file carries those rules.

Set IRONXL_LICENSE_KEY in the environment Copilot starts from, and see the license key guide for other places to keep the key. Keep it out of prompts and commits. Every test here loads a workbook, so the key has to be present wherever the tests run.



Where does Copilot fit into a project already in Visual Studio?

Use Copilot CLI from a terminal, or agent mode in Visual Studio or VS Code, for a styling change you want to watch, since it can build the solution and run the report job itself. Inline chat is enough for a one-file change. The cloud agent on GitHub takes an assigned issue and returns a pull request.

How do you set IronXL conventions for Copilot?

Commit the IronXL conventions as .github/copilot-instructions.md. Copilot CLI, chat in Visual Studio and VS Code, the cloud agent and Copilot code review all read it, so put the package, the license rule and the test rules there.

# Copilot instructions

## Workbooks

- Excel work uses IronXL from the IronXL.Excel NuGet package. Styling types
  live in IronXL.Styles, number formats in IronXL.Formatting, and comparison
  operators in IronXL.Formatting.Enums. Add no other spreadsheet package and
  no Office Interop.
- The IronXL version is pinned in Directory.Packages.props. Leave it alone
  unless the task is an upgrade.
- The license key comes from the IRONXL_LICENSE_KEY environment variable,
  and the program reads it there. Never put a key in code, settings files,
  tests or console output, and don't list environment variables.
- docs/report-style.md is the source for every color, border, format and
  name in the monthly sales workbook.
- IronXL documentation is at https://ironsoftware.com/excel/csharp/llms.txt
  and https://ironsoftware.com/excel/csharp/skill.md
- Say so whenever you depart from docs/report-style.md, and before you use
  an IronXL member you cannot find in IronXL.xml in the NuGet cache.

## Tests

- Build the workbook into a temporary folder, save it, load it again with
  WorkBook.Load and assert on what the loaded file reports.
- Never compare workbook bytes and never edit files under TestData.
Markdown

How do you give the styling code its own rules?

Rules that matter only to code that sets or checks styles go in .github/instructions/workbook-styling.instructions.md. Its applyTo value names two folders, separated by a comma, so the rules reach the styler and its tests and stay out of requests about the CSV import. GitHub's documentation says the CLI includes the file only when Copilot works on a file the glob matches. In our run, Copilot CLI listed the file's pattern and path in every session and told the agent to read it before changing code. The agent opened it before its first edit in every prompt that changed code except the second, which relied on the copy already in the session.

---
applyTo: "src/FinanceReports/Styling/**,tests/FinanceReports.Tests/**"
---

- IronXL returns colors as lowercase hex. A fill set with "#1F4E78" reads
  back as "#1f4e78", so compare colors ignoring case.
- A cell with no fill still reports BackgroundColor "#ffffff". Use
  FillPattern (NoFill or SolidForeground) to tell the two apart.
- FormatString only changes how numbers and dates display. A number written
  as a string keeps its raw text, its DecimalValue is 0, and SUM skips it.
- Font.Height is the size in points, the number Excel shows.
- Call AutoSizeColumn last. It measures the formatted text in the cell's
  font, and on a machine without that font it measures in Arial.
- AddNamedTable and AddNamedRange throw ArgumentException when the name
  already exists, and AddConditionalFormatting adds another copy of a rule
  on every call. Guard each one so restyling a styled workbook changes nothing.
- IronXL has no getter for freeze panes. Check one by reading the pane
  element in the sheet XML.
Markdown

Run /instructions in a CLI session to confirm which of the two files are active before a long prompt.

How does IronXL read back cell styles after a reload?

Some values IronXL hands back after WorkBook.Load differ from what the code passed in. With IronXL 2026.10.1, a font set with "#FFFFFF" reads back as #ffffff, and a border set with "#BFBFBF" as #bfbfbf. Assert.Equal("#1F4E78", cell.Style.BackgroundColor) failed against our reference workbook, and a comparison that ignores case passed. The report writes date values, and IronXL gave those cells the format m/d/yy h:mm until the styler set yyyy-mm-dd. With #,##0.00, FormattedCellValue returned 176,366.84 while Text still returned 176366.84157142858, the number underneath. A margin written as the string "-0.075" kept that text under 0.00%, and IsText was true. A revenue figure written as text kept the format string #,##0.00, so a test that only checks FormatString passes on that broken cell. The XML documentation shipped with the package describes Font.Height in pixels, but a height of 12 was saved as size 12.

How do you add conditional formatting, freeze panes and column widths?

The margin rules come from CreateConditionalFormattingRule, which takes a ComparisonOperator and a formula, and AddConditionalFormatting, which attaches the rule to a range. The frozen header is one call, CreateFreezePane(0, 1), where the first argument counts frozen columns and the second counts frozen rows, so CreateFreezePane(1, 4) froze column A and rows 1 to 4. Column widths come from AutoSizeColumn.

Add three more parts of @docs/report-style.md to ApplyHouseStyle.
It lives in @src/FinanceReports/Styling/ReportStyler.cs.

- The margin highlighting. Both rules go on the data rows of column F and stay off the Total row.
- The frozen header row.
- The column widths. Size them as the very last step, after every font, fill and number format.

Run the job and reload the output. Then print:

- the range, operator and formula of each rule, as IronXL reads them
- the pane element in the Sales sheet's XML
- the width of columns A to F
Text

Our reference styler sized the columns last and got widths of 2,790, 6,988, 3,660, 1,740, 3,430 and 1,792 for columns A to F, in the 1/256-of-a-character units that GetColumn(i).Width returned in our run. Sizing first, before the formats, gave 1,587 for Units and 3,123 for Revenue, too narrow once the thousands separators appeared, and 3,635 for the date column, which was measured against the longer default date and time format.

Fonts change the numbers too. On our Windows machine four fonts that were not installed produced exactly the Arial widths, which ran 6 to 9 percent wider than Calibri. A test that pins exact widths therefore fails on any runner that lacks the report's font.

The freeze pane is written as <pane ySplit="1" topLeftCell="A2" activePane="bottomLeft" state="frozen"/> in the sheet XML and survived a load and save through IronXL.

IronXL saves each rule fill as an entry in a custom indexed palette in xl/styles.xml, and our file held the right colors, FFC7CE and C6EFCE. Read back through PatternFormatting.BackgroundColor, both rules reported #ffc7ce. The range, the operator, the formula and the font color of each rule read back correctly. Only the fill needs the file, though reading both colors from it lets one test helper cover them.

How do you add named tables and named ranges?

The names in the style document come from two calls. AddNamedTable("SalesData", range, true, TableStyle.TableStyleLight9, true) turns the header and data rows into a table with filter buttons. AddNamedRange("TotalRevenue", range, true) gives the Revenue cell of the Total row a workbook-wide name that a Summary formula can use as =TotalRevenue. After EvaluateAll, that formula returned the same total as the cell it names. FindNamedRange("TotalRevenue") returns the reference as the text 'Sales'!$E$38:$E$38 and not a Range. SaveAsNamedTable and SaveAsNamedRange on a Range do the same work.

None of the four calls checks for an existing name. Run the styler a second time on a workbook it has already styled and AddNamedTable throws ArgumentException with the message "The workbook already contains table with name: SalesData". AddNamedRange does likewise for TotalRevenue, and the two SaveAs calls throw the same two exceptions. AddConditionalFormatting does not throw, and in our test a second run took the sheet from two margin rules to four. IronXL also refused the table name Sales Table, with an ArgumentException saying names must not contain spaces.

Finish @src/FinanceReports/Styling/ReportStyler.cs with what's left in @docs/report-style.md.
That's the SalesData table, the TotalRevenue name and the Summary sheet rules.
Column sizing stays the final step.

Finance fixes figures directly in a saved workbook and sends it back for the house style.
So add a restyle <path> command to Program.cs.
It loads the file, calls ApplyHouseStyle and saves the file over itself.
Restyling a file twice has to leave exactly one table, one TotalRevenue name and two margin rules.

Show me that it holds:

1. Run the job once.
2. Make a copy of the output in out/ and restyle that copy twice.
3. After each pass, print the table names, named ranges, rule count and the value of Summary!B2.
Text

Removing the old margin rules with RemoveConditionalFormatting before adding the new ones, or skipping a rule the sheet already holds, keeps the rule count at two, and checking GetNamedTableNames and GetNamedRanges before each add stops the second pass from throwing.

How do you test Excel styles after a reload?

The suite needs a few helpers before any single test is useful, one to read the sheet XML and one to resolve rule colors, so this prompt starts with /plan. In plan mode Copilot CLI blocks edits to project files until you approve the plan.

/plan an xUnit suite in tests/FinanceReports.Tests that checks the house style after a reload.

Build the September report into a temp folder, save it and open it again with WorkBook.Load.
Then check each rule in docs/report-style.md against what the reopened file holds.

- Check FillPattern beside every fill color.
- Read the frozen pane from the Sales sheet XML with ZipArchive.
- For the margin rules, read each rule's range, operator and formula through IronXL.
- Resolve each rule's fill and font color from its dxf entry in xl/styles.xml.
- An indexed color there points into the indexedColors list, so follow it.
- Check FormattedCellValue for a sample of cells in every numeric column.
- Add a test that restyles a saved copy twice.
- For the widths, autosize a reloaded copy again and confirm no column moves.
- Never assert an exact column width.

Implement the plan, then run dotnet test.
Text

Why do formatted cell values depend on the culture?

IronXL formats FormattedCellValue with the current .NET culture. The workbook is the same file either way, and the format string read #,##0.00 in every culture we tried.

  • en-CA: A revenue cell showed 176,366.84.
  • de-DE: The same cell showed 176.366,84.
  • fr-FR: The same cell showed 176 366,84, where the gap is a narrow no-break space (U+202F).

A margin of -0.075 formatted as 0.00% came back as -7,50% in both de-DE and fr-FR. Text changes too, and DecimalValue does not.

The suite Copilot wrote from the /plan prompt compares those strings directly, for example Assert.Equal("47,999.33", ...) on the first revenue cell. We ran it again under de-DE and then fr-FR. The house style test failed both times, with 47.999,33 and 47 999,33 as the actual values, and the other three tests passed.

Setting CultureInfo.CurrentCulture to en-US at the top of that test made it pass again for de-DE and fr-FR. A workbook loaded with de-DE and read after the switch to en-US also gave 176,366.84, so the setting that counts is the one in force when the test reads the cell. Pin the culture in any test that compares display strings, or compare DecimalValue and FormatString instead. A colleague's laptop or the cloud agent's environment can run with a different culture from the machine that produced the expected strings.

What makes an issue the cloud agent can act on?

The cloud agent receives the issue title, the description, the comments already on the issue and any instructions added at assignment. GitHub states that it does not see comments posted afterwards, so every requirement and the test that closes the job belong in the issue body.

Title: Add an XLS copy of the monthly sales workbook for the ERP import

**Files:** src/FinanceReports/Program.cs, src/FinanceReports/Styling/ReportStyler.cs
**IronXL version:** as pinned in Directory.Packages.props
**Framework:** .NET 9

**Expected:** Each run writes out/monthly-sales-2026-09.xls next to the XLSX,
with the same values and the house style in docs/report-style.md, except the
SalesData table. The TotalRevenue name and Summary!B2 stay.

**Actual:** The ERP import accepts only .xls, and finance converts the file by
hand every month.

**Constraints:** Leave the XLSX output unchanged. Build the XLS workbook with
WorkBook.Create(ExcelFileFormat.XLS) rather than saving the XLSX under a new
extension. In XLS, set every fill before any font color. No new packages.

**Acceptance:** dotnet test passes, plus tests that reopen the .xls and check
the header fill and white header text, the four number formats, the
TotalRevenue name and the operator and formula of both margin rules.
Markdown

The XLS rules in the issue come from measurements.

  • Named tables: AddNamedTable and SaveAsNamedTable on an XLS workbook throw NotSupportedException with "Only XLSX supports named tables."
  • SaveAs with an .xls path: On the styled XLSX workbook, SaveAs threw FormulaParseException, and its message said the named range TotalRevenue did not exist in the current workbook.
  • Fill order: XLS stores colors in a shared palette, and in one probe a header font set to white before three fills were added came back as #ddebf7, the second of those fills, while setting the fills first kept it #ffffff.
  • Rule fills: Reading a margin rule's fill from the reloaded XLS file threw NullReferenceException, so the acceptance line asks for each rule's operator and formula and not its colors.

Why do the cloud agent's tests fail on an IronXL project?

By default the agent's environment runs Ubuntu Linux. When all the styling tests fail together on a branch the agent pushed, read the first exception before the diff. A LicensingException points at the license key.

The cloud agent cannot read Actions, Codespaces or Dependabot secrets, so the key needs an Agents secret. Add IRONXL_LICENSE_KEY under Settings, then Secrets and variables, then Agents, and the agent receives it as an environment variable. The agent's pull requests land in the actual repository, so keep branch protection and a human approval switched on.

Why does styling fail on workbooks from other libraries?

A workbook from the EMEA office, written by a Python job on openpyxl with the same layout as the report, threw InvalidOperationException with "Nullable object must have a value" from the first style setter called on it, whether that set bold text or a number format. ApplyHouseStyle worked on every workbook our own job writes, which makes it look like a bug in the styler when the cause sits in the file.

A colleague added tests/FinanceReports.Tests/PartnerWorkbookTests.cs and it fails.
It covers the workbooks the EMEA office sends, which a Python job writes with openpyxl.
It throws InvalidOperationException "Nullable object must have a value".
The exception comes from deep inside the first style setter.
ApplyHouseStyle still passes on our own output.

Before you edit anything, find out what causes it and tell me:

1. Open TestData/partner/emea-2026-09.xlsx and one workbook our job writes with ZipArchive.
2. Compare the fill entries in xl/styles.xml.
3. Try a single style setter on each workbook.

Then fix it at the point where the restyle command and this test load a workbook.
Leave the partner file untouched on disk and add no package.
Run dotnet test when you're done.
Text

The partner file's first fill is <patternFill />, with no patternType attribute, which openpyxl 3.1.5 writes for its default fill. Our job's file has <patternFill patternType="none"/> in the same place. IronXL loaded the partner file and read its values without an error. Loading it, saving it through IronXL and loading it again kept the bare element and still threw. Adding the missing attribute to that element in memory and loading the patched package, through WorkBook.Load(Stream) in our probe or from a temporary copy, let the full house style apply, and the file on disk stayed as the partner sent it.

Keeping the fix at the loading step stops the assistant from catching the exception inside the styler, which would hide the problem and leave the partner workbook unstyled.

What should you check before merging?

GitHub's documentation shows /review sent as a copilot -p prompt, so this review can run from a script before merging.

/review the commits on this branch after the first one.
Check them against docs/report-style.md and both instruction files under .github.

Flag any of these:

- a color compared with case-sensitive equality
- an AutoSizeColumn call that runs before a later format or font change
- an AddNamedTable, AddNamedRange or AddConditionalFormatting call with no guard for a second run
- a test that pins an exact column width or compares workbook bytes
- an IronXL member that isn't in IronXL.xml in the NuGet cache

Give each finding its file and line. Don't edit anything.
Text

The prompt names both instruction files so the styling rules apply to the whole review and not only to files the glob matches. Copilot code review on GitHub also supports the path-specific file.

The diff still needs a human read for the one thing the review and the tests can miss, which is whether the values changed. Styling must leave every number and date as the report wrote it, and every formula except Summary!B2, which the style document moves onto the TotalRevenue name. The totals should match the CSV feed.

What did the prompts produce in a real run?

Copilot CLI received the six prompts in sequence with nobody at the keyboard. The first five shared one session, with the /plan prompt run in plan mode and autopilot, and /review ran as its own copilot -p call. The target was a reporting solution built for this guide on .NET 9, with a console job that writes the unstyled September workbook from a CSV feed, an xUnit project with no tests yet, and the style document. A commit ahead of the partner prompt added the EMEA workbook and the colleague's failing test. Every figure below comes from rebuilding the solution, rerunning the job and running the tests once Copilot had finished. The AI coding assistants guide turns this kind of check into a repeatable test.

  • Our style check passed 23 of 30 checks after the first prompt, every header, data and Total check among them. It passed 25 of 30 after the rules, pane and widths, and 31 of 31 after the names and the restyle command, with every value still matching the CSV feed.
  • After the second prompt the column widths matched our reference styler exactly, and both margin rules sat on F2:F37.
  • Restyling a copy twice left one table, one TotalRevenue name and two margin rules, and Summary!B2 still returned 10,977,073.68.
  • The planned suite passed 3 of 3 tests. Moving the column sizing ahead of the formats in a copy of the final code made its width test fail.
  • After the partner prompt, 4 of 4 tests passed and a restyled copy of the EMEA workbook passed all 32 checks.
  • The /review call found none of the five listed problems.
  • Some steps ran differently from the page. The first prompt also added both margin rules, which the page leaves to the second. In an earlier run the width test only checked that widths existed and passed with columns sized first, so the /plan prompt now asks for a second autosize pass. The issue, the cloud agent, the Agents secret and pull request review need a hosted repository, so none of them ran.

The Copilot CLI version, the model Auto picked, the run time and the test machine are recorded once for every guide in the run record.


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 2,280,941Version:2026.10just released