ScreenshotNeo

BlogScreenshots on your device

How to Improve Electron Screen Capture Quality

Improve Electron screen capture quality by choosing the right source, requesting suitable dimensions and frame rates, and handling platform limits.

By the ScreenshotNeo team1 October 20268 min read

To improve Electron screen capture quality, control four things in order: capture the intended source, request dimensions and frame rates that fit that source, avoid unnecessary thumbnail work, and validate the delivered track on every target operating system. Electron cannot guarantee that a requested width, height, or frame rate will be delivered exactly, so inspect the actual stream before recording or publishing it.

The example in Electron’s documentation uses 320×240 at 30 frames per second. Those values demonstrate the API; they are not universal quality recommendations. Choose dimensions close to the source display and a frame rate that matches the motion in your application and the performance budget.

1. Choose the correct Electron capture path

Electron supports two related approaches:

Approach Use it when Source selection
getDisplayMedia() with setDisplayMediaRequestHandler() You want the browser media API while your main process decides which screen or window is granted. The handler grants a source obtained from desktopCapturer. The API does not accept a deviceId to select a source.
desktopCapturer.getSources() You need to enumerate screens and windows, show your own picker, or grant a known source directly. Your app selects from the returned screen and window sources.

The native system picker option is experimental and available on macOS 15 or later. When it is enabled and available, Electron does not invoke the session request handler. Check the Electron version and platform behavior before depending on it.

2. Request useful dimensions and frame rates

Pass video constraints that match the source and downstream workflow. A 4K source does not automatically produce a 4K recording if the track is negotiated at a smaller size, and requesting an excessive frame rate can increase CPU, memory, encoder, and disk usage.

Renderer: request a high-resolution display stream

const stream = await navigator.mediaDevices.getDisplayMedia({
  video: {
    width: { ideal: 2560 },
    height: { ideal: 1440 },
    frameRate: { ideal: 30, max: 60 }
  },
  audio: false
});

const video = document.querySelector('video');
video.srcObject = stream;
await video.play();

const track = stream.getVideoTracks()[0];
console.log('Requested settings:', track.getSettings());
console.log('Capabilities:', track.getCapabilities ? track.getCapabilities() : {});

Use ideal values when the application can accept a lower result. Use exact constraints only when a mismatch must fail rather than fall back. Always log track.getSettings(); it shows the dimensions and frame rate that Electron actually delivered to the renderer.

Capture a single frame for inspection

async function frameFromStream(stream) {
  const video = document.createElement('video');
  video.srcObject = stream;
  video.muted = true;
  await video.play();

  await new Promise(resolve => {
    if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) resolve();
    else video.addEventListener('loadeddata', resolve, { once: true });
  });

  const canvas = document.createElement('canvas');
  canvas.width = video.videoWidth;
  canvas.height = video.videoHeight;
  canvas.getContext('2d').drawImage(video, 0, 0);
  return canvas.toDataURL('image/png');
}

const pngDataUrl = await frameFromStream(stream);
console.log(pngDataUrl.slice(0, 32));

If videoWidth and videoHeight are lower than expected, fix source selection, constraints, display scaling, or platform permissions before changing image encoding.

3. Grant the intended source from the main process

Electron’s session API lets the main process handle display-media requests. The handler can enumerate sources and grant a selected screen or window.

Main process example

const { app, BrowserWindow, desktopCapturer, session } = require('electron');
const path = require('node:path');

function createWindow() {
  const win = new BrowserWindow({
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      nodeIntegration: false
    }
  });
  win.loadFile('index.html');
}

app.whenReady().then(() => {
  session.defaultSession.setDisplayMediaRequestHandler(async (request, callback) => {
    const sources = await desktopCapturer.getSources({
      types: ['screen', 'window'],
      thumbnailSize: { width: 0, height: 0 }
    });

    // Replace this policy with your own picker or allow-list.
    const screen = sources.find(source => source.id.startsWith('screen:'));
    if (!screen) {
      callback();
      return;
    }

    callback({ video: screen, audio: 'loopback' });
  }, { useSystemPicker: false });

  createWindow();

  app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) createWindow();
  });
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});

The thumbnailSize setting is important when your app does not show source thumbnails. Setting either width or height to zero avoids the processing work required to capture each thumbnail. It reduces enumeration work; Electron does not claim that it increases the quality of an already-running stream.

For a window picker, send the source IDs and names to the renderer, let the user choose one, and apply an allow-list in the main process. Do not grant an arbitrary source merely because it was requested by a renderer.

4. Handle permissions deliberately

Electron distinguishes display capture from camera and microphone capture. Screen, window, and tab requests are categorized as display-capture; camera and microphone requests are categorized as media. If your application installs permission handlers, handle the display-capture path explicitly and restrict grants to the expected origin and window.

const ses = session.defaultSession;

ses.setPermissionCheckHandler((webContents, permission, requestingOrigin) => {
  if (permission === 'display-capture') {
    return requestingOrigin === 'file://' || requestingOrigin.startsWith('https://your-app.example');
  }
  return false;
});

On macOS 10.15 and later, users must grant screen-content capture permission. Desktop audio has additional requirements: macOS 14.2 and later requires NSAudioCaptureUsageDescription for desktop audio capture through desktopCapturer. These permissions affect whether capture succeeds; they are not video-quality controls.

5. Tune quality without wasting resources

Match the source and output

  • Capture at or near the source display’s native pixel dimensions when text clarity matters.
  • Use 60 fps for fast motion only when the source, encoder, and destination can sustain it. For static interfaces, 24 or 30 fps usually reduces work.
  • Keep the recording canvas at the track’s actual dimensions. Scaling a low-resolution track up creates larger files without adding detail.
  • Measure the final encoded output separately from the capture track. A high-resolution track can still be degraded by a low bitrate or a smaller recording canvas.

Reduce avoidable work

  • Set thumbnailSize to zero in one dimension if the UI does not need thumbnails.
  • Enumerate sources only when needed instead of rebuilding a large picker continuously.
  • Capture a single intended window when possible; a full desktop can include unrelated motion and increase encoding cost.
  • Stop tracks when capture ends: stream.getTracks().forEach(track => track.stop()).

Validate on production platforms

Test each Electron version and operating system with the same display scaling, source type, and recording or streaming path used in production. Record the requested constraints, getSettings(), final file dimensions, frame rate, dropped frames, CPU use, and memory use. The official Electron documentation does not establish a universal best profile or an apples-to-apples cross-platform benchmark.

6. Platform limits that affect results

Platform or condition What to account for
Linux with PipeWire Electron returns one source because PipeWire supports a single capture for screens and windows.
macOS 10.15+ Screen-content capture requires user consent in system privacy settings.
macOS 14.2+ Desktop audio through desktopCapturer requires NSAudioCaptureUsageDescription.
macOS 15+ The experimental native system picker may be available; when used, the request handler is not called.
macOS audio versions CoreAudio Tap became Chromium’s default desktop-audio capture path beginning with Electron v39.0.0-beta.4. Older macOS releases have documented system-audio limits.

These details come from Electron’s desktopCapturer documentation and session documentation. Audio behavior should be treated as a separate platform concern from video sharpness.

7. Troubleshooting checklist

The stream is smaller than requested

Cause: constraints are preferences, the source is smaller, display scaling is active, or the platform cannot provide the requested mode.

Fix: inspect track.getSettings(), compare it with the selected source, try an ideal profile that matches the display, and validate on the target operating system.

The wrong screen or window is captured

Cause: the handler grants the first source or assumes source IDs are stable.

Fix: enumerate sources, show a deliberate picker or allow-list, and grant the selected source from the main process. Do not attempt to pass a deviceId to getDisplayMedia(); Electron documents that source selection is not supported that way.

No source appears

Cause: the request handler returned no video source, a permission was denied, or a platform capture service is unavailable.

Fix: log the source list and permission result, call callback() only for a deliberate denial, and check macOS screen-recording permission or Linux PipeWire availability.

Capture fails only on macOS

Cause: screen recording consent is missing, or desktop-audio usage metadata is absent.

Fix: enable the app in Screen Recording privacy settings and add NSAudioCaptureUsageDescription when desktop audio is requested on macOS 14.2 or later.

The image looks blurry after capture

Cause: the delivered track is low resolution, the canvas is being upscaled, or the recorder/encoder bitrate is too low.

Fix: compare track settings with the final file dimensions, keep the canvas at native track dimensions, and tune encoding separately from Electron capture constraints.

CPU or memory usage is too high

Cause: unnecessary thumbnails, excessive frame rate, very large dimensions, multiple simultaneous captures, or expensive encoding.

Fix: set a zero thumbnail dimension when thumbnails are not needed, lower frame rate for static content, capture one source, and profile the encoder and renderer independently.

8. Reliability and cost considerations

Electron capture is local and does not add an API request charge, but it consumes the user’s display, CPU, memory, storage, and permission state. A reliable implementation records the actual track settings, handles a missing source, stops tracks on cleanup, and reports permission errors clearly. Build a small compatibility matrix for your supported Electron versions and operating systems rather than assuming one profile works everywhere.

For repeated website screenshots, Electron is also more machinery than necessary: it requires a packaged browser, source selection, permissions, and capture lifecycle code. A screenshot API can move that browser setup to a service.

Or skip the browser setup

ScreenshotNeo returns a PNG, JPEG, WebP, or PDF from one GET request. Its capture flow accepts cookie and consent banners like a visitor, then removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot. Each step can be turned off.

Only clean shots are billed. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

See the ScreenshotNeo API documentation for all options, including full-page capture, element selectors, device presets, custom CSS and JavaScript, waits, request blocking, cookies, headers, geolocation, caching, signed links, asynchronous jobs, bulk capture, and usage reporting.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const fs = require('node:fs/promises');

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}`);
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

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, and yearly billing gives two months free. Create a free ScreenshotNeo account.

FAQ

Does a higher requested frame rate guarantee smoother capture?

No. The source, platform, display compositor, renderer, and encoder determine what is delivered. Inspect the track settings and validate the final recording.

Can I select an Electron source with a device ID?

No. Electron’s documentation says getDisplayMedia() does not permit deviceId for source selection. Use the session request handler with a source returned by desktopCapturer.

Should I always capture at 4K?

No. Use the source and output dimensions that preserve the detail you need. Larger dimensions increase processing and storage costs.

Does disabling thumbnails improve image quality?

No. A zero thumbnail dimension reduces enumeration work when thumbnails are unnecessary; it does not increase the quality of an existing video track.

Is desktop audio required for better video?

No. Audio permissions and capture paths are separate from video dimensions and frame rate. Configure audio only when the product needs it.