File formats
This is the same document you'll find inside Spredin under Help.
Spredin reads and writes several formats. Each one carries a different amount of
your work. This page is the plain-language map: pick .sprd to keep
everything, Excel to hand off to Excel/Sheets, CSV/TSV or JSON for raw values.
The one-line summary#
| You want to… | Use | Menu |
|---|---|---|
| Keep a perfect copy of your work | .sprd / .sprd |
File → Save |
| Give it to an Excel / Google Sheets user | .xlsx (all sheets) |
File → Export → Excel |
| Feed raw values to another tool or an agent | .json (all sheets) |
File → Export → JSON |
| One flat table of values | .csv / .tsv (active sheet) |
File → Export → CSV / TSV |
Golden rule: work in
.sprd. It is the only format that preserves everything — and it is open, human-readable JSON you (or an agent) can read, diff, and generate.
Opening an Excel file — what survives, and what does not#
The table above is about saving. This one is about the other direction: you double-click a colleague's workbook and open it here. It is a different question with a different answer, and the honest version is that Spredin brings across your data and its formatting and leaves behind Excel's automation and drawing layers.
✅ kept · 🟡 partial · ❌ dropped
| What's in the Excel file | .xlsx |
.xls |
|---|---|---|
| Cell values, all sheets | ✅ | ✅ |
| Formulas | ✅ | ❌ (values imported instead — see below) |
| Number formats (dates, currency, %) | ✅ | ❌ |
| Fonts, sizes, bold/italic/underline | ✅ | ❌ |
| Text and fill colours, incl. theme colours | ✅ | ❌ |
| Borders (presence) | 🟡 no per-edge style | ❌ |
| Alignment, vertical alignment, wrap | ✅ | ❌ |
| Merged cells | ✅ | ❌ |
| Column widths / row heights | ✅ | ❌ |
| Hidden rows / columns | ✅ | ❌ |
| Frozen panes | ✅ | ❌ |
| Cell comments (classic) | ✅ | ❌ |
| Data validation (dropdowns, rules) | ✅ | ❌ |
| Named ranges | ✅ | ❌ |
| Pivot tables | ✅ rebuilt as live Spredin pivots | ❌ |
| Conditional formatting rules | ❌ | ❌ |
| Charts | ❌ rebuild here — Spredin charts are Apache ECharts, and they live in .sprd |
❌ |
| Images, shapes, text boxes | ❌ | ❌ |
| Macros / VBA | ❌ replaced by the Python panel — real Python with pandas/numpy, not a macro language | ❌ |
| Hyperlinks | ❌ | ❌ |
| Sheet & workbook protection | ❌ | ❌ |
| AutoFilter state, sort state | ❌ | ❌ |
| Strikethrough, text rotation, indent | ❌ | ❌ |
Patterned fills (hatching, e.g. gray125) |
❌ solid fills come across; a patterned one arrives unfilled | ❌ |
The short version. Your numbers, formulas and the way the sheet looks come
across from .xlsx. Charts and conditional-formatting rules do not — rebuild
them here (both are better here, and both live in .sprd). VBA never will;
the replacement is the Python panel, which is a real programming language with
pandas in it — see Coming from Excel?.
How big a workbook can Spredin open?#
The limit is cell count, not file size, and it applies per sheet: about 12 million cells in any one sheet. A sheet past that is refused by name and the rest of the workbook still opens.
In practice: on a large real workbook — tens of megabytes, a dozen-plus sheets, one of them well past ten million cells — the first sheet is usable in well under a second and the whole book lands in a few seconds. Sheets hydrate in the background, so a large workbook is workable long before it has finished loading — a sheet still arriving says so, and says how big it is.
If a single sheet is refused: open that one on its own. In Excel, save it as
.csv or as its own .xlsx, and open that. Everything else in the workbook is
unaffected.
Nothing is lost from the original file. Opening never writes to it. If the
import drops something you need, the .xlsx on disk is untouched.
.xlscarries data only. See the next section for why — it is not an oversight, it is a different file format wearing a similar name.
Opening a .csv or .tsv — what gets converted#
Encoding and separator are detected. A CSV saved by Excel on Windows (in
the Windows code page) opens as readily as a UTF-8 one, with or without a
byte-order mark; so does a UTF-16 file that starts with one. A file that
separates fields with semicolons — what Excel writes in regions whose
decimal separator is a comma — is split on the semicolons, and its values are
read that region's way: 1.234,56 is 1234.56 and 15.03.2024 is a date.
Excel's own sep=; first line is honoured. A .tsv is always tab-separated.
A text file has no types, so the reader has to decide what each field means. Spredin applies exactly the same rule as typing into a cell, so a value behaves the same however it arrived:
| In the file | Becomes |
|---|---|
1,200 · $1,200 · €3.5 · (1,200) · 50% |
a number with a matching format |
2024-01-15 · 1/15/2024 (or 15/01/2024 on a day-first Mac — the system region decides, as in Excel) · 12:30 |
a date/time serial with a date format |
1200 · -3.5 · 1.2E5 |
a number |
007 · 00123 |
text — leading zeros are kept |
| a digit string longer than 15 digits | text — every digit is kept |
SEPT2 · MARCH1 · 1-3 · 3/4 · +1 (555) 010-1234 |
text, unchanged |
The last three rows are where we deliberately differ from Excel, which turns
SEPT2 into a date, drops the zeros from 007, and rounds a long account number
away into scientific notation. Spredin does not: a value you can see is a value
you can compute with.
One consequence worth knowing: because 007 is text, SUM over a column of
them is 0 (Excel ignores text inside a range) — while =A1+1 is still 8, since
operators coerce number-looking text. That is Excel's own split between operators
and aggregation, applied to a value Excel would have converted on the way in.
The Python API is exempt — sheet.set(r, c, "007") writes 007 and
sheet.set(r, c, "50%") writes the text 50%, because a script has real types
available and literalness is the more useful contract there.
.xls vs .xlsx — why the older one loses formatting#
They share a name and nothing else. .xlsx is not ".xls version 2"; it is a
complete replacement that happens to serve the same purpose.
.xls (Excel 97–2003) |
.xlsx (Excel 2007+) |
|
|---|---|---|
| What it actually is | a binary OLE2 compound file — a little filesystem of byte records | a ZIP of XML documents (rename one to .zip and look inside) |
| Standard | Microsoft's [MS-XLS] binary spec |
ECMA-376 / ISO 29500, an open standard |
| Max sheet size | 65,536 rows × 256 columns | 1,048,576 rows × 16,384 columns |
| Typical size | larger | smaller (it is compressed) |
| Corruption recovery | poor — one bad region can take the file | good — parts are independent |
Because the containers have nothing in common, the high-fidelity reader that
pulls styles, merges, freezes and comments out of the .xlsx XML cannot be
pointed at a .xls — there is no XML in there to read. Spredin decodes .xls
records for values and formulas, and stops there.
Reading and writing .xls#
- Open / import: yes. File → Open takes
.xlsdirectly, all sheets. - Values, not formulas — on purpose. A
.xlsstores each formula's last computed result alongside the formula, and Spredin imports that result. The formula itself is dropped because the library we decode BIFF with returns wrong references for ranges: a cell holding=SUM(D2:D4)comes back as=SUM($USP$2:$USP$4), which would quietly compute a different number from the one you had. Simple references decode correctly, which makes importing formulas worse rather than better — some would be right and some silently wrong, with nothing on screen to tell them apart. A correct static number beats a formula that lies. (.xlsxis unaffected: its formulas are plain text in the XML and import exactly.) - Save as
.xls: no, and this is deliberate. Writing BIFF8 would mean implementing a 1997 binary format from scratch — there is no maintained library that writes it — in order to produce files capped at 65,536 rows that every supported version of Excel has been able to read out of.xlsxsince
What to do instead: open the .xls, work, then File → Export → Excel to
get .xlsx. Excel 2007 and later open it natively; Excel 2003 opens it with
Microsoft's free Compatibility Pack. If your recipient truly cannot take
.xlsx, export .csv — every version of Excel ever shipped reads that.
Practical note: opening a
.xlsand exporting.xlsxis a one-way upgrade of the container. The formatting the.xlshad was already dropped on import, so the new file has your data and Spredin's formatting, not the original's.
What each format preserves when Spredin saves or exports#
✅ kept · 🟡 partial · ❌ dropped
| Your content | .sprd |
.xlsx |
.json |
.csv/.tsv |
|---|---|---|---|---|
| Cell values | ✅ | ✅ | ✅ | ✅ |
Formulas (=SUM(…)) |
✅ | ✅ | ❌ values only | ❌ values only |
| Number formats, fonts, colours, fills, borders, alignment, wrap | ✅ | ✅ | ❌ | ❌ |
| Merged cells | ✅ | ✅ | ❌ | ❌ |
| Column widths / row heights | ✅ | ✅ | ❌ | ❌ |
| Frozen panes | ✅ | ✅ | ❌ | ❌ |
| Cell comments / notes | ✅ | ✅ | ❌ | ❌ |
| Data validation (dropdowns / rules) | ✅ | ✅ | ❌ | ❌ |
| Named ranges | ✅ | ✅ | ❌ | ❌ |
| Conditional formatting | ✅ | ✅ as live Excel rules | ❌ | ❌ |
| AutoFilter (which rows a filter is hiding) | ✅ | ❌ | ❌ | ❌ |
Structured tables (Table1[Amount]) |
✅ | ❌ | ❌ | ❌ |
| Charts | ✅ | ❌ | ❌ | ❌ |
| Python scripts | ✅ | ❌ | ❌ | ❌ |
| Multiple sheets | ✅ | ✅ | ✅ | ❌ active sheet only |
Anything Spredin-specific — charts, the bundled Python — lives only in
.sprd. Conditional formatting is written to .xlsx as Excel's own rule types
(cell / text / duplicate / top-N / colour scale / data bar), so it keeps
responding to the data over there rather than freezing into fixed fills. It is
not read back IN from .xlsx yet.
The formats, one by one#
.sprd — full fidelity (recommended)#
Spredin's native format, and the default when you Save. Nothing is lost. Inside it is plain, human-readable JSON — one compact CSV line per row for the cells, with separate sections for styles, merges, sizes, freeze, conditional formatting, charts, named ranges, the active AutoFilter, structured tables and the Python files.
The file is gzip-compressed (~80% smaller — a 1M-row sheet is ~16 MB, beating
the equivalent .xlsx or .csv), but the compression is inside the file
rather than in the name: there is one extension to know, and Finder can't
mistake a workbook for an archive. .xlsx is secretly a zip and .numbers a
package; neither says so either. Spredin reads a .sprd whether or not it is
compressed — it checks the file's first two bytes, not its name — so you can
gunzip one to read the JSON by hand and it will still open.
Because it's open JSON, an agent or script can read and write it directly. One
shape worth knowing: formatting that came from an imported .xlsx is stored the
way Excel stores it — a styleTable of the distinct formats plus styleRuns
row spans into it — because a real workbook uses a handful of formats across
millions of cells, and writing one entry per cell made saving a large import
slow enough to look frozen. When styleRuns is present, the styles section
holds only the cells you edited yourself, and an empty {} there means "this
cell was explicitly cleared". A file with no styleRuns has the whole map in
styles, which is how every earlier file reads.
Pre-1.0 files.
.spredyand.spredy.gzstill open. They were the same format under the old product name.
.xlsx — Excel / Google Sheets interop#
Export → Excel writes every sheet (empty ones too) with values, formulas, and per-cell styling (bold/italic/underline, text + fill colours, alignment, wrap, borders, number format, font). Use it to hand data to someone in Excel or Sheets. It does not carry Spredin charts or the Python scripts. Conditional formatting goes as Excel's own live rules (see the table above).
Spredin reads .xlsx at high fidelity — all sheets
with styling, number formats, merges, sizes, frozen panes, comments, data
validation and named ranges. Writing back is .xlsx only (the legacy .xls
binary format is import-only).
Sheet names. Excel caps a sheet name at 31 characters, forbids
: \ / ? * [ ], and will not have two sheets with the same name (it compares
them case-insensitively, so Sales and sales clash). Spredin applies those
rules wherever a name is chosen — renaming a tab refuses a name Excel would not
accept, a script's add_sheet raises an error for one, and importing
data[2025].csv names the sheet data 2025 — so an ordinary workbook exports
unchanged. Renaming a tab updates every formula and defined name that
referred to it, in the same undo step, as Excel does. A workbook saved by an
older version can still hold an illegal name; export makes it legal and
rewrites the formulas and names that point at it, so the file still adds up.
Identifiers stay text. A cell holding 007, a ZIP code or a 16-digit
account number is text in Spredin, and it is written to Excel as text, so no
leading zero or trailing digit is lost.
Defined names are written absolute (Data!$B$2). Excel reads a name
without $ as relative to the cell using it; Spredin treats names as fixed
ranges either way, and the export makes Excel agree.
Very long text. Excel holds at most 32,767 characters in a cell. If a cell holds more, Export → Excel names the cells and asks before writing; the exported copy has that text cut to fit, and your workbook keeps it in full.
.json — plain values out (export only)#
Export → JSON writes every sheet's computed values as
{ "sheets": [ { "name": "Sheet1", "rows": [[…], …] } ] }. Formulas are
evaluated to their results; no styling, formulas or other Spredin-specific
metadata is included — it's the clean data, nothing else. It's the easiest
format for another program or an AI agent to consume.
Export only. Spredin does not open arbitrary
.jsonfiles (there's no single "right" way to lay an arbitrary JSON schema onto a grid). To bring JSON data in, convert it to.csv/.xlsxfirst, or parse it in the Python panel (import json) andsheet.write(...)the rows you want.
.csv / .tsv — one flat table of values#
The active sheet's computed values only. No formulas, no styling, no other
sheets — just a grid of text. Best for piping raw data into another tool. .tsv
uses tabs instead of commas.
Dates and times are written as they display (2024-01-15), so they read as
dates in any tool and come back as dates when the file is opened here again.
Other numbers are written plain — $1,200.50 becomes 1200.5 — which every
tool reads as a number. Export → JSON follows the same rule.
Round-trip expectations (important)#
.sprd→.sprd: identical. Everything survives.- Spredin →
.xlsx→ Excel: your data, formulas and cell styling arrive; conditional formatting arrives as live Excel rules; charts and scripts do not. .xlsx→ Spredin →.xlsx: Spredin writes a fresh.xlsxfrom the cell data + styles it modelled. It does not patch the original file's bytes, so anything Spredin doesn't model (e.g. the original's pivot tables or macros) won't be in the re-saved file. Spredin never runs macros, by design..csv→ edit → export: values round-trip faithfully; formatting and formulas were never in those files to begin with. (.jsonis export-only.)
For agents#
- Read/write
.sprd(documented JSON) for full fidelity; export to.json({sheets:[{name,rows}]}) for plain values (import is not supported — bring data in via.csv/.xlsx, or parse it in the Python panel andsheet.write(...)). - To produce a styled workbook for a human, drive the sheet via the Python
sheetAPI (values, formulas,style,chart, …) and Export → Excel. .xlsx/.xlsreading is desktop-only (it runs in the Rust core); the browser build reads.sprd/.csv/.tsvand can still write.xlsx(values + formulas; styling is desktop-only).
Opening an untrusted spreadsheet is safe: Spredin never runs anything stored inside a file, and its readers are hardened against malformed input.
External-workbook references#
Excel writes a reference into another file as '[1]Sheet Name'!$A$1, where [1]
indexes an external-link record. That file is not here, and Excel itself only
holds a cached value for it until you refresh — so such a cell would import as
#REF!.
Spredin redirects the reference to a local sheet of the same name when one exists, so the formula computes against the data in front of you. Measured on two real workbooks, every external reference named a sheet already present in the same file — the links pointed at an earlier copy of the same model. The count is reported in the status bar on open; it is never done silently.
A reference whose sheet is not present stays #REF!. Inventing a target would
produce a confident wrong number, which is worse than an error.