How to save a Chart.js chart as a PNG
Chart.js draws to a <canvas>, so its built-in toBase64Image() exports the chart canvas, including its built-in labels. When you want the title, HTML legend and container in the picture too, capture the whole component with SnapDOM.
Wrap the chart, wait for it to finish drawing, then const img = await snapdom.toPng(wrapper). You get one PNG with the canvas and everything around it.
The problem
Chart.js renders inside a <canvas> element. Its native chart.toBase64Image() exports exactly that canvas and drops anything you laid out in HTML around it: a heading, a custom legend, annotations, the card background. If your export needs to look like the chart your users actually see on the page, you need to capture the surrounding DOM as well.
SnapDOM captures a DOM subtree — including live <canvas> pixels — into a single image, so the chart and its frame come out as one composited PNG.
Step 1 — Install
npm install @zumer/snapdom@latest chart.js
Without a bundler, load both from a CDN:
import { snapdom } from "https://unpkg.com/@zumer/snapdom@latest/dist/snapdom.mjs"; import Chart from "https://cdn.jsdelivr.net/npm/chart.js/auto/+esm";
Step 2 — Wrap the chart in a capture target
Put the canvas inside an element that also holds whatever you want in the exported image:
<div id="chart-card" style="padding:20px;background:#fff;border-radius:12px;"> <h3>Monthly revenue</h3> <canvas id="chart"></canvas> </div>
Step 3 — Wait for the draw, then capture
A canvas only holds pixels once Chart.js has painted them. Capturing during the entry animation gives you a blank or half-drawn plot. The reliable fix is to capture from the animation's onComplete callback:
import { snapdom } from '@zumer/snapdom'; import Chart from 'chart.js/auto'; const chart = new Chart(document.getElementById('chart'), { type: 'bar', data: { labels: ['Jan', 'Feb', 'Mar', 'Apr'], datasets: [{ label: 'Revenue', data: [12, 19, 9, 22] }] }, options: { animation: { onComplete: async () => { const img = await snapdom.toPng(document.getElementById('chart-card')); document.body.appendChild(img); } } } });
Prefer a static export with no timing games? Set options.animation = false and capture right after new Chart(...) returns.
Step 4 — Download it as a file
const result = await snapdom(document.getElementById('chart-card')); await result.download({ format: 'png', filename: 'revenue-chart' });
Or get a Blob to upload:
const blob = await snapdom.toBlob(document.getElementById('chart-card'), { format: 'png' });
Expected result
A single PNG that matches what's on screen: the <h3> heading, the card's white background and rounded corners, and the fully rendered bar chart — all in one image at your device pixel ratio. Pass { scale: 2 } to toPng for a sharper, higher-resolution export.
Try it live
A real Chart.js bar chart, in a card with an HTML heading. Capture it and compare: the exported PNG includes the heading and the card frame — exactly what toBase64Image() would drop:
Monthly revenue
Going further
Capture the wrapper when you need the chart together with surrounding HTML labels and card styling. For an animation, the optional GIF export plugin can capture a sequence of frames; see the plugins page.
Frequently asked questions
My exported chart is blank — why?
You captured before Chart.js finished drawing. Capture inside options.animation.onComplete, or set options.animation = false so the chart paints synchronously.
Can I export just the canvas without the surrounding HTML?
Use chart.toBase64Image() when the canvas alone is the output you need, including Chart.js titles or legends drawn inside it. Capture the wrapper with SnapDOM when you also need surrounding HTML, custom legends or container styling.
How do I get a higher-resolution image?
Pass { scale: 2 } (or higher) to snapdom.toPng. SnapDOM also respects window.devicePixelRatio automatically.
Does this work with react-chartjs-2?
Yes. Put a ref on the wrapping element and call snapdom.toPng(ref.current) from the chart's onComplete option. The wrapper is just a DOM node — SnapDOM doesn't care how it was rendered.
Try the live demo
SnapDOM captures a live <canvas> in the browser — see it running before you install.