Use when the agent must deliver a .xlsx Excel file — sales reports, query results, multi-sheet data exports, financial summaries, or any structured data that needs polished spreadsheet formatting. This skill handles workbook creation only. Do not use it to read, edit, or preserve existing Excel files.
bash
python3 -m venv ~/.openclaw/workspace/.venv_excel
~/.openclaw/workspace/.venv_excel/bin/pip install xlsxwriter
No credentials required.
bash
~/.openclaw/workspace/.venv_excel/bin/python skills/excel-export/scripts/build_xlsx.py \
--input <path/to/workbook.json> \
--output <path/to/report.xlsx>
Default export directory: ~/.openclaw/workspace/exports/excel/.
The script prints a JSON summary to stdout on success:
json
{
"success": true,
"output": "/absolute/path/to/report.xlsx",
"sheets": [
{ "sheet": "Ventes", "rows": 42 },
{ "sheet": "Charges", "rows": 18 }
]
}
The input is a single JSON file. Top-level shape:
json
{
"sheets": [ ... ]
}
Each sheet object:
FieldRequiredDescriptionnameyesSheet tab name (max 31 chars)titlenoBold title row above the tablesubtitlenoMuted subtitle row below the titlecolumnsyesArray of column definitionsrowsnoArray of data objects (keyed by key)
Each column object:
FieldRequiredDescriptionkeyyesRow-object key for this column's valuesheaderyesDisplay header in the tabletypenoData type (default text) — see table belowwidthnoExplicit column width (overrides auto-estimate)numfmtnoCustom Excel number format (overrides type default)formulanoExcel table structured-reference formulatotalnoTotals-row function: sum, average, or count
Rows are objects keyed by column key, not positional arrays. Missing keys produce blank cells. Unknown keys cause a validation error.
TypeDefault formatNotestext—Always stored as textinteger``#,##0Rounded to whole numbernumber``#,##0.00Two decimal placespercent``0.0%Pass 0.15 for 15 %currency``#,##0.00 "MAD"Moroccan dirham by defaultdate``dd/mm/yyyyAccepts YYYY-MM-DD stringsdatetime``dd/mm/yyyy hh:mmAccepts ISO 8601 stringsboolean—Rendered as Oui / Non
Use numfmt on any column to override its type's default format.
Layout — fixed, deterministic, never configured per-sheet:
Freeze pane — always at the first data row ( A2, A4, or A5 depending on title presence).
Table style — each sheet is one rectangular Excel table with filters, banded rows, and Table Style Medium 9.
Column widths — auto-estimated from headers and sampled values, capped at 50 characters. Explicit width in the column definition overrides the estimate.
Formulas — use Excel structured references (e.g. =[@Revenue]/SUM([Revenue])). Formula columns are injected through the table definition, not written cell-by-cell, so they apply to every data row automatically.
Totals row — if any column has total, the table includes a totals row. The first column displays "Total" as a label unless it has its own total function.
Empty datasets — produce a valid workbook with headers, filters, styling, and zero data rows.
These rules are always enforced — they are not optional:
Preserve data types. Leading-zero strings, phone numbers ( +212…), and numeric strings longer than 15 digits are always stored as text. Excel silently corrupts these if written as numbers.
Keep calculations in Excel. When the workbook should stay live, write formulas — not hardcoded derived values from Python. Use structured references so formulas survive row insertions and deletions.
Treat dates explicitly. Dates are serial numbers with legacy quirks. The script writes real date objects with explicit dd/mm/yyyy formatting — never raw serial numbers or ambiguous strings.
Validate before delivery. The script validates every input field, rejects unknown keys, and fails fast with clear error messages. A workbook should never ship with silent data loss.
Regional defaults are French / Morocco. Date format dd/mm/yyyy, currency MAD, booleans Oui / Non. Note: final display still depends partly on the viewer's local Excel regional settings.
json
{
"sheets": [
{
"name": "Ventes Q1",
"title": "Rapport des Ventes — Q1 2026",
"subtitle": "Direction Commerciale",
"columns": [
{ "key": "region", "header": "Région", "type": "text" },
{ "key": "ca", "header": "CA (MAD)", "type": "currency", "total": "sum" },
{ "key": "volume", "header": "Volume", "type": "integer", "total": "sum" },
{ "key": "growth", "header": "Croissance", "type": "percent" },
{ "key": "date", "header": "Date", "type": "date" },
{ "key": "active", "header": "Actif", "type": "boolean" }
],
"rows": [
{ "region": "Casablanca", "ca": 1250000, "volume": 340, "growth": 0.15, "date": "2026-03-31", "active": true },
{ "region": "Rabat", "ca": 980000, "volume": 210, "growth": -0.03, "date": "2026-03-31", "active": true },
{ "region": "Tanger", "ca": 670000, "volume": 155, "growth": 0.08, "date": "2026-03-31", "active": false }
]
}
]
}
bash
~/.openclaw/workspace/.venv_excel/bin/python skills/excel-export/scripts/build_xlsx.py \
--input ~/.openclaw/workspace/exports/ventes_q1.json \
--output ~/.openclaw/workspace/exports/excel/ventes_q1.xlsx
The script fails fast with a clear error on:
[ ] : * ? / \, or starting/ending with apostrophessubtitle without titletype or total valuesformula definitionstrue/false, 1/0, yes/no, oui/non accepted)integer columns (e.g. 12.9 is rejected, not truncated)When generating Excel files from SQL query results, read references/SQL_TO_EXCEL_RECIPE.md for the standard pipeline: query with mssql, normalize values, build the JSON spec, then render. The recipe includes SQL-to-JSON type mappings, normalization rules, ready-made templates, and common mistakes.
xlsxwriter does not calculate formulas; the recipient's Excel client recalculates on open.