npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

chartsheet

v0.3.1

Published

Write native Excel charts and pivot tables into .xlsx files built with ExcelJS or SheetJS

Readme

chartsheet

Native Excel charts and pivot tables for .xlsx files built with ExcelJS or SheetJS — the support neither library has.

ExcelJS is installed over 11 million times a week and cannot write a chart. Its pivot table code was merged twelve days after its last release and has never been published, so npm install exceljs cannot produce one either. SheetJS Community leaves charts to its paid tier, and does not offer pivot creation at all. So every spreadsheet exported by every app using them arrives as bare numbers, and whoever opens it selects the data and inserts the chart by hand.

This adds them. Real, native Excel objects — click one, change a number, it redraws. Not images.

npm install chartsheet

An Excel worksheet with a column chart written into it

Documentation · charts with ExcelJS · pivot tables · with SheetJS · why charts vanish · check a file in your browser

Use

const fs = require('fs')
const ExcelJS = require('exceljs')
const { addChart } = require('chartsheet')

async function main () {
  const wb = new ExcelJS.Workbook()
  const ws = wb.addWorksheet('Data')
  ws.addRow(['Month', 'Sales', 'Costs'])
  ws.addRow(['Jan', 120, 90])
  ws.addRow(['Feb', 150, 95])
  ws.addRow(['Mar', 180, 110])

  let buffer = await wb.xlsx.writeBuffer()   // ExcelJS writes the sheet

  buffer = await addChart(buffer, {          // chartsheet adds the chart
    type: 'bar',
    title: 'Quarterly performance',
    categories: "'Data'!$A$2:$A$4",
    series: [
      { nameRef: "'Data'!$B$1", ref: "'Data'!$B$2:$B$4" },
      { nameRef: "'Data'!$C$1", ref: "'Data'!$C$2:$C$4" },
    ],
    anchor: { col: 4, row: 1 },              // top-left cell, zero-based: E2
  })

  fs.writeFileSync('report.xlsx', buffer)
}

main()

The snippets after this one are fragments — they assume they sit inside an async function, because await cannot go at the top level of a CommonJS file.

Works the same on a SheetJS workbook:

const buffer = XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' })
const withChart = await addChart(buffer, spec)

Chart types

bar · column · line · pie · doughnut · area · scatter · radar

Options

| Option | Meaning | |---|---| | type | one of the types above; defaults to bar | | title | chart title; omit for no title | | series | [{ ref, nameRef, name, colour }]ref is required | | categories | cell range for the category labels | | sheet | worksheet name to place the chart on; defaults to the first | | anchor | { col, row }, zero-based top-left cell | | width / height | pixels; defaults 600 × 340 | | stacked | stack the series | | horizontal | horizontal bars (bar only) | | dataLabels | print values on the chart | | legend | 'r', 'l', 't', 'b', or false to hide | | numberFormat | value-axis format, e.g. '#,##0.00' | | gridlines | false to remove value-axis gridlines | | xTitle / yTitle | axis titles |

Several charts at once:

const { addCharts } = require('chartsheet')
buffer = await addCharts(buffer, [barSpec, lineSpec, pieSpec])

Pivot tables

exceljs has no pivot table API. Support was merged into its master branch on 2023-10-31, twelve days after the last release, and has never been published — so npm install exceljs cannot give you one. chartsheet writes the pivot parts into the finished file.

const { addPivotTable } = require('chartsheet')

buffer = await addPivotTable(buffer, {
  sourceSheet: 'Data',
  sourceRef: 'A1:C500',      // include the header row
  targetSheet: 'Report',     // must already exist
  anchor: 'A3',
  rows: ['Region'],
  columns: ['Product'],
  values: [{ field: 'Sales', fn: 'sum' }],
})

| Option | Meaning | |---|---| | sourceSheet | Sheet holding the source table. Defaults to the first sheet | | sourceRef | Range including the header row, e.g. 'A1:C500' | | targetSheet | Sheet the table is written to. Must exist | | anchor | Top-left cell of the table. Default 'A3' | | rows / columns / filters | Field names, taken from the header row | | values | ['Sales'], or [{ field, fn, name }] | | name | Table name. Default 'PivotTable1' |

fn is one of sum, count, average, max, min, product, countNums, stdDev, stdDevp, var, varp.

addPivotTables(buffer, [spec, spec]) writes several in one pass.

The aggregation itself is left to Excel. The cache definition sets refreshOnLoad, so Excel rebuilds rows, columns and totals from the cached records when the file opens — the same division of labour the charts use, and far more robust than reimplementing Excel's own aggregation in JavaScript.

validate() checks pivot wiring too: that cacheId resolves to a <pivotCache> in the workbook, that the table part relates to its cache definition, and that recordCount matches the records actually written. All three are silent failures otherwise.

Validating a workbook

Excel reports a damaged file only as "we found a problem with some content", with no detail. This tells you what is actually wrong:

const { validate } = require('chartsheet')

const { valid, errors, warnings } = await validate(buffer)
// errors: [ 'xl/charts/chart1.xml: no <Override> content type — ...' ]

It checks the things that make Excel refuse a file: unresolved relationships, missing content-type overrides, duplicate shape ids, drawing parts that are related but never referenced, and <drawing> placed out of schema order. Useful on any xlsx, not only ones this library touched.

Why this exists

Adding a chart to an xlsx means writing a chart part, a drawing part, two sets of relationships, and content-type overrides — and getting any of it slightly wrong produces a file Excel refuses to open, with no useful error. The most-upvoted feature request on ExcelJS asked for chart support in 2016 and is still open.

Notes

  • Cell references use standard Excel syntax and should be absolute: 'Sheet name'!$B$2:$B$10
  • A worksheet holds one drawing part; every chart on that sheet becomes another anchor inside it, which this handles for you
  • Output opens in Excel, Google Sheets and LibreOffice

Keeping charts through a read-write cycle

ExcelJS does not model chart parts, so opening a workbook that has charts, editing it and writing it back silently deletes every chart. No error, no warning. Reported on ExcelJS in 2020, 2021 and 2023; still open.

const { preserveCharts } = require('chartsheet')

const original = fs.readFileSync('template.xlsx')   // has charts

const wb = new ExcelJS.Workbook()
await wb.xlsx.load(original)
wb.getWorksheet('Data').getCell('B2').value = 999
const rewritten = await wb.xlsx.writeBuffer()       // charts are gone here

const output = await preserveCharts(original, rewritten)   // and back again

Or in two steps, if the edit happens elsewhere:

const record = await captureCharts(original)
// ...
const output = await restoreCharts(rewritten, record)

Sheets are matched by name, so charts land back where they belong even if sheet order changed. A sheet that was renamed or removed is skipped rather than guessed at.

Keeping pivot tables through a read-write cycle

The same hole, and the oldest open issue about it — ExcelJS #261, filed 2017. Load a workbook that has a pivot table, write it back, and the pivot tables, their caches and the workbook's <pivotCaches> entry are all gone. Nothing is corrupt; they were never written, so there is no error to catch.

const { preservePivotTables } = require('chartsheet')

const original = fs.readFileSync('report.xlsx')     // has a pivot table

const wb = new ExcelJS.Workbook()
await wb.xlsx.load(original)
wb.getWorksheet('Data').getCell('C2').value = 999
const rewritten = await wb.xlsx.writeBuffer()       // pivot is gone here

const output = await preservePivotTables(original, rewritten)

capturePivotTables / restorePivotTables split it in two, the same way the chart pair does.

The restored cache is marked refreshOnLoad, so Excel recomputes every total from the sheet as it opens the file. The records captured off the original describe the data before your edit, and a pivot showing yesterday's numbers is worse than one that takes a moment to redraw.

Most real templates have both, so there is one call that does both:

const { preserveAll } = require('chartsheet')
const output = await preserveAll(original, rewritten)   // charts and pivot tables

Licence

MIT