ClosedXML 0.100.3

.NET Standard 2.0
dotnet add package ClosedXML --version 0.100.3
NuGet\Install-Package ClosedXML -Version 0.100.3
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ClosedXML" Version="0.100.3" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ClosedXML --version 0.100.3
#r "nuget: ClosedXML, 0.100.3"
#r directive can be used in F# Interactive, C# scripting and .NET Interactive. Copy this into the interactive tool or source code of the script to reference the package.
// Install ClosedXML as a Cake Addin
#addin nuget:?package=ClosedXML&version=0.100.3

// Install ClosedXML as a Cake Tool
#tool nuget:?package=ClosedXML&version=0.100.3

Basic how to

ClosedXML is a .NET library for reading, manipulating and writing Excel 2007+ (.xlsx, .xlsm) files. It aims to provide an intuitive and user-friendly interface to dealing with the underlying OpenXML API.

using (var workbook = new XLWorkbook())
    var worksheet = workbook.Worksheets.Add("Sample Sheet");
    worksheet.Cell("A1").Value = "Hello World!";
    worksheet.Cell("A2").FormulaA1 = "=MID(A1, 7, 5)";

For more information see the documentation or the wiki.

Release notes from 0.100.3

Fix a regression where some types of numbers were inserted as text by InsertData/InsertTable API.

// Only int and double were inserted as numbers, now all number types are
cell.InsertData(new object[] { (sbyte)1, (byte)2, (short)3, (ushort)4, (uint)6, (long)7, (ulong)8, 15f, 17m });

Release notes for 0.100.2

Added conversion for nullable numbers/DateTime/TimeSpan to XLCellValue. Null values will be converted to Blank.Value in the XLCellValue. There is no value in having user code littered by cell.Value = nullableNumber ?? Blank.Value.

Null strings assigned to XLCellValue will also be converted to Blank.Value (unlike exception from 0.100).

Release notes for 0.100.1

Added implicit conversion from decimal to XLCellValue, so it is not necessary to cast decimal to double explicitly.

Basically this now works, as it did pre-0.100:

decimal number = 15.5m;
ws.Cell(1,1).Value = number;

Release notes for 0.100

These are release notes for a version 0.100. We skipped a few version since the last release (0.97), because 0.100 should denote a major change at the very heart of ClosedXML. Not as clean break as I hoped, but close enough.

The list of all things that were changed from 0.97 to 0.100 is at the migration guide at the

This is more like list of you should upgrade despite breaking changes 😃

Memory consumption significantly decreased

Memory consumption during saving of large data workbooks was significantly improved. Originally, ClosedXML workbook representation was converted to DocumentFomrat.OpenXML DOM representation and the DOM was then saved. Instead of creating whole DOM, sheet data (=cell values) are now directly streamed to the output file and aren't included in the DOM.

To demonstrate difference, see the before and after memory consumption of a report that generated 30 000 rows, 45 columns. Memory consumption has decreased from 2.08 GiB 🡆 0.8 GiB.

Save cells and strings through DOM: 2.08 GiB 1874-dtoMemory-save-30k-text-DOM

Save cell and strings through streaming: 0.8 GiB 1874-dtoMemory-save-30k-text-streaming

The purple area are bytes of uncompressed package zip stream.

Cell value is now strongly typed

IXLCell.Value and IXLCellValue.CachedValue have now type XLCellValue. At the core, xlsx consists of addressable cells with a functions that transform a set of values in source cells to different values in target cells. Is is really important to represent potential values of cells by a sane type. All other things, pivot tables, auto filter, graphs rely on this premise.

Cell value has been represented as string text and a value. The string depended on the value, e.g. 0/1 for boolean. That has been the case since the beginning of the ClosedXML project (see the original XLCell). The value was also returned as an Object. This approach has several drawbacks

  • Object is not suitable representation of cell value. User had no idea what kind of values could be returned as a cell value. Everything could also break down, if a new type would be returned (e.g. XLError).
  • Setter could accept different types that the getter returned. E.g. it was possible to set cell value to a IXLColumn.
  • Values were always boxed/unboxed. That is not a problem for small amount of data, but it is not great for large workbooks.
  • It caused an potentially buggy behavior in other places of the ClosedXML.

Value of a cell is not represented by a XLCellValue structure. It is basically a union of one of possible types that can be value of a cell:

  • blank
  • boolean
  • number
  • text
  • error
  • datetime - basically number representing serial datetime, use serial datetime.
  • duration - basically number representing serial datetime, use serial datetime

Since datetime and duration are basically masqaraded number, you can use XLCellValue.GetUnifiedNumber() to get a backing number, no matter if the type is number, datetime and duration.

The structure contains implicit operators, as well as other methods to make transaction as seamless as possible

// Will use an implicit cast operator to convert string to XLCellValue and pass it to the Value setter
ws.Cell("A1").Value = "Text";

There is also a new singleton Blank.Value that represent a blank value of a cell. Null is not blank. Empty string is not a blank value of a cell. Null instead of blank was considered and everything is just so much easier to work with, if blank is represented as a custom singleton type and not as a null.

XLCellValue will be able to represent all values of a cell and won't be boxed/unboxed all the time.

Cell data type is no longer guessed

ClosedXML used to guess a data type from a value. It caused all sort of unexpected behaviors (e.g. text value Z12.31 has been converted to date time 12/30/2022 19:00). Date caused most problems, but other sometimes too (e.g. text "Infinity" was detected as a number).

This behavior was likely intended to emulate how user interacts with an Excel. Excel guesses type, but only if the cell Number Format is set to "General" (e.g. if NumberFormat is set to Text, there is no conversion even in Excel). Application is not human and doesn't have to interact with xlsx in the same way.

This behavior was removed. Type that is set is the type that will be returned. Note that although XLCellValue can represent date and time as a different types, in reality that is only presentation logic for user. They are both just serial date time numbers.

Cell value now can be XLError or Blank

Cell value now can accurately represent error or a blank value.

ClosedXML used to throw on error value and cell couldn't contain an error. That was a significant problem, especially for formula calculation where formula referenced a cell that should contain an error value.

ClosedXML used to represent blank cell as an empty string, but no longer. It uses Blank.Value singleton, wrapped in XLCellValue. Also brings significant improvement in accuracy for CalcEngine evaluation.

Text to number coercion

Excel has a pretty complicated undocumented coercion process from text to number. It can convert fraction text (="1 1/2"*2 is 3), dates (e.g. ="1900-01-05"*2 is 10, though date format is culture specific), percent (e.g. ="100%"*2), braces imply negative value (="(100%)"*2 = -2) and many more. That causes a significant problems for formula evaluation, especially if the source cell contains a date as a text, not as a date.

ClosedXml used to only convert test that looked like double, it now coerces nearly everything Excel does. Coercion from dates should mostly work, but Excel has it's own database of acceptable formats and it's own format, while we rely on .NET Core infrastructure.

CalcEngine doesn't throw exceptions

Thanks to incorporation of XLError to core of CalcEngine, the exceptions are no longer necessary and have been removed. Error is a normal value type that is used during formula evaluation (e.g. ISNA accepts it and VLOOKUP returns it).

Technically speaking CalcEngine can still throw MissingContextException, but only if evaluation is not called from a cell, but from method like XLWorkbook.Evaluate. Functions like ROW just can't work without the context of the cell.

Unimplemented functions now return #NAME?

If you ever tried to use CalcEngine, you have encountered a dreaded The function *SomeFunctionwas not recognised. exception.

ClosedXML will no longer throw an exception on unimplemented function, but will return #NAME? error instead. It has several reasons

  • It aligns behavior of user defined functions in like with predefined functions. ClosedXML doesn't throw anything on =SOME.UNKNOWN.FUN(4), why should it throw on =LARGE(A1:A5,1)?
  • By default, ClosedXML doesn't save calculated values. A portion of workbook that doesn't use unimplemented function should work correctly, maybe that is enough for some use case? Excel (nearly always) recalculates everything on load anyway.

Basically, the exception doesn't bring any benefit and only imposes costs. User can report missing function on #NAME? error just like on exception.

Array literal can now be parsed

CalcEngine now can evaluate array literal expressions, so formulas like VLOOKUP(4, {1,2; 3,2; 5,3; 7,4}, 2) now actually work.

Array processing is limited to argument parsing across formulas and CalcEngine still needs some love to process it work correctly. Array formulas are still not implemented.

Reimplementation of information and lookup functions

Information and lookup functions were reimplemented to take advantage of other improvements. They should now be compliant with Excel (with exception of wildcard search for VLOOKUP).

Documentation in the version control

Documentation is being moved from wiki to the ReadTheDocs. It has been there for since 2019, but we didn't actually had any documentation. Documentation is super important and ClosedXML lacks in that area. It is of course WIP, but it should improve over the time (see, or infamous

The move to ReadTheDocs has significant advantages:

  • It is in version control. That means every PR now can contain modification to documentation.
  • It is built as part of CI
  • It is versioned.
  • It uses ReStructured Text (rst) that has more rich style options and even plugins. Commonmark is heavily limited in style application.
  • It can generate documentation from xml comments
  • It can use references and includes. That means all examples can be in separate files and only included to documentation. Separate example files could be just complied and checked for correctness (we are not doing that ATM, but will likely do at some point in the future). That would solve the pesky issue of outdated examples in documentation.

Notes about breaking changes

We are not breaking the compatibility just because. Break imposes heavy penalty on users of the library. That makes it less likely to use it and that is definitely not the goal. Even the ClosedXML.Report must be fixed after every release.

That is not desirable situation. Version 1.0 and semantic versioning is certainly the goal. But it must be with an clear API that can endure some development between minor version. That is just not the case at the moment.

API will be reviewed along with the documentation and will be adjusted as necessary. ClosedXML will practice release early, release often. If breaking changes are not acceptable, stay on version that works and wait for 1.0 (though that will likely take at least a year, likely more... we are on a second decade).

Technically we do semver since forever, since Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable. ). Initial development for a decade /sigh.

Future plans

Similar to current release, the general plan is to work on neglected foundational things and bug fixes.

  • Fix AutoFilter - doesn't work correctly, API is a mess and accepts any type. I wanted to have it done for 0.100 ¯_(ツ)_/¯
  • Finish CalcEngine redesign with array formulas.
  • Update XLParser to 1.6.2, I added PRs 162 and163 to improve speed by about factor of 3x (test dataset was parsed in 13 seconds vs 47 originally). But not enough time to upgrade the version ¯_(ツ)_/¯
  • Housekeeping of PR - some PRs were merged, but most are still there.
  • Cell sizing is a mess. Clean it up and fix AdjustToContent to be in line with what Excel does (research was done:
  • Make a fuzzer for function evaluation that compares ClosedXML implementation with result from Excel

It is likely there will be 0.100.x to fix whatever bugs XLCellValue caused that weren't convered by tests.

Pivot tables won't get any love in 0.101, but hopefully in the next one. It is one of distinguishing features of ClosedXML and it has a lot of reported issues.

Product Versions
.NET net5.0 net5.0-windows net6.0 net6.0-android net6.0-ios net6.0-maccatalyst net6.0-macos net6.0-tvos net6.0-windows net7.0 net7.0-android net7.0-ios net7.0-maccatalyst net7.0-macos net7.0-tvos net7.0-windows
.NET Core netcoreapp2.0 netcoreapp2.1 netcoreapp2.2 netcoreapp3.0 netcoreapp3.1
.NET Standard netstandard2.0 netstandard2.1
.NET Framework net461 net462 net463 net47 net471 net472 net48 net481
MonoAndroid monoandroid
MonoMac monomac
MonoTouch monotouch
Tizen tizen40 tizen60
Xamarin.iOS xamarinios
Xamarin.Mac xamarinmac
Xamarin.TVOS xamarintvos
Xamarin.WatchOS xamarinwatchos
Compatible target framework(s)
Additional computed target framework(s)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (228)

Showing the top 5 NuGet packages that depend on ClosedXML:

Package Downloads

Package Description


ClosedXML.Report is a tool for report generation and data analysis in .NET applications through the use of Microsoft Excel. ClosedXML.Report is a .NET-library for report generation Microsoft Excel without requiring Excel to be installed on the machine that's running the code.


MVC extensions for ClosedXML


An implementation of ICsvParser and ICsvSerializer from CsvHelper that reads and writes using the ClosedXml library.


An implementation of ICsvParser and ICsvSerializer from CsvHelper that reads and writes using the ClosedXml library.

GitHub repositories (25)

Showing the top 5 popular GitHub repositories that depend on ClosedXML:

Repository Stars
ASP.NET Core eCommerce software. nopCommerce is a free and open-source shopping cart.
Clean Architecture Template for .NET 6.0 WebApi built with Multitenancy Support.
Yarn Spinner is a tool for building interactive dialogue in games!
你的最佳游戏配置解决方案 {excel, csv, xls, xlsx, json, bson, xml, yaml, lua, unity scriptableobject} => {json, bson, xml, lua, yaml, protobuf(pb), msgpack, flatbuffers, erlang, custom template} data + {c++, java, c#, go(golang), lua, javascript(js), typescript(ts), erlang, rust, gdscript, protobuf schema, flatbuffers schema, custom template} code。
Fast, Low-Memory, Easy Excel .NET helper to import/export/template spreadsheet
Version Downloads Last updated
0.100.3 83,275 1/12/2023
0.100.2 11,186 1/10/2023
0.100.1 3,562 1/9/2023
0.100.0 10,450 1/9/2023
0.97.0 635,864 10/21/2022
0.96.0 1,728,483 6/29/2022
0.95.4 11,381,464 12/16/2020
0.95.3 5,196,062 5/25/2020
0.95.2 536,118 4/26/2020
0.95.1 152,732 4/23/2020
0.95.0 547,588 4/15/2020
0.95.0-beta2 73,729 8/21/2019
0.95.0-beta1 38,705 4/4/2019
0.94.2 4,530,475 12/18/2018
0.94.0 47,990 12/12/2018
0.94.0-rc2 4,476 11/29/2018
0.94.0-rc1 7,299 11/11/2018
0.93.1 855,805 8/7/2018
0.93.0 383,175 6/25/2018
0.93.0-rc3 7,370 6/7/2018
0.93.0-rc2 3,007 5/31/2018
0.93.0-beta4 3,968 5/14/2018
0.93.0-beta2 4,437 4/26/2018
0.93.0-beta1 2,129 4/19/2018
0.92.1 685,390 4/10/2018
0.92.0-beta1 4,284 3/22/2018
0.91.1 41,472 4/4/2018
0.91.0 234,825 1/31/2018
0.91.0-beta3 2,735 1/23/2018
0.91.0-beta2 12,667 12/8/2017
0.91.0-beta1 2,156 11/29/2017
0.90.0 562,533 10/23/2017
0.90.0-beta2 2,448 10/6/2017
0.89.0 322,552 9/12/2017
0.89.0-beta1 4,228 8/23/2017
0.88.0 181,360 7/24/2017
0.88.0-beta1 9,783 7/10/2017
0.87.1 1,075,939 4/3/2017
0.86.0 318,240 1/6/2017
0.85.0 244,089 12/7/2016
0.80.1 500,126 9/15/2016
0.76.0 1,368,178 12/16/2014
0.75.0 163,888 9/17/2014
0.74.0 30,390 8/10/2014
0.73.0 35,704 6/24/2014
0.72.3 18,670 6/4/2014
0.72.2 2,407 6/4/2014
0.72.1 47,896 6/4/2014
0.72.0 5,778 6/4/2014
0.71.1 12,091 5/26/2014
0.70.0 7,631 5/18/2014
0.69.2 139,758 10/3/2013
0.69.1 55,264 8/15/2013
0.69.0 4,708 8/10/2013
0.68.1 77,837 10/20/2012
0.68.0 3,509 10/12/2012
0.67.2 17,005 8/14/2012
0.67.1 2,416 8/13/2012
0.67.0 2,454 8/12/2012
0.66.1 3,113 7/28/2012
0.66.0 2,631 7/18/2012
0.65.2 5,975 4/21/2012
0.64.0 8,125 2/4/2012