mrkeyoor.com_
Tue 22 Sept 18:49 UTC
npmWeb Frontendupdated 22 Sept 2026

canvas review

canvas 3.2.3 brings the browser's 2D Canvas drawing model to Node, with Cairo handling pixels and Pango laying out text. It creates PNG, JPEG, PDF, and SVG output from paths, shapes, images, and fonts. Node-only additions cover file and URL image loading, font registration, buffers, and output streams. The 3.2.3 release fixes compilation with GCC. Our install confirmed that this remains a native package: it ran a native step and occupied 26 MB even though application code can load it through either require or ESM import.

Verdict

Install canvas when your Node service truly needs Canvas 2D drawing or PDF output. For ordinary image transformation, Sharp is a better fit, and any v3 deployment outside the listed binary targets must budget for native build dependencies.

We installed it

Lab card: what happened when we installed canvasScreenshot of canvas documentation
Install✓ · 2s38 packages on disk · 26 MB · 1 deprecation warning · native build step
ImportESM import works · require() works · CommonJS package
Browser0.6 KBgzipped (1.1 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does canvas install cleanly?

Yes. In a fresh container with an empty cache, npm install canvas finished in 2 seconds, leaving 38 packages and 26 MB on disk, after a native build step. npm audit reported no known vulnerabilities. The install printed 1 deprecation warning.

How much does canvas add to a browser bundle?

0.6 KB gzipped (1.1 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does canvas work with both ESM and CommonJS?

Yes. Both import 'canvas' and require('canvas') worked in Node 22 in our run. The package is published as CommonJS.

Does canvas include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

canvas or skia-canvas: which should you use?

skia-canvas: Choose it for a Canvas-like Node API backed by Skia and a different native binary matrix. Install canvas when your Node service truly needs Canvas 2D drawing or PDF output.

When should you not use canvas?

You only resize, crop, rotate, or convert existing images. Sharp's operation pipeline fits that work better than manually painting a canvas.

API stability4/5The central drawing surface and context track the long-lived browser Canvas 2D API, and the v3 README still documents the familiar createCanvas, loadImage, registerFont, toBuffer, and stream calls. Node-specific pixel formats and Cairo controls remain extensions rather than web standards. A v4 prerelease changes installation, font handling, worker support, and supported binaries, so teams planning that upgrade should test native and font behavior instead of assuming a drop-in change.
Docs4/5The versioned README names every v3 binary target, lists native packages for common operating systems, separates optional codec libraries, and documents output methods, font registration, PDF pages, pixel formats, and Cairo-only controls. Standard drawing calls are delegated to MDN and API compatibility lives in a wiki. That split is workable, but readers must make sure they are on the v3 tag because the default branch README now describes the v4 prerelease and a different installation contract.
Maintenance4/5GitHub recorded a push on 2026-08-24, reports 10,689 stars, and shows an unarchived repository with 414 open issues and pull requests. Version 3.2.3 shipped on 2026-03-31 with a focused GCC build fix. Active v4 prerelease work addresses fonts, worker threads, binary size, the postinstall script, and more platforms, but maintaining native bindings across Cairo, codecs, operating systems, and Node releases leaves a broad issue surface.
Ecosystem5/5npm counted 8,347,998 downloads in the latest completed week. The package uses the same path and text concepts as browser Canvas, works with CommonJS and ESM callers in our check, and bundles TypeScript declarations. PNG, JPEG, PDF, and SVG output plus integrations that expect the `canvas` package give it wide reach in server rendering and DOM test setups. That reach still depends on the native Cairo stack, so JavaScript package compatibility alone is not enough.

Use it if

  • Your server generates social cards, certificates, charts, image snapshots, or PDFs with Canvas 2D drawing calls.
  • Existing code or a test environment expects the `canvas` package and browser-like CanvasRenderingContext2D methods.
  • You need Cairo and Pango text rendering, custom font registration, or selectable glyph text in generated PDFs.
  • PNG, JPEG, PDF, and SVG output must come from one drawing surface rather than separate format-specific libraries.
Skip it if

Setup reality

We installed canvas 3.2.3 in a fresh unprivileged Node 22 Bookworm container with no cache. npm finished in 2 seconds, ran a native or compile step, printed one deprecation warning, and left 38 packages using 26 MB. The package itself has 2 direct dependencies, no peer dependencies, and 23872 KB unpacked. npm audit reported zero known vulnerabilities. Its engine range is Node ^18.12.0 || >=20.9.0.

Version 3 downloads binaries for macOS x64 and arm64, glibc Linux x64, and Windows x64. Other systems compile from source with Cairo and Pango. GIF, SVG, and JPEG input require their optional native libraries. A successful laptop install therefore does not prove that Alpine, ARM Linux, or a minimal production image will build. Pin the runtime image and test installation in that exact image.

The package is CommonJS without an exports map, but both require() and ESM import worked in our check. TypeScript declarations are bundled. Our esbuild probe produced 1.1 KB minified and 0.6 KB gzipped because browser resolution reaches a small shim; those numbers do not include native rendering and should not be read as a browser implementation.

Fonts come from the host unless you call registerFont, so the same code can wrap or measure text differently across machines. Ship licensed font files and register them before layout. toBuffer() without a callback and JPEG streaming both perform synchronous encoding according to the README, which can stall a busy event loop. Keep dimensions bounded, prefer asynchronous PNG encoding or streams where supported, and isolate heavy rendering work from request handling.

Patterns

Draw and encode a PNG draw-png

const { createCanvas } = require('canvas')

const canvas = createCanvas(640, 360)
const ctx = canvas.getContext('2d')
ctx.fillStyle = '#0f172a'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.fillStyle = '#fff'
ctx.font = 'bold 42px sans-serif'
ctx.fillText('Server image', 40, 190)

const png = canvas.toBuffer('image/png')

The synchronous toBuffer form blocks the event loop while encoding. Use the callback form for large PNG work.

Encode PNG without blocking on the result encode-png-async

const png = await new Promise((resolve, reject) => {
  canvas.toBuffer((error, buffer) => {
    if (error) reject(error)
    else resolve(buffer)
  }, 'image/png', { compressionLevel: 6 })
})

The callback form is asynchronous for PNG. The canvas pixel surface still stays in process memory.

Register a bundled font register-custom-font

const { registerFont, createCanvas } = require('canvas')

registerFont('./fonts/Inter-Bold.ttf', {
  family: 'Inter',
  weight: '700',
})
const canvas = createCanvas(800, 400)
const ctx = canvas.getContext('2d')
ctx.font = '700 48px Inter'
ctx.fillText('Fixed typography', 40, 100)

Register the font before measuring or drawing text, and deploy the same licensed file everywhere.

Clear registered fonts between tests reset-test-fonts

const { deregisterAllFonts } = require('canvas')

afterEach(() => {
  deregisterAllFonts()
})

Font registration is process-global. Clearing it prevents one test's font setup from leaking into another.

Load and crop a local image load-local-image

const { createCanvas, loadImage } = require('canvas')

const image = await loadImage('./input/photo.jpg')
const canvas = createCanvas(400, 400)
const ctx = canvas.getContext('2d')
ctx.drawImage(image, 100, 0, 400, 400, 0, 0, 400, 400)
const output = canvas.toBuffer('image/png')

JPEG input requires JPEG support in the installed native build. A source build needs the JPEG development library.

Restrict a remote image source validate-remote-image

const source = new URL(userSuppliedUrl)
if (source.protocol !== 'https:' || !allowedHosts.has(source.hostname)) {
  throw new Error('image source is not allowed')
}
const image = await loadImage(source.href)
ctx.drawImage(image, 0, 0, 300, 200)

The package accepts remote URLs but does not enforce SSRF, redirect, response size, or decoded dimension limits.

Encode a JPEG encode-jpeg

ctx.save()
ctx.globalCompositeOperation = 'destination-over'
ctx.fillStyle = '#ffffff'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.restore()

const jpeg = canvas.toBuffer('image/jpeg', {
  quality: 0.85,
  progressive: true,
  chromaSubsampling: false,
})

JPEG has no alpha channel and its encoding is synchronous. Paint a background and avoid this path in latency-sensitive request handlers.

Stream PNG output to a file stream-png

const fs = require('node:fs')
const { pipeline } = require('node:stream/promises')

await pipeline(
  canvas.createPNGStream({ compressionLevel: 6 }),
  fs.createWriteStream('output.png'),
)

Streaming avoids a second full encoded buffer, though the drawing surface remains allocated in memory.

Change raw pixel values edit-pixels

const pixels = ctx.getImageData(0, 0, canvas.width, canvas.height)
for (let i = 0; i < pixels.data.length; i += 4) {
  const gray = Math.round(
    pixels.data[i] * 0.299 +
    pixels.data[i + 1] * 0.587 +
    pixels.data[i + 2] * 0.114
  )
  pixels.data[i] = gray
  pixels.data[i + 1] = gray
  pixels.data[i + 2] = gray
}
ctx.putImageData(pixels, 0, 0)

Large JavaScript pixel loops are expensive. Use Sharp when the job is a standard image filter.

Create a multipage PDF create-multipage-pdf

const { createCanvas } = require('canvas')
const pdf = createCanvas(595, 842, 'pdf')
const ctx = pdf.getContext('2d')
ctx.textDrawingMode = 'glyph'
ctx.font = '24px Helvetica'
ctx.fillText('Page one', 50, 80)
ctx.addPage()
ctx.fillText('Page two', 50, 80)

const buffer = pdf.toBuffer('application/pdf', {
  title: 'Two page report',
  author: 'Example service',
})

Glyph mode keeps text selectable and can reduce file size. PDF metadata needs Cairo 1.16 or newer.

Stream a PDF response stream-pdf-response

res.setHeader('Content-Type', 'application/pdf')
res.setHeader('Content-Disposition', 'inline; filename=report.pdf')
pdf.createPDFStream({ title: 'Report' }).pipe(res)

Create the canvas with type `pdf` first. Drawing state and the page surface still live inside the Node process.

Create SVG output render-svg

const fs = require('node:fs')
const { createCanvas } = require('canvas')
const svg = createCanvas(600, 200, 'svg')
const ctx = svg.getContext('2d')
ctx.fillStyle = '#2563eb'
ctx.fillRect(10, 10, 580, 180)
ctx.fillStyle = '#fff'
ctx.font = '32px sans-serif'
ctx.fillText('Vector output', 180, 115)
fs.writeFileSync('output.svg', svg.toBuffer())

Drawing an SVG image onto a canvas rasterizes that source; it does not preserve the source document structure.

Alternatives

PackageRegistryPick it when
skia-canvasnpmChoose it for a Canvas-like Node API backed by Skia and a different native binary matrix.
sharpnpmChoose it for resizing, compositing, format conversion, and metadata work on existing images.
pureimagenpmChoose it when a pure JavaScript renderer matters more than matching Cairo's text and format behavior.

More web frontend guides

postcss · react · react-dom · tailwindcss · htmlparser2 · tailwind-merge · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.