Dependency-free Office Open XML for .NET 10. Read, write and stream .xlsx with a clean, fluent API — styling, formulas, charts, pivots — on a small engine you can actually audit.
| A | B | C | |
|---|---|---|---|
| 1 | Product | Region | Sales |
| 2 | Coffee | North | 1 240 |
| 3 | Tea | South | 980 |
| 4 | Cocoa | East | 1 530 |
| 5 | Mate | West | 760 |
| 6 | Total | 4 510 | |
A small, readable OOXML (ECMA-376) engine built only on the BCL — no third-party packages, no transitive chain. Small enough to read end to end and audit line by line. You own it, and it's MIT.
Built only on System.IO.Compression + System.Xml. Nothing else, ever — no transitive supply chain.
A hand-written OOXML (ECMA-376) writer/reader. Small enough to read end to end and audit line by line.
Every feature is validated by opening the file in a real spreadsheet app — no "we found a problem / repair" prompt.
Package → worksheet → cells. Style, compute formulas in memory, stream a million rows, read it back.
using var pkg = new SpreadsheetPackage();
var sheet = pkg.Workbook.Worksheets.Add("Contacts");
sheet["A1"].Value = "Name";
sheet["B1"].Value = "Age";
sheet["C1"].Value = "City";
sheet["A2"].Value = "Anna";
sheet["B2"].Value = 28;
sheet["C2"].Value = "Springfield";
sheet.Column(1).Width = 16;
pkg.SaveAs("contacts.xlsx");using Matios.Spreadsheet.Styling;
var s = pkg.Workbook.Worksheets.Add("Styled");
s["A1"].Value = "Title";
s["A1"].Style.Font.Bold = true;
s["A1"].Style.Font.Size = 14;
s["A1"].Style.Font.Color = Colors.White;
s["A1"].Style.Fill.BackgroundColor = Colors.Navy;
s["A1"].Style.Border.SetAll(BorderStyle.Thin, Colors.Black);
s["A1"].Style.HorizontalAlignment = HorizontalAlignment.Center;
s["B2"].Value = new DateTime(2026, 7, 8);
s["B2"].Style.NumberFormat.Format = "dd/MM/yyyy";
s["C2"].Value = 0.1537;
s["C2"].Style.NumberFormat.Format = "0.0%";var s = pkg.Workbook.Worksheets.Add("Calc");
double[] values = { 10, 25, 40, 15, 30 };
for (int i = 0; i < values.Length; i++) s[i + 1, 1].Value = values[i];
s["C1"].Formula = "SUM(A1:A5)";
s["C2"].Formula = "AVERAGE(A1:A5)";
s["C3"].Formula = "MAX(A1:A5)-MIN(A1:A5)";
s["C4"].Formula = "IF(SUM(A1:A5)>100,\"over\",\"under\")";
pkg.Calculate(); // compute in memory — no spreadsheet app needed
Console.WriteLine(s["C1"].Value);// Constant memory — a million rows without holding them all in RAM.
using (var wb = StreamingWorkbook.Create("big.xlsx"))
{
var header = wb.CreateStyle(st =>
{
st.Font.Bold = true;
st.Font.Color = Colors.White;
st.Fill.BackgroundColor = Colors.Navy;
});
var s = wb.AddSheet("Transactions");
s.AddRow(
StreamingWorkbook.Cell("Product", header),
StreamingWorkbook.Cell("Date", header),
StreamingWorkbook.Cell("Amount", header));
for (int i = 1; i <= 1_000_000; i++)
s.AddRow($"Product {i}", baseDate.AddDays(i % 30), i * 100.0);
}using var loaded = SpreadsheetPackage.Load("sales.xlsx"); // path, Stream, byte[] or FileInfo
var sheet = loaded.Workbook.Worksheets[0];
Console.WriteLine(sheet["A2"].Value); // "Coffee"
Console.WriteLine(sheet["B4"].Formula); // "SUM(B2:B3)"
Console.WriteLine(sheet["B4"].Value); // cached result left behindThe streaming path writes each row straight to the archive in constant memory — far faster and lighter than building the whole model first.
| Write path | 200k cells | 1M cells | Memory (1M) |
|---|---|---|---|
| DOM (in-memory) | ~535 ms | ~2,045 ms | ~877 MB |
| Streaming | ~175 ms | ~631 ms | ~272 MB |
Measured with the project's BenchmarkDotNet suite on .NET 10 Release, one machine. Hardware-dependent — treat as a relative reference (DOM vs streaming), not absolute.
SUM · AVERAGE · MIN · MAX · COUNT · IF · ROUND · ABS · CONCAT and more — computed in memory.The engine is feature-complete, green, and published on NuGet.
dotnet add package Matios.Spreadsheet.Open source · MIT · .NET 10 · zero dependencies
dotnet add package Matios.Spreadsheet