mrkeyoor.com_
Sun 20 Sept 19:55 UTC
npmDataupdated 20 Sept 2026

exceljs review

Our ExcelJS 4.4.0 install produced a working CommonJS package for reading, editing, and writing XLSX or CSV files in Node. Its workbook model exposes sheets, rows, cells, styles, formulas, validation, images, tables, and streaming XLSX I/O. The package is substantial: 22,672 KB unpacked, 9 direct dependencies, and a 926.7 KB minified browser bundle in our test. Version 4.4.0 added inline-string cell support and custom table autofilters, fixed boolean parsing and conditional-format number styles, improved large-workbook writing, and introduced `ignoreNodes` for faster reads when some workbook features are irrelevant.

Verdict

ExcelJS remains useful for styled Node exports and row-by-row XLSX writing. Do not choose it for formula calculation, charts, a lean browser bundle, or a project that requires a recently released dependency.

We installed it

Lab card: what happened when we installed exceljsScreenshot of exceljs documentation
Install✓ · 7.1s79 packages on disk · 36 MB · 6 deprecation warnings
ImportESM import works · require() works · CommonJS package
Browser263.5 KBgzipped (926.7 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns20 critical · 0 high · 2 moderate · 0 low (npm audit)

Answers from our run

Does exceljs install cleanly?

Yes. In a fresh container with an empty cache, npm install exceljs finished in 7 seconds, leaving 79 packages and 36 MB on disk. npm audit reported 2 known vulnerabilities. The install printed 6 deprecation warnings.

How much does exceljs add to a browser bundle?

263.5 KB gzipped (926.7 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does exceljs work with both ESM and CommonJS?

Yes. Both import 'exceljs' and require('exceljs') worked in Node 22 in our run. The package is published as CommonJS.

Does exceljs include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

exceljs or write-excel-file: which should you use?

write-excel-file: Use it for generation-only work where a smaller schema-driven writer is enough. ExcelJS remains useful for styled Node exports and row-by-row XLSX writing.

When should you not use exceljs?

The workbook needs formula calculation: ExcelJS stores formula text and an optional cached result, but it does not evaluate formulas

API stability4/5The workbook, worksheet, row, and cell model has stayed on major version 4 since 2020, and 4.4.0 has remained the npm release since October 2023. That makes deployed code predictable, although part of the calm comes from the lack of releases. The README still carries explicit known issues around merged-cell splicing, and document mode and streaming mode expose similar names with different capabilities, so the surface is stable without being uniform.
Docs3/5The README is unusually detailed for a package page: it covers cells, styles, formulas, CSV, streaming readers and writers, browser bundles, protection, images, validation, and performance switches with code. Finding a limit takes work because all of this lives in one long document, several warnings sit far from the relevant examples, and the new-features section mixes release history with merged pull requests rather than giving a concise versioned reference.
Maintenance2/5npm lists 4.4.0 as current and dates it to 2023-10-19. GitHub shows the last repository push on 2025-01-21 and 799 open issues and pull requests. The release did contain meaningful correctness fixes and large-workbook work, but our 2026 install also printed 6 deprecation warnings and reported 2 moderate vulnerabilities. Heavy weekly use has not translated into a newer published build.
Ecosystem4/5ExcelJS logged 13,613,734 downloads for the week ending 2026-08-23 and the repository has 15,445 stars. It works with both `require()` and ESM import in Node 22, includes TypeScript declarations, accepts Node streams, and covers both XLSX and CSV. The score stops short of five because browser delivery is 263.5 KB gzipped in our bundle test, and uncommon spreadsheet features still require another tool or an Office template workflow.

Use it if

  • An exported XLSX needs styled headers, number formats, frozen panes, filters, validation, or embedded images
  • You must read uploaded workbooks and inspect formulas, merges, comments, or styles alongside cell values
  • A server export is too large for an in-memory workbook and needs rows committed through the streaming writer
  • The same service must produce CSV and XLSX while sharing its row-building logic
Skip it if

Setup reality

Our clean install of ExcelJS 4.4.0 completed in 7.1 seconds. npm left 79 packages occupying 36 MB and printed 6 deprecation warnings. Audit found 2 known vulnerabilities, both moderate, with no critical or high findings. The package declares 9 direct dependencies, no peers, and 22,672 KB unpacked. It is CommonJS without an exports map; require() and ESM import both worked on Node 22.23.2. TypeScript declarations ship inside the package.

The ordinary Workbook API keeps the entire document in memory until writeFile() or writeBuffer() finishes. For a large server export, use ExcelJS.stream.xlsx.WorkbookWriter, commit each row, commit the sheet, and await the workbook commit. A committed row cannot be edited again. Streaming has feature gaps: images are unsupported, worksheet removal and unMergeCells() are unavailable, and styles or shared strings must be enabled in the writer options when needed.

Browser use works through the packaged browser build and workbook.xlsx.writeBuffer(). Our esbuild measurement was 926.7 KB minified and 263.5 KB gzipped. That cost arrives before any workbook data. There is no file-system or streaming writer in a tab, so memory grows with the generated file. The package's bundled types make imports pleasant, though the runtime remains CommonJS and has no exports map. Test the exact bundler path rather than importing internal dist files.

Reading can skip expensive XML sections with ignoreNodes, a 4.4.0 option useful when an upload handler only needs values. Cell and worksheet indexes are one-based, while workbook.worksheets is a normal zero-based array; worksheet IDs can have gaps after deletion. Formula cells expose stored formulas and cached results. Set a recalculation flag or provide results if recipients use viewers that do not recalculate on open. Build merges after row insertion because the documented splice behavior around merged ranges is unpredictable.

Patterns

Write a styled workbook to disk create-workbook

const ExcelJS = require('exceljs');

const book = new ExcelJS.Workbook();
const sheet = book.addWorksheet('Orders', {
  views: [{ state: 'frozen', ySplit: 1 }],
});
sheet.columns = [
  { header: 'Order', key: 'id', width: 12 },
  { header: 'Total', key: 'total', width: 14, style: { numFmt: '#,##0.00' } },
];
sheet.addRows(orders);
await book.xlsx.writeFile('orders.xlsx');

Column keys are construction helpers and do not round-trip as workbook metadata. Excel also restricts worksheet names, so validate user-supplied names before calling addWorksheet().

Load an XLSX buffer and visit populated rows read-upload

const book = new ExcelJS.Workbook();
await book.xlsx.load(uploadBuffer, {
  ignoreNodes: ['dataValidations', 'conditionalFormatting'],
});

const sheet = book.getWorksheet('Orders') ?? book.worksheets[0];
sheet.eachRow((row, number) => {
  if (number > 1) consume(row.getCell(1).value, row.getCell(2).value);
});

Cells use one-based indexes. ignoreNodes can speed up value-only imports, but skipped sections will be absent if you write this workbook back out.

Commit a large export row by row stream-export

const book = new ExcelJS.stream.xlsx.WorkbookWriter({
  filename: 'large.xlsx',
  useStyles: true,
  useSharedStrings: false,
});
const sheet = book.addWorksheet('Rows');
sheet.columns = [{ header: 'Id', key: 'id' }, { header: 'Name', key: 'name' }];

for await (const item of source) {
  sheet.addRow(item).commit();
}
sheet.commit();
await book.commit();

Committed rows leave memory and cannot be changed. Keep a row open until every formula, style, and merge involving it has been decided.

Send an XLSX directly through an HTTP response stream-http

res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
res.setHeader('Content-Disposition', 'attachment; filename=report.xlsx');

const book = new ExcelJS.stream.xlsx.WorkbookWriter({ stream: res });
const sheet = book.addWorksheet('Report');
for await (const item of rows) sheet.addRow([item.id, item.total]).commit();
sheet.commit();
await book.commit();

Once workbook bytes reach the client, an export error cannot become a clean JSON error response. Log stream failures because the user will otherwise receive only a truncated workbook.

Iterate through a large XLSX input read-stream

const reader = new ExcelJS.stream.xlsx.WorkbookReader('input.xlsx', {
  sharedStrings: 'cache',
  styles: 'ignore',
  hyperlinks: 'ignore',
});

for await (const sheet of reader) {
  for await (const row of sheet) processRow(row.values);
}

Caching shared strings resolves text cells for ordinary use. Ignoring styles saves work when the import only cares about values.

Store a formula with a cached result write-formula

sheet.getCell('D2').value = { formula: 'B2*C2', result: 42.5 };
book.calcProperties.fullCalcOnLoad = true;

const stored = sheet.getCell('D2');
console.log(stored.formula, stored.result);

ExcelJS does not calculate the expression. The result is supplied by your code or retained from the input file, and it can be stale until a spreadsheet application recalculates.

Apply a readable header and numeric format style-header

const header = sheet.getRow(1);
header.font = { bold: true, color: { argb: 'FFFFFFFF' } };
header.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF1F4E78' } };
header.alignment = { vertical: 'middle', horizontal: 'center' };
sheet.getColumn('total').numFmt = '#,##0.00';

Colors use eight-digit ARGB values with alpha first. Keep numeric cell values as numbers and let numFmt control display, or Excel totals and sorting will treat them as text.

Put a dropdown list on a cell add-validation

sheet.getCell('C2').dataValidation = {
  type: 'list',
  allowBlank: true,
  formulae: ['"Pending,Shipped,Cancelled"'],
  showErrorMessage: true,
  error: 'Select a listed status.',
};

The inline choices are one quoted formula string. For a long list, place values on a worksheet and reference that range because Excel limits inline validation text.

Create a title row and freeze headings merge-and-freeze

sheet.mergeCells('A1:D1');
sheet.getCell('A1').value = 'Quarterly orders';
sheet.views = [{ state: 'frozen', ySplit: 2 }];
sheet.autoFilter = 'A2:D2';

Only the top-left cell owns a merged value. Insert or splice rows before creating merges because the README calls merged-range movement unpredictable.

Place a PNG inside a worksheet embed-image

const imageId = book.addImage({
  filename: 'logo.png',
  extension: 'png',
});
sheet.addImage(imageId, {
  tl: { col: 0, row: 0 },
  ext: { width: 180, height: 60 },
});

Images work in the document workbook and are unavailable in streaming mode. Supply the correct extension because Excel may report a mismatched image part as a damaged file.

Create a browser download from a workbook browser-download

const bytes = await book.xlsx.writeBuffer();
const blob = new Blob([bytes], {
  type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
const url = URL.createObjectURL(blob);
const link = Object.assign(document.createElement('a'), { href: url, download: 'report.xlsx' });
link.click();
URL.revokeObjectURL(url);

Browser generation holds the workbook and output buffer in tab memory. Our package bundle was 263.5 KB gzipped before application code or workbook data.

Export one worksheet as CSV write-csv

await book.csv.writeFile('orders.csv', {
  sheetName: 'Orders',
  formatterOptions: { delimiter: ',', quoteColumns: true },
});

CSV keeps cell text and values only. Styles, formulas, merges, images, and extra worksheets do not survive this export format.

Alternatives

PackageRegistryPick it when
write-excel-filenpmUse it for generation-only work where a smaller schema-driven writer is enough.
xlsxnpmUse the npm package when format coverage matters more than rich workbook styling, after checking its distribution and licensing fit.
excel4nodenpmUse it for server-side XLSX generation when you never need to parse incoming workbooks.

More data guides

numpy · fsspec · pandas · sqlalchemy · pyarrow · lxml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.