SwiftExcel (Free) Free

The free, open-source edition of SwiftExcel. Streams a single-sheet .xlsx document directly to a file or arbitrary Stream, with near-zero memory usage. Source code on GitHub.

Installation

Install via NuGet:

dotnet add package SwiftExcel

Then bring the namespace into scope:

using SwiftExcel;
using SwiftExcel.Extensions; // only if you use ExportToExcel

The library targets netstandard2.0 and depends on SharpCompress for ZIP packaging.

Quick start

The minimal "100 rows × 10 columns" example from the official sandbox:

using (var ew = new ExcelWriter("C:/temp/test.xlsx"))
{
    for (var row = 1; row <= 100; row++)
    {
        for (var col = 1; col <= 10; col++)
        {
            ew.Write($"row:{row}-col:{col}", col, row);
        }
    }
}

Wrapping the writer in using finalizes the document on Dispose(). Calling Save() manually is also supported and equivalent.

Streaming model — what you must know

  • Cells must be written in increasing (row, col) order. Writing to a row or column you have already passed throws SwiftExcelException. This is what makes streaming so cheap; it is intentional.
  • col and row are 1-based. Passing 0 or a negative value throws.
  • Skipping rows or columns is fine — empty <row/> and empty cells are emitted automatically.
  • One ExcelWriter per file/stream. Not thread-safe.

API reference

Namespace: SwiftExcel.

ExcelWriter

Implements IDisposable. Two constructor overloads:

ExcelWriter(string filePath, Sheet sheet = null)

  • filePath — full path to the output file. The directory is created if it does not exist.
  • sheet — optional Sheet with custom name, column widths, RTL and wrap-text settings. null uses defaults ("Sheet1", no column widths, LTR, no wrap).

ExcelWriter(Stream stream, Sheet sheet = null)

  • stream — your own writable stream (FileStream, MemoryStream, an Azure Blob BlockBlobClient.OpenWrite() stream, etc.). Useful for writing directly to cloud storage without a temp file.
  • For non-seekable streams, Zip64 is automatically disabled internally.
  • The library does not close an externally provided stream when disposed; you own its lifetime.

Write()

void Write(string value, int col, int row, DataType dataType = DataType.Text)

  • value — the cell value as a string. null is allowed and is escaped safely.
  • col — 1-based column index (column 1 = A).
  • row — 1-based row index.
  • dataTypeDataType.Text (default) or DataType.Number. Numbers are written without the t="str" attribute so Excel parses them as numeric.

Throws SwiftExcelException if col ≤ 0, row ≤ 0, or you try to write to an already-processed (row, col).

WriteFormula()

void WriteFormula(FormulaType type, int col, int row, int sourceCol, int sourceRowStart, int sourceRowEnd)

  • type — one of FormulaType (Average, Count, Max, Sum).
  • col, row — destination cell of the formula result.
  • sourceCol — column the formula reads from.
  • sourceRowStart, sourceRowEnd — inclusive row range to operate over.

Free SwiftExcel only supports the four built-in formulas. For arbitrary formulas (IF, VLOOKUP, HYPERLINK, etc.) you need SwiftExcel.Pro's DataType.CustomFormula.

Save() and Dispose()

void Save()

Closes the worksheet, finalizes the ZIP entries and disposes the internal stream (only the internal one — externally supplied streams are left open). Calling Save() twice is a no-op because of an internal Finalized guard.

void Dispose() (inherited)

Calls Save() if it has not been called yet. Always wrap ExcelWriter in using unless you have a specific reason to call Save() manually.

DataType (enum)

ValueMeaning
DataType.TextDefault. Cell is emitted with t="str"; Excel treats it as a string. Safe for any value, including numeric strings that should not be coerced.
DataType.NumberCell is emitted without the t="str" attribute, so Excel parses the value as numeric. Use this for integers, decimals, and dates that have been converted with ToOADate().

FormulaType (enum)

ValueExcel function
FormulaType.Average=AVERAGE(range)
FormulaType.Count=COUNT(range)
FormulaType.Max=MAX(range)
FormulaType.Sum=SUM(range)

Sheet

Optional configuration object passed to the ExcelWriter constructor.

PropertyTypeDescription
NamestringSheet tab name. Defaults to "Sheet1". Truncated to 31 characters and XML-escaped automatically.
RightToLeftboolRight-to-left column direction.
WrapTextboolApply wrap-text alignment to all cells. Required if your cell values contain \n and you want them displayed as multi-line.
ColumnsWidthIList<double>Per-column widths starting at column 1. Index i sets width for column i+1. Columns past the list use Excel's default.

Configuration (static)

static bool Configuration.UseEnchancedXmlEscaping = true

When true (default), control characters that are invalid in XML are encoded using Excel's _xHHHH_ escape syntax. Set to false only if you have already sanitized your input and want a small perf boost.

IList<T>.ExportToExcel() — list export extension

For one-shot export of a collection of objects to a sheet, the free edition includes a reflection-based extension method in SwiftExcel.Extensions:

void IList<T>.ExportToExcel(string filePath, string sheetName = null)

  • Public properties become columns.
  • The first row contains headers.
  • Numeric properties (int, decimal, byte, …) are written as DataType.Number; everything else as DataType.Text.
  • Decorate properties with [ExcelExport] to override Name, Order, and column Width.

ExcelExportAttribute

Located in SwiftExcel.Attributes:

public class TestModel
{
    [ExcelExport(Order = 1)]
    public int FirstProperty { get; set; }

    [ExcelExport(Name = "Custom SecondProperty Name", Order = 2)]
    public string SecondProperty { get; set; }

    [ExcelExport(Name = "Third", Order = 3, Width = 40.00)]
    public char ThirdProperty { get; set; }

    [ExcelExport(Width = 60.00)]
    public DateTime FourthProperty { get; set; }

    public byte FifthProperty { get; set; }
}

Examples

Data & structure — Basic write

The canonical example from the official sandbox:

using (var ew = new ExcelWriter("C:/temp/test.xlsx"))
{
    for (var row = 1; row <= 100; row++)
    {
        for (var col = 1; col <= 100; col++)
        {
            ew.Write($"row:{row}-col:{col}", col, row);
        }
    }
}

Data & structure — Sheet name, widths, RTL, wrap text

Configure the sheet via the optional Sheet parameter and use manual Save():

var sheet = new Sheet
{
    Name = "Monthly Report",
    RightToLeft = true,
    WrapText = true,
    ColumnsWidth = new List<double> { 10, 12, 8, 8, 35 }
};

var ew = new ExcelWriter("C:/temp/test.xlsx", sheet);
for (var row = 1; row <= 100; row++)
{
    for (var col = 1; col <= 10; col++)
    {
        ew.Write($"row:{row}-col:{col}", col, row);
    }
}

ew.Save();

Data & structure — Direct streaming to Azure Blob Storage

The Stream constructor lets you upload a workbook to cloud storage while it is being generated — no temporary file, no full-document buffer. Pattern from the sandbox:

var blobServiceClient = new BlobServiceClient(connectionString);
var containerClient   = blobServiceClient.GetBlobContainerClient(containerName);
var blobClient        = containerClient.GetBlockBlobClient("report.xlsx");

using (var stream = blobClient.OpenWrite(true))
using (var ew = new ExcelWriter(stream))
{
    for (var row = 1; row <= 100; row++)
    {
        for (var col = 1; col <= 100; col++)
        {
            ew.Write($"row:{row}-col:{col}", col, row);
        }
    }
}

The Azure Blob stream is non-seekable, so SwiftExcel automatically disables Zip64 internally. The blob client is responsible for closing its own stream.

Formulas — Built-in Average / Count / Max / Sum

From the sandbox: write 20 numeric values, then four aggregate formulas under them.

using (var ew = new ExcelWriter("C:/temp/test.xlsx"))
{
    const int col = 1;
    var row = 1;
    for (; row <= 20; row++)
    {
        ew.Write(row.ToString(), col, row, DataType.Number);
    }

    ew.WriteFormula(FormulaType.Average, col, ++row, col, 1, 20);
    ew.WriteFormula(FormulaType.Count,   col, ++row, col, 1, 20);
    ew.WriteFormula(FormulaType.Max,     col, ++row, col, 1, 20);
    ew.WriteFormula(FormulaType.Sum,     col, ++row, col, 1, 20);
}

Data types — Numbers

Pass DataType.Number so Excel parses the value as numeric instead of text:

ew.Write("42",        1, 1, DataType.Number);
ew.Write("3.14159",   2, 1, DataType.Number);
ew.Write("-12.5",    3, 1, DataType.Number);

Numeric values are written using the invariant culture internally, so 3.14 stays 3.14 regardless of the user's thread culture (no surprise commas).

Data types — Dates with .ToOADate() (critical pattern)

Excel does not have a native date type — internally a date is just a double (an OLE Automation date). To write a real date value you must convert it with DateTime.ToOADate() and write it as DataType.Number:

var dateToExport = new DateTime(2023, 8, 21, 10, 51, 18);

using (var ew = new ExcelWriter("C:/temp/test.xlsx"))
{
    ew.Write($"{dateToExport.ToOADate()}", 1, 1, DataType.Number);
}

Why this matters: if you write the date as a string (DataType.Text) Excel will not let users sort, filter, or compute on it. With ToOADate() + DataType.Number the cell is genuinely a date — but in the Free edition it will display as a raw double until a user formats the column as a date in Excel.

For automatic date formatting use SwiftExcel.Pro with NumberFormat.Custom("dd/mmm/yyyy hh:mm:ss").

Special values — Invalid XML & reserved characters

SwiftExcel automatically escapes < > & ' " and, when Configuration.UseEnchancedXmlEscaping is true (default), encodes XML-invalid control characters using Excel's _xHHHH_ syntax. You can pass any user input safely:

Configuration.UseEnchancedXmlEscaping = true;
using (var ew = new ExcelWriter("C:/temp/test.xlsx"))
{
    ew.Write("<",  1, 1);
    ew.Write(">",  2, 1);
    ew.Write("&",  3, 1);
    ew.Write("'",  4, 1);
    ew.Write("\"", 5, 1);
}

Bulk export — IList<T>.ExportToExcel()

One-line export of a collection. Headers are derived from property names or [ExcelExport] attributes.

var testCollection = new List<TestModel>
{
    new TestModel(),
    new TestModel()
};

// default sheet name "Sheet1"
testCollection.ExportToExcel("C:/temp/test.xlsx");

// or with a custom sheet name
testCollection.ExportToExcel("C:/temp/test.xlsx", sheetName: "Sheet2");

Property order in the workbook follows the [ExcelExport(Order = N)] attribute (ascending). Properties without an explicit order go last in the order returned by reflection.

FAQ

Can SwiftExcel read existing .xlsx files?

No. SwiftExcel is intentionally write-only. Skipping the read path is what allows it to stream and stay near-zero memory. For reading you'll need a different library (e.g. ExcelDataReader).

Does it support .xls or .xlsm?

No. SwiftExcel produces only the modern Open XML .xlsx format. There is no support for legacy binary .xls or macro-enabled .xlsm files.

Is ExcelWriter thread-safe?

No. A single ExcelWriter must not be shared across threads. The streaming model writes XML in strict order, so concurrent access will corrupt the file. Parallelize across files (one writer per thread) instead of within a single workbook.

Which .NET versions are supported?

The package targets netstandard2.0, so it works on .NET Framework 4.6.1+, .NET Core 2.0+, and .NET 5, 6, 7, 8 and 9. The only runtime dependency is SharpCompress.

How big a file can SwiftExcel produce?

Memory consumption is essentially flat regardless of row count, so files with millions of rows are routine. The hard upper bounds are Excel's own: 1,048,576 rows × 16,384 columns per worksheet. Free SwiftExcel writes a single sheet, so that's the absolute ceiling.

How do I output a date?

Convert your DateTime with DateTime.ToOADate() and write it as DataType.Number. The Free edition does not support custom number formats, so the cell will display as a raw double until a date format is applied in Excel. For automatic date formatting use SwiftExcel.Pro.

How do I output a hyperlink?

Free SwiftExcel does not support hyperlinks directly. Use SwiftExcel.Pro's DataType.CustomFormula with Excel's HYPERLINK("url","label") function.

Can I cancel an in-progress export?

Just stop calling Write() and call Dispose() (or let the using block exit). The partial file will be a valid .xlsx containing whatever you have written so far. If you want to discard the result, delete the file or close the stream without flushing.

Why do my cells appear in the wrong column on a new row?

Make sure you have at least version 1.1.10 of the package — earlier versions had a bug where the column counter was not reset between rows. Update via dotnet add package SwiftExcel.

I need styles, multiple sheets, hyperlinks, freeze panes…

Those features live in SwiftExcel.Pro. The streaming engine, performance characteristics and data-flow semantics are the same — only the API surface is richer.

Need styling, multiple sheets, hyperlinks or formatted dates?

See the SwiftExcel.Pro documentation → — full styles, fonts, colors, borders, number formats, custom formulas, merged cells, auto filters, freeze panes and more.