---
name: ironxl
description: >
  Create, read, edit and convert Excel and spreadsheet files in C#/.NET using IronXL (the
  `IronXL.Excel` NuGet package). Use when the task involves XLSX/XLS/XLSM/XLTX/CSV/TSV files,
  WorkBook/WorkSheet/Cell/Range objects, reading or writing cell values, formulas and
  recalculation, cell styling (fonts, borders, backgrounds, number formats), conditional
  formatting, named ranges/tables, charts, freeze panes, merged cells, row/column grouping,
  password-protecting or encrypting a workbook, converting between spreadsheet formats
  (XLSX/XLS/CSV/TSV/JSON/XML/HTML), or moving data between a workbook and
  System.Data.DataSet/DataTable — or whenever a project already references `IronXL`,
  `WorkBook`, `WorkSheet`, or uses the `worksheet["A1"]` indexer syntax.
---

# IronXL (C# / .NET)

IronXL reads, creates and edits Excel and spreadsheet files **without Microsoft Office or
Interop** — a single pure-managed C# library. Cells and ranges are addressed with familiar A1
syntax (`workSheet["A1"]`, `workSheet["A2:B10"]`), formulas recalculate automatically, and
workbooks convert freely to CSV/TSV/JSON/XML/HTML or to `System.Data.DataSet`/`DataTable`.

## Scope of this skill

| | |
|---|---|
| Package | `IronXL.Excel` (one package, no platform variants) |
| Versions | 2023.x – 2026.x |
| Namespaces | `IronXL`, `IronXL.Styles`, `IronXL.Formatting`, `IronXL.Drawing.Charts`, `IronXL.Drawing.Images`, `IronXL.Metadata`, `IronXL.Options`, `IronXL.DataValidations` |
| Runtimes | .NET Framework 4.6.2+, .NET Standard 2.0+, .NET Core 2.0+, .NET 5–10, Xamarin/MAUI |
| Native deps | None documented. IronXL is a pure-managed library — unlike IronPDF/IronOCR there is no embedded browser/OCR engine and no separate Linux/macOS package to pick. |

## Install

```bash
dotnet add package IronXL.Excel
```

One package covers Windows, Linux, macOS, Docker, Azure and AWS — there is nothing else to
choose. If a project references a bare `IronXL` package instead of `IronXL.Excel`, treat that
as a stale/incorrect reference and correct it.

## Licensing — do this first, every time

```csharp
IronXL.License.LicenseKey = Environment.GetEnvironmentVariable("IRONXL_LICENSE_KEY");
if (!IronXL.License.IsLicensed)
    Console.Error.WriteLine("IronXL is unlicensed — production use will throw.");
```

Rules:

- Read the key from the environment (`IRONXL_LICENSE_KEY`) or user secrets. **Never** inline a
  key in source, never write one into a file you commit, never echo one to the terminal.
- **Treat a missing key as a blocker, not a warning.** Verified directly against the shipped
  package: with no key set, the very first `WorkBook.Create`/`WorkBook.Load` call throws
  `IronSoftware.Exceptions.LicensingException: Production License Required`, quoting *"Development
  use: Free for 7 days / Production use: Requires a license"*. There is no silent watermark
  fallback like IronPDF's — **it refuses to run at all** once outside the dev grace window. If
  `IsLicensed` is false, say so and ask the user for a key rather than assuming code will run.
  Trial keys: <https://ironsoftware.com/csharp/excel/licensing/> (30-day trial, full
  functionality).
- `IronXL.License.IsValidLicense(key)` checks a key string without applying it.
- Alternative places to set the key instead of code: `App.Config`/`Web.Config` —
  `<add key="IronXL.LicenseKey" value="..."/>` inside `appSettings` — or `appsettings.json` in
  .NET Core, key name `"IronXL.LicenseKey"`.
- A key is rejected outright (`LicensingException: License Key Not Recognized`) if it's
  truncated, has stray whitespace, or is for a different Iron product.

## Running a one-off task from the terminal

Requires only the .NET SDK. On **.NET 10+** a single file is the whole program — no project,
no `.csproj`, and (verified) **no `PublishAot=false` needed** — IronXL has no native
runtime-codegen dependency the way IronPDF/IronOCR do:

```bash
cat > /tmp/task.cs <<'EOF'
#:package IronXL.Excel@2026.8.1
using IronXL;
IronXL.License.LicenseKey = Environment.GetEnvironmentVariable("IRONXL_LICENSE_KEY");
var wb = WorkBook.Load("input.xlsx");
wb.DefaultWorkSheet["C1"].Formula = "=SUM(A1:B1)";
wb.EvaluateAll();
wb.SaveAs("output.xlsx");
EOF
dotnet run /tmp/task.cs
```

On older SDKs, use a scratch project and reuse the same directory for later tasks:

```bash
dotnet new console -o /tmp/ironxl-scratch && cd /tmp/ironxl-scratch
dotnet add package IronXL.Excel
# write Program.cs, then:
dotnet run
```

When the user is building a feature rather than asking for a one-off artifact, write the same
calls into their application instead of a scratch project.

## Recipes

Every call below was verified either directly against the shipped `IronXL.xml` documentation or
by compiling it against the real assembly (`IronXL.Excel 2026.8.1`).

### Create, load and save workbooks

```csharp
using IronXL;

WorkBook wb = WorkBook.Create(ExcelFileFormat.XLSX);   // or ExcelFileFormat.XLS
WorkSheet ws = wb.CreateWorkSheet("Sheet1");

WorkBook loaded = WorkBook.Load("input.xlsx");         // extension picks the reader:
                                                        // XLS, XLSX, XLSM, XLTX, CSV, TSV, JSON, XML
WorkSheet first = loaded.DefaultWorkSheet;
WorkSheet named = loaded.GetWorkSheet("Sheet2");
foreach (WorkSheet sheet in loaded.WorkSheets) Console.WriteLine(sheet.Name);

loaded.Save();                 // overwrite the file it was loaded from
loaded.SaveAs("copy.xlsx");    // format is inferred from the extension
byte[] bytes = loaded.ToByteArray();
WorkBook fromBytes = WorkBook.Load(bytes);
```

`WorkBook.Create(ExcelFileFormat)` only accepts `XLS`/`XLSX` — that enum has just those two
members. `Load` reads a broader set (XLSM, XLTX, CSV, TSV, JSON, XML) by sniffing the file
extension; use `WorkBook.LoadCSV(path, ExcelFileFormat.XLSX, listDelimiter, convertFieldValues)`
for explicit control over a CSV/TSV import's delimiter.

### Reading and writing cells and ranges

```csharp
WorkSheet ws = wb.DefaultWorkSheet;
ws["A1"].Value = "Total";
ws["B1"].Value = 42;
ws["C1"].Value = new DateTime(2026, 1, 1);

decimal n   = ws["B1"].First().DecimalValue;   // typed accessors: IntValue, DoubleValue,
string  s   = ws["A1"].First().StringValue;    // BoolValue, DateTimeValue, LongValue, ...
string  txt = ws["A1"].First().Text;           // display text
```

**`workSheet["..."]` always returns an `IronXL.Range`, even for a single cell** — `Cell`-only
members (`AddComment`, `RemoveComment`, `FormattedCellValue`) need `.First()` to get the `Cell`:

```csharp
ws["A1"].First().AddComment("Reviewed", author: "qa", isVisible: true);
```

Two gotchas that will bite in a fresh top-level-statements file:

- `IronXL.Range` collides with the built-in `System.Range` (`a[1..3]` slice syntax) — the
  compiler reports `CS0104: 'Range' is ambiguous`. Write `IronXL.Range range = ws["A1:B10"];`
  or alias it (`using Range = IronXL.Range;`).
- Range-level typed accessors (`range.IntValue`, `range.DecimalValue`, …) exist too — they read
  the first cell of the range, same idea as `.First()...Value` on the indexer result.

Bulk range operations: `Range range = ws["A2:A8"]; range.SortAscending(); range.SortDescending();
range.SortByColumn(0, SortOrder.Ascending); range.Trim(); range.ClearContents();` and aggregates
`range.Sum()`, `range.Avg()`, `range.Max()`, `range.Min()`.

### Formulas

```csharp
ws["C1"].Formula = "=SUM(A1:B1)";
wb.EvaluateAll();                                  // recalculates every formula cell in the workbook
string shown = ws["C1"].First().FormattedCellValue; // result, formatted like Excel would show it
bool isFormula = ws["C1"].First().IsFormula;
```

Call `EvaluateAll()` after changing formulas before reading results or saving — cached values
are otherwise stale. `wb.AccellerateFormulaEvaluation` (note the real, misspelled-in-the-API
name — two Ls) is a `bool` that trades memory for a formula-evaluation cache; worth setting on
large recalculation-heavy workbooks.

### Styling and number formats

```csharp
using IronXL.Styles;

IronXL.Range header = ws["A1:C1"];
header.Style.Font.Bold = true;
header.Style.Font.SetColor("#FFFFFF");
header.Style.SetBackgroundColor("#4472C4");
header.Style.BottomBorder.SetColor("#000000");
header.Style.BottomBorder.Type = BorderType.Double;   // Thin, Medium, Thick, Dashed, Dotted, Hair, ...
header.Style.HorizontalAlignment = HorizontalAlignment.Center;
```

Number/date formats use `FormatString`, not a `Style.NumberFormat` object:

```csharp
using IronXL.Formatting;

ws["B2:B100"].FormatString = BuiltinFormats.Currency2;   // Number0/2, Percent2, ShortDate,
                                                          // LongDate1-3, Accounting2Red, Time1-4, ...
ws["D1"].FormatString = "0.00%";                          // or any raw Excel format code
```

`BuiltinFormats` lives in `IronXL.Formatting` — not in the root `IronXL` namespace.

### Conditional formatting

```csharp
using IronXL.Formatting;
using IronXL.Formatting.Enums;

var rule = ws.ConditionalFormatting.CreateConditionalFormattingRule(
    ComparisonOperator.GreaterThan, "100");
ws.ConditionalFormatting.AddConditionalFormatting("B2:B100", rule);
```

`ISheetConditionalFormatting` (exposed as `worksheet.ConditionalFormatting`) also has
`CreateConditionalFormattingRule(formula)` for formula-based rules and
`RemoveConditionalFormatting(index)`.

### Rows, columns, sheets

```csharp
ws.AutoSizeColumn(0);                 // 0-based index; also AutoSizeRow(int)
ws.CreateFreezePane(colSplit: 0, rowSplit: 1);   // freeze header row
ws.GroupRows(1, 5);   ws.UngroupRows(1, 5);      // also GroupColumns/UngroupColumns
ws.Merge("A1:C1");    ws.Unmerge("A1:C1");
wb.RemoveWorkSheet("Sheet2");
wb.SetActiveTab(0);
```

### Named ranges and named tables

```csharp
ws.AddNamedRange("SalesTotal", "Sheet1!$B$2:$B$100", globalName: true);
ws.AddNamedTable("SalesTable", ws["A1:C100"], showFilter: true,
    IronXL.Styles.TableStyle.TableStyleLight9, useFirstRowAsHeader: true);
System.Data.DataTable dt = ws.GetDataTableFromNamedTable("SalesTable", true);
```

### Charts

```csharp
using IronXL.Drawing.Charts;

// row1, col1, row2, col2 are 0-based grid coordinates for the chart's placement
IChart chart = ws.CreateChart(ChartType.Column, row1: 8, col1: 0, row2: 20, col2: 6);
IChartSeries series = chart.AddSeries(xRange: "A2:A6", yRange: "B2:B6");  // range ADDRESSES, not raw values
series.Title = "Q1 Sales";
chart.SetTitle("Quarterly Sales");
chart.SetLegendPosition(LegendPosition.Bottom);   // Top, Bottom, Left, Right, TopRight, None
chart.Plot();

foreach (IChart c in ws.Charts) { /* ... */ }
ws.RemoveChart(ws.Charts[0]);
```

Chart types available: `ChartType.Column`, `.Bar`, `.Line`, `.Pie`, `.Area`, `.Scatter`.
`IChart` only exposes `SetTitle`/`SetLegendPosition` as setters — there are no matching getters.
Charts are **not supported when saving as legacy XLS** — `CreateChart` throws if the workbook's
format is XLS; use XLSX.

### Images and comments

```csharp
using IronXL.Drawing.Images;

ws.InsertImage("logo.png", row1: 0, col1: 0, row2: 5, col2: 3);
ws.InsertImage(imageBytes, ImageFormat.PNG, 0, 0, 5, 3);
foreach (var img in ws.Images) { /* ... */ }
ws.RemoveImage(0);

ws["A1"].First().AddComment("Reviewed by QA", author: "qa", isVisible: true);
```

### CSV, TSV, JSON, XML, HTML

```csharp
wb.SaveAsCsv("out.csv");                 // also on WorkSheet: ws.SaveAsCsv(...)
wb.SaveAsWithCustomDelimiter("out.tsv", "\t");
wb.SaveAsJson("out.json");
wb.SaveAsXml("out.xml");
wb.ExportToHtml("out.html");             // ExportToHtmlString(...) for an in-memory string

WorkBook fromCsv = WorkBook.LoadCSV("data.csv", ExcelFileFormat.XLSX,
    listDelimiter: ",", convertFieldValues: true);
```

### DataSet / DataTable interop

```csharp
using System.Data;

DataTable table = ws["A1:D200"].ToDataTable(useFirstRowAsColumnNames: true);   // Range → DataTable
DataSet   set   = wb.ToDataSet(useFirstRowAsColumnNames: true);                // one DataTable per sheet

WorkBook fromTable = WorkBook.Create();
fromTable.LoadWorkSheet(table);                          // DataTable → new worksheet

WorkBook fromSet = WorkBook.Create();
WorkBook.LoadWorkSheetsFromDataSet(set, fromSet);         // each DataSet table → its own worksheet
```

`ToDataTable`/`ToDataSet` are useful for handing spreadsheet data to a DataGrid, Entity
Framework, or a SQL bulk insert; `LoadWorkSheet`/`LoadWorkSheetsFromDataSet` go the other way.

### Passwords and encryption

```csharp
wb.Password = "secret";
wb.SaveAs("protected.xlsx");             // encrypted using wb.Password when set
System.IO.Stream encrypted = wb.Encrypt("secret");   // or omit to use wb.Password

ws.ProtectSheet("sheetPassword");        // structural protection (lock cells/editing), not encryption
ws.UnprotectSheet();
```

`WorkBook.Password`/`Encrypt` protect the **file** (open-password encryption); `ProtectSheet` is
worksheet structural protection (Excel's "Protect Sheet"), a different mechanism — don't confuse
the two when a user asks to "password protect" a workbook.

### Metadata

```csharp
wb.Metadata.Author = "Finance";
wb.Metadata.Title = "Q3 Report";           // also Subject, Keywords, Company, Manager, Comments
wb.Metadata.CustomProperties.AddProperty("Department", "Finance");
```

## Working with IronPDF

IronXL has no built-in "save as PDF" — confirmed against the shipped XML docs, there is no
`Pdf`-named member anywhere in the `IronXL` assembly. The documented path is to export HTML and
render that with IronPDF (see the **ironpdf** skill):

```csharp
wb.ExportToHtml("report.html");
// then, with IronPdf referenced:
new IronPdf.ChromePdfRenderer().RenderHtmlFileAsPdf("report.html").SaveAs("report.pdf");
```

## Deployment

IronXL is pure managed .NET with no embedded native engine (no Chromium, no Tesseract) and no
platform-specific NuGet package — the same `IronXL.Excel` package runs on Windows, Linux, macOS,
Docker and cloud hosts with no special base image, apt-get step, or GPU/font-rendering concern.
Official guidance for Docker/Azure/AWS focuses on ordinary .NET deployment (Microsoft's
official `dotnet` images, standard App Service/Lambda deployment) — none of it is IronXL-specific
native setup. There is no documented file size, row, or column limit; the historical
65,535-row ceiling some users hit is a legacy **XLS format** limit (1997–2003 file format), not
an IronXL restriction — save as XLSX for large data.

## When something fails

| Symptom | Cause and fix |
|---|---|
| `LicensingException: Production License Required` | No licence applied, or the free 7-day dev grace period has ended. Set `IronXL.License.LicenseKey` before the first `WorkBook.Create`/`Load` call. |
| `LicensingException: License Key Not Recognized` | Key is truncated, has stray whitespace/line breaks, or is for a different Iron product. Re-copy the full key. |
| `CS0104: 'Range' is ambiguous between 'IronXL.Range' and 'System.Range'` | Both are in scope (top-level statements implicitly bring in `System`). Qualify as `IronXL.Range` or alias the type. |
| `worksheet["A1"].AddComment(...)` / `.FormattedCellValue` doesn't compile | The indexer returns a `Range`, not a `Cell`. Call `.First()` to get the `Cell`. |
| Formula shows a stale result | Call `wb.EvaluateAll()` after setting/changing a `Formula` and before reading `FormattedCellValue` or saving. |
| `Exception` thrown from `CreateChart` | Charts aren't supported when the workbook's format is legacy XLS. Create/save as XLSX. |
| Legacy `65,535 row` limit hit | That ceiling belongs to the old XLS (1997–2003) format, not IronXL. Use XLSX. |
| CSV import garbles columns | Wrong delimiter assumed. Use `WorkBook.LoadCSV(path, format, listDelimiter, convertFieldValues)` and pass the actual delimiter explicitly rather than relying on system defaults. |
| A first-chance `FileNotFoundException` appears in the debugger around `SaveAs` on .NET 10 | Documented as benign: .NET's XML serializer probes for an optional `IronXL.XmlSerializers` assembly that doesn't exist; the save still succeeds. Turn on "Just My Code" in the debugger, or ignore it. |
| "Password protected" workbook won't open in Excel after `ProtectSheet` | `ProtectSheet` is structural sheet protection, not file encryption. For an open-password, set `wb.Password` and `SaveAs`/`Encrypt` instead. |

## Rules

- **Never invent a member.** If unsure whether a method or property exists, check the XML
  documentation that ships inside the package before writing code:
  `grep -o 'name="[MPFT]:IronXL\.[^"]*Chart[^"]*"' ~/.nuget/packages/ironxl.excel/<version>/lib/net6.0/IronXL.xml`
  (swap `net6.0` for `netstandard2.0` on older targets). That file is the authoritative surface
  for the installed version — IntelliSense in an IDE reads the same file. This skill's own
  recipes were verified this way; two things worth remembering from that pass: `BuiltinFormats`
  lives in `IronXL.Formatting`, not the root `IronXL` namespace, and the property really is
  spelled `AccellerateFormulaEvaluation` (double L) in the shipped assembly.
- Keep licence keys out of source and out of terminal output.
- Treat an unlicensed/`IsLicensed == false` state as a hard blocker — IronXL throws rather than
  degrading output, so say so and ask for a key instead of assuming code will run past the trial
  window.
- `workSheet["..."]` always returns a `Range`; use `.First()` for `Cell`-only members.
- 0-based row/column coordinates in the chart/freeze-pane/image APIs; A1-style strings elsewhere.
- Call `EvaluateAll()` after editing formulas, before reading results or saving.
- `WorkBook.Password`/`Encrypt` (file encryption) and `WorkSheet.ProtectSheet` (structural
  protection) are different mechanisms — don't conflate them.
- IronXL has no PDF export of its own; route through `ExportToHtml` + IronPDF (see the ironpdf
  skill) when a PDF is requested.
- Official docs and full API reference: <https://ironsoftware.com/csharp/excel/docs/>. Support:
  support@ironsoftware.com.
