xlsxwriter 0.8.0
xlsxwriter: ^0.8.0 copied to clipboard
Native, fast, low-memory Excel .xlsx writer for Dart. An FFI binding to libxlsxwriter with a constant-memory mode for large sheets.
xlsxwriter #

A native, fast, low-memory Excel .xlsx writer for Dart. It is an FFI binding
to libxlsxwriter by John McNamara,
compiled from vendored C source at build time. It writes spreadsheets; it does
not read them. The one feature that sets it apart from the pure-Dart writers is
a constant-memory mode that streams rows to disk, so a sheet of a million rows
never has to fit in RAM.
If you need to read or edit existing files, use
excel or
spreadsheet_decoder. This
package is for the export and report-generation path: turning rows of data into
an .xlsx quickly and with flat memory, with formats, tables, charts, images,
and conditional formatting along the way.
How constant-memory writing works #
An .xlsx file is a ZIP archive of XML documents. Rename one to .zip, unpack
it, and you find a small tree of parts: [Content_Types].xml, _rels/.rels,
xl/workbook.xml, xl/styles.xml, and one xl/worksheets/sheetN.xml per
sheet. For a large export almost all of the bytes are in one part: the worksheet
XML, a flat stream of <row> elements holding <c> cell elements, in strict
document order, top row first and left column first within each row.
That ordering is what makes streaming possible. The worksheet part is written in document order and is never read back while you build it, so the writer never needs to revisit a row it has already emitted. A format that is append-only in document order is one you can stream.
There are two ways to produce that XML:
- Default mode builds the whole workbook in memory first. libxlsxwriter
keeps every row in a red-black tree, and every cell in another, all resident
until
close(). Peak memory grows with the total number of cells. A million rows by ten columns is ten million cell objects alive at once, about 1.4 GiB in the benchmark below. - Constant-memory mode (
Workbook.constantMemory) keeps exactly one row in memory: a single reused row plus a per-column array for the cells of the row you are writing now. When you move to a higher row number, that row is serialized straight to XML in a temporary file on disk and its cells are freed. Atclose(), the temp file (already the finished sheet XML) is copied into the ZIP and deflated with zlib. Peak memory tracks your widest row, not the sheet, so the curve is flat: about 189 MiB from ten thousand rows to a million.

The cost of streaming is random access. Once you advance past a row it is on
disk and gone: writing back to an earlier row throws an XlsxWriterException.
That one rule covers merges too — mergeRange works in constant-memory mode
as long as the range is at or ahead of the current row, and throws if it
reaches back into rows already flushed. Write top to bottom. Column order within the current row
does not matter, since those cells stay in the per-column array until the row
flushes. Default mode gives up the flat curve in exchange for writing and
overwriting cells in any order.
Quick start #
import 'package:xlsxwriter/xlsxwriter.dart';
void main() {
final workbook = Workbook('report.xlsx');
final sheet = workbook.addWorksheet('Summary');
final header = workbook.addFormat()
..bold()
..backgroundColor(0x4472C4)
..fontColor(0xFFFFFF);
// writeRow picks the cell type per value: String -> text, int/double ->
// number, bool -> boolean, DateTime -> date, null -> blank.
sheet.writeRow(0, ['Item', 'Amount'], format: header);
sheet.writeRow(1, ['Widgets', 1250]);
sheet.writeRow(2, ['Gadgets', 340]);
workbook.close(); // close() writes the file; always call it
}
Rows and columns are 0-based integers, matching libxlsxwriter: (0, 0) is cell
A1, (1, 2) is C2. A try/finally around close() is a good fit; a
workbook that is garbage-collected without close() is freed by a
NativeFinalizer (so no leak) but its file is never written.
Streaming a large export #
For an export that might not fit in RAM, open the workbook in constant-memory mode. Rows flush to disk as you go:
final workbook = Workbook.constantMemory('big.xlsx');
final sheet = workbook.addWorksheet('Export');
sheet.writeRow(0, ['id', 'label', 'amount']);
for (var row = 1; row <= 1000000; row++) {
sheet.writeRow(row, ['SKU-$row', 'Item $row', row * 1.5]);
}
workbook.close();
Write rows top to bottom. Writing back to a row you have already passed throws
XlsxWriterException.
Benchmark #
Write-only, N rows by 10 columns (one text column, nine numeric). Each engine is measured in its own process so the peak-memory reading is isolated. Apple Silicon, Dart 3.11; treat the figures as indicative, not a spec.

At 100,000 rows:
| engine | time | peak memory |
|---|---|---|
xlsxwriter (constant memory) |
0.92 s | 189 MiB |
xlsxwriter (default) |
0.94 s | 314 MiB |
excel 4.0.6 (pure Dart) |
4.05 s | 1917 MiB |
Against excel, the pure-Dart writer most people reach for, constant-memory
mode uses about ten times less memory and runs about four times faster at
100,000 rows. Memory is the real argument, and it widens as rows grow: the
constant-memory line stays flat while an in-memory writer keeps climbing. The
excel figure was measured in a separate project, because excel and this
package's dev dependency archive need incompatible major versions of
archive, which is also why bench/bench.dart measures only the two
xlsxwriter modes.
Reproduce this package's two modes with dart run bench/bench.dart 100000 10
(see bench/bench.dart for the workload).
What you can write #
A short tour; see the API docs for the full set.
Values, a row at a time. writeRow takes a List<Object?> and dispatches
each value by runtime type. A DateTime needs a dateFormat to render as a
date rather than a serial number:
final date = workbook.addFormat()..numberFormat('yyyy-mm-dd');
sheet.writeRow(0, ['Item', 'Qty', 'Price', 'Added']);
sheet.writeRow(1, ['Widget', 12, 4.99, DateTime.utc(2026, 7, 20)],
dateFormat: date);
Or write cells one at a time: writeString, writeNumber, writeBool,
writeFormula, writeDateTime, writeUrl, writeBlank.
Formats. workbook.addFormat() returns a Format whose setters chain and
can be reused across any number of cells:
final money = workbook.addFormat()..numberFormat(r'$#,##0.00');
sheet.writeNumber(1, 3, 1999.5, money);
The attributes are bold, italic, underline, fontName, fontSize,
fontColor, backgroundColor, numberFormat, align, verticalAlign,
textWrap, border, and borderColor. Colors are 24-bit RGB, 0xRRGGBB.
Tables. Wrap a range in an Excel table for banded rows, a per-column filter, and a name you can use in formulas:
sheet.addTable(0, 0, 3, 1, name: 'Sales', columns: ['Item', 'Amount']);
Charts. Write a real Excel chart from data on a sheet. The pure-Dart writers cannot produce charts, so this is a reason to reach for a native writer:
final chart = workbook.addChart(ChartType.column)
..setTitle('Units sold')
..setAxisNames(category: 'Item', value: 'Units')
..addSeries(
categories: r'=Summary!$A$2:$A$4',
values: r'=Summary!$B$2:$B$4',
name: 'Units',
);
sheet.insertChart(0, 3, chart);
ChartType covers column, bar, line, area, pie, doughnut,
scatter, and radar.
Images. insertImage places a PNG, JPEG, GIF, or BMP at a cell straight
from bytes in memory (a logo, or a chart you rendered), so no temporary file is
needed:
import 'dart:io';
// ...
sheet.insertImage(0, 0, File('logo.png').readAsBytesSync());
Conditional formatting. Highlight by value, or paint a range as a heatmap or data bars:
final red = workbook.addFormat()..backgroundColor(0xFFC7CE);
sheet.conditionalCell(1, 1, 99, 1,
criteria: ConditionalCriteria.greaterThan, value: 1000, format: red);
sheet.conditionalColorScale(1, 2, 99, 2,
minColor: 0x63BE7B, maxColor: 0xF8696B);
sheet.conditionalDataBar(1, 3, 99, 3, barColor: 0x638EC6);
Bytes for a server response. To serve a generated spreadsheet from a request
handler with no scratch file to name and clean up, use Workbook.toBytes:
final bytes = Workbook.toBytes((workbook) {
final sheet = workbook.addWorksheet('Summary');
sheet.writeRow(0, ['Item', 'Amount']);
sheet.writeRow(1, ['Widgets', 1250]);
});
// return Response.ok(bytes, headers: {
// 'content-type':
// 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
// });
Pass constantMemory: true to build a large sheet with flat memory, with the
same top-to-bottom ordering rule.
What it does not do #
- No reading. It writes files only. To read or edit an existing
.xlsx, useexcelorspreadsheet_decoder. - Constant-memory mode is write-forward only. Rows must be written top to
bottom, and writing back to an earlier row throws
XlsxWriterException. This applies tomergeRangeas well: merging a range at or ahead of the current row works, merging back into flushed rows throws. Use the defaultWorkbook(...)when you need to write out of order. - No NUL bytes in strings. libxlsxwriter has no pointer+length string API, so
every string crosses the boundary NUL-terminated. A string containing a U+0000
code unit throws
ArgumentErrorrather than silently truncating. - Excel's own limits apply. Strings over 32,767 characters and URLs over
2,079 characters throw
XlsxWriterException.
Platforms and requirements #
- Dart 3.10 or newer, standalone (CLI and server). The native library is built by a Dart build hook the first time you run or test the package.
- Linux, macOS, and Windows.
pubspec.yamldeclares exactly these three; the build hook has no Android or iOS handling and has not been verified on them. - A C toolchain on the build machine: Clang or GCC on macOS and Linux, MSVC on Windows. No system libraries are needed. libxlsxwriter and zlib are vendored and compiled from source, so the package is self-contained.
- Flutter is not supported yet. Build hooks target the Dart standalone runtime today; Flutter support depends on native assets stabilizing for Flutter.
CI builds and tests on Ubuntu, macOS, and Windows on every push
(.github/workflows/ci.yaml): dart format, dart analyze --fatal-infos, and
dart test, which writes files and reads them back with an independent XML
reader to check the output.
Install #
dart pub add xlsxwriter
The first build compiles the vendored C, which takes a few seconds; later builds are cached.
Credits and license #
The engine that does the real work is
libxlsxwriter by John McNamara,
under the BSD 2-Clause license. Please credit that project for the .xlsx
writing itself.
The Dart binding is under the MIT license (see LICENSE). Vendored C keeps its
own licenses: libxlsxwriter (BSD 2-Clause), zlib (zlib license), and the small
libraries libxlsxwriter bundles (minizip, md5, dtoa, tmpfileplus). See
THIRD_PARTY_NOTICES.md and src/third_party/README.md.