Skip to main content
Back to BlogConverter Guides

How to Convert XLSX to CSV (and Why the Sheet, the Formulas, and the Date Formatting Are the Hard Parts)

An XLSX file is not a spreadsheet. It is a ZIP archive containing a folder of XML files, and converting it to CSV is an exercise in unpacking the archive, reading the XML, and reconstructing the two-dimensional grid of cells that the spreadsheet application rendered on screen. The format is called Office Open XML, and it was designed for Microsoft Excel, not for data interchange. Every piece of information a spreadsheet needs — the cell values, the shared strings, the styles, the number formats, the formulas, the sheet names, the charts, the images — is stored in its own XML file inside the ZIP. A converter that wants to produce CSV has to open the ZIP, find the right XML files, parse them, and walk the cell references to build a grid. The hard parts are not the conversion itself. The hard parts are the shared strings table, the formula cache, the date detection, and the multi-sheet problem. Learn how an XLSX file is structured (the ZIP contains xl/sharedStrings.xml for strings, xl/styles.xml for number formats, xl/worksheets/sheet1.xml for the first sheet's cells, and xl/workbook.xml for the sheet list), why shared strings exist (Excel deduplicates repeated strings to keep file size down — the cell stores an index into a shared table, not the string itself), why a converter that does not read the shared strings table produces a column of integers instead of the actual text, why formulas are not computed by the converter (the XML stores the cached value, not the formula's result, and a freshly inserted formula that was never opened in Excel has no cached value), why the 1900 leap year bug means a date serial of 61 is March 1, 1900 not March 2 (Excel inherited the Lotus 1-2-3 bug that treats 1900 as a leap year; converters must subtract a day for serials above 60), why the date format ID is the only reliable way to detect dates (a numeric cell with a date number format is a date; a numeric cell without one is a number), why the time component is usually lost (most converters output date-only YYYY-MM-DD and discard the fractional part of the serial), why only the first sheet is converted by default (the other sheets are in sheet2.xml, sheet3.xml, and the converter has to be told to look at them), and what a converter cannot do (read .xls binary files, extract charts or images, preserve formatting, compute formulas, handle multi-sheet workbooks without explicit selection).

The Toolbox TeamAugust 13, 20268 min read

The problem: an XLSX file is a ZIP of XML, not a spreadsheet

An XLSX file is not a single document. It is a ZIP archive. Unzip one and you get a folder structure: xl/sharedStrings.xml holds the deduplicated strings, xl/styles.xml holds the number formats and cell styles, xl/worksheets/sheet1.xml holds the first worksheet's cells, xl/workbook.xml holds the list of sheet names, and a handful of other XML files hold charts, images, comments, and pivot tables. The format is called Office Open XML, and it was designed by Microsoft for Excel to read and write, not for other applications to parse. The structure is verbose, the relationships between the files are indirect (a cell stores a style index that points into styles.xml, which points into numFmts.xml, which points at a format string), and the spec is over a thousand pages.

A CSV file, by contrast, is plain text. Rows are separated by newlines, fields within a row are separated by commas, fields containing commas or quotes are wrapped in double quotes with embedded quotes doubled. The spec is a single page (RFC 4180). A CSV file is what you get when you strip everything except the values.

Converting XLSX to CSV is therefore not a format conversion. It is a strip-down. You unpack the ZIP, walk the cell references in the sheet XML, resolve the shared strings and the styles, build a two-dimensional grid, and emit it as comma-separated text. Everything that is not a cell value — the formatting, the formulas, the charts, the images, the multi-sheet structure, the named ranges, the comments — is dropped on the floor. The CSV has the values. It has nothing else.

The hard parts are the parts where the XLSX format does not store what you expect. The shared strings table is one. The formula cache is another. The date detection is a third. The multi-sheet structure is a fourth. Knowing where each of these traps is, and what a converter does about each, is the difference between a clean conversion and a column of integers where you expected names.

The Excel to CSV Converter does this unpacking in your browser using JSZip to decompress the archive and the browser's DOMParser to read the XML. It is the right tool for single-sheet workbooks with cached values and built-in date formats. It is the wrong tool for multi-sheet workbooks, formula-driven sheets, or files with custom date formats. Knowing which is which is the skill.

Fastest path

Open the Excel to CSV Converter. Drag an .xlsx file onto the dropzone, or click to pick one. The tool unzips the archive in your browser, reads the shared strings and styles, parses the first worksheet, and shows a preview of the rows. Pick a delimiter (comma, semicolon, or tab). Toggle "Quote all fields" if you want every field wrapped in quotes. Click Download for a .csv file, or Copy to copy the CSV text to the clipboard.

How the XLSX file is structured, and what a converter reads

The ZIP archive inside an XLSX file has a predictable structure. The files a converter cares about are:

  • xl/workbook.xml — the list of sheet names and their order. A converter that wants to support multi-sheet workbooks reads this to know which sheetN.xml files exist.
  • xl/sharedStrings.xml — the deduplicated string table. Every string cell in every sheet stores an index into this table, not the string itself.
  • xl/styles.xml — the cell styles. Each style has a numFmtId that points at a number format. Date cells have a numFmtId in the date format range (14-22, 27-36, 45-47, 50-58 for built-in formats).
  • xl/worksheets/sheet1.xml, sheet2.xml, sheet3.xml, and so on — the worksheets. Each one has a <sheetData> element containing <row> elements, each containing <c> (cell) elements. Each cell has a reference like A1, a type attribute like s for shared string or b for boolean, and a value element <v> containing the value.

A converter that wants the first sheet reads sheet1.xml. A converter that wants a different sheet reads sheetN.xml where N is the sheet's position in workbook.xml. Most browser-based converters read only the first sheet. The multi-sheet case is the most common reason a conversion comes out wrong.

Why shared strings exist, and what happens if you skip them

Excel deduplicates strings. If a worksheet has the word "Approved" in 500 cells, the XLSX file does not store "Approved" 500 times. It stores "Approved" once in sharedStrings.xml, and each of the 500 cells stores the index 0 (assuming "Approved" is the first string). This cuts file size dramatically for worksheets with repeated values like status columns, category labels, or country names.

A cell with a shared string has t="s" (type = shared) and a <v> element containing the integer index. The converter has to look up the index in the shared strings table to get the actual text. A converter that does not read sharedStrings.xml will produce a column of integers — 0, 0, 0, 0 — where the strings should be. This is the single most common bug in a hand-rolled XLSX parser.

The fix is to read sharedStrings.xml first, build an array of strings indexed by position, and look up each t="s" cell's <v> value in that array. The Excel to CSV Converter does this. The lookup is the conversion.

There is also an inlineStr type (t="inlineStr") where the string is stored directly in the cell inside an <is> element, not in the shared table. Excel uses this rarely, but a converter has to handle it, or those cells come out empty.

Why formulas are not computed, and what that means for the output

A formula cell in XLSX has a <f> element with the formula and a <v> element with the cached value. The cached value is whatever Excel computed the last time the file was saved. The converter does not compute the formula. It reads the cached value.

This means three things:

  • A formula that was just typed and never opened in Excel has no cached value. The cell comes out empty. This happens when a spreadsheet is generated programmatically (by a Python script, by a database export, by a reporting tool) without Excel being opened to recalculate.
  • A formula with a volatile function like TODAY(), NOW(), or RAND() will show the value from when the file was last saved, not the current value. A spreadsheet that was saved last week and converted today will show last week's date in TODAY() cells.
  • A formula that depends on cells that have changed since the last save will show the stale cached value, not the new computed value. This is a silent corruption — the CSV looks correct, but the numbers are wrong.

There is no fix in the converter. Computing formulas requires a formula engine, which is a spreadsheet application, which is what the converter is trying not to be. If you have a formula-driven sheet and you need the current values, open the file in Excel or LibreOffice, recalculate (Ctrl+Alt+F9 in Excel), save, and then convert.

Why the 1900 leap year bug exists, and how date detection works

Excel stores dates as serial numbers. January 1, 1900 is serial 1. January 2, 1900 is serial 2. Each day adds 1. January 1, 2026 is serial 46023. The date is stored as a number, and the date formatting (the YYYY-MM-DD display) is stored in the cell's style, not in the value.

The catch is the 1900 leap year bug. Excel inherited from Lotus 1-2-3 the assumption that 1900 is a leap year. It is not. 1900 is divisible by 100 and not by 400, so it is not a leap year. Excel treats February 29, 1900 as a real day, which means Excel's serial numbers are off by one for every date after February 28, 1900. Serial 60 is February 29, 1900 (which does not exist), and serial 61 is March 1, 1900 (which should be serial 60).

A converter that does not account for this will produce dates one day off for everything after February 1900. The fix is to subtract one day from any serial greater than 60. The Excel to CSV Converter does this. The Excel epoch is December 30, 1899 (so that serial 1 is January 1, 1900), and the conversion is new Date(Date.UTC(1899, 11, 30) + (serial > 60 ? serial - 1 : serial) * 86400000).

Date detection is the other half. A numeric cell is a date only if its style has a date number format. The built-in date format IDs are 14-22, 27-36, 45-47, and 50-58. A converter reads the cell's style index, looks up the numFmtId in styles.xml, and checks whether the ID is in one of those ranges. If it is, the cell is a date and the serial is converted. If it is not, the cell is a number and the serial is output as-is.

Custom date formats — the ones a user creates in Excel's "Format Cells" dialog — have IDs of 164 and up. A converter that only checks the built-in ranges will miss these and output the serial as a number. This is the second most common date bug, and there is no clean fix without parsing the custom format string to see if it contains date tokens (Y, M, D, H, S). The Excel to CSV Converter does not parse custom formats. If your dates come out as five-digit numbers, you have a custom format. Open the file in Excel, apply a built-in format (like YYYY-MM-DD), save, and convert.

Why the time component is usually lost

Excel serials can have a fractional part. The integer part is the day, the fractional part is the time. Noon on January 1, 2026 is 46023.5. Six PM is 46023.75.

Most converters, including the Excel to CSV Converter, output date-only format (YYYY-MM-DD) and discard the fractional part. The time is in the serial, but the converter's date formatter uses UTC date getters that do not include the time. A cell with January 1, 2026 at 14:30 comes out as "2026-01-01" with no time.

This is a deliberate simplification. CSV has no standard time format. ISO 8601 has one (YYYY-MM-DDTHH:MM:SS), but most consumers of the CSV expect date-only. If your data has time components that matter, you will need a different tool, or you will need to write the time yourself by extracting the fractional part of the serial and converting it to hours, minutes, seconds.

Why only the first sheet is converted by default

A multi-sheet workbook has sheet1.xml, sheet2.xml, sheet3.xml, and so on. The order and names are in workbook.xml. A converter that wants to support multi-sheet has to read workbook.xml, list the sheets, let the user pick one, and parse the corresponding sheetN.xml. Most browser-based converters, including the Excel to CSV Converter, do not do this. They read sheet1.xml and stop.

If your workbook has data on the second sheet, that data will not be in the CSV. The fix is to split the workbook into single-sheet files first, or to use a converter that supports sheet selection. LibreOffice on the command line (libreoffice --headless --convert-to csv --sheet 2 file.xlsx) handles multi-sheet workbooks. Python with openpyxl or pandas does too, with explicit sheet name selection.

Gotchas

  • Only the first sheet is converted. The tool reads sheet1.xml and stops. Data on other sheets is not in the output. If your workbook has multiple sheets, split it first or use a converter that supports sheet selection.
  • Formulas are not computed. The tool reads the cached value in the <v> element, not the formula in the <f> element. A freshly inserted formula that was never opened in Excel has no cached value and comes out empty. Volatile functions like TODAY() and RAND() show stale values. If your sheet is formula-driven, open it in Excel, recalculate, save, then convert.
  • Custom date formats are not detected. The tool checks the built-in date format IDs (14-22, 27-36, 45-47, 50-58). Custom formats (IDs 164 and up) are not recognized, and dates with custom formats come out as five-digit serial numbers. Apply a built-in date format in Excel before converting.
  • The time component is lost. The tool outputs date-only YYYY-MM-DD and discards the fractional part of the serial. A cell with January 1, 2026 at 14:30 comes out as "2026-01-01" with no time. If you need the time, use a different tool or extract it manually.
  • The 1900 leap year bug is handled, but only for serials above 60. Dates from January 1 to February 28, 1900 are correct. The non-existent February 29, 1900 (serial 60) will be produced if it is in the file, because the adjustment only kicks in for serials above 60. This is almost never a real problem because no real data has dates in February 1900.
  • No UTF-8 BOM in the output. The CSV is encoded as UTF-8 but has no byte-order mark. Excel on Windows may misinterpret UTF-8 characters without a BOM, showing accented characters and CJK text as garbage. If you are opening the CSV in Excel on Windows, open it in a text editor first, save with BOM, or import it through Excel's Data → From Text wizard.
  • No CRLF line endings. The output uses LF (Unix line endings). Most CSV consumers accept this, but some older Windows tools expect CRLF. If your tool rejects the file, convert the line endings with a text editor or unix2dos.
  • No file size limit. The entire XLSX file is read into memory and the entire XML is parsed into a DOM. A 100 MB XLSX file can freeze the tab. For large files, use a command-line converter.
  • The first row is treated as the header. There is no option to skip header rows or treat all rows as data. If your sheet has no header, the first data row will be treated as the header in the preview, but the CSV output will still contain it.
  • Error cells are not handled. A cell with t="e" (error) like #REF! or #DIV/0! is not explicitly handled. The output will contain whatever is in the <v> element, which for error cells usually contains the error string. This may work by accident, but it is not intentional.
  • Boolean cells are output as TRUE and FALSE. A boolean cell (t="b") with value 1 becomes "TRUE" and 0 becomes "FALSE" (uppercase). Some downstream consumers expect lowercase true and false. Check before you trust the output.
  • Charts, images, comments, hyperlinks, named ranges, pivot tables, and conditional formatting are not extracted. The CSV has the cell values and nothing else. If you need the chart, you need Excel.
  • Legacy .xls binary files are not supported. The input is .xlsx only. A .xls file (the pre-2007 binary format) cannot be parsed by a JSZip-based converter, because it is not a ZIP. Use LibreOffice or a dedicated .xls parser for those files.
  • No multi-sheet CSV output. Even if the tool supported sheet selection, CSV has no concept of multiple sheets. A multi-sheet workbook has to be converted to multiple CSV files, one per sheet.

Summary

  • An XLSX file is a ZIP archive of XML files in the Office Open XML format. The cell values are in xl/worksheets/sheetN.xml, the deduplicated strings are in xl/sharedStrings.xml, the styles and number formats are in xl/styles.xml, and the sheet list is in xl/workbook.xml. A converter unzips the archive, reads these files, resolves the shared strings and the styles, builds a grid, and emits CSV.
  • The hard parts are the shared strings table (skip it and you get a column of integers), the formula cache (the converter reads cached values, not computed results), the 1900 leap year bug (subtract a day for serials above 60), the date detection (only built-in format IDs are recognized, not custom formats), the time component (usually discarded, output is date-only), and the multi-sheet problem (only the first sheet is converted by default).
  • Use the Excel to CSV Converter for single-sheet workbooks with cached values and built-in date formats. It runs in your browser and does not upload the file. For multi-sheet workbooks, formula-driven sheets, custom date formats, or large files, use LibreOffice on the command line or Python with openpyxl or pandas. The CSV to XLSX Converter handles the reverse direction, the CSV to Markdown Table Converter converts the CSV to a Markdown table for documentation, and the JSON to CSV Converter handles the JSON-to-CSV case.