Compatibility Matrix
Last rendered: 2026-07-07 from docs/migration/_compat_spec.py. Do not edit this file by hand; run python scripts/render_compat_matrix.py.
This page is the public scoreboard for wolfxl's openpyxl-API compatibility. Each row maps an openpyxl idiom to its wolfxl equivalent and the current implementation status. Gap IDs (e.g. G11) correspond to rows in the internal Plans/openpyxl-parity-program.md, where the work to close them is sequenced into sprints.
Status legend
| Symbol |
Meaning |
| โ
Supported |
Implemented and covered by tests / fixtures. |
| ๐ก Partial |
Common case works; edge cases tracked under a gap ID. |
| โ Not Yet |
Not implemented; tracked under a gap ID. |
| โ Out of Scope |
Explicitly excluded from the roadmap. |
Totals
- โ
Supported: 76 / 83
- ๐ก Partial: 0 / 83
- โ Not Yet: 0 / 83
- โ Out of Scope: 7 / 83
Workbook + Worksheet
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
Workbook() |
wolfxl.Workbook() |
โ
Supported |
|
Write-mode default. |
load_workbook(path) |
wolfxl.load_workbook(path) |
โ
Supported |
|
|
load_workbook(path, data_only=True) |
wolfxl.load_workbook(path, data_only=True) |
โ
Supported |
|
|
Implicit (full DOM rewrite) |
wolfxl.load_workbook(path, modify=True) |
โ
Supported |
|
Surgical patcher; faster than DOM rewrite. |
load_workbook(path, read_only=True) |
wolfxl.load_workbook(path, read_only=True) |
โ
Supported |
|
Streaming reads (auto-engages > 50k rows). |
wb.save(path) |
wb.save(path) |
โ
Supported |
|
Eager workbooks can be edited and saved repeatedly; write_only=True remains consumed-on-save like openpyxl. |
wb["Sheet"], wb.active, wb.sheetnames |
wb["Sheet"], wb.active, wb.sheetnames |
โ
Supported |
|
|
wb.create_sheet(title) |
wb.create_sheet(title) |
โ
Supported |
|
|
wb.create_sheet(title, index=...) on loaded workbook |
wb.create_sheet(title, index=...) on modify=True workbook |
โ
Supported |
|
Modify-mode blank sheet creation updates workbook.xml, workbook rels, content-types, tab order, and accepts cell edits before save. |
wb.remove(ws) on loaded workbook |
wb.remove(ws) on modify=True workbook |
โ
Supported |
|
Modify-mode sheet removal prunes the workbook tab, workbook rel, worksheet part, local defined-name indexes, and reachable sheet subgraph parts. |
ws.title = 'Renamed' on loaded workbook |
ws.title = 'Renamed' on modify=True workbook |
โ
Supported |
|
Modify-mode sheet title changes update workbook.xml and subsequent queued sheet mutations target the renamed tab. |
wb.copy_worksheet(ws) |
wb.copy_worksheet(ws) |
โ
Supported |
|
Diverges from openpyxl in 5 documented ways (always more preservation). |
Cell + style API
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
ws["A1"].value, ws.cell(r, c).value |
ws["A1"].value, ws.cell(r, c).value |
โ
Supported |
|
|
cell.font / fill / border / alignment / number_format |
cell.font / fill / border / alignment / number_format |
โ
Supported |
|
All five attributes (plus protection and border) round-trip together on one cell. Python flush layer merges format and border keys into a single dict so the native writer interns one combined xf record, instead of minting two ids that overwrite each other. |
Border(diagonal=Side(...), diagonalUp=True) |
Border(diagonal=Side(...), diagonalUp=True) |
โ
Supported |
|
Round-trips through both write mode and modify mode; diagonalUp/diagonalDown attrs and the child of persist via the patcher's BorderSpec and the writer's intern path. |
cell.protection = Protection(...) |
cell.protection = Protection(...) |
โ
Supported |
|
Round-trips through wolfxl reload; locked/hidden flags persist via child of with applyProtection="1". |
NamedStyle(name=...) + wb.add_named_style(...) |
NamedStyle(name=...) + wb.add_named_style(...) |
โ
Supported |
|
cell.style binds a registered NamedStyle and round-trips through cellStyleXfs/cellStyles + the xfId attr on ; reader resurfaces the name via cell.style. |
GradientFill(stop=(...)) |
GradientFill(stop=(...)) |
โ
Supported |
|
GradientFill round-trips: writer emits with type/degree/path attrs and ordered stops; reader parses gradient back into Python GradientFill via the format dict's gradient sub-dict. |
Charts
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
BarChart / LineChart / PieChart / DoughnutChart |
BarChart / LineChart / PieChart / DoughnutChart |
โ
Supported |
|
|
AreaChart / ScatterChart / BubbleChart / RadarChart |
AreaChart / ScatterChart / BubbleChart / RadarChart |
โ
Supported |
|
|
BarChart3D / LineChart3D / PieChart3D / AreaChart3D |
BarChart3D / LineChart3D / PieChart3D / AreaChart3D |
โ
Supported |
|
|
SurfaceChart / SurfaceChart3D / StockChart / ProjectedPieChart |
SurfaceChart / SurfaceChart3D / StockChart / ProjectedPieChart |
โ
Supported |
|
|
ws.add_chart / remove_chart / replace_chart |
ws.add_chart / remove_chart / replace_chart |
โ
Supported |
|
Covers pending charts plus source-loaded worksheet chart removal, replacement, and title edits in modify mode. |
wb.create_chartsheet(...).add_chart(chart) |
wb.create_chartsheet(...).add_chart(chart) |
โ
Supported |
|
Chartsheet authoring emits chartsheet, drawing, chart, rels, and content-type parts that openpyxl reloads; existing chartsheet tabs load back as workbook.chartsheets rather than worksheets. |
bar + line on shared category axis with secondary value axis |
bar + line on shared category axis with secondary value axis |
โ
Supported |
|
Combination charts ship as a multi-family <plotArea> (RFC-069 ยง6) for real Excel/LibreOffice rendering, plus per-family standalone shadow chartspaces parked at row 1048576 so openpyxl's reader exposes each family as a distinct ws._charts entry without the shadows visually overlapping the real combo. Secondary value axis (right side) honored when line.y_axis.crosses='max' and line.y_axis.axId is set. Closes G15. |
data label + axis label rich-text runs |
data label + axis label rich-text runs |
โ
Supported |
G10 |
Data label <c:txPr> runs and axis-title rich text round-trip end-to-end (G10). |
pivot chart with per-point overrides |
pivot chart with per-point overrides |
โ
Supported |
|
Per-point <c:dPt> overrides round-trip through openpyxl on pivot-source charts (G16). Bar/line/scatter all share the same dict bridge: Series.dPt -> data_points -> Rust DataPoint -> <c:dPt> with <c:spPr>. Pivot vs non-pivot only differ in the chart-level <c:pivotSource> block. |
Pivot tables
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
PivotCache + PivotTable construction |
wolfxl.pivot.PivotCache + PivotTable |
โ
Supported |
|
v2.0 ships pre-aggregated records emit (no refresh-on-open required). |
chart.pivot_source = pt |
chart.pivot_source = pt |
โ
Supported |
|
|
edit existing pivot's source range, field order, etc. |
edit existing pivot source + layout fields |
โ
Supported |
|
Source-range mutation is supported; layout edits that change cache semantics stamp refresh-on-open rather than regenerating pivot cache records. |
edit existing pivot row/column fields |
PivotTableHandle.row_fields / column_fields |
โ
Supported |
|
Mutates existing pivot table layout XML and marks the linked cache refresh-on-open. |
edit existing pivot page field filter |
PivotTableHandle.page_fields / set_filter |
โ
Supported |
|
|
edit existing pivot data-field aggregation |
PivotTableHandle.data_fields / set_aggregation |
โ
Supported |
|
|
copy_worksheet of pivot-bearing sheet (drops in openpyxl) |
copy_worksheet of pivot-bearing sheet (deep-clone) |
โ
Supported |
|
|
Images + drawings
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
Image("logo.png") + ws.add_image(img, "B5") |
Image("logo.png") + ws.add_image(img, "B5") |
โ
Supported |
|
|
ws.replace_image / remove_image |
ws.replace_image / remove_image |
โ
Supported |
|
|
Worksheet structural ops
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
ws.insert_rows / delete_rows |
ws.insert_rows / delete_rows |
โ
Supported |
|
|
ws.insert_cols / delete_cols |
ws.insert_cols / delete_cols |
โ
Supported |
|
|
ws.move_range(range, rows, cols) |
ws.move_range(range, rows, cols) |
โ
Supported |
|
|
Modify-mode mutations
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
wb.properties.title = ... |
wb.properties.title = ... |
โ
Supported |
|
|
wb.defined_names[name] = DefinedName(...) |
wb.defined_names[name] = DefinedName(...) |
โ
Supported |
|
|
ws.add_table(Table(...)) |
ws.add_table(Table(...)) |
โ
Supported |
|
|
ws.data_validations.append(...) |
ws.data_validations.append(...) |
โ
Supported |
|
|
Read-side parity
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
load_workbook(path) |
load_workbook(path) |
โ
Supported |
|
|
not supported in openpyxl |
native BIFF12; values, cached formulas, read-side styles |
โ
Supported |
|
|
not supported in openpyxl |
calamine; value-only, styles raise |
โ Out of Scope |
|
Style accessors raise; values + formulas read. openpyxl does not support this surface; tracked as a wolfxl-extra roadmap item, not an openpyxl-parity gap. |
not supported in openpyxl |
not supported (out of scope) |
โ Out of Scope |
G27 |
|
Utility functions
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
openpyxl.utils.get_column_letter |
wolfxl.utils.cell.get_column_letter |
โ
Supported |
|
|
openpyxl.utils.column_index_from_string |
wolfxl.utils.cell.column_index_from_string |
โ
Supported |
|
|
openpyxl.utils.range_boundaries |
wolfxl.utils.cell.range_boundaries |
โ
Supported |
|
|
openpyxl.utils.coordinate_to_tuple |
wolfxl.utils.cell.coordinate_to_tuple |
โ
Supported |
|
|
Streaming write (write_only=True)
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
Workbook(write_only=True) |
wolfxl.Workbook(write_only=True) |
โ
Supported |
|
Bounded-memory streaming append-only path (G20 / RFC-073). Per-sheet temp files splice into the <sheetData> slot; row data scales O(rows on disk), not O(rows in RAM). SST and styles still grow in-memory as in openpyxl's lxml.xmlfile model. |
Protection
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
ws.protection = SheetProtection(...) |
ws.protection = SheetProtection(...) |
โ
Supported |
|
Round-trips through openpyxl reload (sheet flag, formatCells override, password hash). |
wb.security = WorkbookProtection(...) |
wb.security = WorkbookProtection(...) |
โ
Supported |
|
camelCase aliases (lockStructure, workbookPassword, etc.) accepted alongside snake_case; round-trips through openpyxl reload. |
External links
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
wb._external_links + xl/externalLinks/* parts |
wb._external_links + xl/externalLinks/* parts |
โ
Supported |
|
Inspection (target / sheet_names / cached_data), opaque modify-mode preservation when unchanged, and append/remove/edit authoring. keep_links=False strips preserved source links, and file-like/bytes-backed xlsx reads expose the same collection. |
append/remove/edit external workbook links |
wb._external_links append/remove/update_target |
โ
Supported |
|
Authoring emits externalLink parts, workbook rels/references, content-types, and per-link rels. Cached data is preserved when provided but linked workbooks are not dereferenced. |
VBA macros
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
.xlsm preserved on read+write |
.xlsm preserved on modify-mode save |
โ
Supported |
|
|
workbook.vba_archive (read-only inspection) |
workbook.vba_archive (read-only inspection) |
โ
Supported |
|
v1.0: modify-mode loads only; returns raw xl/vbaProject.bin bytes. No authoring API (tracked under G28). |
not supported in openpyxl |
macro authoring from Python |
โ Out of Scope |
G28 |
Decision-gated (S11). openpyxl does not support this surface; tracked as a wolfxl-extra roadmap item, not an openpyxl-parity gap. |
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
not supported |
write.xlsb`` |
โ Out of Scope |
G25 |
Decision-gated (S9). openpyxl does not support this surface; tracked as a wolfxl-extra roadmap item, not an openpyxl-parity gap. |
not supported |
write.xls`` |
โ Out of Scope |
G26 |
Decision-gated (S9). openpyxl does not support this surface; tracked as a wolfxl-extra roadmap item, not an openpyxl-parity gap. |
not supported |
read+write.ods`` |
โ Out of Scope |
G27 |
Decision-gated (S10). openpyxl does not support this surface; tracked as a wolfxl-extra roadmap item, not an openpyxl-parity gap. |
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
cell.comment = Comment(text, author) |
cell.comment = Comment(text, author) |
โ
Supported |
|
|
threaded comment write + modify (xl/threadedComments) |
threaded comment write + modify (xl/threadedComments) |
โ
Supported |
|
|
Rich text
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
cell.value = CellRichText(...) |
cell.value = CellRichText(...) |
โ
Supported |
|
|
ws.oddHeader / oddFooter rich-text runs |
ws.oddHeader / oddFooter rich-text runs |
โ
Supported |
|
|
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
cellIs / containsText / expression / colorScale (basic) |
cellIs / containsText / expression / colorScale (basic) |
โ
Supported |
|
|
IconSetRule (3 / 4 / 5 icons + percentile / number ladders) |
IconSetRule (3 / 4 / 5 icons + percentile / number ladders) |
โ
Supported |
|
|
DataBarRule (gradient + solid; min / max / percent / formula) |
DataBarRule (gradient + solid; min / max / percent / formula) |
โ
Supported |
|
Round-trips through openpyxl reload; secondary probes cover cfvo edge cases and minLength/maxLength. |
DataBarRule with percent / num / formula cfvo + showValue=False |
DataBarRule with percent / num / formula cfvo + showValue=False |
โ
Supported |
|
|
ColorScaleRule with 3-stop, percentile / formula / number cfvo |
ColorScaleRule with 3-stop, percentile / formula / number cfvo |
โ
Supported |
|
|
stopIfTrue + explicit priority + dxf integration |
stopIfTrue + explicit priority + dxf integration |
โ
Supported |
|
|
Defined names
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
wb.defined_names + DefinedName(name=, value=) |
wb.defined_names + DefinedName(name=, value=) |
โ
Supported |
|
|
full ECMA-376 ยง18.2.5 attribute surface (13 attrs) |
full ECMA-376 ยง18.2.5 attribute surface (13 attrs) |
โ
Supported |
|
|
Print settings + page setup
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
ws.print_options / page_setup / page_margins / print_title_rows |
ws.print_options / page_setup / page_margins / print_title_rows |
โ
Supported |
|
|
full PageSetup / PrintOptions surface (~30 attrs) |
full PageSetup / PrintOptions surface (~30 attrs) |
โ
Supported |
|
|
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
ArrayFormula(ref, text) |
ArrayFormula(ref, text) |
โ
Supported |
|
Round-trips through openpyxl reload (ref + text preserved as openpyxl ArrayFormula). |
DataTableFormula(...) |
DataTableFormula(...) |
โ
Supported |
|
|
Worksheet.array_formulae + DataTableFormula del flags |
Worksheet.array_formulae + DataTableFormula del flags |
โ
Supported |
|
openpyxl 3.1.5 exposes array/spill ranges via Worksheet.array_formulae; formula_attributes is not part of the supported reference surface. |
Calc chain
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
calcChain rebuild on modify |
calcChain rebuild on modify |
โ
Supported |
|
|
cross-sheet calc-chain ordering, deleted-cell pruning |
cross-sheet calc-chain ordering, deleted-cell pruning |
โ
Supported |
|
Rebuild scans post-mutation sheet XML across tab order, prunes stale refs, and preserves a source calcChain extLst when emitting a non-empty chain. |
Slicers
| openpyxl |
wolfxl |
Status |
Gap |
Notes |
Slicer + SlicerCache wired to a PivotCache |
Slicer + SlicerCache wired to a PivotCache |
โ
Supported |
|
|
Slicer outside pivot context (table-driven, etc.) |
Slicer outside pivot context (table-driven, etc.) |
โ Out of Scope |
G21 |
openpyxl 3.1.5 exposes no public slicer modules/classes or table-driven slicer authoring API; tracked as a possible wolfxl-extra, not an openpyxl parity gap. |
How this page is maintained
This file is generated from _compat_spec.py. To update a row, edit the spec module and run:
python scripts/render_compat_matrix.py
The accompanying tests/test_openpyxl_compat_oracle.py harness imports ENTRIES from the same module and uses each entry's probe field to drive a live test. Adding a new probe means adding a new entry in the spec and a matching probe function in the harness.