The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
Nightmare’s .screenshot() action supports error-first callbacks in four useful forms. Call .screenshot(done) to receive a PNG Buffer in memory, .screenshot(path, done) to save a PNG file, .screenshot(clip, done) to receive a clipped buffer, or .screenshot(path, clip, done) to save a clipped PNG. Keep .end() after the screenshot action so the browser does not close before capture finishes.
The callback receives done(err, value) when the result is an in-memory buffer. When a path is supplied, Nightmare writes the buffer to disk and calls the callback after the write; handle the callback as completion/error notification rather than expecting image bytes as its second argument.
Nightmare screenshot callback signatures
The documented signature is .screenshot([path][, clip]); both arguments are optional. Nightmare always generates a PNG. A path changes the result from an in-memory value to a file write, while a clip limits the captured rectangle.
| Call | Where the PNG goes | Callback form | Use it when |
|---|---|---|---|
.screenshot(done) |
Node.js memory | done(err, buffer) |
You need to inspect, upload or transform the bytes yourself. |
.screenshot(path, done) |
File at path |
Usually done(err) after writing |
You want Nightmare to persist the PNG. |
.screenshot(clip, done) |
Node.js memory | done(err, buffer) |
You need only a rectangular region. |
.screenshot(path, clip, done) |
Clipped file at path |
done(err) after writing |
You want a cropped PNG saved directly. |
The action implementation accepts the arguments in that order and treats a function in the first or second position as the callback. Supplying both a path and a clip explicitly is the least ambiguous form.
#1 Best Overall
Get a screenshot buffer in a callback
Use the callback-only overload when your code needs PNG bytes rather than a filename. The callback is error-first: check err before reading buffer.
const Nightmare = require('nightmare');
const nightmare = Nightmare();
nightmare
.goto('https://example.com')
.wait('body')
.screenshot((err, buffer) => {
if (err) {
console.error('Screenshot failed:', err);
return;
}
console.log('PNG bytes:', buffer.length);
// buffer is a Node.js Buffer containing PNG data.
})
.end()
.then(() => console.log('browser closed'))
.catch(console.error);
.wait('body') makes the example wait for a document body before capturing. Replace it with a selector that identifies the content your page actually needs. The callback runs as part of Nightmare’s queued action sequence; .end() remains after .screenshot().
Write the buffer yourself
Keeping the result in memory gives you control over the destination and any later processing. For example:
const fs = require('fs');
const Nightmare = require('nightmare');
const nightmare = Nightmare();
nightmare
.goto('https://example.com')
.wait('body')
.screenshot((err, buffer) => {
if (err) return console.error(err);
fs.writeFileSync('/tmp/example.png', buffer);
})
.end()
.then(() => console.log('saved /tmp/example.png'))
.catch(console.error);
Use an asynchronous file operation in production if the callback performs other substantial work. The important distinction is that this overload returns the PNG to your callback; Nightmare itself has not been asked to write a path.
Save a PNG directly with the callback
Pass a filename first when Nightmare should perform the write:
Rank #2
const Nightmare = require('nightmare');
const nightmare = Nightmare();
nightmare
.goto('https://example.com')
.wait('body')
.screenshot('/tmp/example.png', err => {
if (err) {
console.error('Could not save screenshot:', err);
return;
}
console.log('saved /tmp/example.png');
})
.end()
.then(() => console.log('browser closed'))
.catch(console.error);
When a path is supplied, the generated action captures a buffer, calls Node’s file-writing operation, and invokes the callback after that write. Do not rely on a second callback argument containing the image in this form. A missing directory, an unwritable location or another filesystem problem is reported through err.
Capture only a clipped rectangle
A clip is an Electron-style capture rectangle. Represent it with the rectangle’s position and dimensions, for example { x: 0, y: 0, width: 800, height: 600 }. Without a path, the callback still receives a PNG buffer:
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →const Nightmare = require('nightmare');
const clip = { x: 0, y: 0, width: 800, height: 600 };
const nightmare = Nightmare();
nightmare
.goto('https://example.com')
.wait('body')
.screenshot(clip, (err, buffer) => {
if (err) return console.error(err);
console.log(`clipped PNG bytes: ${buffer.length}`);
})
.end()
.catch(console.error);
To write that same rectangle directly to disk, make the path explicit:
const Nightmare = require('nightmare');
const clip = { x: 120, y: 180, width: 640, height: 400 };
const nightmare = Nightmare();
nightmare
.goto('https://example.com')
.wait('body')
.screenshot('/tmp/example-card.png', clip, err => {
if (err) return console.error(err);
console.log('saved clipped PNG');
})
.end()
.catch(console.error);
Make clipping predictable
- The rectangle is measured against the visible capture context, not automatically against the page’s full document coordinates.
- If the target element is below the fold, scroll it into view before calculating or applying the rectangle.
- Obtain the element’s bounds in the same viewport state in which you capture it. Responsive layout, zoom and scrolling can otherwise produce an apparently empty or shifted crop.
- When you do not need cropping, omit
clip; the full screenshot is less sensitive to coordinate mistakes.
Use Nightmare’s Promise style instead
Nightmare wraps callback results into a native Promise. In modern code, omit the callback and consume the PNG in the next .then() handler:
const fs = require('fs');
const Nightmare = require('nightmare');
const nightmare = Nightmare();
nightmare
.goto('https://example.com')
.wait('body')
.screenshot()
.then(buffer => {
fs.writeFileSync('/tmp/example.png', buffer);
})
.end()
.then(() => console.log('saved and closed'))
.catch(console.error);
The Promise version has the same capture behavior: no path produces a PNG Buffer, and a clip can be supplied. Its practical differences are sequencing and error handling:
| Concern | Callback style | Promise style |
|---|---|---|
| Result delivery | Inside done(err, buffer) |
Next .then(buffer => ...) |
| Errors | Check err first |
Handle with .catch() |
| File output | Pass a path and receive completion | Write the returned buffer yourself, or use the path overload in a queued action |
| Clipping | Optional rectangle argument | Same optional rectangle argument |
| Browser lifetime | Keep .end() after the action |
Await or return the screenshot chain before closing |
Choose callbacks when you are integrating with an existing error-first API. Choose Promises when you are composing several asynchronous steps or using async/await.
Recommended Free Tools
Understand the overloads Nightmare selects
Nightmare’s screenshot action examines argument types. If the first argument is a function, it is the callback. If the second argument is a function, it is the callback and the first argument is interpreted as either a path or a clip object. This is why the following forms work:
// Buffer in memory
.screenshot(done)
// File on disk
.screenshot('/tmp/page.png', done)
// Clipped buffer
.screenshot({ x: 0, y: 0, width: 400, height: 300 }, done)
// Clipped file
.screenshot('/tmp/region.png', { x: 0, y: 0, width: 400, height: 300 }, done)
When a call could be read as either a path or a clip, use the three-argument form with a clear object for the rectangle and a function in the final position. Avoid passing a string when you intend an in-memory result: a string is interpreted as the output path.
Callback lifecycle and reliable sequencing
- Navigate with
.goto(). - Wait for a selector, page state or other condition that means the content is ready.
- Queue
.screenshot()with the desired overload. - Process the callback result or the Promise value.
- Only then queue or await
.end().
Calling .end() too early can close the browser before the capture or file write completes. In a callback chain, leave .end() after the screenshot action. In Promise code, return or await the screenshot Promise before ending the session. Always attach a final .catch() so an action failure does not become an unhandled rejection.
Troubleshoot common screenshot callback failures
The callback has no buffer
Check whether you passed a path. With .screenshot('/tmp/page.png', done), Nightmare is reporting completion of a file write; it is not promising image bytes as the second callback value. Remove the path when you need done(err, buffer), or read the saved file after a successful callback.
Rank #4
The callback never appears to fire
Verify that the Nightmare chain reaches the screenshot action and that .end() is not being called in a separate branch first. Keep the actions in one chain, return the chain from the surrounding function, and add a final .catch(console.error).
The overload is being interpreted incorrectly
Use explicit argument order. A callback-only capture is .screenshot(done); a clipped in-memory capture is .screenshot(clip, done); a clipped file is .screenshot(path, clip, done). Ensure clip is an object and done is a function.
The crop is blank or shifted
The rectangle uses the visible capture context. Scroll the element into view, calculate its bounds after layout has settled, and capture without changing the viewport between those operations. Check width and height for positive values and confirm that the coordinates fall inside the current viewport.
The callback receives an error
Log the complete error and stop processing the result. For a path capture, inspect filesystem permissions and confirm that the parent directory exists. For either style, also verify that navigation and any selector wait completed successfully before the screenshot action.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
The PNG is not the format you expected
Nightmare’s screenshot output is always PNG. If a downstream service requires JPEG or another format, keep the buffer overload and convert the bytes in a separate image-processing step.
Best Value
Performance, reliability and maintenance considerations
Capturing to memory avoids an intermediate file but keeps the entire PNG in your Node.js process. Capturing to a path lets Nightmare perform the write immediately, which is convenient for batch jobs that only need files. For either approach, wait for the actual content you need rather than relying on a fixed delay alone; this reduces screenshots taken before the page has rendered.
Clipping can reduce the amount of image data your application handles, but it adds coordinate management. If a responsive page changes its layout, a rectangle calculated at one viewport size may no longer match the intended element. Keep viewport and scroll state stable when repeatability matters.
Nightmare is a legacy project: its repository is in Segment’s boneyard and marked no longer maintained. Pin the Nightmare and Electron versions used by an existing application, record the runtime that your captures depend on, and assess a maintained browser-automation or screenshot service before starting new production work. The callback overloads above remain useful when you must support an existing Nightmare codebase, but they should not be treated as an actively evolving API.
Free tools Windows power users keep installed
One-click scans. No signup required.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server, so you can request a capture without managing Nightmare, Electron, browser shutdown or callback sequencing. One GET request returns PNG, JPEG, WebP or PDF; the example below requests a screenshot of Stripe:
ScreenshotNeo API documentation
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);
Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and each response identifies the page verdict and billing status with X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
For automation beyond a basic URL, ScreenshotNeo also supports full-page captures with lazy images loaded, CSS-selector element captures, dark mode, 12 device presets and arbitrary viewports, retina scale, PDF paper and page options, custom CSS and JavaScript, pre-capture clicks, selector or network-idle waits, request and resource blocking, custom headers/cookies/user agents/Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable caching, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work to ease migration.
The Free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account to try it with no card.
Which Nightmare style should you choose?
Use .screenshot(done) when another API needs the PNG buffer, .screenshot(path, done) when a completed file is all you need, and a clip only when you can keep viewport coordinates stable. Promise-style .screenshot().then(...).catch(...) is usually easier to compose, but all forms depend on the same rule: finish the capture before calling .end().
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

