Skip to main content

SheetJS vs ExcelJS vs node-xlsx: Excel Files in Node.js (2026)

·PkgPulse Team

TL;DR

For Excel file handling in Node.js: SheetJS (xlsx) is the most capable and widely-used — it reads/writes nearly every spreadsheet format (XLSX, XLS, ODS, CSV) and works in both Node.js and browsers. ExcelJS has a cleaner streaming API for large files and better formatting control. node-xlsx is a lightweight wrapper for simple read/write operations. For most projects, SheetJS is the default choice.

Key Takeaways

  • SheetJS (xlsx): ~7.8M weekly downloads — most feature-complete, handles 20+ file formats
  • ExcelJS: ~1.9M weekly downloads — streaming support, rich cell formatting, pivot tables
  • node-xlsx: ~450K weekly downloads — simple API, lightweight, XLSX only
  • SheetJS Community Edition is free; SheetJS Pro adds better streaming and some formats
  • ExcelJS is better for generating formatted Excel reports (rich styles, charts, conditional formatting)
  • All three work in browsers (Blob/ArrayBuffer output) as well as Node.js

PackageWeekly DownloadsFormatsBrowser Support
xlsx (SheetJS)~7.8M20+
exceljs~1.9MXLSX, CSV
node-xlsx~450KXLSX

SheetJS

SheetJS handles virtually every spreadsheet format:

import * as XLSX from "xlsx"
import { readFileSync, writeFileSync } from "fs"

// Read an Excel file:
const workbook = XLSX.readFile("packages.xlsx")

// Get first sheet:
const sheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[sheetName]

// Convert sheet to JSON (array of objects using first row as headers):
const data: PackageRow[] = XLSX.utils.sheet_to_json(worksheet, {
  header: 1,           // Return arrays (default is objects using header row)
  defval: null,        // Default value for empty cells
  blankrows: false,    // Skip blank rows
  raw: false,          // Return formatted strings instead of raw values
})

// Or with automatic header detection:
const objects: PackageData[] = XLSX.utils.sheet_to_json(worksheet, {
  defval: null,
  raw: true,           // Keep numbers as numbers, dates as numbers
})

// Get range of data:
const range = XLSX.utils.decode_range(worksheet["!ref"] ?? "A1")
console.log(`Rows: ${range.e.r + 1}, Cols: ${range.e.c + 1}`)

SheetJS write to Excel:

// Create from scratch:
const wb = XLSX.utils.book_new()

const packageData = [
  ["Package", "Weekly Downloads", "Version", "License"],
  ["react", 25000000, "18.2.0", "MIT"],
  ["vue", 7000000, "3.4.0", "MIT"],
  ["angular", 3500000, "17.0.0", "MIT"],
]

// From 2D array:
const ws = XLSX.utils.aoa_to_sheet(packageData)

// Or from array of objects (auto-generates headers):
const ws2 = XLSX.utils.json_to_sheet(packages, {
  header: ["name", "downloads", "version", "license"],
  skipHeader: false,  // Include header row
})

// Column widths:
ws["!cols"] = [
  { wch: 20 },  // Column A — 20 chars wide
  { wch: 18 },  // Column B
  { wch: 10 },  // Column C
  { wch: 10 },  // Column D
]

// Append sheet to workbook:
XLSX.utils.book_append_sheet(wb, ws, "Packages")

// Write to file:
XLSX.writeFile(wb, "output.xlsx")

// Or get as buffer for API response:
const buffer = XLSX.write(wb, { type: "buffer", bookType: "xlsx" })
// Buffer can be sent as HTTP response or uploaded to S3

SheetJS in the browser (file download):

import * as XLSX from "xlsx"

function downloadExcel(data: PackageData[], filename = "packages.xlsx") {
  const wb = XLSX.utils.book_new()
  const ws = XLSX.utils.json_to_sheet(data)
  XLSX.utils.book_append_sheet(wb, ws, "Data")

  // Download in browser:
  XLSX.writeFile(wb, filename)
  // Or: const blob = new Blob([XLSX.write(wb, { type: "array" })], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" })
}

SheetJS cell-level access:

// Access individual cells:
const cell = worksheet["B2"]
console.log(cell.v)  // Raw value (number, string, boolean, Date)
console.log(cell.t)  // Type: "n"=number, "s"=string, "b"=boolean, "d"=date
console.log(cell.w)  // Formatted string (how it appears in Excel)
console.log(cell.f)  // Formula (if applicable)

// Modify a cell:
worksheet["B2"] = { t: "n", v: 25000000, z: "#,##0" }  // Number with comma format

// Add formula:
worksheet["E2"] = { t: "n", f: "=B2/C2" }

ExcelJS

ExcelJS has a more OOP API with better streaming and rich formatting:

import ExcelJS from "exceljs"

// Create a workbook:
const workbook = new ExcelJS.Workbook()
workbook.creator = "PkgPulse"
workbook.created = new Date()

const worksheet = workbook.addWorksheet("Package Downloads", {
  pageSetup: { paperSize: 9, orientation: "landscape" },
  views: [{ state: "frozen", ySplit: 1 }],  // Freeze header row
})

// Define columns with widths and header styles:
worksheet.columns = [
  { header: "Package", key: "name", width: 20 },
  { header: "Downloads", key: "downloads", width: 18, style: { numFmt: "#,##0" } },
  { header: "Version", key: "version", width: 10 },
  { header: "License", key: "license", width: 10 },
  { header: "Updated", key: "updatedAt", width: 14, style: { numFmt: "yyyy-mm-dd" } },
]

// Style the header row:
const headerRow = worksheet.getRow(1)
headerRow.font = { bold: true, size: 12, color: { argb: "FFFFFFFF" } }
headerRow.fill = {
  type: "pattern",
  pattern: "solid",
  fgColor: { argb: "FF2D6A4F" },
}
headerRow.alignment = { vertical: "middle", horizontal: "center" }
headerRow.height = 25

// Add rows:
packages.forEach((pkg, index) => {
  const row = worksheet.addRow({
    name: pkg.name,
    downloads: pkg.downloads,
    version: pkg.version,
    license: pkg.license,
    updatedAt: new Date(pkg.updatedAt),
  })

  // Zebra striping:
  if (index % 2 === 0) {
    row.fill = { type: "pattern", pattern: "solid", fgColor: { argb: "FFF5F5F5" } }
  }

  // Conditional formatting: red if downloads < 1000/week:
  if (pkg.downloads < 1000) {
    row.getCell("downloads").font = { color: { argb: "FFFF0000" }, bold: true }
  }
})

// Add auto-filter:
worksheet.autoFilter = { from: "A1", to: "E1" }

// Add a totals row:
worksheet.addRow([])  // Blank row
const totalRow = worksheet.addRow({
  name: "TOTAL",
  downloads: { formula: `SUM(B2:B${packages.length + 1})` },
})
totalRow.font = { bold: true }

// Write to file:
await workbook.xlsx.writeFile("packages-report.xlsx")

// Or write to buffer:
const buffer = await workbook.xlsx.writeBuffer()

ExcelJS streaming for large files:

import ExcelJS from "exceljs"
import { createReadStream, createWriteStream } from "fs"

// Stream reading (low memory):
async function* streamReadExcel(filepath: string) {
  const workbook = new ExcelJS.Workbook()
  const stream = createReadStream(filepath)

  await workbook.xlsx.read(stream)

  const worksheet = workbook.worksheets[0]
  worksheet.eachRow({ includeEmpty: false }, (row, rowNumber) => {
    if (rowNumber > 1) {  // Skip header
      yield row.values
    }
  })
}

// Stream writing (doesn't hold entire workbook in memory):
const workbookWriter = new ExcelJS.stream.xlsx.WorkbookWriter({
  filename: "large-output.xlsx",
  useStyles: true,
})

const worksheetWriter = workbookWriter.addWorksheet("data")
worksheetWriter.columns = [{ header: "Name", key: "name" }, { header: "Value", key: "value" }]

for await (const record of largeDataSource) {
  worksheetWriter.addRow(record).commit()  // Commit each row to disk immediately
}

await worksheetWriter.commit()
await workbookWriter.commit()

node-xlsx

node-xlsx is the simplest API — thin wrapper around SheetJS:

import xlsx from "node-xlsx"
import { readFileSync, writeFileSync } from "fs"

// Parse:
const workSheetsFromFile = xlsx.parse(readFileSync("packages.xlsx"))

const firstSheet = workSheetsFromFile[0]
console.log(firstSheet.name)  // "Sheet1"
console.log(firstSheet.data)  // 2D array: [[header1, header2], [val1, val2], ...]

// Build and write:
const data = [
  ["Package", "Downloads", "Version"],
  ["react", 25000000, "18.2.0"],
  ["vue", 7000000, "3.4.0"],
]

const buffer = xlsx.build([{ name: "Packages", data }])
writeFileSync("output.xlsx", buffer)

node-xlsx is perfect when you just need to read or write simple 2D data without any formatting.


Feature Comparison

FeatureSheetJS (xlsx)ExcelJSnode-xlsx
XLSX read/write
XLS (legacy)
CSV, ODS, Numbers✅ 20+ formats
Streaming read✅ (Pro)
Streaming write✅ (Pro)
Cell styling✅ Limited✅ Excellent
Formulas✅ Read✅ Read + Write
Charts
Conditional formatting
Pivot tables
Browser support
TypeScript
Bundle size~900KB~600KB~1MB (wraps xlsx)

When to Use Each

Choose SheetJS if:

  • Reading legacy XLS or other non-XLSX formats (ODS, Numbers, CSV)
  • You need browser + Node.js support with the same API
  • Simple JSON ↔ spreadsheet conversion without formatting

Choose ExcelJS if:

  • Generating formatted Excel reports (colors, fonts, borders, charts)
  • Processing large files via streaming (memory efficiency)
  • Pivot tables, conditional formatting, or advanced Excel features

Choose node-xlsx if:

  • Maximum simplicity for basic XLSX read/write
  • TypeScript project that needs tiny API surface
  • You're processing simple 2D data without any cell formatting

Methodology

Download data from npm registry (weekly average, February 2026). Feature comparison based on SheetJS Community Edition 0.20.x, ExcelJS 4.x, and node-xlsx 0.23.x.

Compare data processing library packages on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.