Skip to the tool workspace
Browser 123

How-to Data and documents

How to convert CSV to Excel without breaking the data

Turn a CSV into an .xlsx that keeps leading zeros, long ID numbers and real dates: one pandas script or one LibreOffice command with each column given its type, or a browser converter that keeps them without being asked, and what a converter does when it guesses.

A CSV is text with commas in it. It has no column types: nothing in the file says that 00412 is a customer number and not the number 412, or that 2026-03-04 is a date. Every program that turns a CSV into a spreadsheet guesses, and for IDs, postcodes, phone numbers and long reference numbers the guess is wrong. Converting without breaking the data means making those decisions yourself, column by column.

The CSV to XLSX Converter after a run: sales.csv converted, with sales.xlsx, its size and a Download button

A CSV converted to an Excel workbook in the browser.

The direct waypandasmacOS · Linux · Windows

Read every column as text, then convert only the columns that really are numbers or dates:

import pandas as pd

df = pd.read_csv("customers.csv", dtype=str, keep_default_na=False)
df["joined"] = pd.to_datetime(df["joined"], format="%Y-%m-%d").dt.date
df["balance"] = pd.to_numeric(df["balance"])

df.to_excel("customers.xlsx", index=False)

It needs pandas and openpyxl (on Debian and Ubuntu, sudo apt install python3-pandas python3-openpyxl). Opened again with openpyxl, the result has 00412 and the 19-digit order numbers as text cells, joined as a date formatted YYYY-MM-DD, and balance as a number.

What the guessing does

The test file is three customers, written the way a CRM export writes them (UTF-8, comma-separated):

customer_id,name,postcode,phone,order_ref,joined,balance
00412,Zoë Müller,02134,0612345678,7310000012345678901,2026-03-04,1234.50
00977,Łukasz Nowak,08001,0031201234567,7310000012345678902,2026-11-12,87.05
01500,Søren Ågård,2100,+4512345678,7310000012345678903,2026-01-31,-15.00

Each converter's .xlsx was opened again to read the type and value of every cell. The first data row:

Column In the CSV LibreOffice, no options pandas, typed CSV to XLSX (browser)
customer_id 00412 412 00412, text 00412, text
postcode 02134 2134 02134, text 02134, text
phone 0612345678 612345678 0612345678, text 0612345678, text
order_ref 7310000012345678901 7.31000001234568e+18 7310000012345678901, text 7310000012345678901, text
joined 2026-03-04 2026-03-04, a date 2026-03-04, a date 2026-03-04, a date
balance 1234.50 1234.5 1234.5 1234.5

The order numbers are the worst case. Read as numbers, all three rows become the same value, 7310000012345679000, because a spreadsheet number holds about 15 significant digits and these have 19. LibreOffice's 7.31000001234568e+18 is that value. Microsoft's own page on keeping leading zeros and large numbers gives the same limit for Excel: 15 digits, and anything longer must be text.

The browser converter's date is the date serial 46085 with the number format yyyy-mm-dd. It is worked out from the digits alone, so the time zone doesn't come into it: we got exactly 46085 with the computer set to New York.

There is one difference between pandas and the browser converter that the first row doesn't show. pandas was told that postcode is a text column. The browser converter decides cell by cell, so Søren's postcode 2100, which has no leading zero, became the number 2100, in a column where the other two are text. Sorting and lookups treat the two kinds differently. When a column has to be text all the way down, say so in pandas or LibreOffice.

Convert with pandas

The script above is the whole job for one file. Reading with dtype=str keeps every value exactly as it was written; keep_default_na=False stops pandas turning empty cells and words like NA into missing values. Then convert the columns that are numbers or dates on purpose.

Dates need .dt.date. Without it pandas writes a date and time (format YYYY-MM-DD HH:MM:SS), and with openpyxl as the writer the date_format option of ExcelWriter was ignored in our test (pandas 2.2.3).

If you would rather name the text columns and let pandas read the rest, pass a dictionary. This version also handles a European export, with semicolons between fields and a comma as the decimal mark:

import pandas as pd

df = pd.read_csv("customers-semicolon.csv", sep=";", decimal=",",
                 keep_default_na=False,
                 dtype={"customer_id": str, "postcode": str,
                        "phone": str, "order_ref": str})
df["joined"] = pd.to_datetime(df["joined"], format="%Y-%m-%d").dt.date
df.to_excel("customers-eu.xlsx", index=False)

1234,50 comes out as the number 1234.5. Three more things the file may need:

  • Day-first dates (04/03/2026 for 4 March): format="%d/%m/%Y". An explicit format is the only safe way. Guessed, that date is 3 April.
  • A Windows export in the old Western code page: encoding="cp1252". Without it, pandas stops with UnicodeDecodeError rather than writing broken accents, which is the better failure.
  • A byte order mark at the start of the file: nothing to do. pandas 2.2.3 skipped it and read the first header as customer_id.

A folder of exports

from pathlib import Path
import pandas as pd

TEXT = {"customer_id": str, "postcode": str, "phone": str, "order_ref": str}

for csv in Path("exports").glob("*.csv"):
    df = pd.read_csv(csv, dtype=TEXT, keep_default_na=False)
    df["joined"] = pd.to_datetime(df["joined"], format="%Y-%m-%d").dt.date
    df.to_excel(csv.with_suffix(".xlsx"), index=False)
    print(csv.with_suffix(".xlsx"), len(df), "rows")
exports/february.xlsx 3 rows
exports/january.xlsx 3 rows

Convert with LibreOffice

LibreOffice converts from the command line without opening a window, and its CSV filter takes the column types as a string of numbers:

soffice --headless \
  --infilter="Text - txt - csv (StarCalc):44,34,76,1,1/2/2/2/3/2/4/2/5/2/6/5/7/1,1033" \
  --convert-to xlsx customers.csv
convert /w/customers.csv as a Calc document -> /w/customers.xlsx using filter : Calc Office Open XML

The string, token by token:

Token Here Means
1 44 Field separator, as a character code: 44 comma, 59 semicolon, 9 tab
2 34 Text is quoted with "
3 76 Character set UTF-8. Use 1 for a Windows-1252 file
4 1 Start at line 1
5 1/2/2/2/… Column/format pairs: 1 standard, 2 text, 4 DD/MM/YY, 5 YY/MM/DD
6 1033 Language used to read numbers: 1033 English (US), 1031 German for decimal commas

So columns 1 to 5 are text, column 6 is a year-month-day date and column 7 is read as a number. In the result, the IDs, postcode, phone and order numbers are text cells with Excel's text format (@), so they stay text if someone edits them later. LibreOffice's CSV filter options page lists the rest.

For the semicolon file with decimal commas, change two tokens: 59,34,76,1,1/2/2/2/3/2/4/2/5/2/6/5/7/1,1031. For day-first dates, give that column format 4 instead of 5. For a folder, pass a wildcard and an output directory:

soffice --headless \
  --infilter="Text - txt - csv (StarCalc):44,34,76,1,1/2/2/2/3/2/4/2/5/2/6/5/7/1,1033" \
  --convert-to xlsx --outdir out exports/*.csv

Without --infilter, LibreOffice guesses: it dropped the leading zeros, turned the order numbers into 7.31000001234568e+18, and put the semicolon file into one column, split again at each decimal comma. It did read the ISO dates as dates.

In the browser

The CSV to XLSX Converter types each cell by rules that never change a value: text that only looks like a number stays text, plain numbers become numbers, and YYYY-MM-DD becomes a date. It is the quick route for one file when you can't install anything.

  1. Drop the CSV on the page. It reads the encoding (a byte order mark, then UTF-8, then Windows-1252), the delimiter (comma, semicolon, tab or pipe: whichever gives the rows the same number of columns, not counting delimiters inside quotes, or the one a sep= first line names) and the decimal mark.
  2. Click the eye icon (Show preview) and look at the data before you convert. What you see is what the file will hold.
  3. If something was read wrong, open the file's settings (Show settings, or Open settings on a phone) and set Input delimiter, Decimal separator or Dates like 04/03/2026.
  4. Click Convert 1 File, then Download.
The Data Preview of customers.csv after converting it: customer IDs 00412, 00977 and 01500 with their leading zeros, accented names intact, postcodes 02134 and 08001, phone numbers with their leading 0 and +, and three different 19-digit order references in full

The preview shows what the workbook holds before you download it.

The workbook made from our test file holds what the preview shows: 00412, 02134, 0612345678 and the order numbers as text cells, Zoë Müller with its accents, the date as 46085 formatted yyyy-mm-dd, and 1234.5 as a number.

Two settings cover what a file can't say for itself:

  • Decimal separator. A semicolon file is read with decimal commas when its numbers look like 1234,50 and none look like 1234.50, so 1234,50 becomes 1234.5. A comma-separated file always uses a point. Set it by hand for a file that mixes the two.
  • Dates like 04/03/2026. Nothing in the file says whether that is 4 March or 3 April, so by default such dates stay text, exactly as written. Choose EU (DD/MM/YYYY) or US (MM/DD/YYYY) and they become real dates, shown as yyyy-mm-dd.

Which to use

  • One file, no install: the CSV to XLSX Converter, after a look at the preview. It keeps IDs, phone numbers and long references as text and reads ISO dates as dates without being told.
  • A column that must be one type all the way down (postcodes where some have no leading zero, a code that is sometimes all digits): pandas with dtype=str, or LibreOffice with a filter string. They type the column, not the cell.
  • Many files with the same columns: the pandas loop or soffice with a wildcard. Check one result before you send the batch.
  • A one-off file in Excel itself: Data › From Text/CSV, with types set per column. See why your CSV opened wrong.

Before you send the .xlsx on, open it and click a cell in each ID column: the leading zeros should be there, and the full number should show in the formula bar.

Questions people ask

Why did my ID numbers lose their leading zeros?

Because the converter decided the column was a number, and as a number 00412 is 412. A CSV has no column types, so every program guesses. Tell it the column is text: dtype=str in pandas, format code 2 in LibreOffice's filter string, or the Text type in Excel's import. Our CSV to XLSX Converter keeps any value with a leading zero as text without being told.

Why are my long numbers wrong, or shown as 7.31E+18?

Excel keeps 15 significant digits. An order number, parcel number or card number with 16 or more digits loses the rest the moment it becomes a number: in our test, three different 19-digit order numbers came out as the same value. Keep any column like that as text.

Can I convert a whole folder of CSVs at once?

Yes. LibreOffice takes a wildcard, soffice --headless --convert-to xlsx --outdir out exports/*.csv, with the same filter string for every file. The pandas loop in this guide does the same and prints the row count of each file it writes.

My CSV uses semicolons and decimal commas. What changes?

Tell the converter both. In pandas that is sep=";" and decimal=",". In LibreOffice the field separator becomes 59 and the language a decimal-comma one such as 1031 (German). Without that, 1234,50 can turn into 123450 or split into two columns. The browser converter detects both, and its Decimal separator setting overrides it.

Is the browser converter safe for customer data?

It reads the file in your browser and uploads nothing. It keeps IDs, phone numbers and long references as text and turns YYYY-MM-DD into real dates. Its limit is that it decides cell by cell: it can't be told that a whole column is text, so a postcode without a leading zero becomes a number. Check the preview before you download.

Tools in this guide

Everything runs on your device

All guides First published 18 Oct 2026

/
123 items
Runs in your browser·Your files stay on this device
Out0 B
Ready—