Skip to main content

The .012 format

.012 files are JSON with one line per cell, keyed by address, so diffs read naturally and files merge reasonably in version control. This page is the format at version 7, the newest; Versions says what each version adds and what 012 promises about old files. A cell without formatting is just what was typed; a formatted cell is a small object:

{
"version": 2,
"widths": {"A": 14},
"cells": {
"A1": "Rent",
"B1": {"input":"1450","format":"currency","decimals":2},
"B2": {"input":"95","note":"Due on the 1st"},
"B3": "=SUM(B1:B2)"
}
}

A formatted cell has its input and the formatting that isn't the default: format (as number_format in macros names it: currency, percent, date, ...), decimals, pattern for a custom format, bold, italic, underline, strikethrough, align (left, center, right), valign (top, middle, bottom), wrap (wrap or clip) and borders, each edge's line by name, with topColor, bottomColor, leftColor and rightColor for a line in a color: "borders":{"top":"thin","bottom":"double","left":"thick","right":"thin","bottomColor":"red"} (formatting).

Only what's typed is saved. What 012 computes is computed again when the file opens: formula results, pivot tables' results, and the arrays formulas spill, whose cells are kept only for their formatting and notes, and the rows of regions, made again by their commands or read again from their files.

Reading and writing​

Any JSON with the same fields reads the same: the order of keys, spacing, and the case of field names don't matter. A cell given twice takes its last entry, though an invalid earlier one is refused; the version or the sheets list given twice around cells is refused.

012 writes one form of each workbook: the fields in the order this page shows them, cells by row and then column, widths and heights by position, column and row formats with the whole sheet's first, then columns', then rows', names by name, and charts, rules, regions and macros in the order they were made. Saving a file 012 wrote, without changing it, gives back the same bytes.

012 reads and writes the file as a stream, each cell straight into or out of its sheet, so opening a file takes about the memory of the workbook however large the file; what that costs, and why there is no binary format beside this one, is in Bounds of support.

Versions​

012 writes the lowest version that holds the workbook, so older builds open what they can, and refuses a version newer than it knows rather than lose sheets or pivots. Version 1 files (cells as plain strings) still load.

VersionWritten whenAdds
2One sheet, using none of the belowcells, widths
3A sheet has named ranges, frozen panes or a filternames (each name's range, #REF! once its cells were deleted), freeze (rows, cols), filter (its range, and columns by letter, each with hidden values or a condition and its value)
4Several sheets, or a formula naming a sheetsheets, a list; see Several sheets
5A pivot tablea sheet's pivot; see Pivot tables
6A tablea sheet's tables; see Tables
7A linked sourcea sheet whose region is paged; see Linked sources

What stays the same from release to release:

  • Every file a release saved opens in every later build, and saving it unchanged gives back its bytes. Each release keeps workbooks it saved as fixtures (internal/sheet/testdata/fixtures/<release>), which every build's tests open and save again (Releasing).
  • Fields are only added, never renamed or given another meaning; the version rises only for a field older builds can't do without, and the rest are listed under Fields that need no version.
  • An older build ignores a field it doesn't know, but refuses a value it doesn't know in a field it does (a chart type, a rule's condition such as a date period), rather than open the file with the rule or chart quietly changed.

Fields that need no version​

Builds that don't know these fields ignore them (and drop them if they save), so they raise no version:

FieldOnHolds
notea cellIts note
owna celltrue when its formatting is its own, not its column's or row's (Automatic in a currency column)
texta cellHow its input reads when its format would say otherwise: true for text typed into a Plain text cell whose format changed since ({"input":"007","text":true}), false for a number or formula typed before the cell was formatted as Plain text; either stays as typed, as in Sheets. Older builds read the input as the format says
wrap, bordersa cell or a lineHow its text wraps and its borders; older builds show the text overflowing, without lines
valign, and colors in bordersa cell or a lineWhere its text sits in a tall row, and its borders' colors; older builds show the text at the bottom and the lines in the text's ink
linesa sheetColumn and row formats
heightsa sheetRow heights set by hand, in lines, by row number: "heights": {"3": 2, "7": 4}
mergesa sheetMerged cells, by range: "merges": ["A1:C1", "D2:D5"]
name, hiddena sheetIts name when renamed; true when hidden (a file whose sheets are all hidden opens with the first one shown)
chartsa sheetOne chart per line: type (column, bar, line, pie, area, scatter), data, at, width, height, byRow, header, labels, title, and options left out at their defaults: stack (stacked, percent), trend, min, max, log, gridlines (only when off), legend (right, none). A build that charts but lacks a chart's type refuses the file
protecteda sheetProtected ranges: {"range":"B2:C9","description":"Totals"}, or {"sheet":true}
conditionalFormats, validationsa sheetRules, one per line
arithmeticthe workbookdecimal for decimal arithmetic
localethe workbookThe locale it's typed and shown in ("de-DE"), when File > Settings > Locale chose one; without it, the file follows the reader's locale setting. Cells are stored the same way in every locale: input is always as typed in en-US (1,234.5, 9/26/2026, =ROUND(A1,2))
macros, macroOriginthe workbookMacros as Starlark scripts, and the computer they were made or trusted on (macros', notebook cells' and linked files' alike): see Macro scripting API. Opening a file never runs them
regionsa sheetIts linked files and notebook cells' outputs sent there: see Regions. Older builds open the sheet without them
tab, reactive, notebookCellsa sheetA notebook tab and its cells: see Notebooks. Older builds open it as an empty sheet

Column and row formats​

Formats of whole columns and rows are kept in a lines field after the widths, runs of lines with the same formatting together; A:XFD is the whole sheet's format:

"lines": {"A:XFD": {"italic":true}, "B:D": {"format":"currency","decimals":2}, "1:1": {"bold":true}},

Several sheets​

In version 4 the sheets are a list, each with its name and the fields a version 3 file has at the top, and named ranges say their sheet. The workbook's settings (named ranges, decimal arithmetic, the locale, the sheet shown when saved as active) stay at the top:

{
"version": 4,
"names": {"Rent": "Budget!B1"},
"active": 1,
"sheets": [
{
"name": "Budget",
"cells": {
"A1": "Rent",
"B1": "1450"
}
},
{
"name": "Q3 plan",
"hidden": true,
"cells": {
"A1": "=Budget!B1*3"
}
}
]
}

Pivot tables​

A pivot's sheet has a pivot field, on one line after its cells, holding the definition. The results are never saved; they are computed again when the file opens, so the file stays small and can't disagree with its data.

{
"name": "Pivot Table 1",
"cells": {},
"pivot": {"source":"Sales!A1:D200","rows":[{"column":"B"},{"column":"A","order":"desc","sortBy":1}],"columns":[{"column":"C"}],"values":[{"column":"D","summarize":"sum"},{"column":"D","summarize":"counta","showAs":"percent_of_total","name":"Share"}],"filters":[{"column":"A","hidden":["North"]}],"rowTotals":true,"columnTotals":false}
}
  • source is the data, with its sheet; Sales!#REF! once the range was deleted. The sheet is by name, as in formulas, and follows renames.
  • rows and columns are fields by column letter on the source sheet, with order desc for Z to A and sortBy the value, counting from 1, whose totals order the groups.
  • values summarize a column: sum, counta, count, countunique, average, max, min, or rows (every row, blank or not, which frequency tables use); showAs is percent_of_row, percent_of_column or percent_of_total; name replaces the header.
  • filters take a filter column's criteria: hidden values and a condition with its value.
  • rowTotals and columnTotals are the grand total row and column.

Tables​

A sheet's tables are a tables list among its view's fields, before its cells:

"tables": [{"name":"Sales","range":"A1:C20","columns":["Region","Month","Amount"],"banded":true,"header":true}],
  • name is what formulas name it by (Sales[Amount]).
  • range is its cells, header row included.
  • columns are its columns' names, left to right, as its header row shows them. Left out, or of another number than the range's columns, they're read from the header row.
  • banded shades every other data row, and header styles the header row.

Tables need version 6: a build without them can't read the formulas that read them, and refuses the file rather than show them as errors.

Regions​

A sheet's regions, linked files and notebook cells' outputs sent there, are a regions list after its cells, charts and rules, one per line, in the order they were made. The file keeps where each region is and what feeds it, never its table: opening the file reads linked files again and sends each output from its cell's saved output.

"regions": [
{"name":"big","at":"A1","output":true},
{"name":"app","at":"F1","path":"logs/app.csv","window":500}
]
  • name is what formulas name it by (nu.big, nu.app) and notebook cells read a linked file as ($app); an output's is its cell's.
  • at is its table's first cell, the header row's.
  • output marks a notebook cell's output.
  • A linked file has path: the file, relative to the workbook's folder when it's saved there or below, and otherwise as it was linked. format names the file's format (CSV, TSV, JSON, NUON, XLSX, SQLite, Parquet) when the extension doesn't tell it, table or query say what to read from a SQLite database, and window keeps the last rows under the header.

The table's cells are saved only for their formatting and notes, as a spill's are. A region with a command, which the notebook sheets of earlier versions wrote (with input, the range it read as $in, and rows, cols, reads and sort, which opening it doesn't need), opens as a code cell of the workbook's notebook, its table sent where it was (Notebook sheets).

Linked sources​

A linked source's tab is a sheet with no cells whose one region, at A1, is paged: the file keeps what the source reads and how its tab orders the rows, never the rows.

{
"name": "trips",
"cells": {},
"regions": [
{"name":"trips","at":"A1","path":"trips.parquet","paged":true,"order":{"sort":[{"column":"C","desc":true}],"filter":[{"column":"B","condition":"eq","value":"card"}]}}
]
}
  • name is the table formulas read (trips[fare], nu.trips); the tab is named the same when linked, and keeps its own name when renamed.
  • path, format, table and query are what a linked file's are (Regions).
  • paged marks the region as a source, read in place.
  • order is how the tab sorts and filters the rows: sort, the columns by letter, desc for Z to A, and filter, each a column by letter with a condition and its value, as a filter's column has (Filter). Formulas read the source in its own order whatever the tab shows.

Sources need version 7: a build without them would read the region as a linked file and load the whole source into cells.

Notebooks​

A notebook tab is a sheet with "tab": "notebook", "reactive": true when it's reactive, and its cells in notebookCells, one per line:

"tab": "notebook",
"notebookCells": [
{"kind":"note","source":"# Files"},
{"source":"files = ls","output":"[[name, size]; [go.mod, 812b]]"},
{"source":"$files | nope","error":"Command `nope` not found","detail":"help: ..."},
{"source":"ls **/*","unsaved":true}
]
  • kind is note for Markdown, left out for code; source is what the cell holds, a code cell's name and all, as files above.
  • output is what the cell's last run printed, as NUON text, kept when it fits the nu-save-cell-kb and nu-save-notebook-kb caps (Configuration); unsaved marks one left out for its size.
  • error and detail are a failed run's message and help line, note what an output left out.

Run counts, timings and stale marks aren't saved: an output read from the file is what its cell's source and inputs gave.

Conditional formats and data validation​

A sheet's rules are two lists after its cells and charts, one rule per line:

"conditionalFormats": [
{"ranges":"A2:A6","condition":"formula","values":["=$D2"],"text":"green","strikethrough":true},
{"ranges":"C2:C6","scale":[{"type":"min","color":"green"},{"type":"percentile","value":"50","color":"yellow"},{"type":"max","color":"red"}]},
{"ranges":"E2:E6","dataBar":{"color":"blue","min":{"type":"min"},"max":{"type":"max"}}},
{"ranges":"F2:F6","iconSet":{"icons":"arrows","points":[{"type":"percent","value":"33"},{"type":"percent","value":"67"}]}}
],
"validations": [
{"ranges":"B2:B6","criteria":"list","items":["Ann","Bo","Cy"],"help":"Pick who does it"},
{"ranges":"D2:D6","criteria":"checkbox"},
{"ranges":"C2:C6","criteria":"number","condition":"between","values":["0","80"],"reject":true}
]
  • ranges are the rule's ranges, A2:A9,C2:C9.
  • A single-color rule has a condition (empty, not_empty, contains, not_contains, starts_with, ends_with, exactly, date_is, date_before, date_after, gt, ge, lt, le, eq, ne, between, not_between, formula, and those comparing a cell with the rest: top, bottom, top_percent, bottom_percent, above_average, below_average, duplicate, unique), its values as typed (a date rule's may be a period: this week), and its style: text and fill colors (red, yellow, green, cyan, blue, magenta) and bold, italic, underline, strikethrough.
  • A color scale has scale: two or three points, each a type (min, max, num, percent, percentile), a value where it takes one, and a color.
  • A data bar has dataBar: its color, its min and max points as a scale's, and barOnly to hide the values. An icon set has iconSet: its icons (arrows, circles, symbols, rating), the points where each icon after the first starts, reverse for the other way round and iconOnly to hide the values. Older builds leave both out.
  • A validation rule has criteria (list, range, checkbox, number, date, length, formula) with its items, source (Lists!A1:A20), or condition (between, not_between, eq, ne, gt, ge, lt, le; none for any date) and values; reject refuses invalid entries, and help replaces the rule's own help text. A checkbox's items, when it has them, are its checked and unchecked values (["Yes", "No"]); a dropdown's display is chip or plain when it doesn't show the arrow. Older builds draw every dropdown with its arrow, and a checkbox with values of its own as invalid entries.

A line of either list is also what Add conditional format rule and Add data validation rule take (in the palette, and as run(..., answer=...) in macros), which is how a macro records a rule added from the panel.