Fix "Sheet Already Exists" Errors in pandas ExcelWriter
A common reporting job updates one sheet of an existing workbook — refreshes the Summary sheet every morning while the rest of the file, with its notes and charts, stays as it is. The first attempt is usually pd.ExcelWriter(path, mode="a"), and the second run fails with:
ValueError: Sheet 'Summary' already exists and if_sheet_exists is set to 'error'.
The message points at the fix — the if_sheet_exists argument — but its four values behave quite differently, and one of them, overlay, quietly leaves old data in the sheet. There is also a closely related error, Append mode is not supported with xlsxwriter!, which appears when the engine is not specified. This page reproduces both errors, shows exactly what each option does to a sheet, and settles on a safe way to refresh one sheet in a workbook. It belongs to Troubleshooting Common Python Excel Errors.
Prerequisites
pip install pandas openpyxl xlsxwriter
The examples were run with pandas 3. The behaviour described has been the same since pandas 1.4, when overlay was added.
Step 1 — Reproduce the errors
import pandas as pd
from openpyxl import load_workbook
def make_report():
with pd.ExcelWriter("report.xlsx", engine="openpyxl") as xw:
pd.DataFrame({"Region": ["North", "South", "East", "West"], "Sales": [120, 95, 80, 60]}).to_excel(xw, sheet_name="Summary", index=False)
pd.DataFrame({"Note": ["Figures exclude returns."]}).to_excel(xw, sheet_name="Notes", index=False)
wb = load_workbook("report.xlsx")
wb["Summary"]["D1"] = "Checked by: AH"
wb.save("report.xlsx")
new_summary = pd.DataFrame({"Region": ["North", "South"], "Sales": [131, 88]})
make_report()
for kwargs in [{}, {"engine": "openpyxl"}]:
try:
with pd.ExcelWriter("report.xlsx", mode="a", **kwargs) as xw:
new_summary.to_excel(xw, sheet_name="Summary", index=False)
except ValueError as e:
print(f"{str(kwargs):<22} ValueError: {e}")
The first attempt fails before even looking at the sheet: without an engine argument pandas picks XlsxWriter for .xlsx files whenever it is installed, and XlsxWriter can only create new files, so append mode is refused. With engine="openpyxl" the file is opened, and the error is the one in the title: the default for if_sheet_exists is "error", which refuses to touch a sheet that is already there. That default is deliberate — pandas will not guess whether you want to keep, replace or merge with the existing sheet.
Notice when the error appears. A job written with mode="a" often works the first time it runs against a fresh template that has no Summary sheet yet, and fails on the second run, when the sheet it created the day before is in the way. That is why the error tends to surface in production rather than in testing: test the job by running it twice against the same file.
Step 2 — What each option does
Run the same append once for each value and look at the workbook afterwards:
for option in ["new", "replace", "overlay"]:
make_report()
with pd.ExcelWriter("report.xlsx", mode="a", engine="openpyxl", if_sheet_exists=option) as xw:
new_summary.to_excel(xw, sheet_name="Summary", index=False)
wb = load_workbook("report.xlsx")
ws = wb["Summary"]
regions = [c.value for c in ws["A"]]
print(f"{option:<8} sheets={wb.sheetnames} Summary column A={regions} D1={ws['D1'].value!r}")
new leaves Summary untouched and writes the data to a new sheet called Summary1 — rarely what a refresh wants, and each run adds another sheet. replace deletes the old Summary sheet and writes a fresh one in the same position: the data is exactly the new DataFrame, but everything else on the sheet is gone too, including the Checked by note in D1, any formatting, column widths and charts placed on that sheet. overlay writes the new data into the existing sheet cell by cell, leaving everything it does not overwrite — which keeps the note in D1, but also keeps East and West with their old figures in rows 4 and 5, because the new data has only two rows. The sheet now mixes this morning's data with yesterday's, with nothing to show which is which.
Step 3 — Refresh a sheet safely
Which option is right depends on what else is on the sheet. If the sheet holds only data, replace is correct and simple. If it holds things you want to keep — notes, formatting, a chart over the data — use overlay, but clear the old data area first so no stale rows survive:
def refresh_sheet(path, sheet, df, keep_cols=None):
wb = load_workbook(path)
ws = wb[sheet]
last_col = keep_cols or len(df.columns)
for row in ws.iter_rows(min_row=1, max_row=ws.max_row, max_col=last_col):
for cell in row:
cell.value = None
wb.save(path)
with pd.ExcelWriter(path, mode="a", engine="openpyxl", if_sheet_exists="overlay") as xw:
df.to_excel(xw, sheet_name=sheet, index=False)
make_report()
refresh_sheet("report.xlsx", "Summary", new_summary)
ws = load_workbook("report.xlsx")["Summary"]
print([c.value for c in ws["A"]], [c.value for c in ws["B"]], "| D1:", ws["D1"].value)
Clearing the values in the data columns — and only those, here A and B — removes yesterday's rows while leaving the note in D1, cell formatting and anything in other columns in place; overlay then writes the new data on top. The result has exactly the two new rows and keeps the note. If the data has a fixed layout that people format by hand — a header style, number formats — clearing values rather than deleting cells is what preserves it. When the whole report is generated by code, the simplest robust alternative is to regenerate the entire workbook each time rather than patching one sheet; Append a DataFrame to an Existing Excel File with pandas covers the related case of adding rows below existing data.
What append mode keeps — and what it cannot
Append mode with openpyxl loads the whole workbook, changes the sheet you write to, and saves everything again. Most content survives the round trip: other sheets, their values and formatting, defined names, data validation and conditional formatting. Some does not, because openpyxl does not model it fully: charts and images are read back but can lose formatting details, shapes and text boxes are dropped, pivot tables are kept only partially, slicers and form controls are lost, and a macro-enabled .xlsm loses its VBA unless loaded with keep_vba=True, which pandas does not do. If the workbook contains such things, append mode is the wrong tool; either keep the generated data in a separate workbook that the main one links to, or drive Excel itself with xlwings. A quick test is to append to a copy of the real workbook and open both side by side before putting the job into production.
Checking before you write
Two further habits make append-mode jobs predictable. First, look before writing: load_workbook(path, read_only=True).sheetnames lists the sheets cheaply, so the job can decide between creating a sheet and refreshing it, or refuse with a clear message if the workbook is not the one it expects. Second, remember that mode="a" requires the file to exist; a job that must work on its first ever run should create the workbook with mode="w" when the file is missing and switch to append mode afterwards, rather than catching the exception. And because openpyxl rewrites the whole file when it saves, an append is not cheaper than regenerating the file for small workbooks — it is the right tool when the workbook contains things your code did not create and should not lose.
Common pitfalls
- Leaving out
engine="openpyxl". pandas picks XlsxWriter, which cannot append. overlaywith fewer rows than before. Old rows remain; clear the data area first.replaceon a formatted sheet. It deletes notes, formats and charts on that sheet.newin a scheduled job. Each run adds Summary1, Summary2, …- A file that does not exist yet. Append mode needs an existing file; create it with
mode="w"first.
Conclusion
The "Sheet already exists" error comes from if_sheet_exists defaulting to "error" in append mode. Open the writer with mode="a" and engine="openpyxl", then choose deliberately: replace when the sheet holds only data, or clear the old data values and use overlay when the sheet holds content worth keeping. Avoid new in recurring jobs, and remember that overlay alone leaves old rows behind whenever the new data is shorter.
Frequently asked questions
How do I fix "Sheet already exists and if_sheet_exists is set to 'error'"? Pass if_sheet_exists="replace" or "overlay" to pd.ExcelWriter together with mode="a" and engine="openpyxl", choosing based on what else is on the sheet.
What is the difference between replace and overlay in pandas ExcelWriter? replace deletes the sheet and writes a new one; overlay writes over the existing cells and leaves anything it does not overwrite, including old rows.
Why do I get "Append mode is not supported with xlsxwriter"? pandas chose the XlsxWriter engine, which only creates new files; pass engine="openpyxl" when using mode="a".
Why does my refreshed sheet still contain old rows? overlay only writes the cells covered by the new data; clear the old data area first or use replace.
Related
- Up one level: Troubleshooting Common Python Excel Errors — fixes for common errors.
- Write Multiple DataFrames to One Excel File — several sheets in one workbook.
- Fix "ExcelWriter Object Has No Attribute save" — another ExcelWriter error.
- Fix XlsxWriter Overwriting an Existing Workbook — the other side of the engine choice.
More on errors & fixes
- FixHandle Permission Denied When Writing Excel in Python
- How-toWhat openpyxl Loses When You Save an Existing Workbook
- FixFix Excel Serial Numbers Showing Instead of Dates
- How-toHandle Daylight Saving Time in Excel Timestamps
- How-toHandle Microsoft Graph Throttling in Excel Automation
- GuideError Handling and Logging in Excel Automation