Installs into .claude/skills of the current project.
Are you the author of Sandbox Xlsx?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/nodetool-ai-sandbox-xlsx)
---
name: sandbox-xlsx
description: Read and write Excel workbooks in a Code node or CodeAct action, with exceljs running on the host
---
# Excel workbooks in the sandbox
Specifier: `@nodetool-ai/sandbox-xlsx`. Import it at the top of the body.
exceljs is built on Node streams and ships its own zip layer; it will never be a
guest module. This pack is a **host module**: the import resolves to a generated
facade over NodeTool's own implementation.
## parse — workbook bytes to records
```js
import { parse } from "@nodetool-ai/sandbox-xlsx";
const bytes = await workspace.readBytes("report.xlsx");
const sheets = await parse(bytes); // { "Sheet1": [...], "Costs": [...] }
const rows = await parse(bytes, { sheet: "Costs" }); // one sheet's rows
return { sheetNames: Object.keys(sheets), rows };
```
Options: `sheet` (name — returns that sheet's rows directly) and `header`
(default `true`; `false` gives raw cell arrays). Formula cells yield their
computed result, dates come back as ISO strings, and rich text is flattened.
Get the bytes from `workspace.readBytes`, or from a fetched body with
`await response.bytes()`.
## write — records to workbook bytes
```js
import { write } from "@nodetool-ai/sandbox-xlsx";
const bytes = await write({ Costs: inputs.rows, Notes: [{ note: "draft" }] });
await workspace.writeBytes("report.xlsx", bytes);
```
`{sheetName: rows}` is the short form. The long form is an array, one entry per
sheet, which is where per-sheet options live:
```js
const bytes = await write([
{
name: "Costs",
rows: inputs.rows,
columns: ["item", "usd"], // pin the column order
styles: [
{ range: "A1:B1", bold: true, background: "FFE9A8" },
{ range: "B2:B999", numberFormat: "$#,##0.00" }
]
}
]);
```
Rows are records by default, and the header is the union of their keys in
first-seen order unless `columns` pins it. `header: false` takes arrays of
cells instead and writes no header row. Column widths are fitted to the
content; pass `{ autoFitColumns: false }` to leave them alone. A style's
`range` is `A1` or `A1:C20`, and it takes `bold`, `italic`, `size`, `color`,
`background` (hex, with or without `#`) and `numberFormat`.
## Gotchas
- **Both exports are async.**
- **10 MB per workbook read.** Larger input is refused by name.
- **A missing sheet name throws**, and the error lists the sheets that exist.
- **`write` caps a workbook at 64 sheets and 250 000 cells.**
- **Values that are not a number, string, boolean or date are stored as JSON
text.** A cell cannot hold bytes.
- **Nothing is saved for you.** `write` returns bytes; `workspace.writeBytes`
(or `sandboxToAsset`) is what puts them somewhere.