chartsheet

ExcelJS charts disappearing after a read and write

You load a template that has charts, change one cell, save it — and every chart is gone. No error, no warning.

Short answer. ExcelJS does not model chart or drawing parts. When it loads a workbook it keeps only the parts it understands, so anything it cannot represent is simply absent from the file it writes back. The charts are not corrupted; they were never written out. You have to lift them off the original and put them back afterwards.

Three workbooks in sequence: a template with a chart, the same file after ExcelJS rewrites it with the chart gone, and the file again with the chart restored.
Load, edit, write back — and the chart is not in the output. Nothing errors. preserveCharts carries it across.

Reproducing it

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

async function main () {
  // a template with a chart in it, standing in for yours
  const wb = new ExcelJS.Workbook()
  const ws = wb.addWorksheet('Data')
  ws.addRow(['Month', 'Sales'])
  ws.addRow(['Jan', 120])
  ws.addRow(['Feb', 150])
  const template = await addChart(await wb.xlsx.writeBuffer(), {
    type: 'bar',
    categories: "'Data'!$A$2:$A$3",
    series: [{ ref: "'Data'!$B$2:$B$3" }],
  })
  fs.writeFileSync('template.xlsx', template)

  // open it, change one number, write it back — the ordinary thing
  const edit = new ExcelJS.Workbook()
  await edit.xlsx.load(template)
  edit.getWorksheet('Data').getCell('B2').value = 999
  const rewritten = await edit.xlsx.writeBuffer()
  fs.writeFileSync('out.xlsx', rewritten)

  console.log('charts before:', chartCount(await captureCharts(template)))  // 1
  console.log('charts after: ', chartCount(await captureCharts(rewritten))) // 0

  // out.xlsx opens without complaint. It has your edit. It has no chart.
}

main()

This is not a bug you can configure away. It has been reported on ExcelJS in 2020 ("charts are disappearing from the worklist"), in 2021 ("read excel file with chart and write back but the chart is lost") and in 2023 ("accept and preserve files with charts"). All three are still open. The project's last release was October 2023 and its last commit January 2024, with 143 pull requests waiting.

It matters most in the workflow it breaks: a business keeps a formatted template with charts already laid out, and software fills in this month's numbers. That is the single most common way Excel is used programmatically, and it is exactly the case that loses everything.

Keeping the charts

Take a copy of the chart parts before ExcelJS touches the file, then put them back on the output:

const fs = require('fs')
const ExcelJS = require('exceljs')
const { addChart, preserveCharts, chartCount, captureCharts } = require('chartsheet')

async function main () {
  const wb = new ExcelJS.Workbook()
  const ws = wb.addWorksheet('Data')
  ws.addRow(['Month', 'Sales'])
  ws.addRow(['Jan', 120])
  ws.addRow(['Feb', 150])
  const original = await addChart(await wb.xlsx.writeBuffer(), {
    type: 'bar',
    categories: "'Data'!$A$2:$A$3",
    series: [{ ref: "'Data'!$B$2:$B$3" }],
  })

  const edit = new ExcelJS.Workbook()
  await edit.xlsx.load(original)
  edit.getWorksheet('Data').getCell('B2').value = 999
  const rewritten = await edit.xlsx.writeBuffer()

  const output = await preserveCharts(original, rewritten)   // charts put back
  fs.writeFileSync('out.xlsx', output)

  console.log('charts restored:', chartCount(await captureCharts(output)))  // 1
}

main()

If the edit happens somewhere else — another process, a queue, a different machine — split it into the two halves:

const { captureCharts, restoreCharts } = require('chartsheet')

const record = await captureCharts(original)   // plain data, serialisable
// ... later, elsewhere ...
const output = await restoreCharts(rewritten, record)

What it does carefully

Checking it worked

const { chartCount, captureCharts, validate } = require('chartsheet')

console.log(chartCount(await captureCharts(output)))   // 3
console.log(await validate(output))                    // { valid: true, errors: [] }

Does SheetJS have the same problem?

Yes, in its community build. Charts are not part of what it reads or writes, so a round trip drops them in the same way. The same capture-and-restore approach applies.

Questions

Will ExcelJS fix this?

Nothing suggests so. The last commit was January 2024 and 143 pull requests are unmerged. Our test suite asserts that ExcelJS still drops the chart before asserting we restore it, so it will tell us if that ever changes.

Does the restored chart still update when cells change?

Yes. It is the original native chart, still bound to its cell ranges, so your new values are what it draws.

What about images and other drawings?

Only chart anchors are carried across. ExcelJS handles images itself, and a sheet whose drawing it already rewrote is left alone rather than overwritten.