Export Charts as PNG, SVG, or CSV

ApexCharts provides three programmatic export methods: dataURI for PNG images, getSvgString for SVG vector files, and exportToCSV for data downloads. All three require the Exports feature to be registered before any chart is created.

Registering the Exports Feature

The Exports feature is not included in the lean core (apexcharts/core). This is intentional: keeping optional features out of the core reduces the JavaScript your users download. On the core you must import the feature once, before calling new ApexCharts(...).

If you import the default bundle (import ApexCharts from 'apexcharts'), the feature is already included and no extra import is needed.

// Option A: tree-shaking setup (import only the exports feature)
import ApexCharts from 'apexcharts/core'
import 'apexcharts/features/exports'

// Option B: default bundle (Exports included, no extra import needed)
import ApexCharts from 'apexcharts'

The import registers the feature globally. Any chart created after the import can use the export methods.

Download PNG

chart.dataURI() returns a Promise that resolves to { imgURI: string }. The imgURI value is a base64-encoded PNG data URI. You can assign it to an <img src>, open it in a new tab, or attach it to a download link.

When the PNG cannot be made (the browser gives no drawing context, an image in the chart fails to decode, or a cross-origin image taints the canvas), it resolves with an empty imgURI and logs a warning, so check imgURI before you use it. On the server it always resolves with an empty imgURI.

The example below triggers a file download when the user clicks a button:

document.querySelector('#download-btn').addEventListener('click', async () => {
  const { imgURI } = await chart.dataURI()
  const a = document.createElement('a')
  a.href = imgURI
  a.download = 'chart.png'
  a.click()
})

Internally, dataURI clones the chart SVG, strips tooltip and toolbar elements so they do not appear in the exported image, inlines legend CSS, converts any image fills to base64, then serializes to canvas and produces a PNG data URL.

High-DPI (2x) Export

By default, dataURI exports at 1:1 pixel density relative to the rendered chart. On high-DPI displays this can look soft when the PNG is viewed at full size. Pass scale: 2 to produce a 2x-resolution image. The chart on screen does not change; only the exported PNG is larger.

const { imgURI } = await chart.dataURI({ scale: 2 })

You can also fix the export width independently of the rendered chart width using the width option:

const { imgURI } = await chart.dataURI({ width: 1200 })

Preview the Chart in an img Element

Because imgURI is a standard data URI, you can assign it directly to any <img> element. This is useful for an in-page preview before prompting the user to save.

const { imgURI } = await chart.dataURI()
document.querySelector('#preview').src = imgURI

Export SVG

chart.getSvgString() returns a Promise that resolves to the raw SVG markup as a string. SVG is a vector format, so the exported file scales to any size without quality loss. It is also suitable as input for PDF renderers and design tools.

document.querySelector('#export-svg').addEventListener('click', async () => {
  const svgString = await chart.getSvgString()
  const blob = new Blob([svgString], { type: 'image/svg+xml' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = 'chart.svg'
  a.click()
  URL.revokeObjectURL(url)
})

URL.revokeObjectURL releases the in-memory blob URL after the download starts. Omitting it causes a memory leak for long-running pages with repeated exports.

Export CSV

chart.exportToCSV() triggers a browser file download immediately. It returns no value; the download starts as a side effect.

chart.exportToCSV({ fileName: 'sales-data' })

The default delimiter is a comma. For European locales where commas are used as decimal separators, switch to a semicolon:

chart.exportToCSV({
  fileName: 'sales-data',
  columnDelimiter: ';',
  lineDelimiter: '\n'
})

Available options:

OptionTypeDescription
fileNamestringBase file name without extension. Defaults to the chart title.
columnDelimiterstringCharacter between column values. Default: ,
lineDelimiterstringCharacter between rows. Default: \n
seriesarraySubset of series to include. Omit to export all series.

A pie, donut, radialBar or polarArea chart writes one row per slice under two headers, category and value (set by toolbar.export.csv.headerCategory and headerValue).

On an axis chart the file has one row per category: a category column, then one column per series, headed with the series name. A series with several values per point gets a column for each value:

Series typeColumns
candlestickopen, high, low, close
boxPlotminimum, q1, median, q3, maximum
rangeBar, rangeAreaminimum, maximum
violinminimum, maximum, observations (the count)

With more than one series those headers carry the series name too (AAPL open, AAPL high), so every series sits on the same row for its category. A series hidden from the legend keeps its columns, left empty. Streamgraph, waterfall and dumbbell charts export the values you gave, not the offsets or running totals they draw.

On a datetime x axis the category is written as the day (Mon Jan 01 2024). When two categories fall on the same day, every category is written with its time instead (2024-01-01 09:30:00), in UTC or local time as xaxis.labels.datetimeUTC says. A toolbar.export.csv.categoryFormatter replaces both.

Toolbar Built-in Export

When the Exports feature is registered, the toolbar's download button automatically renders a dropdown with PNG, SVG, and CSV options. No extra code is required to wire up the downloads. Enable the toolbar in your chart options:

const options = {
  chart: {
    toolbar: {
      show: true,
      tools: {
        download: true
      }
    }
  },
  // ... rest of your options
}

Users can then click the camera icon in the top-right corner of the chart and choose their preferred format. This is the fastest path to giving users export capability without writing any download logic yourself.

React Example

When using react-apexcharts, access the underlying ApexCharts instance through the chartRef prop. The ref is populated after the chart mounts, so call export methods only in response to user events or after componentDidMount / useEffect.

import { useRef } from 'react'
import ReactApexChart from 'react-apexcharts'

export default function MyChart({ options, series }) {
  const chartRef = useRef(null)

  async function handleDownload() {
    const { imgURI } = await chartRef.current.dataURI()
    const a = document.createElement('a')
    a.href = imgURI
    a.download = 'chart.png'
    a.click()
  }

  return (
    <>
      <ReactApexChart
        chartRef={chartRef}
        type="line"
        options={options}
        series={series}
        height={350}
      />
      <button onClick={handleDownload}>Download PNG</button>
    </>
  )
}

Generate a PDF with jsPDF

dataURI integrates directly with jsPDF. Use scale: 2 to keep the PNG sharp at print resolution. addImage accepts the data URI string directly; no conversion is needed.

import { jsPDF } from 'jspdf'

async function exportToPDF(chart) {
  const { imgURI } = await chart.dataURI({ scale: 2 })
  const pdf = new jsPDF({ orientation: 'landscape' })
  // addImage(imageData, format, x, y, width, height) with dimensions in mm
  pdf.addImage(imgURI, 'PNG', 10, 10, 280, 150)
  pdf.save('report.pdf')
}

Install jsPDF separately: npm install jspdf. ApexCharts does not bundle it.

Printing the Page

Printing the page the chart lives on is a different problem from exporting an image, and since 7.1.0 ApexCharts handles it for you. The printed sheet is a layout the page never sees: nothing measures it, no resize is reported for it, and matchMedia('print') is still false while beforeprint runs. A chart sized from a wide screen would print at its screen width and lose the right-hand side off the paper.

ApexCharts hooks beforeprint / afterprint, re-lays the chart out to a printable width, and restores it when the dialog closes. It is on by default:

chart: {
  print: {
    enabled: true,
    width: 700    // CSS px; the default suits A4 and Letter portrait
  }
}

A chart already narrower than print.width is left alone. Set print: false to opt out and leave printing to the browser. See chart.print for the full reference.

Server-Side Rendering

dataURI and getSvgString require a rendered DOM chart. They cannot be called on a Node.js server because there is no browser canvas or SVG serialization context.

If you need to generate a chart image on the server (for email, PDF pipelines, or open-graph images), use the apexcharts/ssr entry point instead:

import { renderToString } from 'apexcharts/ssr'

const svgString = await renderToString(options)

renderToString runs without a DOM and returns an SVG string. From there you can pipe it to sharp, puppeteer, or any headless rasterizer to produce a PNG.

Limitations

Web fonts embed only from stylesheets. With toolbar.export.embedFonts on (the default), the export inlines the @font-face rules and font files for the families the chart uses, so SVG and PNG text keeps its face. Rules are read from the page's stylesheets. In a same-origin stylesheet that includes @imported sheets and rules nested in @media, @supports or @layer. A stylesheet from another origin is fetched again and only the @font-face rules written in it are read, so its @imports are not followed. A relative url() resolves against its own stylesheet. A font file on another origin that denies CORS, or a font added only through the JavaScript FontFace API, falls back to a generic face.

dataURI produces PNG only. The method converts SVG to canvas internally. Some advanced SVG features (complex CSS filters, <foreignObject>, certain blend modes) may render differently or not at all in the canvas step. If exact vector fidelity matters, use getSvgString instead and work with the SVG directly.

Call export methods only after render completes. Calling dataURI or getSvgString before chart.render() resolves throws an error because there is no DOM node to serialize. Always await chart.render() first, or call export methods in response to the mounted event.