NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
pub.dev · #2153 most downloaded on pub.dev
Fast, low-memory Excel (.xlsx/.xls) library for Dart & Flutter to read, create, edit and style spreadsheets, charts, formulas and CSV. A faster drop-in for the excel package.
Last release 4 days ago
04 Oct 2026
Ships on a steady schedule
a new release about every 9 days
Nearly every release is documented
notes for 47 of 48 stable releases
Nothing withdrawn
no release was ever pulled
5 months old
48 releases · first in 2026
One column per month.
Every built-in table style is named. TableStyle exposed five of Excel's sixty, so the other fifty-five had to be written as raw strings. All of them a
TableStyle exposed five oflight1 to light21, medium1 tomedium28, dark1 to dark11, plus none. A style is only a name in theThe five names that existed before keep the same values, so nothing changes
for code already using them.
Structured table references in formulas, Japanese era dates, and scientific notation, which closes out the number-format work.
Structured table references in formulas, Japanese era dates, and scientific
notation, which closes out the number-format work.
Structured table references. A formula can name a table instead of a
cell range, so it keeps working as rows are added:
cell.setFormula('SUM(Sales[Amount])'); // a column's data cells
cell.setFormula('SUM(Sales[[Q1]:[Q4]])'); // a span of columns
cell.setFormula('COUNTA(Sales[#Headers])'); // or [#Data] / [#Totals] / [#All]
cell.setFormula('[@Amount]*0.2'); // this row, inside the tableThe reference resolves when the formula is evaluated rather than when it is
parsed, so appending a row or inserting one above the table is picked up
without rewriting anything. A table is found by name anywhere in the
workbook; the unqualified [@Column] form means the table the formula's own
cell sits in, and is #REF! outside one, as in Excel. Column names are
matched ignoring case, and a name holding a space or a comma can be written
[[Total Due]].
ggge rendered as the literalggge. The g tokens now write the era (g the romaji initial, ggggg the full name) and e the year within it, so[$-411]ggge"年"m"月"d"日" reads 令和5年3月15日. All five eras from Meiji0.00E+00 on 1234.5 gave1234.5000E+, because the E was a literal and the exponent's placeholdersE- shows its sign only when negative, and three integer##0.0E+0 on 123456 reads123.5E+3.A mantissa that rounds up a decade is renormalised, so 0.0E+0 on 9.99 reads
1.0E+1 rather than 10.0E+0, keeping the one integer digit the format asked
for.
The number-format display path was mangling several common codes, and three chart types, table totals and outline placement were missing.
The number-format display path was mangling several common codes, and three
chart types, table totals and outline placement were missing.
[Red]0.00 came out as[Re31]0.00, because the d in the colour name sent the whole code to the[$$-409]#,##0.00 had the 409 read as digit[>100] selects the section, and# ?/? on a third gave 0 /. The?/8, and ? reserves a blank so a column of fractions[h]:mm on a day and a half[12]:00 instead of 36:00. [h], [m] and [s] now measure themm aftermm:ss.0 on one and a00:01.0. The whole seconds also truncate now, so the two-0.00._ and * were printed literally. _) reserves the width of a*- fills the column, which every built-in accounting format0 put in front of it, so the zero0.00;-0.00;"zero" read 0zero.0.00;-0.00;"zero";"["@"]" now shows hi as [hi].<outlinePr summaryBelow="0" summaryRight="0"/>,Chart.bubble (an XY scatter where each pointChartSeries.bubbleSizes), Chart.stock (high-low-closeChart.ofPie (a pie with itsOfPieSplit). AllChartSeriesStyle gives a series a fill and a stroke, with a width inExcelTable.totals says what each column shows: aSUBTOTAL over theSheet.appendTableRow, Sheet.tableRowsAsMaps and Sheet.table.Sheet.outlineSettings places a group's summary row and column, and canSUBTOTAL, with the 1-11 codes and the 101-111 variants that leaveA stock chart is fixed at three or four series by the schema, and Excel refuses
the file outright otherwise, so Chart.stock rejects anything else up front
rather than letting it become a repair prompt.
Inserting or removing a row or a column now moves everything on the sheet, not just the cells. Saved files no longer carry a stale calculation chain.
Inserting or removing a row or a column now moves everything on the sheet, not
just the cells. Saved files no longer carry a stale calculation chain.
SUM(B2:B4) kept summing B2:B4 after a row$ markers are preserved. A#REF!, matchingxl/calcChain.xml. The chain namesremoveRow,insertColumn and removeColumn left the coordinates on a live Datadata.value = ...,data.hyperlink or data.comment, silently landed on the wrongText inside a formula is left alone, so CONCATENATE("B2 and ", A1) keeps its
"B2 and " intact, and an existing #REF! literal is not mistaken for a
reference. Function names, defined names and whole-row or whole-column ranges
are recognized as such and not rewritten as cells.
A structural edit now brings every sheet in rather than leaving the unopened
ones lazy, because a formula on a sheet you have not touched still has to move
when it refers to the sheet you are editing. Saving already did this, so in
practice nothing extra is read. On a 500x20 sheet, a hundred inserts take about
120ms with formulas present and 31ms without.
The date and time functions a spreadsheet of schedules needs.
The date and time functions a spreadsheet of schedules needs.
WORKDAY, WORKDAY.INTL, NETWORKDAYS, NETWORKDAYS.INTL: step over.INTL pair takes a weekend code or a seven-character pattern such as"0000011", so a working week that is not Monday to Friday is expressible.WEEKNUM and ISOWEEKNUM, including WEEKNUM's type 21, which is theYEARFRAC on all five day-count bases, which is what a term or anDATEVALUE and TIMEVALUE, which read a date or a time out of text.Reversing the two dates given to NETWORKDAYS returns the same count negated,
and YEARFRAC is unaffected by their order, both matching Excel. A weekend
pattern of every day off is #NUM! rather than a count that never finishes.
That takes the engine to 331 function names.
INDIRECT finishes R1C1: whole rows and whole columns work too.
INDIRECT finishes R1C1: whole rows and whole columns work too.
INDIRECT. A part namingR2, R1:R3) or only columns (C3, C1:C2) now resolves to theR[-1], a bare R or C) areBoth halves of a range have to be the same kind, so a mix such as R2:C3 is
#REF! rather than a guess. Whole-row and whole-column references already
worked in A1, so these resolve onto the same path the engine already had.
Formulas themselves are still always A1, which is how the file format stores
them; INDIRECT and ADDRESS are where R1C1 text is read and written.
INDIRECT reads R1C1 references, and ADDRESS joins the engine to write them.
INDIRECT reads R1C1 references, and ADDRESS joins the engine to write
them.
INDIRECT honours its second argument. With FALSE the text is read asR2C3, relative ones such as R[-1]C[2], and aR or C for the formula's own row or column. Relative parts areADDRESS, which was missing entirely. It writes an A1 or R1C1 referenceINDIRECT(ADDRESS(...)) round trips inINDIRECT used to ignore its second argument, so INDIRECT("R2C3", FALSE)#REF!.A reference that would fall off the grid, such as R[-1]C in row 1, is
#REF!, and A1 text passed with FALSE is rejected rather than quietly read
the other way. Whole-row and whole-column R1C1 forms (R2, C3) are not
supported yet.
That takes the engine to 322 function names.
The array-returning statistics land, which closes the last gap the README listed. A real spilling bug turned up while building them and is fixed here
The array-returning statistics land, which closes the last gap the README
listed. A real spilling bug turned up while building them and is fixed here too.
FREQUENCY, MODE.MULT, LINEST, LOGEST,TREND and GROWTH, plus TRANSPOSE. They spill across the grid onrecalculate exactly as SEQUENCE and FILTER already do.LINEST and LOGEST handle several predictors, not just one. CoefficientsTRUE adds the five-row statistics block: standard errors, rFALSETREND and GROWTH predict at new points, or reproduce the fitted pointsnew_x is left out. Omitting known_x uses 1, 2, 3 and so on.#SPILL!. The anchor'sFormulaCellValue, so replacing theupdateCell, which is how you edit one, threw that record#SPILL! it stayed that way. The workbookSEQUENCE(2) toSEQUENCE(4), shrinking it back, or replacing it with an ordinary formulaSEQUENCE, FILTER, SORT,UNIQUE) since they shipped, not only the new functions.A correction to 2.20.0. That release said these functions "need a formula to
spill a computed block rather than read one from the sheet, which the engine
does not do yet". That was wrong. SEQUENCE has always built a computed block
out of nothing and spilled it, so the machinery was already general and the
functions only ever needed writing. The README and the function reference
carried the same wrong claim and are corrected.
The regression sits on one shared least-squares fit, solved through the normal
equations with a Gauss-Jordan inverse and partial pivoting, so LINEST,
LOGEST, TREND and GROWTH cannot disagree with each other. Predictors that
repeat each other leave the system singular and report #NUM! rather than
returning an arbitrary answer. The suite checks the new functions against the
scalar ones that take a different route: LINEST against SLOPE and
INTERCEPT, TREND against FORECAST.
That takes the engine to 321 function names.
The statistical library is complete. Every distribution Excel offers, both of its tails, its inverse, and the four hypothesis tests now evaluate in pu
The statistical library is complete. Every distribution Excel offers, both of
its tails, its inverse, and the four hypothesis tests now evaluate in pure Dart,
so the README no longer has to point at registerFunction for anything
statistical. Trigonometry and combinatorics landed in the same pass: the engine
had no SIN at all before this.
NORM.DIST,NORM.INV, NORM.S.DIST, NORM.S.INV, GAUSS, PHI, LOGNORM.DIST,LOGNORM.INV, BINOM.DIST, BINOM.DIST.RANGE, BINOM.INV,NEGBINOM.DIST, HYPGEOM.DIST, POISSON.DIST, EXPON.DIST,WEIBULL.DIST, GAMMA, GAMMALN, GAMMALN.PRECISE, GAMMA.DIST,GAMMA.INV, BETA.DIST, BETA.INV, CHISQ.DIST, CHISQ.DIST.RT,CHISQ.INV, CHISQ.INV.RT, T.DIST, T.DIST.RT, T.DIST.2T, T.INV,T.INV.2T, F.DIST, F.DIST.RT, F.INV and F.INV.RT.Z.TEST, T.TEST (paired, equalF.TEST, CHISQ.TEST, CONFIDENCE.NORM andCONFIDENCE.T.AVEDEV, DEVSQ, GEOMEAN, HARMEAN,TRIMMEAN, SKEW, SKEW.P and KURT.SLOPE, INTERCEPT, RSQ, STEYX,FORECAST, FORECAST.LINEAR, COVAR, COVARIANCE.P, COVARIANCE.S,PEARSON, STANDARDIZE, FISHER and FISHERINV.RANK.AVG, PERCENTILE.EXC, QUARTILE.EXC,PERCENTRANK, PERCENTRANK.INC and PERCENTRANK.EXC.AVERAGEA, MAXA, MINA,STDEVA, STDEVPA, VARA and VARPA.SIN, COS, TAN, ASIN,ACOS, ATAN, ATAN2, SEC, CSC, COT, SINH, COSH, TANH,ASINH, ACOSH, ATANH, SECH, CSCH, COTH, DEGREES and RADIANS.FACT, FACTDOUBLE, COMBIN,COMBINA, PERMUT, PERMUTATIONA, MULTINOMIAL, GCD, LCM, QUOTIENT,SUMSQ, SQRTPI, EVEN, ODD, RAND and RANDBETWEEN.ERF, ERF.PRECISE, ERFC, ERFC.PRECISE,DELTA and GESTEP.NORMDIST, NORMINV, NORMSDIST, NORMSINV, LOGNORMDIST,LOGINV, BINOMDIST, CRITBINOM, NEGBINOMDIST, HYPGEOMDIST, POISSON,EXPONDIST, WEIBULL, GAMMADIST, GAMMAINV, BETADIST, BETAINV,CHIDIST, CHIINV, TDIST, TINV, FDIST, FINV, ZTEST, TTEST,FTEST, CHITEST and CONFIDENCE.That takes the engine from 167 function names to 314.
Every distribution is built on one set of shared numerics (a Lanczos log gamma,
the incomplete gamma series and continued fraction, the incomplete beta
continued fraction, and a single inverse solver), so an inverse can never
disagree with its own forward function. The suite checks that directly: each
*.INV is run against its own *.DIST, each pair of tails is checked to add to
one, and each discrete cumulative form is checked against the running total of
its own mass function.
ATAN2 takes its x before its y, matching Excel rather than most maths
libraries. A unary minus still binds tighter than ^, so -2^2 is 4, also as
in Excel.
The formula engine remains opt-in: none of this is built or touched unless you
call evaluate or recalculate, so plain reading and writing pays nothing for
it.
Array-returning statistics (FREQUENCY, MODE.MULT, LINEST, LOGEST,
TREND, GROWTH) are still out. They need a formula to spill a computed block
rather than read a range from the sheet, which is a separate piece of work.
Conditional formatting rules now survive a round trip intact.
Conditional formatting rules now survive a round trip intact.
top10, aboveAverage, containsText, notContainsText,beginsWith, endsWith, timePeriod, duplicateValues and uniqueValues,rank,text, no stdDev, no timePeriod, and no formula children. Opening acfRuleaboveAverage="0" silently inverted it.text, rank, percent, bottom, aboveAverage,equalAverage, stdDev and timePeriod, the writer emits them, and rulesConditionalFormat.containsText, .notContainsText, .beginsWith,.endsWith, .top10 (with percent and bottom), .aboveAverage (withbelow, orEqual and standardDeviations), .duplicateValues,.uniqueValues and .timePeriod.ConditionalFormat: text, rank,rankIsPercent, rankFromBottom, aboveAverage, equalAverage, stdDevtimePeriod.Excel stores a text rule as both an attribute and an equivalent formula and
expects the two to agree, so the factories generate the formula for you.
Validate an import one row at a time, instead of all or nothing.
Validate an import one row at a time, instead of all or nothing.
Excel.validateRows(sheetName, schema) yields a SheetRowValidation per rowstreamRows, so it is lazy and memory-bounded. Rejecting a file onSheetRowValidation.displayRow is the 1-based number a spreadsheet shows, soCsvSchema, the same vocabulary the CSV import already uses,CsvValidationException is now re-exported alongside it.Read a large sheet a row at a time, without building the cell grid.
Read a large sheet a row at a time, without building the cell grid.
Excel.streamRows(sheetName) walks a worksheet row by row straight out of theList<CellValue?> without materialising the sheet's cellExcel.streamRowsAsMaps(sheetName) does the same keyed by a header row.Values are typed exactly as the ordinary reader types them. Both paths now share
one decoder, and a test asserts they return identical values for every sheet of
a real file, so the two cannot drift apart. Styles, merges and row metadata are
not read on the streaming path; use the ordinary path when you need those.
Get the text a spreadsheet would show, and read the currency and accounting formats real files use. Additive and backward-compatible.
Get the text a spreadsheet would show, and read the currency and accounting
formats real files use. Additive and backward-compatible.
NumFormat.format(value) renders a value the way a spreadsheet displays it.TEXT formula function already used internally, nownum, aDateTime, a bool, a String or null; a date is converted to its serialData.displayText applies a cell's own number format to its value. Reading a1234.5; displayText gives you 1,234.50. ThisExport a worksheet as JSON, or as plain Dart maps.
Export a worksheet as JSON, or as plain Dart maps.
Sheet.rowsAsMaps() reads a worksheet as a List<Map<String, dynamic>>,headerRow). Every map holds a key for each column in the sheet's usednull. An_2 suffix, so no column is dropped. All-empty rows are skipped unlessskipEmptyRows: false.Sheet.toJson() serialises a worksheet to a JSON string, either as an arrayheaderRow: null, as an array of arrays.pretty: true indents the output.Excel.toJson() serialises the whole workbook to an object keyed by sheetsheet:.Values map the same way the CSV bridge maps them: numbers and booleans keep
their Dart types, dates and times become ISO-8601 strings, a formula exports its
cached result (or its text with formulasAsText: true), and a cell error
exports its literal.
Sheet.rowsAsMaps reads a worksheet as a List<Map<String, dynamic>>, taking
the keys from a header row. Every map holds a key for each column in the
used width, an empty header cell falls back to its column letter, and a
repeated name gets a _2 suffix, so no column is silently dropped.
Sheet.toJson serialises one sheet, either as header-keyed objects or, with
headerRow: null, as an array of arrays. Excel.toJson serialises the whole
workbook keyed by sheet name, or a single named sheet.
Values reuse the CSV bridge's scalar mapping, so numbers and booleans keep
their Dart types, dates and times become ISO-8601 strings, and a formula
exports its cached result. Adds a docs page at /excel-to-json.
The graph keyed formulas by a string joining sheet name, column and row with a raw NUL separator, which put a literal 0x00 byte in the source. Git the
The graph keyed formulas by a string joining sheet name, column and row
with a raw NUL separator, which put a literal 0x00 byte in the source.
Git then treated the file as binary, so it had no readable diff or blame.
The spill tracker in excel.dart already moved to a (name, row, column)
record; this applies the same shape here, which is also cheaper than
building a string per formula per recalculation.
The writer test deliberately feeds U+0000 through U+0002 into a cell to
prove they are stripped on write. It now spells them as escapes, which is
identical at runtime and keeps that file readable too.
No tracked text file in the repo contains a NUL byte any more.
.xls, and large files.documentation field.Documentation and source hygiene.
Documentation and source hygiene.
A <numFmt> declared with an id below 164 is now honoured instead of being ignored. Ids under 164 are nominally reserved for built-in formats, but Exce
<numFmt> declared with an id below 164 is now honoured instead of beingpub.dev allows five topics and all five were spent. topic:workbook holds
three packages and is effectively unbrowsed; topic:csv holds nine and is a
real browse category that excel_plus belongs in, since it has had CSV
import and export since 29511cd. topic:xls stays: it is only a two-package
category, but it is the one that separates this package from alternatives
that read .xlsx only.
Database and engineering formula functions.
Database and engineering formula functions.
DSUM, DPRODUCT, DCOUNT, DCOUNTA, DAVERAGE,DMAX, DMIN, DGET, DSTDEV, DSTDEVP, DVAR, DVARP. Each takes aDEC2BIN / DEC2OCT /DEC2HEX, BIN2DEC / OCT2DEC / HEX2DEC, and the cross conversions),BITAND, BITOR, BITXOR, BITLSHIFT, BITRSHIFT, and CONVERTAdd the database family (DSUM, DPRODUCT, DCOUNT, DCOUNTA, DAVERAGE, DMAX,
DMIN, DGET, DSTDEV, DSTDEVP, DVAR, DVARP) and engineering functions
(number-base conversions, BITAND/BITOR/BITXOR/BITLSHIFT/BITRSHIFT with
web-safe 24-bit-half math, and CONVERT for common length/mass/time/
temperature units). Doc and README updated; also corrected stale spilling
notes in doc/functions.md.
Borders (and other styles) on empty cells and merged regions no longer disappear on read and save. A styled empty cell written in self-closing form (
<c s="1"/>) was dropped along with its style, and a merged region's coveredA styled but empty cell written self-closing () was skipped by the
SAX reader (no end event) and lost its style; a merged region's covered cells
were removed on read, losing the borders Excel draws from them. Self-closing
styled cells are now processed, and covered cells keep their style (only the
value is cleared). Refs #3.
Reading robustness fixes, all from issue #2 (thanks to @albertexye for the detailed reports and a sample file).
Reading robustness fixes, all from issue #2 (thanks to @albertexye for the
detailed reports and a sample file).
.xlsx no longer drops, misplaces, or misreads text cells because<si/> (skipped while.xlsx whose workbook relationships use absolute part pathsTarget="/xl/worksheets/sheet1.xml", as Excel and several generators writeA relationship Target may be package-absolute (Target="/xl/worksheets/
sheet1.xml"), which Excel and several generators emit. The reader assumed a
relative target and built "xl//xl/..." then crashed on the null archive file.
Targets are now normalized (absolute or relative resolve alike), and a
worksheet that still cannot be found degrades to an empty sheet instead of
throwing. Refs #2.
See CHANGELOG.md for the changes in this version.
See CHANGELOG.md for the changes in this version.
parseEvents emits no end event for a self-closing tag, and the shared-string
reader only added entries on the end event, so an empty was skipped and
every later index shifted (later text cells then read the wrong value or null).
Reading now records a self-closing as an empty string at its own index.
Refs #2.
See CHANGELOG.md for the changes in this version.
See CHANGELOG.md for the changes in this version.
The reader deduplicated shared strings the way the writer does, so a file
whose sharedStrings.xml held duplicate entries had every later index
shifted down. Text cells after a duplicate then read the wrong string, and
cells past the shortened list returned null (dropped). Reading now appends
each at its own index; writing still deduplicates. Fixes #2.
Name charts, formulas, pivot tables and CSV in the pubspec description and the README capability list, so the package surfaces for those searches too.
Name charts, formulas, pivot tables and CSV in the pubspec description
and the README capability list, so the package surfaces for those
searches too. No code change.
Chart.radar authors a radar (spider) chart. Choose RadarStyle.standard, RadarStyle.marker (the default), or RadarStyle.filled. Radar charts also read
Radar charts.
Chart.radar authors a radar (spider) chart. Choose RadarStyle.standard,
RadarStyle.marker (the default), or RadarStyle.filled. Radar charts also
read back from an opened file, like the other chart types.Documentation only; no code change from 2.11.1.
Documentation only; no code change from 2.11.1.
A defined name that refers to itself no longer overflows the stack during recalculate (full or incremental). It resolves to #CIRC, the same as a self-
Formula recalculation fixes.
recalculate (full or incremental). It resolves to #CIRC, the same as a
self-referential cell.recalculate(changed: ...) now recomputes the whole workbook when none of the
given references parse, so a typo can't leave stale results. An empty list
still does nothing.Excel.recalculate({Iterable ? changed}) can now recompute incrementally: pass the A1 references that changed (optionally sheet-qualified, e.g. ['A1',
Incremental recalculation.
Excel.recalculate({Iterable<String>? changed}) can now recompute
incrementally: pass the A1 references that changed (optionally
sheet-qualified, e.g. ['A1', 'Sheet2!B3'], ranges allowed) and only the
formulas that transitively depend on them are recomputed, instead of the whole
workbook. A static dependency graph built from the formula ASTs (with
bounding-box edges for ranges and cross-sheet references) drives it. The result
matches a full recalculate; a formula that uses a dynamic reference
(INDIRECT / OFFSET) or a volatile function (NOW / TODAY / RAND)
always recomputes, and each affected formula still spills exactly as in a full
pass. Calling recalculate() with no argument recomputes everything, exactly
as before (no behaviour change).Excel.fromCsv and Excel.importCsv accept an optional schema (a CsvSchema, now re-exported from excel_plus along with CsvColumnDef). The first row is t
Typed CSV import via a schema.
Excel.fromCsv and Excel.importCsv accept an optional schema (a
CsvSchema, now re-exported from excel_plus along with CsvColumnDef). The
first row is treated as the header and each named column's values are coerced
to the declared type (int, double, num, bool, String, DateTime)
instead of being inferred, so a column such as an id can be forced to stay
text (007 does not become 7). A value that cannot be converted, or a null
in a nullable: false column, throws CsvParseException. Requires
csv_plus: ^1.2.0.Broader image-format support for Sheet.insertImage.
Broader image-format support for Sheet.insertImage.
insertImage now accepts BMP, TIFF, WebP, ICO, and the EMF and WMF metafiles,
in addition to the existing PNG, JPEG, and GIF. Each is detected from its magic
bytes, written with the correct OpenXML content type, and (unless a size is
passed) has its intrinsic pixel size read from the header, so anchored pictures
are sized correctly without a manual width/height. WebP dimensions are read
from the VP8X, VP8 (lossy), and VP8L (lossless) chunks; TIFF from its first
IFD; BMP, ICO, EMF, and WMF from their headers.Complete, Excel-correct dynamic-array spilling in recalculate().
Complete, Excel-correct dynamic-array spilling in recalculate().
FormulaCellValue.spillRange reports the range a dynamic-array or array
formula spilled into (for example "A1:C3"), or null for an ordinary
single-value formula. It is set by recalculate() and round-trips through a
saved file's <f t="array" ref="...">.recalculate() now spills array results (from SEQUENCE, FILTER, SORT,
UNIQUE, or a range like =A1:A3) the way Excel does:
#SPILL! and leaves the blocking cells untouched,
instead of silently overwriting them or spilling around them.recalculate() are cleared
before it recomputes, so an array that shrinks no longer leaves stale
values behind.#SPILL!.<f t="array" ref="...">) is now
read back, so reopening a workbook and re-running recalculate() clears and
refills the range correctly.Dependency and documentation update; no excel_plus API changes.
Dependency and documentation update; no excel_plus API changes.
csv_plus: ^1.1.0. Its new decode-only options flow through
Excel.fromCsv and importCsv via their existing config: parameter:
comment skips comment lines (for example #-prefixed), skipRows drops a
leading preamble before the data, and maxRows caps how many rows are read.Documentation only; no API or behaviour changes.
Documentation only; no API or behaviour changes.
CSV import and export. Read and write CSV (and TSV, pipe-delimited, or any custom-delimiter) data through a new bridge built on csv_plus, a first-part
Excel.fromCsv(csv, {sheetName, inferTypes, config}) builds a workbook from
CSV text; excel.importCsv(csv, {sheetName, ...}) adds a sheet to an
existing workbook and returns it.sheet.toCsv({config, formulasAsText}) and excel.toCsv({sheet, ...})
serialise a worksheet back to CSV.007
stays text); numbers, booleans, dates, times, errors, and formulas each map
to a sensible CSV field. Pass a CsvConfig (re-exported from excel_plus) to
control the delimiter, quoting, line ending, or BOM.dart:io on the CSV
path.Legacy `.xls` (Excel 97-2003) files can now be opened. Excel.decodeBytes and decodeBytesAsync detect the binary BIFF8 format from the file's magic byt
.xls (Excel 97-2003) files can now be opened. Excel.decodeBytes
and decodeBytesAsync detect the binary BIFF8 format from the file's magic
bytes, so .xls and .xlsx open through the same call. The workbook is
decoded read-only into the regular model: cell values, dates and times in
both the 1900 and 1904 epoch systems, shared strings (including split and
UTF-16 strings), merged cells, sheet order and tab visibility, built-in and
custom number formats, fonts, fills, borders, alignment, and column widths
and row heights. Formulas are decoded from their binary token streams back
to real formula text (FormulaCellValue), covering the full operator set,
the built-in function table, absolute and relative references, shared and
array formulas, cross-sheet references, defined names, and constant arrays;
the last-calculated result is kept as the cached value, and any token the
decoder does not model degrades to that cached result instead of failing.
Saving always produces a modern .xlsx, so opening an old file and saving
it is a complete migration. Password-protected and pre-BIFF8 (Excel 5.0/95)
files are rejected with clear typed errors. Pure Dart, no new dependencies,
and works on every platform including the web.M/D/YYYY was treated as
numeric, so its date cells decoded as plain serial numbers, and the d in a
[Red] color prefix made currency formats such as [Red]-#,##0.00 classify
as dates. Both are fixed for .xlsx and .xls; elapsed-time brackets like
[h]:mm:ss still classify as time.Async decode and encode on a background isolate. Excel.decodeBytesAsync(bytes) and excel.encodeAsync() run the parse and the serialize-plus-zip work v
Excel.decodeBytesAsync(bytes) and excel.encodeAsync() run the parse and
the serialize-plus-zip work via Isolate.run, so a Flutter app can open and
save large workbooks without blocking the UI thread. Results return without
copying, the calling instance is never mutated, and errors keep their types.
On the web, where isolates are unavailable, both fall back to the main
thread so shared code behaves identically. encodeAsync throws a clear
ExcelEncodeException for workbooks that cannot cross an isolate, such as
one opened over a live InputFileStream; use encode() there.CellStyle per number format instead of allocating a
fresh instance per cell (the style is copied privately on first read, so
editing one cell never affects another); equality and hashing dropped
derived fields that re-parsed hex strings per comparison; and the writer
resolves each style once instead of re-fetching it per cell. Writing 100k
mixed cells went from 5.1 s to 0.09 s, encode from 0.9 s to 0.13 s, and
decode from 5.7 s to 0.31 s. A 1M-cell build, encode, and decode cycle went
from about 18 s to 3.8 s, with peak memory down from 1.16 GB to 0.72 GB.<fonts> container so an out-of-range font id can no longer read an
unrelated element. A 2,500-style workbook that took 12 s to open now opens
in well under a second.styles.xml as a fresh,
unreferenced record on the first save, roughly doubling a style-heavy
workbook's styles part per open-and-save cycle. Styles equal to a parsed
record are now skipped.CellStyle object, so a change through cell.cellStyle silently
applied to all of them. The getter now returns the cell's own private copy.excel 4.0.6 with this release's
performance work: encode 6.5x to 7.5x, decode 3.3x, and create 3x to 3.5x
faster at 1M and 5M cells. The raw numbers in benchmark/compare/ match.`InputStream` and `InputFileStream` are re-exported, so Excel.decodeBuffer(InputFileStream('big.xlsx')) streams a large .xlsx straight from disk witho
InputStream and InputFileStream are re-exported, so
Excel.decodeBuffer(InputFileStream('big.xlsx')) streams a large .xlsx
straight from disk without holding the whole compressed file in memory and
without adding a separate archive dependency. It reads a file path, so it
is for native platforms; use decodeBytes for asset, network, or web bytes.Gradient cell fills. CellStyle gains an optional gradientFill: GradientFill.linear(degree:, stops:) for an angled sweep or GradientFill.path(...) for
CellStyle gains an optional gradientFill:
GradientFill.linear(degree:, stops:) for an angled sweep or
GradientFill.path(...) for a gradient radiating from an inner box, each
blending two or more GradientStops. A gradient takes precedence over a
solid background or pattern. Gradients in opened workbooks read back onto
CellStyle.gradientFill and round-trip.setAutoFilter gains a criteria: list of
FilterColumns that actually hide non-matching rows: FilterColumn.values
(a checkbox list, optionally including blanks), FilterColumn.custom (one
or two comparisons combined with AND/OR, with wildcard text matching), and
FilterColumn.top10. Applied criteria read back on
sheet.autoFilterColumns and round-trip; unmodeled filter kinds are
preserved untouched.sheet.conditionalFormats, exposing type, operator, formulas, colors,
range, and a best-effort style for cell-is and formula rules. Read rules
are for inspection; they round-trip untouched and are never duplicated.ConditionalFormat.iconSet(...)
authors 3, 4, and 5-icon rules (arrows, traffic lights, flags, ratings, and
more) with optional reverse order, hidden values, and custom thresholds.
Icon-set rules also read back.sheet.addSparklineGroup(SparklineGroup(...)) or the single-cell
sheet.addSparkline(...): line, column, and win-loss types with
high/low/first/last/negative markers and colors. Groups read back on
sheet.sparklineGroups; existing sparklines round-trip untouched.excel.encodeToStream(onBytes) writes the .xlsx to a
callback chunk by chunk as the zip is produced instead of buffering the
whole file, cutting peak memory for large workbooks. onBytes matches
IOSink.add, and the output is byte-for-byte identical to encode().encode() and save() are idempotent. Saving the same workbook
instance more than once no longer appends duplicate font, format, or rule
records; mutated parts are restored to their originally parsed state before
every build.<fills> children directly, so a gradient fill (or a stray pattern inside a
differential style) can no longer misalign later fills against their ids.Workbooks no longer open with a repair prompt in Excel. The bundled template's theme part carried invalid XML (introduced in 2.1.0 when the template w
Custom chart colors. ChartSeries gains color (fills the bars or area, or colors the line) and pointColors (per-slice colors for pie and doughnut chart
ChartSeries gains color (fills the bars or
area, or colors the line) and pointColors (per-slice colors for pie and
doughnut charts, aligned to the values). Anything omitted falls back to the
built-in Office palette, so existing charts are unchanged.Split panes. sheet.splitPanes(xSplit:, ySplit:, topLeftCell:) creates independently scrolling panes (positions in twips), complementing freezePanes. R
sheet.splitPanes(xSplit:, ySplit:, topLeftCell:) creates
independently scrolling panes (positions in twips), complementing
freezePanes. Read back via sheet.splitX and sheet.splitY; splits
round-trip and are mutually exclusive with frozen panes.MAXIFS, MINIFS, DATEDIF, REPLACE,
MROUND, ISEVEN, ISODD.XLOOKUP enhancements: wildcard match mode and reverse search mode.sheet.charts (type, title, series, categories, grouping, legend, axis
titles, anchor). Existing charts still round-trip untouched.Chart.plotVisibleOnly. Set it to false to plot data kept in hidden
rows and columns.Chart.anchorTo. When set, the chart is written as a two-cell anchor
spanning anchor to anchorTo, so it lines up with the grid and resizes
with the columns and rows instead of using a fixed pixel size.sheet.pivotTables (name, anchor, source range, row, column, page, and
nested fields, and data fields with their aggregation). Existing pivots
still round-trip untouched; an unmodeled pivot shape is preserved on save
but omitted from the list.indent) is no longer dropped. Indented
left-aligned cells now emit an explicit left alignment so the padding
applies.Typed exception hierarchy. Failures now throw a sealed ExcelException instead of the generic error types used before:
Typed exception hierarchy. Failures now throw a sealed ExcelException
instead of the generic error types used before:
ExcelArchiveException: the bytes are not a readable .xlsx container.
Replaces the old UnsupportedError and ArgumentError for unreadable
files.ExcelFormatException: a valid archive with malformed or inconsistent
XML. Replaces the old ArgumentError.ExcelEncodeException: the workbook could not be encoded on save.FormulaParseException: raised inside the formula parser; it implements
FormatException, so existing handlers keep working. Through the public
API a bad formula still surfaces as an #ERROR! cell value.Each carries a message, an optional part, and an optional cause.
Corrupt input was previously signalled with Error subtypes, which Dart
reserves for programming bugs; bad input is an expected runtime condition,
so it now throws an Exception you are meant to catch. Genuine argument
validation still throws ArgumentError. To migrate, replace
on ArgumentError, on UnsupportedError, or on Error around decode
calls with on ExcelException or a specific subtype.
<pivotCaches> workbook ordering. It was written in an invalid
position that made Excel offer to repair files containing certain optional
elements; it is now ordered correctly.COUNTIF, SUMIF, and their multi-criteria
variants honor * and ? wildcards, with ~ as the literal escape.WEEKDAY supports return types 11 to 17 and returns #NUM! for an
unsupported type.INDEX with a zero row or column returns the whole column or row as an
array instead of #REF!.VLOOKUP, HLOOKUP, MATCH, and LOOKUP compare within a
value type, so a number is never matched against a text key.TEXT scaling commas. A comma after the last digit placeholder scales
the value by 1000 per comma, distinct from a grouping comma.addPivotTable rejects field indices
outside the source range instead of crashing on save, addChart rejects a
chart with no series, and removeTable deletes the orphaned table part and
its content-type entry.*.xlsx to
.pubignore.Images (read and write). Embed pictures with sheet.insertImage(bytes, anchor:, width:, height:) and read them back via sheet.images. PNG, JPEG, and GI
sheet.insertImage(bytes, anchor:, width:, height:) and read them back via
sheet.images. PNG, JPEG, and GIF are supported; format and intrinsic size
are detected from the bytes. Existing images are preserved and new ones are
appended alongside them.sheet.pageSetup = PageSetup(...)
controls orientation, paper size, scaling, fit-to-page, centering, printed
gridlines and headings, and margins with normal, wide, and narrow presets.setPrintArea, setPrintTitleRows and setPrintTitleColumns for repeated
headers, and insertRowPageBreak and insertColumnPageBreak, each with
matching getters and removers. All page-setup features are change-gated: an
opened file keeps its existing setup byte-for-byte unless changed through
the API.groupRows, groupColumns,
and their ungroup counterparts nest outline levels; read levels and control
visibility with setRowHidden, setColumnHidden, and their getters.
Outline state round-trips.sheet.setComment(index, Comment(...))
or cell.comment attach classic notes; authoring writes the comments part
and its legacy plumbing, and existing comments are read and preserved.excel.protectWorkbook(...) locks
the workbook structure and windows, with matching getters and
unprotectWorkbook(). The optional password uses Excel's legacy hash.CellStyle.fillPattern draws a hatch or
shade using the background color as the pattern color over an optional fill
background. Non-solid patterns now survive a read round-trip.sheet.evaluate(cell) computes a
formula's value, and excel.recalculate() recomputes every formula cell
and stores the results so a saved file shows them. Around 130 built-in
functions across math, statistics, criteria, logic, text, lookup,
financial, and date and time, plus dynamic arrays (FILTER, SORT,
UNIQUE, SEQUENCE). References resolve lazily with memoization and cycle
detection; shared formulas are expanded on read; array results spill into
their range on recalculate. Register custom functions with
excel.formula.registerFunction. Nothing runs during normal read or write.sheet.addTable(ExcelTable(...)) turns a
range into a named table with a styled header and autofilter; read via
sheet.tables and remove with removeTable. Column names come from the
header row or an explicit list, de-duplicated as Excel requires. Existing
tables round-trip untouched.sheet.addChart(Chart.column(...)) plus bar, line,
area, pie, doughnut, and scatter constructors, each supporting multiple
series, category labels, titles, legend position, grouping, and a pixel
size anchored to a cell. Charts already in a file round-trip untouched.sheet.addPivotTable(PivotTable(...))
summarises a range with a row field and one or more measures; column
fields, page fields, and nested row fields are supported. The cache is
marked refresh-on-load so Excel rebuilds it on open. Existing pivots
round-trip untouched.First major release: a broad set of worksheet features built on the performance-focused engine, with a single contained breaking change. excel_plus re…
First major release: a broad set of worksheet features built on the
performance-focused engine, with a single contained breaking change.
excel_plus remains a source-compatible drop-in for the excel package.
CellValue is now sealed and gains a CellErrorValue member. The only code
affected is an exhaustive switch over a CellValue, which must now handle
CellErrorValue. No other public type, method, or signature changed. Colour
authoring is additive and existing literal colors behave exactly as before.ExcelColor.theme(...) and
ExcelColor.indexed(n) write real references for font, fill, and border
colors, so authored colors stay linked to the document theme.Hyperlink.url, Hyperlink.email, and
internal Hyperlink.location jumps, each with optional display text and
tooltip, set via sheet.setHyperlink or cell.hyperlink.setAutoFilter adds header dropdowns over
a range; files opened with applied criteria keep them.sheet.protect(password:, allow:)
with typed permission options; passwords use Excel's legacy hash and an
opened file's existing hash is preserved.excel.moveSheet and excel.sheetOrder.CellStyle, plus two and
three-color scales and data bars. Existing rules are preserved on save.CellErrorValue. Error cells such as #DIV/0! and #N/A read as a
typed value and write back, instead of being coerced to text.FormulaCellValue.cachedValue. A formula's last cached result is
preserved on read and re-emitted on save, so formula cells keep a value
until the app recalculates.CellStyle.indent for alignment-side cell padding, with a full
round-trip.TextCellValue.span are written as styled runs instead of being flattened
to plain text.Excel.findAndReplace returns the actual replacement count and accepts
non-string targets.save() triggers the browser download under wasm builds as
well as JS builds.getColumnWidth and getRowHeight return Excel's defaults instead of
throwing when a sheet defines none.Nothing published for this version
Upgraded the xml dependency to ^7.0.1 and updated internal XML name handling for compatibility.
xml dependency to ^7.0.1 and updated internal XML name
handling for compatibility.skwasm.Organized API docs into five categories: Core, Cell Values, Styling, Number Formats, Layout.
Removed the collection and equatable dependencies, reducing the package to three runtime dependencies: archive, xml, web.
collection and equatable dependencies, reducing the package
to three runtime dependencies: archive, xml, web.xml constraint to ^6.3.0 for downgrade compatibility.Initial release: a performance-optimized fork of the excel package.
excel package.excel package.Your coding agent can read these notes before it upgrades. Set up the MCP server →