How to Fix Electron’s screen.getPrimaryDisplay Is Undefined Error
Fix Electron’s screen.getPrimaryDisplay error by checking the process boundary, import, and app readiness. Includes working main-process and IPC patterns.
screen.getPrimaryDisplay() is undefined when the call is made from the wrong Electron process, before Electron has emitted ready, or with an import that resolves to the browser’s window.screen. Put the call in the main process, import screen from electron/main, and run it after app.whenReady() resolves.
Electron classifies the screen module as main-process only. Its documentation also says that the module cannot be used until the app’s ready event has been emitted. The official reference is Electron’s screen API documentation; lifecycle details are in the app API documentation.
Use the documented main-process pattern
This is the smallest correct example. It obtains the primary display after startup, reads its work area, and creates a window sized to that area.
const { app, BrowserWindow, screen } = require('electron/main')
app.whenReady().then(() => {
const primaryDisplay = screen.getPrimaryDisplay()
const { width, height } = primaryDisplay.workAreaSize
const mainWindow = new BrowserWindow({ width, height })
mainWindow.loadURL('https://electronjs.org')
})
Adapt the window creation and URL to your application. The important details are the process, import, and timing:
| Check | Correct condition |
|---|---|
| Process | Main process |
| Import | screen imported from Electron’s main-process entry point |
| Timing | Inside app.whenReady() or after the ready event |
| Result | A Display object, whose workAreaSize contains usable width and height |
Diagnose the error in order
1. Identify the process running the failing line
Open the file and startup path containing screen.getPrimaryDisplay(). If it is loaded by a renderer window, a preload script, or DevTools, it is not running in the main process. The screen API reference labels this module Process: Main.
A renderer can use the browser property window.screen, but that is a different API. It describes the browser display environment and is not Electron’s screen module.
2. Check the import and variable name
In renderer code and DevTools, Electron warns that window.screen is a reserved DOM property. This pattern therefore does not provide Electron’s screen module in that context:
let { screen } = require('electron')
Use the main-process import shown above instead. If your project uses ES modules, the equivalent main-process import is:
import { app, BrowserWindow, screen } from 'electron/main'
app.whenReady().then(() => {
const display = screen.getPrimaryDisplay()
console.log(display.workAreaSize)
})
Keep the syntax consistent with your project’s module configuration. The process boundary and readiness requirement do not change.
3. Check startup timing
Calling the module at top level can happen before Electron has finished initialization:
// Too early: this runs while the module is being loaded.
const { screen } = require('electron/main')
const display = screen.getPrimaryDisplay()
Move the call into app.whenReady():
const { app, screen } = require('electron/main')
app.whenReady().then(() => {
const display = screen.getPrimaryDisplay()
console.log(display.bounds)
})
If startup can be reached from several paths, app.isReady() lets you check whether the event has already fired. Prefer a single initialization path so display queries are not scattered across module loading and window creation.
Expose display information to a renderer safely
If the user interface needs display dimensions, do not move the Electron screen call into the renderer. Keep it in the main process and send only the data the UI needs over IPC.
Main process
const { app, BrowserWindow, ipcMain, screen } = require('electron/main')
let mainWindow
app.whenReady().then(() => {
mainWindow = new BrowserWindow({
webPreferences: {
preload: require('node:path').join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
})
ipcMain.handle('display:primary', () => {
const display = screen.getPrimaryDisplay()
return {
id: display.id,
bounds: display.bounds,
workArea: display.workArea,
workAreaSize: display.workAreaSize,
scaleFactor: display.scaleFactor
}
})
mainWindow.loadFile('index.html')
})
Preload script
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('displayAPI', {
getPrimary: () => ipcRenderer.invoke('display:primary')
})
Renderer code
async function showDisplaySize() {
const display = await window.displayAPI.getPrimary()
document.querySelector('#size').textContent =
`${display.workAreaSize.width} × ${display.workAreaSize.height}`
}
showDisplaySize()
This arrangement keeps privileged Electron APIs in the main process while giving the renderer a small, explicit interface. The exact IPC wiring can vary by project; the screen query itself should remain main-process code.
Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
screen.getPrimaryDisplay is undefined |
The imported value is the DOM window.screen or an incorrect Electron export. |
Move the call to the main process and import from electron/main. |
Cannot read properties of undefined during startup |
The call runs before ready. |
Await app.whenReady() before accessing screen. |
| The code works in one file but not another | The files run in different processes. | Log which entry point loads each file and identify the process before changing imports. |
| Renderer needs dimensions but cannot require Electron | Node integration is disabled or the API is intentionally isolated. | Use a preload bridge and an IPC handler in the main process. |
| Behavior differs from the current documentation | The installed Electron version may differ from the rolling latest reference. | Check the documentation matching the version in your project and inspect the exact stack trace. |
What to inspect when the fix does not work
- Print the Electron version used by the application and compare it with the documentation version.
- Read the complete stack trace, including the file that loaded the failing line.
- Confirm whether that file is the main entry point, preload, renderer bundle, or DevTools snippet.
- Search for another variable named
screenthat could shadow the Electron import. - Confirm that the call is inside the
app.whenReady()callback, rather than merely in a file imported by it. - Reduce the code to the documented example, then reintroduce your window and IPC code one piece at a time.
The title alone cannot establish which cause applies to a particular project. The process location, installed Electron version, import statement, and stack trace are needed for a project-specific diagnosis.
Display data, windows, and edge cases
Primary display versus current display
getPrimaryDisplay() returns the system’s primary Display. If your app needs the display containing a particular window, use the appropriate Electron display method for that use case rather than assuming the primary display is the window’s display.
Work area versus bounds
bounds describes the display rectangle. workArea and workAreaSize exclude areas reserved by system UI such as taskbars or docks. Use the work area when sizing an application window that should remain usable.
Scaling and high-DPI screens
The returned display also includes a scale factor. Avoid treating physical pixels and logical window units as interchangeable. Read the display object’s dimensions and scale factor together when positioning or sizing windows.
Multiple monitors and changes
A user can attach, remove, or rearrange displays while the app is running. If your layout depends on the monitor configuration, listen for Electron’s display-change events and refresh the data in the main process. Do not cache the primary display forever.
Performance and reliability notes
- Querying display metadata is lightweight, but perform it once during initialization when possible instead of repeatedly from a renderer animation loop.
- Return only the fields the renderer needs across IPC. This keeps the bridge easy to audit and avoids coupling UI code to the entire Electron
Displayobject. - Handle the case where display configuration changes after a window is created.
- Keep the readiness gate on every startup path, including code reached from an imported module.
- When diagnosing a production-only failure, record the Electron version, process entry point, and stack trace rather than only the final error message.
Or skip the browser setup
If your goal is to capture a website image rather than inspect the user’s desktop monitors, ScreenshotNeo provides a website screenshot API. It avoids Electron window and display setup: cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; and each response identifies the result with X-Page-Verdict and X-Billed headers. It also includes an MCP server so Claude, Cursor, and other MCP clients can take screenshots. The free plan includes 1,000 screenshots per month without a card, and paid plans start at $5 for 3,000 shots. See the ScreenshotNeo API documentation for options.
cURL
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 fs = await import('node:fs/promises')
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()))
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
FAQ
Does screen.getPrimaryDisplay() work in a renderer?
No. Electron documents screen as a main-process module. Query it in the main process and send required values to the renderer.
Is window.screen the same API?
No. It is the browser’s DOM screen property and does not provide Electron’s main-process Display object.
Can I call the method before creating a BrowserWindow?
Yes, as long as the call occurs after app.whenReady() resolves. Window creation itself is not the readiness signal.
What if the primary monitor changes?
Refresh display information in response to Electron’s display events instead of relying on an initialization-time value forever.


