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.

Measured 2026-08-27 / SwiftSheets 0.7.2+ (including unreleased work) / environment: macOS 15, Numbers 15.3.1, LibreOffice 26.2.3, numbers-parser 4.16.3


In three lines

4 outcomes
There are no others
Goes through as is / kept because it was never touched / replaced by the nearest thing / dropped, but always said. There is no fifth — which is to say, nothing disappears in silence
9 directions
All of them walked
3 formats × 3 formats, including the three that save back into the same format. For each of 47 features we look at whether it survived at the destination
7 fixes
What was repaired
One of them: "the second save corrupts the file". All seven are fixed, each with a test that catches a regression (confirmed to go red when the fix is taken out)

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.

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.

Source file .xlsx / .ods / .numbers carries things only its own format has read One model contents independent of format values, formulas, styles, … parts it could not read (kept as is) write New file the destination format only what has a place goes in a list of what did not fit (warnings) always comes back
A conversion is not "A → B" but "A → one model → B". That is why there are only four outcomes.

The four outcomes

The tables on this page use these four marks and no others.

Goes through as is
The destination has the same idea, so it is rewritten without changing its meaning. Merged cells, for example, exist in every format.
No warning
Kept because it was never touched
Charts, images, macros — parts whose contents this library does not interpret — are kept as the bytes they were read as, and come back intact when the file is saved in the same format.
Same format only. Into another format it is ✕
Replaced by the nearest thing
The thing itself does not exist, but the closest other way of saying it does. A gradient fill becoming a solid colour, for instance.
A substituted warning says what it was replaced with
Dropped, but always said
Something with no place at all. It is never deleted in silence. What was dropped, on which sheet, in which cell, comes back as a list.
dropped (whole) or degraded (in part)

How "always said" is guaranteed

Anyone can promise. Here a machine does the cross-checking.

  1. Take one workbook with all 47 features in it and save it once as a real file in each format.
  2. Open that file and save it in another format.
  3. Open the result and count the features that went missing.
  4. 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.

Test: CrossFormatConversionTests.everyLossIsNamedByItsOwnWarning (9 directions)

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 fine-grained rules inside the grid editable ranges in a protected sheet scenarios (named sets of input values) sheet tab colours workbook protection (fixed sheet structure) VBA macros (.xlsm) pivot tables, data validation, print setup OpenDocument only the idea that "it is written in the document" label ranges (headings used in formulas) consolidation definitions detective arrows (reference tracing) calculation settings (regex, two-digit years, …) any date as the epoch a cell that knows it is "currency" Numbers only a sheet is a canvas, not a grid several tables on one sheet charts, shapes, images beside a table cell controls (pop-up menus etc.) decimals as decimal128 (no digits lost) the idea of header rows / header columns (no defined names, no sheet protection, …)
The blue box is "what no other format can ever hold": Numbers' several tables on one sheet.

Excel only — taken to the other formats

Feature→ ODS→ NumbersWhat 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.

FeatureWhat it is→ Excel→ Numbers
Label rangesWrite =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 definitionsA setting that sums several ranges, and stays in the document. In Excel it is a one-off command that leaves nothing behind
Detective arrowsArrows tracing precedents and dependents. Excel draws them but does not save them
Calculation settingsWhether 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 epochODF can take any date as the epoch. Excel has only two: 1900 and 1904
Currency cell typeThe cell itself knows "this is yen". In Excel that information lives only inside the number-format code

△ is "replaced by the nearest thing". The date epoch is moved to whichever of 1900 and 1904 is nearer, and currency becomes a number-format code such as [$¥-411]#,##0.00. The values do not move.

Numbers only — taken to the other formats

Feature→ Excel→ ODSWhat 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.

DirectionFeatures lostWarningsSuggested insteadHow to read it
From an Excel (.xlsx) file
Excel → Excel00 Nothing is lost. That this was not 0 is the defect found this time (below)
Excel → ODS48xlsx Protected ranges, scenarios, tab colours, workbook protection. Pivot tables survive, with the proviso "layout only"
Excel → Numbers2520xlsx 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 → Excel44 Only the four that exist solely in OpenDocument (label ranges, consolidation definitions, detective arrows, calculation settings)
ODS → ODS01 The one warning explains that a pivot table is written as layout only and the numbers are recalculated by whoever opens it
ODS → Numbers2519xlsx 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 → Excel11numbers Only "several tables on one sheet". Do not read this 1 as "Numbers → Excel is safe" (see the caution below)
Numbers → ODS11numbers Same as above
Numbers → Numbers00 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.

How to read it Take an Excel file (41 features) to Numbers and back to Excel and you get 16 features. Measured, the return trip gives 0 warnings — not a lie: there is nothing left to lose. The cost of a round trip is decided by the outbound leg.
Excel file 41 features −25 20 warnings Numbers 16 features −0 0 warnings Back to Excel 16 features The return trip answers "nothing was lost" — because there is nothing left to lose
"One-way loss" and "round-trip loss" are different things. The numbers in the table are always one-way.

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.

FormatParts not touchedWhat the model readIn 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.

First save <pivotTableDefinition updatedVersion="8" createdVersion="8" …> correct open The reader's memory attributes it knows: 5 remembered as "unknown": updatedVersion, … 15 of them overlapping what the writer always writes save Second save updatedVersion="8" updatedVersion="8" ← the same attribute twice not well-formed XML Excel says "we found a problem with some content"; our own reader threw the pivot table away in silence
XML with an attribute written twice is not "hard to read" — by the standard it is broken.

Test: CrossFormatConversionTests.threeGenerationsStayWellFormedAndKeepEverything — saves the same document three times, re-parses every part as XML each time to check it is well-formed, and looks for anything lost in an intermediate generation.

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.

The fix: the writer collects "the attribute names actually written in this call", and attributes from the file are added only when they are not in that set (RootAttributes). Keeping the two lists in step by hand was abandoned because this defect was exactly that slip of memory.

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

CaseBeforeNow
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

A warning's subject is not decoration for display; it is the basis for deciding which format to suggest instead. So calling "macros dropped" "parts dropped" was not loose wording but wrong advice.

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.

After the fix The same conversion returns 2 warnings (1 chart, 1 cell controls).

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.

SettingWhat LibreOffice always writesDoes it actually take effect?
Automatic detection of headingsoff 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 years1950 Yes, but it only changes how years typed from now on are read; existing values do not move
Iteration step size0.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.

DocumentBeforeNowWhat it says
A brand-new workbook00
An ODS saved by LibreOffice (nothing set)11 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)10 A step count no calculation ever reaches is not a loss worth a sentence

To be candid: the LibreOffice file did not lose a warning. The two-digit-year window really does differ, and "1/1/50" would be read differently. What was reduced is the vagueness, not the count, and the one warning left is something a reader can actually act on.

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 pageWhat measures itCount
How far each of 47 features survives in each formatFormatSupportTests.matchesThePublishedTable3
Everything lost has a warning that names it (9 directions) CrossFormatConversionTests.everyLossIsNamedByItsOwnWarning9
Three saves later the parts are still well-formed and nothing is lost CrossFormatConversionTests.threeGenerationsStayWellFormedAndKeepEverything3
A pivot table survives the second save CrossFormatConversionTests.aPivotTableSurvivesBeingSavedTwice1
A lost macro is reported as a macro loss whatever the destination CrossFormatConversionTests.aDroppedVBAProjectIsAlwaysAMacroLoss3
The conversion entry point returns both the read and the write warnings CrossFormatConversionTests.convertAnswersWithBothHalvesOfTheTrip1
A calculation setting is reported only when the destination would read it differently ODFOnlyFeatureTests.aCalculationSettingIsReportedOnlyWhenItWouldChangeSomething1
Fix one cell and save, and not one other byte movesPreservationTests.editOneCellKeepsEverythingElse1
The six OpenDocument-only features round-trip, and LibreOffice reads themODFOnlyFeatureTests8
Numbers shapes and cell controls are reported on readNumbersSilentLossTests9
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 valuesTests/OpenpyxlParity/verify_with_openpyxl.py
Broken input never crashes and always becomes a SheetErrorFuzzTests8

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.

JudgeWhat it checks
Numbers.app 15.3.1Opens the written .numbers, is asked what it sees, and saves it again
LibreOfficeOpens the written .ods / .xlsx; can it convert to PDF, and do the elements survive
openpyxlReads the written .xlsx; same values, same types (it is also the yardstick for behaviour)
numbers-parserReads 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