Call runstamp.operations.run("xlsx.render", { sheets }) with one rows entry per JSON record, then write result.value.bytes to a .xlsx file. The code below ran on @runstamp/sdk 3.1.0 (npm latest, published 2026-09-14) with no API key, and wrote a 12,456-byte workbook with an empty loss ledger.
By Jake Jinyong Kim. Last checked 2026-10-20.
Requirements
- Node.js 18.14.1 or later. The code below ran on Node.js 22.23.1; other versions are unknown.
@runstamp/sdk3.1.0:npm install @runstamp/sdk@3.1.0. The package is ESM, so save the script asjson-to-xlsx.mjs.- No API key.
runstamp.operations.runuses the default local transport, so the render runs locally. The run was made withRUNSTAMP_API_KEYunset.
Code
Save this as records.json:
[
{ "id": "C-101", "owner": "Priya", "state": "Open", "amount": 1200, "due": "2026-11-15" },
{ "id": "C-102", "owner": "Marcus", "state": "In Progress", "amount": 845.5, "due": "2026-11-30" },
{ "id": "C-103", "owner": "Ana", "state": "Closed", "amount": 0, "due": "2026-10-01" }
]Save this as json-to-xlsx.mjs:
import { readFile, writeFile } from "node:fs/promises";
import { runstamp } from "@runstamp/sdk";
const records = JSON.parse(await readFile("records.json", "utf8"));
const keys = Object.keys(records[0]);
const result = await runstamp.operations.run("xlsx.render", {
meta: { title: "Findings export" },
sheets: [
{
name: "Findings",
columns: keys.map(() => ({ width: 16 })),
rows: [
{ cells: keys.map((key) => ({ value: key, style: { font: { bold: true } } })) },
...records.map((record) => ({
cells: keys.map((key) => ({ value: record[key] })),
})),
],
},
],
});
if (!result.ok) throw new Error(`${result.error.code}: ${result.error.remediation}`);
await writeFile("findings.xlsx", result.value.bytes);
console.log("ok:", result.ok);
console.log("byteLength:", result.value.byteLength);
console.log("mediaType:", result.value.mediaType);
console.log("extension:", result.value.extension);
console.log("hash:", result.value.hash);
console.log("losses:", JSON.stringify(result.losses));
console.log("diagnostics:", JSON.stringify(result.diagnostics));The first row holds the keys of the first record as bold headers. Every later row is one record, in the same key order. The code reads the keys from records[0] only, so a record with extra keys loses them.
Run it
node json-to-xlsx.mjsOutput on 2026-10-20 (Node.js 22.23.1, @runstamp/sdk 3.1.0, exit code 0):
ok: true
byteLength: 12456
mediaType: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
extension: xlsx
hash: sha256:9669022dcae5e1e934fcf97c91abfc9b2e134725039ddab04f4dd76edacf4888
losses: []
diagnostics: []
ls -l findings.xlsx shows 12456 bytes, and shasum -a 256 findings.xlsx returns the same SHA-256 as hash. unzip -tq reports no errors in the archive, and unzip -l lists 10 parts: the workbook, one worksheet, shared strings, styles, a theme, and the document properties. A second run produced the same SHA-256.
What it keeps / what it can't do
What the run kept, read from the worksheet XML inside the file:
- Text values are stored as text and numbers (
1200,845.5,0) as numeric cells. - The
duevalues are ISO date strings in the JSON, so they are stored as text, not as Excel dates. - Header cells carry a bold style, and the five columns have width 16.
meta.titleis stored as the workbook title in the document properties.- The file carries a landscape page setup that fits the sheet to one page, although the input has no print settings. Set print options yourself if you will print long sheets.
What it refuses or can't do:
- A
Dateobject as a cell value is refused withcommon/CONTRACT_VIOLATION. Convert it first, for example withdate.toISOString(). - A sheet name over 31 characters, or one containing
[ ] : * ? / \, is refused withcommon/SCHEMA_REJECTED. - A nested object or array of objects as a cell value is refused with
common/SCHEMA_REJECTED("Invalid input"). Flatten the record first. - The input limits are 255 sheets per workbook, 1,048,576 rows per sheet and 16,384 cells per row.
result.ok being true means the render completed. It does not mean the file is ready to send. The default lossPolicy is collect, so a run can succeed with a non-empty losses array; read the array. The SDK types name lossPolicy as a field of the operation call option, and the concepts page lists failOnDropped and failOnAny as the stricter values (see Losses and refusals). This guide did not test those values, so their effect is unknown. This run reported no losses.
This check opened the file as a zip archive and read its XML. It did not open the file in Microsoft Excel or LibreOffice, so how it looks in those applications is unknown.
Operations used
xlsx.render: renders a structuredsheetsdocument to a native.xlsxworkbook. It is deterministic by default and has no side effects.
For other XLSX work, see XLSX and Change an existing XLSX file.
Sources and last checked
Last checked 2026-10-20.
- xlsx.render reference: input fields, limits, defaults and declared losses. The page is dated 2026-09-01.
- Losses and refusals: what
ok,lossPolicyand the loss ledger mean. - @runstamp/sdk on npm:
latestis 3.1.0, published 2026-09-14, requires Node.js>=18.14.1, license Apache-2.0. - The code, the output, the file size and the refusal messages above come from one local run on 2026-10-20 in a clean
npm installof@runstamp/sdk@3.1.0.