Create, edit, audit, and extract Excel spreadsheets (.xlsx): generate reports/exports, apply formulas/formatting/charts/data validation, parse existing workbooks, and avoid spreadsheet risks (formula injection, broken links, hidden rows). Supports ExcelJS, openpyxl, pandas, XlsxWriter, and SheetJS.
This skill enables creation, editing, inspection, and safe distribution of .xlsx workbooks. Use it for report exports, spreadsheet models, spreadsheet QA, workbook automation, and Excel-compatible deliverables.
Modern best practices (July 2026):
XlsxWriter is write-only: it cannot open, read, or edit an existing .xlsx file.
If the task is "edit this workbook" rather than "create a new one," reach for openpyxl (or ExcelJS in Node) instead — choosing XlsxWriter for an edit task is a common non-expert mistake that fails immediately.openpyxl or XlsxWriter has no cached result until some calculation engine (Excel, LibreOffice headless, or a session-based tool such as xlwings) opens and recalculates the file.
Reading that same file back with openpyxl(..., data_only=True) before any recalculation returns None, not the computed value — this looks like a bug but is expected behavior. If a downstream step (pandas, another script, an LLM) needs the number immediately, compute it in Python and write the literal value, or write both the formula and a plausible cached value only if you can guarantee it matches.openpyxl can preserve VBA with keep_vba=True, but this skill does not author or execute macros.openpyxl, default to keep_links=False unless external links must be preserved.openpyxl usage can balloon memory (a ~150MB source DataFrame has been observed using 2GB+ RAM with the default XML parser). Install lxml and use Workbook(write_only=True) for writing or load_workbook(read_only=True) for reading — both stream rather than build a full in-memory tree, and lxml alone materially cuts memory even outside those modes. Write-only workbooks can be saved exactly once; a second save() call raises WorkbookAlreadySaved, so batch all writes before saving.| Task | Tool/Library | Language | When to Use |
|------|--------------|----------|-------------|
| Table-first exports | XlsxWriter | Python | New .xlsx reports with tables, formats, and charts |
| Edit existing workbook | openpyxl | Python | Modify sheets, formulas, tables, validation, and protection |
| DataFrame export | pandas + XlsxWriter/openpyxl | Python | Data pipeline to Excel with styling and reviewable outputs |
| DataFrame export | Polars + XlsxWriter | Python | Fast dataframe pipeline with Excel output |
| Server-side workbook generation | ExcelJS | Node.js | Typed Node/TS stacks, workbook structure, styles, tables |
| Workbook ingestion | SheetJS / pandas / openpyxl | Node.js / Python | Parse existing spreadsheet data and metadata |
| Cloud automation | Office Scripts | TypeScript | Excel on the web, OneDrive/SharePoint workbooks, native pivots/tables |
| Microsoft 365 workbook API | Microsoft Graph Excel | REST | Remote workbook sessions, ranges, tables, charts, named items |
| Desktop Excel automation | xlwings | Python | Native Excel features on a machine with Excel installed |
| Workbook review | scripts/xlsx_audit.py | Python | Read-only QA pass before sharing or refactoring |
| Safe distribution | scripts/xlsx_sanitize.py | Python | Sanitize dangerous text prefixes and strip external links |
| Repeatable export | scripts/xlsx_export_report.py | Python | Opinionated CSV/JSON/Parquet to .xlsx export helper |
Invoke this skill when a user requests:
.xlsx reports, dashboards, models, or exportsXlsxWriter, openpyxl, ExcelJS) or cloud automation (Office Scripts, Graph, xlwings), then start from a table-first layout.python3 scripts/xlsx_audit.py workbook.xlsx --format md and compare the results against assets/spreadsheet-model-review-checklist.md.XLSX request
|
v
Classify workbook task
|-- new export / report
|-- edit existing workbook
|-- audit / sanitize
|-- cloud or desktop automation
|
v
Choose runtime
|-- Python data pipeline -----> pandas / Polars + XlsxWriter
|-- Python workbook edits ----> openpyxl
|-- Node / TS service --------> ExcelJS
|-- M365 live workbook -------> Office Scripts or Graph Excel
|-- desktop Excel ------------> xlwings
|
v
Apply table-first structure
|-- inputs
|-- calculations
|-- outputs
|-- instructions / summary
|
v
Review formulas, links, hidden content, and accessibility
|
v
Sanitize and verify in target viewers
openpyxl and XlsxWriter still do not create native pivot tables.XLOOKUP, FILTER, UNIQUE, SORT, IFS, SEQUENCE) require Microsoft 365 / current Excel.
Writing them into a workbook targeted at Excel 2019/2016, Google Sheets (partial support), or older LibreOffice will show #NAME? for recipients on those versions — confirm the audience's Excel channel before defaulting to these over VLOOKUP/INDEX-MATCH/nested IF.pandas.read_excel() picks its engine by file extension (openpyxl for .xlsx), not by what wrote the file. It never surfaces conditional formatting, data validation, protection, or charts — if the audit needs those, read the OOXML parts directly (see scripts/xlsx_audit.py) or use openpyxl directly instead of pandas.Excel Task: [What do you need?]
├─ New workbook export?
│ ├─ Python data/report pipeline → pandas/Polars + XlsxWriter
│ ├─ Edit-heavy workbook logic → openpyxl
│ └─ Node/TypeScript service → ExcelJS
│
├─ Existing workbook review?
│ ├─ Read-only audit → scripts/xlsx_audit.py
│ ├─ Data extraction → pandas or SheetJS
│ └─ Structural edits → openpyxl
│
├─ Native Excel features on a live workbook?
│ ├─ Web / M365 workbook → Office Scripts or Graph Excel
│ └─ Desktop Excel installed → xlwings
│
└─ Safe distribution?
├─ Sanitize text / strip links → scripts/xlsx_sanitize.py
├─ Accessibility review → Excel checker + accessibility reference
└─ Sensitive data → encryption + platform access controls
import pandas as pd
df = pd.DataFrame(
[
{"product": "Widget A", "qty": 100, "price": 10.0},
{"product": "Widget B", "qty": 50, "price": 25.0},
]
)
df["total"] = df["qty"] * df["price"]
with pd.ExcelWriter("report.xlsx", engine="xlsxwriter") as writer:
df.to_excel(writer, sheet_name="Sales", index=False, startrow=1)
workbook = writer.book
worksheet = writer.sheets["Sales"]
header_fmt = workbook.add_format({"bold": True, "bg_color": "#D9E2F3"})
money_fmt = workbook.add_format({"num_format": "$#,##0.00"})
worksheet.write("A1", "Sales report")
worksheet.freeze_panes(2, 0)
worksheet.autofilter(1, 0, len(df), len(df.columns) - 1)
worksheet.set_column("C:D", 14, money_fmt)
worksheet.add_table(
1,
0,
len(df) + 1,
len(df.columns) - 1,
{
"name": "SalesTable",
"style": "Table Style Medium 2",
"columns": [{"header": col, "header_format": header_fmt} for col in df.columns],
"total_row": True,
},
)
from openpyxl import load_workbook
wb = load_workbook("input.xlsx", keep_vba=False, keep_links=False)
ws = wb["Sales"]
ws["A1"] = "Sales report for Q1 2026"
ws.freeze_panes = "A2"
ws.sheet_view.showGridLines = True
wb.save("output.xlsx")
function main(workbook: ExcelScript.Workbook) {
const dataSheet = workbook.getWorksheet("Raw Data");
const sourceRange = dataSheet.getUsedRange();
const sourceTable = dataSheet.addTable(sourceRange, true);
sourceTable.setName("SalesTable");
const pivotSheet = workbook.addWorksheet("Pivot");
const pivot = workbook.addPivotTable("SalesPivot", sourceTable, pivotSheet.getRange("A1"));
pivot.addRowHierarchy(pivot.getHierarchy("Region"));
pivot.addColumnHierarchy(pivot.getHierarchy("Product"));
pivot.addDataHierarchy(pivot.getHierarchy("Revenue"));
}
A1.#REF!, broken names, stale links, or silent formula inconsistencies.Use only when explicitly requested and policy-compliant.
scripts/xlsx_audit.py; humans review the findings.Resources
Scripts
python3 scripts/xlsx_audit.py workbook.xlsx --format mdpython3 scripts/xlsx_export_report.py input.csv output.xlsxpython3 scripts/xlsx_sanitize.py input.xlsx output.xlsx --strip-external-linksTemplates
Related Skills
Before applying this skill on a non-trivial task, read learnings.consolidated.md in this directory (and learnings.md if present).
After applying it, if you encountered a pattern worth remembering, a mistake worth preventing, or a domain fact that surprised you, append one dated bullet to learnings.md via agents-skills-feedback-loop/scripts/append_learning.py. Do not modify SKILL.md itself.
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer