npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

screencut

v0.2.4

Published

Headless Chromium screenshots — Node.js SDK and CLI

Readme

screencut

Headless Chromium screenshots — CLI and Node.js SDK.

npm version license node


Install

npm install screencut

Chromium is downloaded automatically by Puppeteer on install. If you already have Chromium on your system (Docker, CI), skip the download and point to your binary instead:

PUPPETEER_SKIP_DOWNLOAD=true npm install screencut
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium screencut https://example.com

CLI

npx screencut <url> [output] [options]

output defaults to screenshot-YYYY-MM-DD-HH-mm-ss.png (with timestamp). The image format is inferred from the file extension when --type is not set.

| Flag | Default | Description | |------------------|------------------|---------------------------------------------------------------------------------| | --width | 1280 | Viewport width in pixels | | --height | 720 | Viewport height in pixels | | --type | png | Image format: png, jpeg, webp | | --quality | 80 | Compression quality for jpeg / webp (0–100) | | --scale | 1 | Device pixel ratio for HiDPI output (2 = retina) | | --full-page | off | Capture the full scrollable page | | --wait-for | — | CSS selector to wait for before capturing | | --wait-until | networkidle2 | Navigation strategy: load, domcontentloaded, networkidle0, networkidle2 | | --timeout | 30000 | Navigation timeout in ms | | --delay | 0 | Extra wait time in ms after page load | | --wait-for-stable | off | Enable intelligent waiting for page stability | | --scroll-delay | 500 | Delay in ms between scrolls when waitForStable is enabled | | --max-scroll-attempts | 5 | Max scroll attempts for lazy loading | | --no-wait-for-network-idle | off | Disable network idle check between scrolls | | --quiet | off | Suppress all output except errors | | --verbose | off | Enable verbose debug output | | --no-timestamp | off | Disable timestamp in output filename (enabled by default) | | --dark-mode | off | Emulate prefers-color-scheme: dark | | --emulate-media-type| screen | Emulate media type: screen, print, handheld | | --user-agent | — | Override the browser user-agent string | | --stdout | off | Write image bytes to stdout instead of a file | | -v, --version | | Print version and exit | | -h, --help | | Show help |

# Save to a file
screencut https://example.com
screencut https://example.com shot.png

# Full-page JPEG
screencut https://example.com shot.jpeg --full-page --quality 90

# Retina screenshot
screencut https://example.com retina.png --scale 2

# Faster capture — skip waiting for network idle
screencut https://example.com --wait-until domcontentloaded

# Wait for a specific element to appear
screencut https://example.com --wait-for "#hero" --timeout 10000

# Mobile user-agent
screencut https://example.com --user-agent "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)"

# Pipe bytes to another tool
screencut https://example.com --stdout | convert - -resize 400x thumb.jpg
screencut https://example.com --stdout > shot.png

# Full-page capture with lazy-loading and animations
screencut https://example.com --full-page --wait-for-stable
screencut https://example.com --full-page --wait-for-stable --scroll-delay 1000

# Dark mode
screencut https://example.com --dark-mode

# Disable timestamp
screencut https://example.com --no-timestamp

SDK

const screencut = require('screencut');

require('screencut') returns a ready-to-use instance. No setup needed.


capture(url, options?)Promise<Buffer>

const buf = await screencut.capture('https://example.com');

const buf = await screencut.capture('https://example.com', {
  width: 1920,
  height: 1080,
  type: 'jpeg',
  quality: 90,
  fullPage: true,
});

captureAsStream(url, options?)Readable

Returns a stream synchronously — the capture runs in the background and data is pushed when ready. Pipe it anywhere without awaiting.

// Stream to an HTTP response
res.setHeader('Content-Type', 'image/png');
screencut.captureAsStream('https://example.com').pipe(res);

// Stream to a file
screencut.captureAsStream('https://example.com').pipe(fs.createWriteStream('shot.png'));

captureAsBase64(url, options?)Promise<string>

const b64 = await screencut.captureAsBase64('https://example.com');
// "iVBORw0KGgoAAAANSUhEUgAA..."

captureAsDataURL(url, options?)Promise<string>

const dataUrl = await screencut.captureAsDataURL('https://example.com', {
  type: 'jpeg',
  quality: 85,
});
// "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."

const html = `<img src="${dataUrl}" />`;

captureToFile(url, outputPath, options?)Promise<void>

await screencut.captureToFile('https://example.com', './shot.png');

await screencut.captureToFile('https://example.com', './shot.jpeg', {
  type: 'jpeg',
  quality: 80,
  fullPage: true,
});

close()Promise<void>

Closes the underlying Chromium instance. The default instance closes itself automatically on SIGINT, SIGTERM, and beforeExit. Call close() manually when using a custom instance.

await screencut.close();

Options

All capture methods accept the same options object. Every field is optional.

| Option | Type | Default | Description | |---------------------|-----------|------------------|-------------------------------------------------------------------------------------| | width | number | 1280 | Viewport width in pixels | | height | number | 720 | Viewport height in pixels | | type | string | 'png' | Image format: 'png', 'jpeg', 'webp' | | quality | number | 80 | Compression quality for jpeg and webp (0–100) | | fullPage | boolean | false | Capture the full scrollable page | | waitFor | string | — | CSS selector to wait for before capturing | | waitUntil | string | 'networkidle2' | Navigation strategy: 'load', 'domcontentloaded', 'networkidle0', 'networkidle2' | | timeout | number | 30000 | Navigation timeout in milliseconds | | userAgent | string | — | Override the browser user-agent string | | deviceScaleFactor | number | 1 | Device pixel ratio for HiDPI / retina output | | delay | number | 0 | Extra wait time in ms after page load | | waitForStable | boolean | false | Enable intelligent waiting for page stability | | scrollDelay | number | 500 | Delay in ms between scrolls when waitForStable is enabled | | maxScrollAttempts | number | 5 | Max scroll attempts for lazy loading | | waitForNetworkIdle| boolean | true | Wait for network idle between scrolls | | quiet | boolean | false | Suppress all output except errors | | verbose | boolean | false | Enable verbose debug output | | darkMode | boolean | false | Emulate prefers-color-scheme: dark | | timestamp | boolean | true | Add timestamp to output filename (default: true) | | emulateMediaType | string | screen | Emulate media type: screen, print, handheld | | clip | object | — | Restrict capture to a region: { x, y, width, height }. Ignored when fullPage is true. |


Custom instance

Use new Screencut(defaults?) when you need an isolated browser with its own defaults. You are responsible for calling close().

const { Screencut } = require('screencut');

const mobile = new Screencut({
  width: 390,
  height: 844,
  userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15',
});

const buf = await mobile.capture('https://example.com');
await mobile.close();

LaunchDefaults extends all capture options and adds two browser-level fields:

| Option | Type | Default | Description | |------------|------------|---------|-----------------------------------------------| | headless | boolean | true | Set to false to debug captures visually | | args | string[] | [...] | Additional Chromium launch flags |


Recipes

Express screenshot endpoint

const express = require('express');
const screencut = require('screencut');

const app = express();

app.get('/screenshot', (req, res) => {
  const { url } = req.query;
  if (!url) return res.status(400).send('Missing ?url=');

  res.setHeader('Content-Type', 'image/png');
  screencut.captureAsStream(url).pipe(res);
});

app.listen(3000);

Thumbnail generation with sharp

const sharp = require('sharp');
const screencut = require('screencut');

const buf = await screencut.capture('https://example.com');
const thumb = await sharp(buf).resize(400, 225).toBuffer();

Batch captures

One instance handles multiple concurrent captures — no need to spawn a browser per URL.

const screencut = require('screencut');

const urls = ['https://example.com', 'https://github.com', 'https://nodejs.org'];

const buffers = await Promise.all(urls.map(url => screencut.capture(url)));

If you need truly isolated browser processes (different proxies, separate sessions), create one instance per process:

const { Screencut } = require('screencut');

const a = new Screencut({ args: ['--proxy-server=socks5://proxy1:1080'] });
const b = new Screencut({ args: ['--proxy-server=socks5://proxy2:1080'] });

const [bufA, bufB] = await Promise.all([
  a.capture('https://example.com'),
  b.capture('https://example.com'),
]);

await Promise.all([a.close(), b.close()]);

Clip a region

const buf = await screencut.capture('https://example.com', {
  clip: { x: 0, y: 0, width: 600, height: 400 },
});

TypeScript

Types are bundled. No @types package needed.

import screencut, { Screencut, CaptureOptions, LaunchDefaults } from 'screencut';

const options: CaptureOptions = {
  width: 1440,
  type: 'webp',
  quality: 90,
  fullPage: true,
  waitUntil: 'domcontentloaded',
};

const buf: Buffer = await screencut.capture('https://example.com', options);
import { Screencut, LaunchDefaults } from 'screencut';

const defaults: LaunchDefaults = {
  width: 390,
  height: 844,
  headless: true,
};

const instance = new Screencut(defaults);
const buf = await instance.capture('https://example.com');
await instance.close();

Requirements

  • Node.js >= 18.0.0
  • Chromium — downloaded automatically by Puppeteer on npm install

License

MIT