Interoperability guide — what happens when a file goes between Excel, ODS and Numbers
Save a spreadsheet in another format and something always changes. This page lists what that "something" is — measured, not guessed. A machine walked all nine directions, and for every feature that went missing we checked whether a warning names it.
In three lines
Introduction — why it is never "as is"
Excel, ODS (LibreOffice and others) and Numbers are not tools that make the same thing. They only look alike; the ideas inside are quite different.
- Excel is a grid. One sheet is one enormous piece of graph paper.
- ODS is mostly a grid too, but it has a few ideas that exist only in OpenDocument.
- Numbers is a canvas. Tables, charts and shapes are laid out side by side on a sheet — so one sheet can hold several tables.
So the question is never "how to put an Excel feature into Numbers"; whether there is a place for it decides everything. What has no place cannot go in, however carefully it is converted. What this page does is list where the places are missing.
The four outcomes
The tables on this page use these four marks and no others.
substituted warning says what it was replaced withdropped (whole) or degraded (in part)How "always said" is guaranteed
Anyone can promise. Here a machine does the cross-checking.
- Take one workbook with all 47 features in it and save it once as a real file in each format.
- Open that file and save it in another format.
- Open the result and count the features that went missing.
- For each missing feature, check that some warning contains a word that names that feature. So that a vague warning like "a table was dropped" cannot stand in for the loss of an array formula, each feature has exactly one matching word.
What each format has that the others do not
What trips interoperability up is almost always something only one format has. So first, those things, sorted into three groups.
Excel only — taken to the other formats
| Feature | → ODS | → Numbers | What actually comes back |
|---|---|---|---|
| Editable ranges in a protected sheet (protected ranges) | ✕ | ✕ | ODF can only protect a whole sheet, and Numbers only a whole document. Fixed here On the Numbers side this used to hide inside "sheet protection dropped" |
| Scenarios | ✕ | ✕ | An ODF scenario is a whole shadow sheet, so writing one would add sheets. We do not go that far |
| Tab colours | ✕ | ✕ | Not in ODF 1.3; LibreOffice drops them in the same conversion |
| Workbook protection (fixed sheet structure) | ✕ | ✕ | ODF locks the document; it has no idea of protecting the list of sheets |
| VBA macros | ✕ | ✕ | Fixed here Now named as "macros dropped", and
.xlsm is suggested instead (it used to suggest XLSX, where they are dropped too) |
| Pivot tables | △ | ✕ | Into ODS only the layout goes, as a "data pilot"; the numbers are recalculated by whoever opens it |
| Data validation (drop-downs) | ○ | ✕ | Interestingly, Numbers itself imports these by replacing them with a pop-up menu (see below) |
| Print setup, page breaks, print areas | ○ | ✕ | Numbers prints a canvas, not a grid of pages, so there is no place for them |
| Row and column grouping (outlines) | ○ | ✕ | Numbers groups by "categories", not by outline depth |
| Array formulas (with a range) | ○ | △ | Fixed here The formula itself stays in its anchor cell. The warning now says that only "how far it spread" is lost |
| Charts, images, shapes | △ | ✕ | Saving back into the same format is ▣ (kept as is). An image placed with addImage can be written to ODS (0.16.0, Appendix B.43). Charts and shapes cannot be taken to another format |
OpenDocument only — taken to the other formats
The comparison runs both ways: counting only "what Excel has and ODF lacks" would not be fair. These are the six found by reading the OASIS ODF 1.3 schema.
| Feature | What it is | → Excel | → Numbers |
|---|---|---|---|
| Label ranges | Write =SUM(売上) and the column whose heading is "売上" is found.
Excel could do this up to 2003, but today's OOXML has no way to write it | ✕ | ✕ |
| Consolidation definitions | A setting that sums several ranges, and stays in the document. In Excel it is a one-off command that leaves nothing behind | ✕ | ✕ |
| Detective arrows | Arrows tracing precedents and dependents. Excel draws them but does not save them | ✕ | ✕ |
| Calculation settings | Whether search criteria are regular expressions, whether they must match the whole cell, where two-digit years turn over. In Excel these are application settings; in ODF they are file settings | ✕ | ✕ |
| Date epoch | ODF can take any date as the epoch. Excel has only two: 1900 and 1904 | △ | △ |
| Currency cell type | The cell itself knows "this is yen". In Excel that information lives only inside the number-format code | △ | △ |
Numbers only — taken to the other formats
| Feature | → Excel | → ODS | What actually happens |
|---|---|---|---|
| Several tables on one sheet | ✕ | ✕ | An Excel sheet and an ODS sheet are each one grid. Only the first table goes across;
the rest are reported as dropped. And this is the one case where the suggested format is .numbers |
| Charts, shapes, text boxes | ✕ | ✕ | Reported at read time as "the sheet has drawings". The model has no place for them, so they are not preserved either |
| Cell controls (pop-up, checkbox, ★) | △ | △ | The chosen value stays; only the control is dropped. Reported with the location: "8 cells from B2 have Numbers controls" |
| Decimals (decimal128) | ○ | ○ | No digits are lost, because the model holds them as Decimal |
| Header rows / header columns | ○ | ○ | Written as Excel's and ODS's frozen panes (a ○ that is close to △) |
All nine directions, measured
One workbook with all 47 features, saved once in each format to make a real file, then rewritten from there into each of the three formats.
| Direction | Features lost | Warnings | Suggested instead | How to read it |
|---|---|---|---|---|
| From an Excel (.xlsx) file | ||||
| Excel → Excel | 0 | 0 | — | Nothing is lost. That this was not 0 is the defect found this time (below) |
| Excel → ODS | 4 | 8 | xlsx | Protected ranges, scenarios, tab colours, workbook protection. Pivot tables survive, with the proviso "layout only" |
| Excel → Numbers | 25 | 20 | xlsx | The direction that loses most. There are fewer warnings than losses because one warning can name several features at once ("print setup" covers five: headers, orientation, area, title rows and page breaks) |
| From an ODS file | ||||
| ODS → Excel | 4 | 4 | — | Only the four that exist solely in OpenDocument (label ranges, consolidation definitions, detective arrows, calculation settings) |
| ODS → ODS | 0 | 1 | — | The one warning explains that a pivot table is written as layout only and the numbers are recalculated by whoever opens it |
| ODS → Numbers | 25 | 19 | xlsx | Almost the same as Excel → Numbers. What was already gone in the ODS file (tab colours and so on) is not counted |
| From a Numbers file | ||||
| Numbers → Excel | 1 | 1 | numbers | Only "several tables on one sheet". Do not read this 1 as "Numbers → Excel is safe" (see the caution below) |
| Numbers → ODS | 1 | 1 | numbers | Same as above |
| Numbers → Numbers | 0 | 0 | — | But writing Numbers rebuilds the file from a template every time, so there is no "untouched parts stay byte for byte" preservation |
The easiest thing to misread
"Numbers → Excel loses only one thing" does not mean Numbers is the strong format. What this table counts is whether what is in the file you have now survived at the destination. A Numbers file has no data validation, no pivot tables and no print setup in it to begin with. What gets dropped was dropped completely on the way into Numbers.
Saving back into the same format (the most ordinary use)
In practice the commonest case is not a conversion but "open, fix one cell, save under the same name". The promise here is stronger than for a conversion: what you did not touch comes back byte for byte.
| Format | Parts not touched | What the model read | In a word |
|---|---|---|---|
| Excel (.xlsx / .xlsm) | Identical to the byte (charts, themes, images, VBA) | Rewritten as XML with the same meaning (conditional formatting, data validation and so on) | The strongest preservation |
| ODS | Repackaged, but content.xml is rebuilt, so images and objects are not reconnected |
Rewritten as XML with the same meaning | The contents stay, the pictures are not connected (with a warning) |
| Numbers | No preservation. Rebuilt from an empty template every time | Values, formulas, styles, conditional formatting, links, notes, merges and so on | Rebuilt, so the original shapes do not come back |
Nobody was watching the second save
This was the biggest discovery of the review. The tests had always checked "build, write, read". "Write again what was read" — nobody had checked that.
What the review found
Five defects, and two cases where the line had to be drawn by judgement. All seven are fixed, each with a test that catches a regression.
1. An Excel file with a pivot table was corrupted on save Fixed
The most serious one. The pivot table part has 15 attributes the writer always writes itself
(updatedVersion, createdVersion and so on). But the reader's list of "known attributes"
had only 5 on it. The other 15 were remembered as "attributes the model cannot express"
and written again on the next save, alongside the writer's own.
XML with an attribute written twice is broken by the standard. Excel offers to repair it; our own reader skipped that part in silence, so the pivot table vanished without a warning.
And files written by Excel carry these attributes too. So this was not limited to round-tripping our own files: open a workbook with a pivot table made in Excel, save it, and it was broken on the first save.
2. A part that could not be read was skipped in silence Fixed
This is why defect 1 went unnoticed. When a part's XML could not be read, the reader simply moved on. It now says "this part could not be read, so the pivot table in it was skipped", with the part's name. Reading Excel now returns warnings, like the other formats.
3. Four things dropped in silence Fixed
| Case | Before | Now |
|---|---|---|
| Array formula → Numbers | The anchor cell's formula was written, but nothing said the range it spread over was gone | Says "the formula stays in its anchor cell; only the range is lost" |
| Protected ranges → Numbers | Hidden inside "sheet protection dropped"; a document with only protected ranges set got no warning at all | Split into its own warning |
| Table column names (Excel → Excel) | The reader took the names in, but the writer never wrote them. They vanished in silence on every round trip | Now written. The responsibility table was out of step in both directions |
| VBA → ODS / Numbers | Buried in a count of "N parts cannot be carried", which led to the suggestion "XLSX would keep them" — where macros are dropped too | Named as "macros dropped", and the suggested format is now .xlsm |
4. Numbers itself replaces data validation with a pop-up menu Recorded
An observation rather than a defect. The same workbook was written as Excel, imported into Numbers 15.3.1 and saved again — and Excel's drop-downs (data validation) had become Numbers pop-up menus. In terms of the four outcomes, Numbers itself is doing △ (replaced by the nearest thing).
Our reader reported this correctly as "8 cell controls (values kept, controls dropped)", but it was not on the test's allow list, so the test went red on this Mac alone. Rather than loosen the allow list, the behaviour was fixed as a statement — it is a record of what Numbers does.
Two cases decided on policy first, then fixed
A. Workbook.convert threw the read warnings away Fixed
Workbook.convert, the easiest way in to a conversion, returned only the write result.
Warnings raised during the read appeared nowhere in the return value.
Measured. Converting a Numbers document with one chart and 8 cell controls to .xlsx gave
0 warnings. The reader had said its piece; it just went nowhere.
What is more, Numbers drawings and cell controls can only be reported on read —
they never enter the model, so the write has nothing left to report.
Anyone using this entry point had no way at all of knowing.
It now returns both halves of the trip, in the order they happened (read first). The suggested format is unchanged — that is a question about the write.
B. Converting from ODS said "calculation settings dropped" every time Fixed
This one was the opposite mistake. The test was "does this setting differ from the default we chose". But LibreOffice writes its own defaults into every ODS it saves, and they differ from ours.
| Setting | What LibreOffice always writes | Does it actually take effect? |
|---|---|---|
| Automatic detection of headings | off | No. It is a permission — "a formula may use a heading that was not declared" — and means something only once a formula uses it. Such formulas are reported separately |
| Start of two-digit years | 1950 | Yes, but it only changes how years typed from now on are read; existing values do not move |
| Iteration step size | 0.0001 | No. Iteration itself is switched off, so no calculation ever reaches this value |
The result: even a file in which the user had set nothing was warned about on every conversion. Not a lie — but a warning that always appears stops being read. This one case was eroding trust in all the others.
The question was changed. Not "does it differ from our default" but "would the destination read this document differently". And a setting that is not in effect is not counted.
| Document | Before | Now | What it says |
|---|---|---|---|
| A brand-new workbook | 0 | 0 | — |
| An ODS saved by LibreOffice (nothing set) | 1 | 1 | The same count, but a different message. Instead of a vague "calculation settings dropped", it names the difference: "two-digit years start at 1950 here and at 1930 there". The other four items stay silent, because the destination behaves the same way |
| A workbook with only an iteration count (iteration off) | 1 | 0 | A step count no calculation ever reaches is not a loss worth a sentence |
Which test measures which claim
Every statement on this page is backed by a running test. If the prose goes stale, a test goes red first.
| Claim on this page | What measures it | Count |
|---|---|---|
| How far each of 47 features survives in each format | FormatSupportTests.matchesThePublishedTable | 3 |
| Everything lost has a warning that names it (9 directions) | CrossFormatConversionTests.everyLossIsNamedByItsOwnWarning | 9 |
| Three saves later the parts are still well-formed and nothing is lost | CrossFormatConversionTests.threeGenerationsStayWellFormedAndKeepEverything | 3 |
| A pivot table survives the second save | CrossFormatConversionTests.aPivotTableSurvivesBeingSavedTwice | 1 |
| A lost macro is reported as a macro loss whatever the destination | CrossFormatConversionTests.aDroppedVBAProjectIsAlwaysAMacroLoss | 3 |
| The conversion entry point returns both the read and the write warnings | CrossFormatConversionTests.convertAnswersWithBothHalvesOfTheTrip | 1 |
| A calculation setting is reported only when the destination would read it differently | ODFOnlyFeatureTests.aCalculationSettingIsReportedOnlyWhenItWouldChangeSomething | 1 |
| Fix one cell and save, and not one other byte moves | PreservationTests.editOneCellKeepsEverythingElse | 1 |
| The six OpenDocument-only features round-trip, and LibreOffice reads them | ODFOnlyFeatureTests | 8 |
| Numbers shapes and cell controls are reported on read | NumbersSilentLossTests | 9 |
| Numbers.app itself opens the written file, calculates and saves it again | Tests/NumbersParity/verify_with_numbers_app.py | — |
| openpyxl reads the written file with the same values | Tests/OpenpyxlParity/verify_with_openpyxl.py | — |
Broken input never crashes and always becomes a SheetError | FuzzTests | 8 |
The judge is never ourselves
Our own reader reading our own writer proves nothing. "Supported" is claimed here only when something that did not come out of this project agrees.
| Judge | What it checks |
|---|---|
| Numbers.app 15.3.1 | Opens the written .numbers, is asked what it sees, and saves it again |
| LibreOffice | Opens the written .ods / .xlsx; can it convert to PDF, and do the elements survive |
| openpyxl | Reads the written .xlsx; same values, same types (it is also the yardstick for behaviour) |
| numbers-parser | Reads the written .numbers as an independent implementation |
Measure it yourself
To check that the numbers have not gone stale, run the tests in the repository.
swift test # 839 tests (everything)
swift test --filter CrossFormat # this page itself: the nine-direction check and the three-generation test
swift test --filter FormatSupport # the 47-row support table (docs/format-support.html)
swift test --filter Preservation # fix one cell and save: is everything else byte-identical
swift test --filter ODFOnly # the six OpenDocument-only features
python3 Tests/NumbersParity/verify_with_numbers_app.py # have Numbers.app itself open the files
python3 Tests/NumbersParity/compare_excel_import_with_numbers.py # compare the same workbook: our write against Numbers' own import
python3 Tests/OpenpyxlParity/verify_with_openpyxl.py # have openpyxl read them
- How are the warnings received in code?
- Both reading and writing come back with a list attached to the result. Unless you throw it away, nothing is missed.
let result = try wb.write(as: .numbers)
for warning in result.warnings {
print(warning.kind, warning.sheet ?? "-", warning.message)
// kind is dropped (whole) / degraded (in part) / substituted (replaced)
}
if let suggestion = result.suggestion {
print(suggestion.message) // "N items cannot be expressed in this format. They would survive as XX"
}
print(wb.readWarnings) // what was said at read time