What xl3 preserves — and what it doesn't
One question decides whether xl3 fits your template: does the workbook come out the other side intact? This page answers it feature by feature.
Everything below is a spec decision, not a quirk of one implementation. The rows marked deferred are deferred on purpose, with an ADR saying so — that is different from "nobody checked."
The four verdicts
| Verdict | What it means |
|---|---|
| Preserved | Comes out identical to the template. Every conforming implementation must do this. |
| Preserved and extended | The rule survives and a range fully contained in an @repeat block grows with the emitted rows. Partial overlaps are left unchanged so the engine never guesses beyond the authored block. |
| Preserved, range not extended | The feature survives, but its range keeps the coordinates the template gave it. If @repeat grows the sheet from 1 row to 50, the range still covers the 1 row. There is an author-side fix for each — see the notes. |
| Deferred | XTL 0.1 asserts nothing. It may survive, it may be dropped, and two conforming implementations may disagree. Don't build on it before 1.1. |
Layout and formatting
| Feature | Verdict | Notes |
|---|---|---|
| Cell style — font, fill, border, alignment | Preserved | |
| Number and date formats | Preserved | Also inherited by every @repeat-expanded row, not just the first — see Formatting inside a repeat block. |
| Row height, column width | Preserved | |
| Merged cells | Preserved | In the template and in source data rows (ADR-0033, ADR-0035). |
| Images and their anchors | Preserved | The anchor does not shift when @repeat inserts rows above it. Put images in a header, footer, or sidebar — outside the block. |
| Freeze panes and splits | Preserved | |
| Cell comments (notes) | Preserved | A comment on a {{ [Column] }} cell survives onto the rendered cell. |
| Sheet protection, per-cell lock/hidden flags | Preserved | Engine-written cells inherit the template cell's lock state rather than a synthesized default. |
| Workbook properties — theme, document properties | Preserved | Fixture 120-workbook-properties-preserved. |
| Native Excel formulas in static cells | Preserved | A formula xl3 didn't generate is carried through untouched (ADR-0046). Use this for anything Excel can compute at open time — see Cookbook 16. |
Rules with ranges
Range-bearing features follow one of two contracts. Conditional formatting
and data validation grow only when their authored range is fully contained in
an @repeat block. A partial overlap is left untouched, so a rule cannot bleed
into a footer the author meant to exclude. Named ranges and print settings are
preserved without coordinate changes.
| Feature | Verdict | Author-side fix |
|---|---|---|
| Conditional formatting | Preserved and extended | A rule on A2:A2 inside a 1-row block expands to A2:A4 when the block emits 3 rows. Whole-column ranges already cover the output and remain unchanged; partial overlaps are preserved as authored. |
| Data validation (dropdowns, constraints) | Preserved and extended | Validation on a cell inside the repeated template rows is copied to the corresponding emitted rows. Validation outside the block is untouched. |
| Named ranges / defined names | Preserved, range not extended | Workbook-scope and sheet-scope both survive; references into a repeat-expanded area are not re-pointed. |
| Print area, print titles | Preserved, range not extended | Set the print area to a full column span ($A:$Z) or repeating header rows ($1:$1). |
Deferred to 1.1
Four features are deliberately left implementation-defined for 1.0 (ADR-0076). The reference implementation's current behavior is recorded below so you can plan around it, but none of it is a promise — that is what deferred means.
| Feature | Reference implementation today |
|---|---|
| Pivot tables | Not carried through. Cannot be authored or preserved. |
| Sparklines | Not carried through. |
| Structured tables (ListObject) | The table part survives, but its ref is not extended: a table authored over a @repeat row still covers only the rows it originally spanned. Widen the ref to a whole column in the template. |
| Page breaks | Dropped. Despite sitting next to "print area" conceptually, page breaks are not covered by it. Set them host-side after conversion. |
| Charts | Implementation-defined (ADR-0036 item 3). The reference implementation reads and writes through ExcelJS, whose chart support is incomplete — do not rely on charts surviving. |
Removed on purpose
| Feature | Behavior |
|---|---|
| Macros (VBA, XLM) | Stripped, always. An .xlsm input is accepted as XLSX-with-macros-removed and a warning is emitted. Macros never execute, and no host or template can opt back in during 1.x. See SECURITY.md. |
Formatting inside a repeat block
Range rules and per-cell formatting grow under different contracts, and the difference is worth knowing. Conditional formatting and data validation fully contained in the repeated block extend with it; named ranges and print settings do not. Independently, all emitted rows carry the template cell's number format and style — not only the first row.
That last guarantee matters more than it sounds. Losing the format on rows
2..N is silent data loss: a column whose first row reads 1,234.50 and
whose remainder reads 1234.5 looks like a styling slip and is
indistinguishable from one. Nine fixtures tagged data-loss exist to
keep that from regressing.
Size limits
| Limit | Value |
|---|---|
| Memory | ~2.2 KB per output cell at scale, on a ~130 MB floor. 2M cells needs roughly 4.2 GB peak RSS. |
| Cells per conversion | ~2,000,000 verified end to end; ~500,000 fits in a 2 GB host. Size the host at 2.2 KB/cell before assuming a bigger job runs. |
| Cell string length | xl3 writes what you give it. Excel rejects cells over 32,767 characters on read, and xl3 does not pre-validate — check length upstream if the output must open in Excel (ADR-0032). |
Exceeding available memory surfaces as a host-level out-of-memory, not
as an xl3 error — xl3 cannot catch it. Long conversions accept an
AbortSignal.
How much of this is machine-checked
Partly, and it is worth knowing which parts.
123-feature-preservationchecks named ranges and cell comments surviving the render cycle, as a Stage 1 cell-value comparison.170-data-loss-numfmt-preserved-across-expansionchecks, in Stage 2, that number formats survive@repeatexpansion — the trap described above.171-cf-dv-range-extensionchecks, in Stage 2, that contained conditional-formatting and data-validation ranges extend while partial overlaps remain unchanged.- A full Stage 2 canonical comparison of every row in the tables above is still pending canonicalizer work under ADR-0006.
So the rows without a fixture behind them are spec promises rather than machine-verified ones. Every implementation runs the same conformance corpus, so what is checked is checked everywhere — but the corpus does not yet cover the whole matrix, and this page would rather say so than imply otherwise.
Where these decisions live
This page is a plain-language reading of the normative sources. When they disagree, they win:
spec/evaluation.md§ Styles and Workbook Structure — the normative rules- ADR-0036 — the nine-feature preservation matrix and why each is preserve-verbatim rather than preserve-and-extend
- ADR-0076 — pivot / sparkline / ListObject / page break deferred to 1.1, with measured behavior
- ADR-0046 — native Excel formulas in static cells
- ADR-0032 — string length and other niche limits
- ADR-0022 — Excel version compatibility
- SECURITY.md — the macro, network, and filesystem stance