screencut
repoHeadless Chromium screenshots - CLI and Node.js SDK.
README.md
screencut
Headless Chromium screenshots — CLI and Node.js SDK.
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.png. 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 |
--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
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 |
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
