csvjson
·8 min read

How to Convert Excel to CSV Without Opening Excel

Convert .xlsx files to CSV on any OS — Python, PowerShell, LibreOffice CLI, and a browser tool. Covers batch conversion, multi-sheet files, and encoding issues.

How to Convert Excel to CSV Without Opening Excel

Opening Excel just to save a file as CSV is a tax on your time — especially when you have dozens of files, you're on a server without a GUI, or you simply don't have Excel installed. There are faster paths.

This guide covers four methods: Python (the most portable), PowerShell (Windows-native, no installs), LibreOffice CLI (Linux/macOS), and a browser-based converter for one-off files. Each section includes the exact commands, the edge cases to watch for, and when to pick that method over the others.


Why not just "Save As CSV" in Excel?

If you have Excel open anyway, File → Save As → CSV works fine. But it has limitations:

  • One sheet at a time — Excel only exports the active sheet. Multi-sheet workbooks require you to repeat the process for each sheet.
  • Format loss warnings — Excel shows a dialog every time about features not supported in CSV.
  • Can't automate — you can't script "Save As CSV" without Excel macros or COM automation.
  • Not available on Linux or macOS without Office — Excel is a Windows/macOS application and requires a license.

The methods below sidestep all of that.


pandas is the fastest path to a working script, and it runs on Windows, macOS, and Linux.

Install

pip install pandas openpyxl

openpyxl is the Excel reading engine; pandas requires it for .xlsx files.

Single file, single sheet

import pandas as pd

df = pd.read_excel("report.xlsx", sheet_name=0)
df.to_csv("report.csv", index=False)

sheet_name=0 reads the first sheet. index=False omits the pandas row index from the CSV output — you almost always want this.

Single file, specific sheet by name

df = pd.read_excel("report.xlsx", sheet_name="Sales")
df.to_csv("sales.csv", index=False)

Export all sheets from one workbook

import pandas as pd

xl = pd.ExcelFile("report.xlsx")

for sheet in xl.sheet_names:
    df = xl.parse(sheet)
    safe_name = sheet.replace(" ", "_").replace("/", "-")
    df.to_csv(f"{safe_name}.csv", index=False)

Each sheet becomes its own CSV file, named after the sheet.

Batch convert an entire folder

import pandas as pd
from pathlib import Path

input_dir = Path("excel_files")
output_dir = Path("csv_output")
output_dir.mkdir(exist_ok=True)

for xlsx_path in input_dir.glob("*.xlsx"):
    df = pd.read_excel(xlsx_path, sheet_name=0)
    out_path = output_dir / xlsx_path.with_suffix(".csv").name
    df.to_csv(out_path, index=False)
    print(f"Converted: {xlsx_path.name} → {out_path.name}")

Run this from any directory and it converts every .xlsx in excel_files/ to a matching .csv in csv_output/.

Encoding

to_csv defaults to UTF-8 encoding, which is what most downstream tools expect. If the recipient opens the CSV in Excel on Windows and sees garbled characters, add encoding="utf-8-sig" — this writes a UTF-8 BOM that Excel on Windows needs to detect encoding correctly:

df.to_csv("report.csv", index=False, encoding="utf-8-sig")

Date formatting

By default, pandas serializes dates as "2024-01-14 00:00:00" — the full datetime string even for date-only columns. To get cleaner ISO dates:

# Option 1: format at write time
df.to_csv("report.csv", index=False, date_format="%Y-%m-%d")

# Option 2: convert the column first
df["joined"] = df["joined"].dt.strftime("%Y-%m-%d")
df.to_csv("report.csv", index=False)

Where pandas falls short

  • Merged cells — only the top-left cell has a value; others are NaN. You'll get blank cells in the CSV where merged cells span multiple rows or columns.
  • Formulas — pandas reads the last-computed value, not the formula. Usually correct, but stale caches cause silent errors.
  • .xls (old format) — requires xlrd instead of openpyxl. Install pip install xlrd and pass engine="xlrd".

Method 2: Python with openpyxl (more control)

Use openpyxl directly when you need to handle merged cells, read cell formatting, or access metadata that pandas abstracts away.

Install

pip install openpyxl

Basic conversion

from openpyxl import load_workbook
import csv

wb = load_workbook("report.xlsx", data_only=True)
ws = wb.active

with open("report.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    for row in ws.iter_rows(values_only=True):
        writer.writerow(row)

data_only=True returns the last computed value for formula cells instead of the formula string.

Handling merged cells

In openpyxl, merged cells beyond the top-left return None. If a merged cell spans rows (like a category label), you'll get blank cells in the CSV unless you forward-fill:

from openpyxl import load_workbook
from openpyxl.utils import range_boundaries
import csv

wb = load_workbook("report.xlsx", data_only=True)
ws = wb.active

# Unmerge: copy the top-left value into all cells of the merged range
for merged_range in list(ws.merged_cells.ranges):
    top_left = ws.cell(merged_range.min_row, merged_range.min_col).value
    ws.unmerge_cells(str(merged_range))
    for row in ws.iter_rows(
        min_row=merged_range.min_row, max_row=merged_range.max_row,
        min_col=merged_range.min_col, max_col=merged_range.max_col
    ):
        for cell in row:
            cell.value = top_left

with open("report.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    for row in ws.iter_rows(values_only=True):
        writer.writerow(row)

Method 3: PowerShell (Windows, no installs)

On Windows, PowerShell can open an Excel COM object without displaying the Excel window, convert the file, and close — no Excel license dialog, no GUI. This requires Excel to be installed, but doesn't require you to open it manually.

param(
    [string]$InputPath = "report.xlsx",
    [string]$OutputPath = "report.csv"
)

$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $false

$workbook = $excel.Workbooks.Open((Resolve-Path $InputPath).Path)
$worksheet = $workbook.Sheets.Item(1)

$workbook.SaveAs(
    (Join-Path (Get-Location) $OutputPath),
    6  # xlCSV format code
)

$workbook.Close($false)
$excel.Quit()
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($excel) | Out-Null

Save as excel-to-csv.ps1 and run:

.\excel-to-csv.ps1 -InputPath "report.xlsx" -OutputPath "report.csv"

Batch convert with PowerShell

$excel = New-Object -ComObject Excel.Application
$excel.Visible = $false
$excel.DisplayAlerts = $false

Get-ChildItem ".\excel_files\*.xlsx" | ForEach-Object {
    $wb = $excel.Workbooks.Open($_.FullName)
    $outPath = Join-Path ".\csv_output" ($_.BaseName + ".csv")
    $wb.SaveAs($outPath, 6)
    $wb.Close($false)
    Write-Host "Converted: $($_.Name)"
}

$excel.Quit()

Caveats

  • Requires Excel installed — the COM object is part of the Excel installation. Without Excel, this script won't run.
  • Format code 6 is xlCSV. For UTF-8 CSV use format code 62 (xlCSVUTF8) — available in Excel 2016+.
  • Only exports the first sheet — to export all sheets, loop over $workbook.Sheets.
  • Not available on Linux or macOS — COM automation is Windows-only.

Method 4: LibreOffice CLI (Linux and macOS)

LibreOffice ships with a headless mode that converts Office files without opening a window. It's available on Linux (via package manager) and macOS (via the installer or Homebrew).

Install

# Ubuntu / Debian
sudo apt install libreoffice

# macOS via Homebrew
brew install --cask libreoffice

Convert a single file

libreoffice --headless --convert-to csv report.xlsx

This produces report.csv in the current directory.

Specify output directory

libreoffice --headless --convert-to csv --outdir ./csv_output report.xlsx

Batch convert

libreoffice --headless --convert-to csv --outdir ./csv_output excel_files/*.xlsx

LibreOffice processes all matching files in one pass — faster than looping in shell.

Encoding and delimiter

The default output is UTF-8 with comma delimiter. To customize (e.g. semicolons for European locale):

libreoffice --headless --convert-to "csv:Text - txt - csv (StarCalc):44,34,76,1,,0,false,true,false,false,false,-1" report.xlsx

The filter string parameters map to: field_delimiter(44=comma),text_delimiter(34=quote),charset(76=UTF-8),first_row,…. Change 44 to 59 for semicolon.

Caveats

  • Only exports the first sheet by default. To export all sheets, use a LibreOffice macro or convert each sheet with a loop.
  • Date formatting matches LibreOffice's locale settings, which may differ from Excel's. Verify date columns in the output.
  • Large files (100k+ rows) are slower than pandas — LibreOffice loads the full spreadsheet rendering engine.

Method 5: Browser tool (no setup, one-off files)

For a single file when you don't want to write code or install anything, csvjson.tools Excel to CSV converts in the browser:

  1. Upload your .xlsx file
  2. Select the sheet if the workbook has multiple sheets
  3. Download the CSV

The conversion runs client-side — your file is never uploaded to a server. Numbers, booleans, and dates are handled correctly: ISO date strings, not Excel serial numbers.

Also useful as a sanity check before automating — paste the output into CSV Viewer to spot encoding or delimiter issues before writing a script.


Comparison

| Method | OS | Excel required | Batch support | Merged cells | Speed | |---|---|---|---|---|---| | pandas | Any | No | Yes | NaN (needs fill) | Fast | | openpyxl | Any | No | Yes | Manual fill | Moderate | | PowerShell COM | Windows only | Yes | Yes | Yes (Excel handles) | Moderate | | LibreOffice CLI | Linux/macOS | No | Yes | Partial | Slow on large files | | Browser tool | Any | No | No | Yes | Instant |


Common problems

Output CSV has garbled characters

The file likely contains non-ASCII characters (accented letters, CJK, symbols). The CSV is probably being opened in Excel on Windows, which defaults to the system code page (Windows-1252) rather than UTF-8.

Fix: write UTF-8 with BOM (encoding="utf-8-sig" in pandas, or format code 62 in the PowerShell COM script). Excel on Windows recognizes the BOM and reads UTF-8 correctly.

Date columns show as numbers (e.g. 45305)

The converter is emitting the raw Excel date serial number. In pandas, date columns should be inferred automatically if the cells are formatted as Date in Excel. If not, use parse_dates=["column_name"] or convert after reading: df["date_col"] = pd.to_datetime(df["date_col"]).dt.strftime("%Y-%m-%d").

Empty rows in the CSV

Blank rows in the spreadsheet pass through to the CSV. In pandas, remove them with df.dropna(how="all") before calling to_csv. In openpyxl, check all(v is None for v in row) before writing each row.

Only the first sheet was exported

pandas sheet_name=0 reads the first sheet only. Use sheet_name=None to get a dict of all sheets, or loop over xl.sheet_names. LibreOffice CLI also exports only the first sheet by default — use a macro or script to loop over sheets.


  • Excel to CSV — upload an xlsx and download a clean CSV in one click
  • Excel to JSON — convert to JSON instead of CSV, with full type preservation
  • CSV Cleaner — fix encoding issues, inconsistent columns, and stray quotes after conversion
  • CSV to JSON — take your new CSV further and convert to JSON for APIs or code