Build an Excel (.xlsx) file straight from JSON. It is a thin, typed wrapper
around the SheetJS-compatible @e965/xlsx
library, so it works both in the browser (downloads the file) and in
Node.js (can return a buffer when configured).
You can see a live demo on any of these sites (there are several, just in case):
- 📊 Turn an array of JSON sheets into a multi-sheet workbook.
- 🧱 Render several tables in the same sheet (opt-in, vertical or horizontal layout).
- 🧭 Read deeply nested values (
"more.phone") or compute them with a function. - 🎨 Per-column number, date, currency and hyperlink formatting.
- 🖌️ Opt-in cell styling for fonts, fills, borders, alignment and number formats.
- ⬜ Opt-in true blank cells for empty, null or missing values.
- 📐 Automatic column widths (tunable with
extraLength). ↔️ Right-to-left (RTL) sheet support.- 🌐 Works in the browser (file download) and in Node.js (file or buffer output).
- 🟦 Written in TypeScript — ships with type definitions.
npm install json-as-xlsx
# or
yarn add json-as-xlsx
# or
pnpm add json-as-xlsximport xlsx from "json-as-xlsx"
// Alternative import styles:
// import { xlsx } from "json-as-xlsx"
// const xlsx = require("json-as-xlsx")
let data = [
{
sheet: "Employees",
columns: [
{ label: "Name", value: "name" }, // Top level data
{ label: "Salary", value: (row) => row.salary + " USD" }, // Custom format
{ label: "Email", value: (row) => (row.contact ? row.contact.email || "" : "") }, // Run functions
],
content: [
{ name: "Ada Lovelace", salary: 5000, contact: { email: "ada@example.com" } },
{ name: "Grace Hopper", salary: 6200, contact: { email: "grace@example.com" } },
],
},
{
sheet: "Products",
columns: [
{ label: "Product", value: "product" }, // Top level data
{ label: "Price", value: "price", format: "$#,##0.00" }, // Column format
{ label: "Stock", value: "inventory.stock", format: "#,##0" }, // Deep props and column format
],
content: [
{ product: "Keyboard", price: 29.99, inventory: { stock: 1200 } },
{ product: "Monitor", price: 199.5, inventory: { stock: 340 } },
],
},
]
let settings = {
fileName: "MySpreadsheet", // Name of the resulting spreadsheet
enableStyles: false, // Set to true to write cell styles (`s`) into the .xlsx file
extraLength: 3, // A bigger number means that columns will be wider
writeMode: "writeFile", // The available parameters are 'writeFile' and 'write'. This setting is optional. Useful in such cases https://docs.sheetjs.com/docs/solutions/output#example-remote-file
writeOptions: {}, // Style options from https://docs.sheetjs.com/docs/api/write-options
RTL: true, // Display the columns from right-to-left (the default value is false)
writeEmptyValuesAsBlankCells: false, // Set to true so Excel treats empty, null and missing values as blank cells
}
xlsx(data, settings) // Will download the excel file| Option | Type | Default | Description |
|---|---|---|---|
enableStyles |
boolean |
false |
Write cell style objects into the .xlsx file. Only supported with bookType: "xlsx". |
fileName |
string |
"Spreadsheet" |
Name of the resulting file (the .xlsx extension is added automatically). |
extraLength |
number |
1 |
Extra characters added to every auto-calculated column width. |
writeMode |
"writeFile"/"write" |
"writeFile" |
"writeFile" downloads/writes the file; "write" returns the raw data (e.g. a Node buffer). |
writeOptions |
object |
{} |
Passed straight to SheetJS — see write options. |
RTL |
boolean |
false |
Render every sheet right-to-left. |
writeEmptyValuesAsBlankCells |
boolean |
false |
Omit empty-string, null, undefined and missing-path values so Excel treats them as blank cells. |
For backward compatibility, empty values are written as empty strings by
default. Excel can count those cells as text values even when they look blank.
Set writeEmptyValuesAsBlankCells: true to omit empty-string, null,
undefined and missing-path values from the worksheet XML:
let data = [
{
sheet: "Sparse data",
columns: [
{ label: "ID", value: "id" },
{ label: "Email", value: (row) => row.contact?.email ?? "" },
{ label: "Score", value: "score", format: "0.00" },
],
content: [
{ id: "ID-101", contact: { email: "ada@example.com" }, score: 98.5 },
{ id: "ID-102", contact: { email: "" } },
{ id: "ID-103", score: null },
{ id: "ID-104" },
],
},
]
xlsx(data, {
fileName: "BlankCellSpreadsheet",
writeEmptyValuesAsBlankCells: true,
})Values like 0 and false are still written normally.
This option applies to both string-path columns (value: "email") and function
columns (value: (row) => row.email ?? ""). Formats, hyperlinks and cell styles
are applied only to cells that still have a value; blank cells remain truly
blank. Sheets or multi-table entries with no content still render their headers.
If you want to inspect or post-process the workbook, pass a callback as the
third argument. It receives the generated WorkBook
right before it is written, so you can read or mutate it:
let callback = function (workbook) {
console.log("Workbook ready:", workbook.SheetNames)
}
xlsx(data, settings, callback) // Will download the excel fileSet writeOptions.type to "buffer" (or writeMode: "write") to get the file
contents back instead of writing them to disk. This is handy for sending the
spreadsheet over HTTP:
import xlsx from "json-as-xlsx"
const settings = {
writeOptions: {
type: "buffer",
bookType: "xlsx",
},
}
app.get("/download", (_, res) => {
const buffer = xlsx(data, settings)
res.writeHead(200, {
"Content-Type": "application/octet-stream",
"Content-Disposition": "attachment; filename=MySheet.xlsx",
})
res.end(buffer)
})Note: Cell formatting is type based, i.e. the format type and value type must match.
If you want to use a Date format, the value must be of type Date; if you want a number format, the value must be a Number.
Column formatting can be provided in the column object, i.e.
columns: [{ label: "Income", value: "income", format: "€#,##0.00" }]- A list of SheetJS format examples can be found here: SSF library
- ECMA-376 number formatting specification: Number formats
Examples
// Number formats
"$0.00" // Basic
"\£#,##0.00" // Pound
"0%" // Percentage
'#.# "ft"' // Number and text
// Date formats
"d-mmm-yy" // 12-Mar-22
"ddd" // (eg. Sat)
"dddd" // (eg. Saturday)
"h:mm AM/PM" // 1:10 PMUse the special "hyperlink" format to turn a column's text values into
clickable links:
columns: [{ label: "Website", value: "url", format: "hyperlink" }]Cell styling is disabled by default to keep the regular export path unchanged.
Set enableStyles: true to write style objects into the generated .xlsx file.
Styled exports only support XLSX output; if you set writeOptions.bookType, keep
it as "xlsx".
You can style headers, full columns, or individual cell values:
let data = [
{
sheet: "Styled employees",
columns: [
{
label: "Name",
value: "name",
headerStyle: {
fill: { fgColor: { rgb: "21A366" } },
font: { bold: true, color: { rgb: "FFFFFF" } },
},
cellStyle: {
alignment: { wrapText: true },
},
},
{
label: "Salary",
value: "salary",
format: "$#,##0.00",
cellStyle: {
font: { italic: true },
},
},
],
content: [
{
name: {
v: "Ada\nLovelace",
t: "s",
s: { font: { bold: true, color: { rgb: "FF0000" } } },
},
salary: 5000,
},
],
},
]
xlsx(data, {
fileName: "StyledSpreadsheet",
enableStyles: true,
})Supported style groups are alignment, border, fill, font, and numFmt.
Column format values are also preserved as number formats when styles are
enabled.
By default a sheet renders a single table from its columns and content. To
place several independent tables in the same sheet, provide a tables array
instead — each entry has its own columns and content (with the same
formatting and styling options). This is fully opt-in: sheets that don't set
tables keep working exactly as before.
let data = [
{
sheet: "Quarter summary",
tablesLayout: "vertical", // "vertical" (default) stacks tables; "horizontal" places them side by side
tablesGap: 1, // blank rows (vertical) or columns (horizontal) between tables — defaults to 1
tables: [
{
columns: [
{ label: "Product", value: "product" },
{ label: "Revenue", value: "revenue", format: "$#,##0.00" },
],
content: [
{ product: "Keyboard", revenue: 35988 },
{ product: "Monitor", revenue: 67830 },
],
},
{
columns: [
{ label: "Team", value: "team" },
{ label: "Expenses", value: "expenses", format: "$#,##0.00" },
],
content: [
{ team: "Engineering", expenses: 42000 },
{ team: "Marketing", expenses: 18500 },
],
},
],
},
]
xlsx(data, { fileName: "MultiTableSpreadsheet" })When tables is present and non-empty it takes precedence over the sheet's
top-level columns/content.
| Sheet option | Type | Default | Description |
|---|---|---|---|
tables |
{ columns, content }[] |
— | Render multiple tables in the sheet. Each table keeps its own formatting. |
tablesLayout |
"vertical"/"horizontal" |
"vertical" |
Stack tables top-to-bottom or place them left-to-right. |
tablesGap |
number |
1 |
Blank rows (vertical) or columns (horizontal) left between tables. |
The package is written in TypeScript and ships its own type definitions. The
public interfaces are exported for your convenience. The xlsx function is
available as both the default export and a named export:
import xlsx, { IJsonSheet, ISettings, IColumn, IContent, ICellStyle } from "json-as-xlsx"
// or:
// import { xlsx, IJsonSheet, ISettings, IColumn, IContent, ICellStyle } from "json-as-xlsx"
const data: IJsonSheet[] = [
/* ... */
]
const settings: ISettings = {
/* ... */
}
xlsx(data, settings)These examples are part of the Yarn workspace and are intended to be installed and run from the repository root.
Contributions are welcome! Please read the contributing guide and the code of conduct before opening a pull request. To report a security issue, see the security policy.