mrkeyoor.com_
Sat 08 Aug 17:40 UTC
npmDataupdated 08 Aug 2026

canvas

canvas, usually called node-canvas, implements much of the browser Canvas 2D API in Node.js using the native Cairo and Pango graphics stack. It can draw shapes, paths, text, gradients, pixels, and loaded images, then encode PNG, JPEG, PDF, SVG, data URLs, or streams. It adds server-specific tools for font registration, image buffers, multipage PDFs, output metadata, and Cairo rendering controls. This is a native Node addon, not a small browser canvas polyfill, and supported-platform binaries determine whether installation is one command or a system-library project.

Verdict

node-canvas is the established choice when server code genuinely needs the Canvas 2D drawing model. Do not install it for routine image transformations, and confirm your deployment has a prebuilt binary or budget the native Cairo toolchain before committing.

API stability5/5Most drawing calls follow the stable Web Canvas 2D API, while node-specific createCanvas, loadImage, registerFont, buffer, and stream methods are mature.
Docs5/5The README clearly lists binary platforms and native dependencies, then documents every utility, output format, encoding option, PDF feature, pixel format, and caveat while linking standard drawing calls to MDN.
Maintenance4/5Version 3.2.3 is current and the repository was pushed yesterday, but 454 open issues and PRs reflect the burden of native builds, codecs, platforms, and partial web API compatibility.
Ecosystem5/5It is widely expected by Node rendering and DOM-test tooling, follows a familiar web API, includes TypeScript declarations, and outputs raster, PDF, and SVG formats.

Use it if

  • You already know the browser Canvas 2D API and need the same drawing model in Node
  • You generate charts, social cards, certificates, image snapshots, or simple PDFs on the server
  • Accurate text layout through Cairo and Pango matters more than having a pure JavaScript install
  • A test or rendering tool expects the canvas package specifically, as jsdom and related libraries often do
Skip it if

Setup reality

npm install canvas downloads prebuilt binaries only for macOS x64 or arm64, glibc Linux x64, and Windows x64. Everything else compiles from source and needs Cairo plus Pango; JPEG, GIF, and SVG input add optional system libraries. Node 18.12+ is required, with the current engine range preferring 20.9+ outside Node 18. Font availability changes output across machines, so register and ship fonts for deterministic renders.

Patterns

Draw and encode a PNGdraw-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-rendered image', 40, 190)

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

toBuffer without a callback encodes synchronously, so large renders can block the Node event loop.

Register a bundled font before drawingregister-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('Consistent typography', 40, 100)

Register fonts before creating dependent text layouts and ship the licensed font file with every deployment.

Load and crop an imageload-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 loading only works when the installed binary was built with JPEG support; source builds need the JPEG development library.

Validate a remote image URL before loadingconstrain-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)

loadImage accepts remote URLs. Enforce host, protocol, redirect, response-size, and image-dimension limits outside this library.

Encode a JPEG with quality controlsencode-jpeg

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

JPEG drops transparency and requires JPEG support in the native build; paint an explicit background before encoding.

Stream PNG output to a filestream-png

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

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

Streaming avoids holding the encoded output buffer all at once, though the drawing surface itself remains in memory.

Read and modify pixel dataedit-pixels

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

Per-pixel JavaScript loops are expensive for large images; Sharp is usually faster for standard filters.

Generate a multipage PDFcreate-multipage-pdf

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 text mode makes PDF text selectable and smaller; metadata needs Cairo 1.16 or newer.

Stream a PDF responsestream-pdf

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. Streaming reduces encoded-buffer memory but drawing work still happens in process.

Create SVG outputrender-svg

const fs = require('node:fs')
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 SVG structure.

Alternatives

PackageRegistryPick it when
skia-canvasnpmYou want a Canvas-like Node API backed by Skia with a different prebuilt-binary matrix
sharpnpmYour work is image resizing, compositing, conversion, metadata, and efficient pipelines rather than freeform Canvas drawing
pureimagenpmYou need a pure JavaScript Canvas-style renderer and can accept lower fidelity and a smaller feature set