XLSX File Documentation
Summary
An Microsoft Excel Open XML Spreadsheet is the default workbook format Excel has used since 2007, saved as a .xlsx file. It is a ZIP archive of XML parts, standardised as ECMA-376 / ISO/IEC 29500, with the MIME type application/vnd.openxmlformats-officedocument.spreadsheetml.sheet. You do not need Excel to open one: Google Sheets, LibreOffice Calc, and Excel for the web all read it for free.
Technical details
| Feature | Value |
|---|---|
| Full name | Microsoft Excel Open XML Spreadsheet |
| File extension | .xlsx |
| MIME type | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
| Format type | Spreadsheet workbook (Office Open XML / OPC package) |
| Container / base format | ZIP archive of XML parts |
| Developer | Microsoft |
| Introduced | 2007 (Excel 2007), replacing the binary .xls |
| Standard | ECMA-376; ISO/IEC 29500 (2008) |
| Open standard | Partial — published standard, some Microsoft-specific extensions |
| Magic number (hex) | 50 4B 03 04 (ZIP local file header, ASCII PK) |
| Compression | DEFLATE (ZIP) |
| Macro support | No — macros require the .xlsm format |
| Encryption | Yes — AES workbook encryption and password protection |
| Maximum rows per sheet | 1,048,576 |
| Maximum columns per sheet | 16,384 (up to column XFD) |
| Multi-sheet support | Yes — one or more worksheets per workbook |
| Content held | Text, numbers, dates, formulas, charts, images, pivot tables |
| Cell references | Relative, absolute, mixed; 3-D references across sheets |
| Opens in | Microsoft Excel, LibreOffice Calc, Google Sheets, Apple Numbers, Gnumeric, WPS Spreadsheets |
| Related extensions | .xls, .xlsm, .xlsb, .xltx, .csv, .ods |
| Specification | learn.microsoft.com/openspecs/office_standards/ms-xlsx/ |
What is an XLSX file?
An XLSX file is the default workbook of Microsoft Excel, introduced with Excel 2007 when Microsoft replaced the old binary XLS with an XML-based container. Its internal dialect is called SpreadsheetML, part of the Office Open XML family standardised as ECMA-376 in 2006 and as ISO/IEC 29500 in 2008. A single .xlsx holds one or more worksheets of cells, where each cell carries a number, a date, a string, a boolean or a formula, along with the styles, defined names, charts and pivot definitions that describe the workbook.
The point of the switch was that the layout became documented rather than reverse-engineered. Because every value, formula and format is stored as XML text inside a well-known folder structure, a Python library, a Java tool or a server-side script can produce a valid workbook without ever running Excel. The rest of this page walks through that structure part by part, down to the individual cell attributes and the rule that turns a stored number into a visible date.
The ZIP container and the OPC package
An .xlsx is not a single XML file. It is a ZIP archive that follows the Open Packaging Conventions (OPC), the same packaging model used by DOCX and PPTX. Rename a copy to .zip, open it with any archive tool, and you see a tree of XML parts. The first four bytes on disk are 50 4B 03 04 (ASCII “PK”), the ordinary ZIP local-file header, identical to every other ZIP. Nothing in that header says “spreadsheet”. What identifies the file as XLSX is the presence of [Content_Types].xml at the root and the xl/workbook.xml part inside.
my-workbook.xlsx (a ZIP / OPC package)
├── [Content_Types].xml maps every part (by name or extension) to a content type
├── _rels/
│ └── .rels package root relationships → points at xl/workbook.xml
├── docProps/
│ ├── core.xml title, author, created/modified dates
│ └── app.xml application name, list of sheet titles
└── xl/
├── workbook.xml sheet list, sheet r:id refs, defined names
├── _rels/
│ └── workbook.xml.rels workbook → each sheet, sharedStrings, styles
├── sharedStrings.xml de-duplicated table of every cell string
├── styles.xml numFmts, fonts, fills, borders, cellXfs
├── calcChain.xml order in which formula cells recalculate
└── worksheets/
├── sheet1.xml the sheetData tree: rows and cells
└── sheet2.xml
Two mechanisms hold this together. [Content_Types].xml declares a media type for every part, either by a default rule keyed on file extension (all .xml parts, all .rels parts) or by an explicit override for a single part; that override is how a worksheet part and the shared-string part get their distinct SpreadsheetML types. The _rels folders carry the relationships: _rels/.rels at the root points at the workbook, and xl/_rels/workbook.xml.rels maps each relationship id used inside workbook.xml to a concrete part such as worksheets/sheet1.xml. Parts refer to each other by relationship id, never by hard-coded path, which is what lets the package be rearranged without breaking links.
The workbook and worksheet parts
The xl/workbook.xml part is the index of the file. Its <sheets> element lists every worksheet with a display name, an internal sheetId, and an r:id that resolves through workbook.xml.rels to the actual sheet part. The tab order you see in Excel is the order of these elements, not the sheet file names, so sheet1.xml is not guaranteed to be the leftmost tab. The same part holds <definedNames>, where a named range such as Sales maps to a formula like Sheet1!$B$2:$B$20, and <calcPr>, which records calculation settings.
Each worksheet is its own file under xl/worksheets/. The payload is the <sheetData> tree: a sequence of <row> elements, each with an r attribute giving the 1-based row index, and inside each row a sequence of <c> cell elements. A cell carries an r reference in A1 notation (r="B3"), an optional t type attribute, an optional s style index, an optional <f> formula child, and a <v> value child. Empty cells are usually omitted entirely, so rows can be sparse and column positions are read from the r reference rather than from counting siblings.
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<sheetData>
<row r="1">
<c r="A1" t="s"><v>0</v></c> <!-- string: index 0 in sharedStrings -->
<c r="B1" t="s"><v>1</v></c> <!-- string: index 1 -->
</row>
<row r="2">
<c r="A2" t="s"><v>2</v></c> <!-- string: index 2 -->
<c r="B2"><v>42</v></c> <!-- no t: numeric 42 -->
<c r="C2" s="3"><v>45566</v></c> <!-- style 3 formats 45566 as a date -->
<c r="D2"><f>B2*2</f><v>84</v></c> <!-- formula with cached result -->
</row>
</sheetData>
</worksheet>
Note cell D2: the <f> child holds the formula text B2*2 and <v> holds the last computed result 84. A reader can display that cached result immediately without a calculation engine, then recalculate when needed. Formula grammar is the ordinary Excel one, so SUM(A1:A9), IF(A1>0,"y","n") and CONCATENATE(A1," ",B1) are all stored verbatim as text inside <f>.
Cells, the shared string table and why
The t attribute on a cell decides how <v> is interpreted. This is the single most important detail when reading a worksheet by hand, because the same digits mean completely different things depending on t.
t value | Meaning | What <v> holds |
|---|---|---|
(omitted) or n | Number | A numeric literal, e.g. 42 or 45566 |
s | Shared string | A 0-based index into sharedStrings.xml, not the text |
str | Formula string | Inline string result of a formula |
inlineStr | Inline string | Text held in an <is> child, bypassing the shared table |
b | Boolean | 1 for TRUE, 0 for FALSE |
e | Error | An error literal such as #DIV/0! |
A cell with t="s" does not contain its text. Its <v> is an integer index into the <si> (string item) list in xl/sharedStrings.xml. So <c r="A1" t="s"><v>0</v></c> means “the text of A1 is string item 0.”
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
count="9" uniqueCount="3">
<si><t>Region</t></si> <!-- index 0 -->
<si><t>Revenue</t></si> <!-- index 1 -->
<si><t>North</t></si> <!-- index 2 -->
</sst>
The reason for this indirection is de-duplication. A label such as “North” that repeats down 50,000 rows is stored once in the table and referenced by a small integer in every cell, rather than repeated as full text 50,000 times. The count and uniqueCount attributes on <sst> record total references versus distinct strings. This is one reason an XLSX is usually far smaller than the equivalent CSV, which repeats every string in full. The inlineStr type exists for the opposite case: streaming writers that emit cells one pass at a time can put text directly in an <is> child and skip building a shared table at all.
How a double becomes a date
Numbers, dates, times and currency are all stored the same way: as an IEEE-754 double in <v> with no type marker. There is no date type in the cell. What makes 45566 render as a calendar date instead of the number forty-five thousand is the cell’s number format, referenced through its style index.
The s attribute on a cell is an index into the <cellXfs> list in xl/styles.xml. Each entry there (an <xf>) points to a numFmtId, and the number-format string, either a built-in id or a custom one defined in <numFmts>, tells the renderer how to draw the double. A serial date works on the 1900 date system: the integer part counts days since 30 December 1899 (with the well-known leap-year quirk that treats 1900 as a leap year), and the fractional part is the fraction of a 24-hour day. So 45566 under a date format is a specific day in 2024, and 45566.5 is midday of that day. Change the number format and the very same stored 45566 shows as a plain integer.
<styles>
<numFmts>
<numFmt numFmtId="164" formatCode="yyyy\-mm\-dd"/>
<numFmt numFmtId="165" formatCode=""$"#,##0.00"/>
</numFmts>
<cellXfs count="4">
<xf numFmtId="0" .../> <!-- s="0": General, plain number -->
<xf numFmtId="164" .../> <!-- s="1": renders the double as a date -->
<xf numFmtId="165" .../> <!-- s="2": renders the double as currency -->
</cellXfs>
</styles>
The same indirection covers currency. A cell holding 1234.5 with a style whose formatCode is "$"#,##0.00 displays as $1,234.50, but the stored value is still the bare double, so a reading tool that ignores styles sees the raw number and not the dollar sign. This separation of value from presentation is deliberate: the data survives even when the formatting is stripped.
The calculation chain and the grid limits
When a workbook contains formulas, xl/calcChain.xml records the order in which cells must be recalculated so that dependencies resolve correctly (a cell that feeds another is computed first). It is a cache, not source data: delete it and Excel rebuilds it on the next open. Corrupt or stale calc chains are a common cause of “we found a problem” repair prompts, and many third-party writers simply omit the part and let Excel regenerate it.
The grid itself is bounded by the format. A single worksheet holds at most 1,048,576 rows (220) by 16,384 columns (214), the last column being XFD. Column letters run A, B, … Z, AA, AB, … up to XFD, a base-26 style labelling of the 16,384 columns. A single cell’s text is capped at 32,767 characters; anything longer is truncated on entry. These ceilings are set by the SpreadsheetML schema, not by available memory, so a dataset taller than 1,048,576 rows cannot live in one sheet regardless of hardware and belongs in a database or a columnar store instead.
XLSX vs XLSM vs XLSB vs XLS
Four extensions describe closely related workbooks, and the differences are structural, not cosmetic.
XLSX is the OPC/XML package described above, and it has no part for executable code. XLSM is byte-for-byte the same OPC package plus one extra part, xl/vbaProject.bin, an OLE compound stream that holds the VBA macro project; it also carries a distinct content type in [Content_Types].xml so Excel can tell a macro-enabled workbook from a plain one before opening it. XLSB stores the same logical workbook, but the parts inside the ZIP are binary (the BIFF12 record format) instead of XML, which parses faster and shrinks very large models at the cost of being harder to read without Excel or a dedicated library. XLS is the pre-2007 format and is not a ZIP at all: it is a single OLE2 compound file using the BIFF8 binary record stream, the format Open XML replaced.
A useful way to remember it: XLSX and XLSM share a container and differ by one macro part; XLSB swaps XML parts for binary ones inside that container; XLS predates the container entirely. For an open-standard editable alternative there is also ODS, the OpenDocument spreadsheet, which uses its own ZIP-plus-XML packaging under a different schema.
Frequently asked questions
Why is cell text stored in a separate sharedStrings.xml part instead of in the cell?
To de-duplicate strings. Each distinct string is written once as an <si> item, and every cell that shows it holds only a small integer index with t="s". When the same label repeats across thousands of rows, storing the index rather than the full text keeps the package much smaller than a CSV, which repeats every string in full.
How does Excel know a cell is a date if the value is just a number?
It does not store a date type. The cell holds an IEEE-754 double (a serial day count in the 1900 date system) and a style index. The style points to a number format in xl/styles.xml; that format string is what tells the renderer to draw the double as a date, a time or currency. Remove the format and the same value shows as a plain number.
References
- Microsoft — [MS-XLSX] / Office Open XML overview
- ISO/IEC 29500 — Office Open XML File Formats
- The Document Foundation — LibreOffice Calc
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.