Surgical .xlsx editing that preserves formatting
Project description
xlsxedit
Surgical .xlsx editing for Python — change only what you ask for and leave styles, themes, charts, and other package parts intact.
Website: xlsxedit.jonasruilong.com
The problem
You have a styled Excel report: merged headers, brand colors, a chart, maybe images. A Python script opens it, updates the data, saves. Excel then warns the file is damaged, or the chart disappeared, or formatting shifted.
Most libraries rebuild the workbook from a Python object model. Anything that model does not know about is often dropped on save.
xlsxedit loads the file as an OPC package, patches targeted XML, and writes back — so templates survive.
Why xlsxedit
- Template fidelity — charts, drawings, themes, and unknown OOXML round-trip when you do not touch them
- Headless — no Excel app; runs on servers, CI, and cron jobs
- Fast file I/O — direct
.xlsxediting; ~20k rows/s bulk export in benchmarks - Search-and-replace —
replace,replace_image, typed placeholders - Bulk export —
write_dataframeinto a designed layout with row/column styles - Pandas — optional
engine="xlsxedit"forExcelWriterandread_excel(template-friendly I/O) - Familiar API —
Workbook.open→ mutate →save
Features
Workbook — open, create, save, sheetnames, add_worksheet, rename_worksheet, remove_worksheet, properties (title, author, created, …), orphan_partnames; opens .xlsm / .xltx too (VBA round-trips untouched)
Cells — typed value (str, int, float, bool, date, datetime), formula, clear, find / findall, offset, iter_cells, iter_rows
Styles — cell.style read (bold, colors, fonts, alignment, number format), apply_style, apply_date_format, apply_number_format
Layout — column_dimensions, row_dimensions, merge_cells, unmerge_cells, clear_range, write_rows, insert_rows, insert_columns
Links — cell.hyperlink.url, cell.hyperlink.location, cell.hyperlink.display
Images — add_image, images, Picture.replace, replace_image, insert_image_at_placeholder
Charts — add_chart, charts, read/set title, set_series_formula
Tables — add_table, tables, Table.resize
Conditional formatting — read blocks and rules; add_conditional_formatting (cellIs); add_color_scale_formatting
Search & replace — workbook/sheet replace (substring or whole-cell with value_type: text, number, date)
Bulk export — write_dataframe, write_rows, insert_rows, row_styles, column_styles; Workbook.open(..., large=True) for big files
Fidelity — round-trip tested on real fixtures: images, charts, tables, conditional formatting, merges, hyperlinks, custom sizes
Compared to other libraries
| xlsxedit | openpyxl | xlsxwriter | xlwings | |
|---|---|---|---|---|
| Open existing file | Yes (direct file) | Yes | No | Yes (launches Excel) |
| Template fidelity | Designed for preservation | Often loses styles/charts/unknown XML | N/A (create only) | Depends on Excel |
| Headless / server / CI | Yes | Yes | Yes | No |
| Typical speed | Fast direct I/O | Moderate | Fast (create) | Slow (Excel startup + COM) |
| Excel app required | No | No | No | Yes |
Install
pip install xlsxedit
pip install xlsxedit[pandas] # optional: ExcelWriter / read_excel engine
Development:
pip install -e ".[dev]"
Quick start — fill a template
from xlsxedit import Workbook
wb = Workbook("invoice-template.xlsx") # same as Workbook.open(...)
wb.replace("{client}", "Jonas Corp")
wb.replace("{amount}", 520, value_type="number")
wb.replace("{date}", "2025-08-07", value_type="date")
wb.save("invoice-filled.xlsx")
API showcase
from datetime import datetime
from xlsxedit import Workbook
wb = Workbook() # new blank workbook; same as Workbook.create()
ws = wb["Sheet1"]
ws["A1"].value = "hello"
ws["A2"].value = 42
ws["A3"].value = datetime(2025, 8, 7)
ws["A3"].apply_date_format()
ws["B1"].formula = "=A2*2"
ws.column_dimensions["C"].width = 20.0
ws.row_dimensions[4].height = 30.0
ws.merge_cells("A1:C1")
# insert_rows: insert one row at row 10 — writes A10="New line", B10=100; existing row 10+ moves down
ws.insert_rows([["New line", 100]], at_cell="A10")
# insert_columns: insert one column at C — values top→bottom; existing C+ move right
ws.insert_columns([("Note", "detail")], at_col="C")
# write_rows: write at fixed rows without shifting (overwrites cells in that range)
ws.write_rows([["Total", 520]], at_cell="A20")
ws["D1"].value = "Docs"
ws["D1"].hyperlink.url = "https://xlsxedit.jonasruilong.com"
ws.add_image("logo.jpg", anchor="E2", width=180, height=135)
ws.add_chart("bar", anchor="G2", data_range="A1:B5", title="Sales")
ws.add_table("A1:B10", ["Item", "Qty"], name="Items")
ws.add_conditional_formatting("A2:A20", operator="greaterThan", formula="0")
report = wb.add_worksheet("Report")
wb.rename_worksheet("Report", "Summary")
wb.replace("PLACEHOLDER", "Jonas Corp")
wb.replace("{qty}", 888, value_type="number")
wb.save("out.xlsx")
See tutorial/run_tutorial.py for a full walkthrough.
Bulk export (many rows)
wb = Workbook("report-template.xlsx") # same as Workbook.open(...)
wb.write_dataframe(
df,
at_cell="A5",
header=False,
mode="overwrite",
row_styles=[{"bg_color": "FFFFFFFF"}, {"bg_color": "FF9DC3E6"}],
)
wb.save("report.xlsx")
Tutorials: tutorial/pandas_tutorial.py (pandas engine), tutorial/run_tutorial.py, tutorial/export_pandas_example.py (advanced bulk), tutorial/bench_large_export.py
Pandas quick start
import pandas as pd
import xlsxedit.pandas_io as xpi
xpi.register() # once per process
df = pd.DataFrame({"Item": ["Widget", "Gadget"], "Qty": [2, 5]})
with pd.ExcelWriter("out.xlsx", engine="xlsxedit") as writer:
df.to_excel(writer, sheet_name="Data", index=False)
got = pd.read_excel("out.xlsx", engine="xlsxedit")
Opt-in engine today (register()); a future pandas PR may add official reader registration. Full docs: Pandas engine. Tutorial: python tutorial/pandas_tutorial.py.
When to use / when not
Use xlsxedit when:
- Filling styled Excel templates from Python
- You need charts, images, or layout to survive after edits
- Running headless on a server or in CI without Excel installed
Use something else when:
- You need live Excel recalc, VBA, or UI automation → xlwings
- You only create new workbooks from scratch → xlsxwriter
- You need pivot editing or every openpyxl feature today → openpyxl (xlsxedit API is still growing)
Support & sponsorship
xlsxedit is free and open source under the Apache License 2.0 — you can use it anywhere, including in commercial and closed-source products, at no cost.
If xlsxedit saves you or your company real time, consider sponsoring it — it's what keeps the project maintained and improving. There's no fixed price: pay what it's worth to you. Bigger companies more, smaller ones less.
- Sponsor: xlsxedit.jonasruilong.com/sponsor
Or, even better — give me a job. I'm Jonas, the person behind xlsxedit. I'll be honest: I'm a little desperate — not for money, but to hopefully live in the same city as the person I love, instead of continents away.
Acknowledgments
The OPC package layer in xlsxedit (src/xlsxedit/opc/** and src/xlsxedit/oxml/parser.py) is adapted from python-docx and python-pptx by Steve Canny (scanny), which are MIT licensed (Copyright (c) 2013 Steve Canny). That MIT notice is reproduced in NOTICE and THIRD_PARTY_LICENSES. xlsxedit is independent and not affiliated with those projects.
License
xlsxedit is licensed under the Apache License 2.0. Portions adapted from python-docx / python-pptx remain under their original MIT license; see NOTICE and THIRD_PARTY_LICENSES. Contributions are accepted under the Contributor License Agreement — see CONTRIBUTING.md.
Documentation
In-repo copies: docs/
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file xlsxedit-1.0.0.tar.gz.
File metadata
- Download URL: xlsxedit-1.0.0.tar.gz
- Upload date:
- Size: 92.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
21f852c6286cdbdf74da934ecad35828c21a79a9bace0c54ccc33163baad1294
|
|
| MD5 |
9560b7c03efbddcbab7460bc3e0d6552
|
|
| BLAKE2b-256 |
35e693c069a1c1e07ea3bf26186fd6237b7b126d40cca968a68c3e04c79048b7
|
Provenance
The following attestation bundles were made for xlsxedit-1.0.0.tar.gz:
Publisher:
release.yml on jonas-kupferschmid/xlsxedit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
xlsxedit-1.0.0.tar.gz -
Subject digest:
21f852c6286cdbdf74da934ecad35828c21a79a9bace0c54ccc33163baad1294 - Sigstore transparency entry: 2203091284
- Sigstore integration time:
-
Permalink:
jonas-kupferschmid/xlsxedit@053fa0c028fd638024735144cc3bc897972ba0cf -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/jonas-kupferschmid
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@053fa0c028fd638024735144cc3bc897972ba0cf -
Trigger Event:
release
-
Statement type:
File details
Details for the file xlsxedit-1.0.0-py3-none-any.whl.
File metadata
- Download URL: xlsxedit-1.0.0-py3-none-any.whl
- Upload date:
- Size: 86.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
76dd3a29229a810753ffccbab21e78eaa531c3352dd23689bc3dcf5b39228db6
|
|
| MD5 |
a67f4eca5cfd08b935c59bfa3f415c35
|
|
| BLAKE2b-256 |
1e7414b845c0fab389cb03070d75cee9a7cc64a014fabda9d7cf11ce25f44eb0
|
Provenance
The following attestation bundles were made for xlsxedit-1.0.0-py3-none-any.whl:
Publisher:
release.yml on jonas-kupferschmid/xlsxedit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
xlsxedit-1.0.0-py3-none-any.whl -
Subject digest:
76dd3a29229a810753ffccbab21e78eaa531c3352dd23689bc3dcf5b39228db6 - Sigstore transparency entry: 2203091310
- Sigstore integration time:
-
Permalink:
jonas-kupferschmid/xlsxedit@053fa0c028fd638024735144cc3bc897972ba0cf -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/jonas-kupferschmid
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@053fa0c028fd638024735144cc3bc897972ba0cf -
Trigger Event:
release
-
Statement type: