Guide
openpyxl vs xlsxwriter vs pandas.ExcelWriter
Understand how pandas.ExcelWriter wraps the openpyxl and xlsxwriter engines, when each one wins, and write the same DataFrame three ways with a clear decision table.
The three names look like competing libraries, but they sit at different layers. pandas.ExcelWriter is not an engine at all — it's a thin wrapper that delegates to one. The real engines are openpyxl and xlsxwriter, and they have opposite strengths: openpyxl can read and write existing files and preserves styles, while xlsxwriter is write-only but faster and richer at formatting and charts. This guide, part of Writing DataFrames to Excel with Pandas, clears up the relationship, gives you a decision table, and writes the same DataFrame three ways.
Prerequisites
Install pandas and both engines:
pip install pandas openpyxl xlsxwriter
How the pieces relate
df.to_excel(path) and pd.ExcelWriter(path) both produce a file by handing your data to an engine. When you don't name one, pandas picks a default (openpyxl for .xlsx). You choose explicitly with engine=:
- openpyxl — read + write. Opens an existing
.xlsx, edits cells in place, appends sheets, and keeps the styles already in the file. This is the only engine that can modify a workbook you already have. - xlsxwriter — write only. It builds a brand-new file fast and exposes the richest formatting, conditional formats, and native charts. It cannot open or read an existing workbook.
- pandas.ExcelWriter — the wrapper. It exposes
mode="a"(append) and abook/sheetshandle so you can reach the underlying engine object for formatting.
Decision table
| Need | openpyxl | xlsxwriter | plain to_excel |
|---|---|---|---|
| Read an existing file | Yes | No | n/a (write-only) |
| Edit a file in place | Yes | No | No |
Append a sheet (mode="a") | Yes | No | Uses openpyxl |
| Native charts | Limited | Yes (strong) | Whatever engine you pass |
| Conditional formatting | Limited | Yes | Via engine |
| Raw write speed on large files | Good | Fastest | Matches the engine |
| Preserve styles in source file | Yes | n/a | Only via openpyxl |
| Quick one-off dump | Overkill | Overkill | Best fit |
Write 1: plain to_excel for a quick dump
No engine argument, no formatting — the fastest way to get a DataFrame onto disk:
import pandas as pd
df = pd.DataFrame({
"Region": ["North", "South", "West"],
"Revenue": [12000, 9800, 15400],
})
df.to_excel("dump.xlsx", index=False) # defaults to openpyxl
print("wrote dump.xlsx")
Write 2: xlsxwriter with a cell format
Reach the workbook and worksheet objects through the ExcelWriter, then add a currency format. xlsxwriter shines here because formatting is first-class:
import pandas as pd
df = pd.DataFrame({
"Region": ["North", "South", "West"],
"Revenue": [12000, 9800, 15400],
})
with pd.ExcelWriter("styled.xlsx", engine="xlsxwriter") as writer:
df.to_excel(writer, sheet_name="Report", index=False)
workbook = writer.book
worksheet = writer.sheets["Report"]
money = workbook.add_format({"num_format": "$#,##0", "bold": True})
worksheet.set_column("B:B", 14, money) # format the Revenue column
print("wrote styled.xlsx")
Write 3: openpyxl direct, to edit an existing file
This is where openpyxl is the only option: load a file that already exists, change a cell, and save. xlsxwriter physically cannot do this because it never reads:
import pandas as pd
from openpyxl import load_workbook
# Start from a file written earlier
pd.DataFrame({"Region": ["North"], "Revenue": [12000]}).to_excel(
"ledger.xlsx", index=False
)
wb = load_workbook("ledger.xlsx") # read the existing workbook
ws = wb.active
ws["B2"].value = 13500 # edit a cell in place
ws["C1"] = "Adjusted" # add a column header
wb.save("ledger.xlsx")
print("updated:", load_workbook("ledger.xlsx").active["B2"].value)
Appending a sheet needs openpyxl
mode="a" opens the existing file to add to it, so it requires the read-capable engine. Passing engine="xlsxwriter" with mode="a" raises a ValueError:
import pandas as pd
pd.DataFrame({"Region": ["North"], "Revenue": [12000]}).to_excel(
"book.xlsx", sheet_name="Jan", index=False
)
with pd.ExcelWriter("book.xlsx", engine="openpyxl", mode="a") as writer:
pd.DataFrame({"Region": ["South"], "Revenue": [9800]}).to_excel(
writer, sheet_name="Feb", index=False
)
print(pd.ExcelFile("book.xlsx").sheet_names)
Common pitfalls
| Symptom | Cause | Fix |
|---|---|---|
ValueError: Append mode is not supported with xlsxwriter | xlsxwriter can't open existing files | Use engine="openpyxl" for mode="a" |
FileNotFoundError/empty file when "editing" | Tried to load an existing file with xlsxwriter | Use load_workbook (openpyxl); xlsxwriter only creates new files |
| Styles vanish after a pandas round-trip | Reading to a DataFrame keeps values, not formatting | Edit with openpyxl directly instead of read_excel then to_excel |
| Chart or conditional format ignored | Tried to add it through the wrong engine | Build new styled reports with xlsxwriter |
The styling-loss trap is the one that bites teams: pd.read_excel() returns only the data, so a read_excel → to_excel cycle silently drops every fill, border, and number format the source had. To keep formatting, never pass through a DataFrame — open the file with openpyxl and modify cells in place.
Performance and scale
For large write-only jobs, xlsxwriter is the fastest and supports constant_memory mode to stream rows without holding the whole sheet in RAM. openpyxl is competitive and adds a write_only=True mode for the same reason, but it pays a cost to preserve existing content. For a quick dump under a few thousand rows, the engine choice is irrelevant — use plain to_excel.
Frequently asked questions
Is pandas.ExcelWriter an engine?
No. It's a context-manager wrapper that delegates to openpyxl or xlsxwriter. You pick the real engine with engine=.
Which engine does to_excel use by default?
For .xlsx, pandas defaults to openpyxl when it's installed. Name engine="xlsxwriter" explicitly when you want xlsxwriter's formatting.
Can xlsxwriter edit an existing workbook? Never. It is write-only and creates a fresh file each run. Any in-place edit or append must go through openpyxl.
How do I add a chart to a pandas export?
Write with engine="xlsxwriter", grab writer.book and writer.sheets[...], then build the chart with xlsxwriter's chart API.
Conclusion
Think in layers: pandas.ExcelWriter is the wrapper, openpyxl and xlsxwriter are the engines. Reach for openpyxl when you must read, edit in place, or append to a file that already exists; reach for xlsxwriter to build new, heavily styled reports and charts from scratch; and use plain to_excel for a quick dump where formatting doesn't matter. The single rule that prevents most surprises: a pandas round-trip keeps data but loses styling, so edit existing files with openpyxl directly.
Where to go next
Return to Writing DataFrames to Excel with Pandas for the full export workflow. To drop the index column cleanly on any of these engines, see Write a Pandas DataFrame to Excel Without the Index. For the openpyxl append pattern in depth, read Append Data to an Existing Excel Sheet with openpyxl. To take styling further, explore Formatting and Charting Excel Reports with Python.