chartjs-node-canvas

Server-side Chart.js rendering to PNG, JPEG, SVG, PDF, or animated GIF frames, with no browser or DOM required.

Library
npm
v5.0.0
258stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
44/100Fair
Development Activity8
Maintenance20
Community68
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
60/100Good
Architecture62
Code Quality58
Innovation65
Learning Curve55

chartjs-node-canvas renders Chart.js charts on the server using node-canvas instead of a headless browser or a jsdom shim. It wraps Chart.js’s rendering pipeline around a Canvas 2D context so any Chart.js configuration — bar, line, pie, radar, or a fully custom chart type — can be turned into a PNG, JPEG, SVG, PDF, or raw buffer without ever touching a DOM.

Because Chart.js is a peer dependency, projects control their own Chart.js version and register plugins through one of four supported loading conventions: modern ES-module plugins, or three flavors of legacy loading for plugins that expect a require call or a global Chart variable. A second class, AnimatedChartJSNodeCanvas, exposes Chart.js’s animation lifecycle to produce per-frame buffers or data URLs — useful for generating animated GIFs of a chart’s transitions — which the base synchronous renderer intentionally disables for stability.

What You Get

  • A ChartJSNodeCanvas class with renderToBuffer, renderToDataURL, and renderToStream (plus synchronous variants) for PNG, JPEG, SVG, and PDF output
  • An AnimatedChartJSNodeCanvas class that drives Chart.js’s animation lifecycle to produce an array of per-frame buffers or data URLs
  • Four plugin-loading strategies (modern, requireChartJSLegacy, globalVariableLegacy, requireLegacy) covering both current Chart.js plugin APIs and older plugins that expect a global Chart variable
  • Custom font registration via registerFont, so charts can use fonts that aren’t installed as system fonts
  • A chartCallback hook that exposes the ChartJS global for one-time configuration, such as global chart defaults, before any chart is rendered

Common Use Cases

  • Generating chart images for PDF reports or emailed dashboards where a browser can’t render Chart.js client-side
  • Producing chart thumbnails or preview images for a web app’s server-rendered pages
  • Building scheduled reporting pipelines that render the same Chart.js configuration used in the frontend, from a cron job or serverless function
  • Creating animated GIFs of a chart’s transition or entrance animation via AnimatedChartJSNodeCanvas

Under The Hood

Architecture ChartJSNodeCanvasBase is an abstract class that handles canvas/context/font setup and plugin registration in its initialize() method; both ChartJSNodeCanvas (synchronous single-frame rendering) and AnimatedChartJSNodeCanvas (animated multi-frame capture) extend it and implement their own private renderChart. A freshRequire helper gives each instance an isolated require of the canvas module and flushes Chart.js’s require cache after initialization, so multiple instances with different plugin configurations don’t leak global mutation into each other. The render path is consistent across both classes: construct a canvas at the configured width/height/type, force-disable responsive/animation flags that would otherwise throw outside a browser, temporarily monkey-patch global.Image so plugins expecting the browser Image API still work, construct a Chart.js instance against the canvas context, capture output, then destroy the chart to release memory. Because every public method funnels through this single construct-render-destroy path, a change to the underlying Canvas or Chart.js constructor shape would ripple through the entire public API at once.

Tech Stack Written in TypeScript and compiled via tsc into dist/, with canvas (^3.1.0, a native Cairo binding) providing the Canvas 2D implementation and chart.js (^4.4.8) as a peer dependency accessed through the auto-registering chart.js/auto entry point. tslib supplies compiled TypeScript helpers, mocha plus c8 handle testing and coverage, and clean-dest/release-it support the build and release scripts. Docker configuration (a docker/ directory and compose files) exists to pin native Cairo dependencies consistently across environments, and CI runs on a Windows matrix specifically to install Cairo/libjpeg binaries and a custom test font before building.

Code Quality Testing is split between src/index.spec.ts (unit tests for the synchronous renderer) and a substantially larger src/index.e2e.spec.ts, which does visual-regression testing via resemblejs against reference images checked into testData/; a separate tests/require/wtfnode.js helper detects leaked async handles after test runs. Coverage is collected with c8 (.c8rc) and uploaded to Codecov in CI. TypeScript typing is present throughout, but integration points with Chart.js and node-canvas frequently fall back to as any casts, so type safety has real gaps at the library’s edges. Notably, a tslint.json configuration exists in the repo, but the lint npm script is a literal echo TODO: Add linting — so static analysis isn’t actually enforced despite the config’s presence. Error handling is minimal: explicit Error throws for missing required options, no typed error hierarchy.

API Design The public surface is compact and mirrors node-canvas’s own API shape — renderToBuffer/renderToDataURL/renderToStream plus Sync variants — which keeps the learning curve low for anyone who has used node-canvas or Chart.js’s browser canvas API before. Getting started takes three lines: construct new ChartJSNodeCanvas({ width, height }), then call renderToBuffer(configuration). The plugin-loading API is the one notably idiosyncratic part of the design — four distinct properties exist purely to paper over several eras of Chart.js’s plugin-registration conventions, which is a legitimate ergonomic wart even though it’s clearly documented in the README. JSDoc comments cover every public method and are compiled into a generated API.md, giving reasonably thorough inline and generated documentation.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search