One dependable CLI for repeatable web screenshots. Every project ends up with a
throwaway screenshot.js that hardcodes a viewport and a 2x device scale and
produces something slightly different each run. This is that script, done once
and done properly.
- Predictable by default. Device scale factor is 1 unless you ask for more, so output dimensions match the numbers you passed and diffs are clean.
- Device presets for the phones and breakpoints you actually test.
- Element and region capture —
--selectoror--clip. - Dark mode via real
prefers-color-schemeemulation. - Batch mode — a list of URLs in, a folder of PNGs out, one browser launch.
- Built on Playwright / Chromium.
npm i -g @abdullahisactive/shot
npx playwright install chromium # one-time browser downloadOr run ad hoc:
npx @abdullahisactive/shot https://example.com -o home.pngRequires Node 20+.
shot <url> [options]
shot --batch list.txt [options]| Option | Meaning |
|---|---|
-o, --out <file> |
Output path. Default: auto-named from the URL. .jpg/.jpeg → JPEG |
-p, --preset <name> |
iphone-se iphone-15 iphone-15-pro-max pixel-7 ipad ipad-pro desktop desktop-lg desktop-xl og-card |
--width / --height |
Viewport size, overriding the preset |
--dpr <n> |
Device scale factor. Default 1. Use 2 for retina assets |
--full |
Full-page capture (scrolls to trigger lazy media first) |
--clip <x,y,w,h> |
Capture a fixed rectangle — handy for OG images |
--selector <sel> |
Capture only the first element matching sel |
--wait <sel> |
Wait for sel to appear before capturing |
--wait-until <e> |
load | domcontentloaded | networkidle (default networkidle) |
--delay <ms> |
Extra pause before capture (web fonts, entrance animations) |
--scheme <s> |
light | dark |
--hide <sel,sel> |
visibility:hidden these before capture — cookie bars, chat bubbles |
--lazy |
Scroll the page first even without --full |
--timeout <ms> |
Navigation timeout, default 30000 |
--quality <n> |
JPEG quality 1–100 |
# Desktop hero, exact 1:1 pixels
shot https://example.com -p desktop-lg -o hero.png
# Full mobile page in dark mode, cookie banner hidden
shot https://example.com -p iphone-15 --full --scheme dark \
--hide "#cookie-banner,.intercom-launcher" -o mobile-dark.png
# Just the pricing table
shot https://example.com/pricing --selector "#pricing" -o pricing.png
# A perfectly sized Open Graph image
shot https://example.com --clip 0,0,1200,630 -o og.png
# Shoot a whole site from a list, one browser launch
shot --batch pages.txt -p desktop-lg --fullpages.txt:
# marketing pages
https://example.com home.png
https://example.com/pricing pricing.png
https://example.com/about
Lines are URL [outfile]; omit the outfile and it's named from the URL.
Blank lines and # comments are ignored.
import { shoot } from "@abdullahisactive/shot/src/shoot.js";
await shoot({
url: "https://example.com",
out: "home.png",
preset: "desktop-lg",
full: true,
scheme: "dark",
});MIT © Abdullah Al Akhzar