Diagnosing Missing contextBridge APIs in Electron Renderer Processes
Renderer code calling window.myAPI gets undefined even though the preload script exposes it. This guide shows how to isolate the cause and fix it without weakening renderer security.
15 Sept 2026, 14:03 UTC

The problem and the useful takeaway
Your renderer code calls window.myAPI.doSomething() and receives undefined or myAPI is not a function. The preload script appears to define the API with contextBridge.exposeInMainWorld, yet nothing reaches the page.
The takeaway: when contextIsolation is enabled—the default in modern Electron—the preload script runs in an isolated JavaScript world separate from the renderer's main world. Properties exposed through contextBridge.exposeInMainWorld are copied onto the renderer's window only if the preload loads successfully, the exposed value is a plain object, and the renderer reads the exact property name you registered. A missing or misconfigured preload path is the most common culprit.
Recognizable condition
You see one of the following in the renderer DevTools console:
window.myAPI is undefinedmyAPI is not a functionorCannot read properties of undefined- The preload script's
console.logstatements never appear
Cause and diagnostic table
| Observed condition | Likely cause | First check |
|---|---|---|
window.myAPI is undefined, no preload logs in console | Preload script is not loaded | Verify webPreferences.preload path and file existence |
window.myAPI is undefined, preload logs appear | Exposure call missing, wrong property name, or preload threw before registration | Inspect preload source for exposeInMainWorld and exceptions |
window.myAPI exists but methods are missing or throw | Exposed value is not a plain serializable object, or class instance/DOM node was exposed | Check what was passed to exposeInMainWorld |
| API exists, calls reach main process but silently fail | ipcMain handler channel mismatch or missing handler | Compare preload channel name with ipcMain.handle registration |
Ordered checks
- Confirm the preload path resolves. In your main process file, locate the
BrowserWindowconstructor.webPreferences.preloadmust be an absolute path to the preload file. Relative paths resolve against the working directory, which can differ between development and production. - Confirm
contextIsolationis explicitly set. Modern Electron defaults it totrue, but relying on defaults across versions is risky. SetcontextIsolation: trueandnodeIntegration: false. - Open renderer DevTools and evaluate
window.myAPI. If it is an object with your methods, the bridge works and the issue is in your renderer code or timing. If it isundefined, continue. - Add a temporary
console.logat the top of the preload script. Reload the window. If the log does not appear, the preload file is not executing. - Check the preload for early exceptions. Any thrown error before
contextBridge.exposeInMainWorldprevents registration. Look at the main process console or Electron's preload error output.
Fixes matched to findings
Fix 1: Resolve an absolute preload path
Use Node's path module so the path does not depend on the current working directory.
const { BrowserWindow } = require('electron');
const path = require('path');
const win = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false
}
});
Where to run: in your main process source file, typically main.js or index.js. Permissions: read access to the project directory. Placeholder: replace preload.js with your actual preload filename. Expected check: the resolved path exists and matches the file you intend to load. Risk: none from the path itself, but an incorrect path silently fails to load the preload.
Fix 2: Expose a plain object through contextBridge
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
ping: () => ipcRenderer.invoke('app:ping'),
getVersion: () => process.versions.electron
});
Where to run: in the preload script referenced by webPreferences.preload. Permissions: none beyond normal file access. Placeholder: electronAPI is the property name that appears on window; change it to match your renderer code. Expected check: in renderer DevTools, window.electronAPI.ping is a function. Risk: exposing ipcRenderer.sendSync or unrestricted channels lets the renderer invoke arbitrary main-process operations; validate input in the preload wrapper.
Fix 3: Do not enable nodeIntegration as a workaround
If the bridge is missing, do not set nodeIntegration: true to give the renderer direct Node access. That disables the context isolation boundary and exposes Node.js APIs to any content loaded in the renderer, including untrusted remote content.
Fix 4: Match IPC channel names
If window.electronAPI exists but calls fail, compare the channel string in the preload with the handler in the main process.
// preload.js
contextBridge.exposeInMainWorld('electronAPI', {
ping: () => ipcRenderer.invoke('app:ping')
});
// main.js
ipcMain.handle('app:ping', async () => 'pong');
Expected check: the channel string app:ping is identical in both files. A typo such as app:ping with a trailing space will not match.
Escalation criteria
Escalate to main-process debugging when:
window.electronAPIis present and methods execute, but the main process never receives the call.- The preload loads and registers the API, but the renderer navigates away before your code runs, so the reference is lost.
- You need to expose objects that contain class instances, DOM nodes, or functions with prototype methods; structured cloning across the context boundary may drop or mangle them.
In those cases, keep the preload surface minimal—expose only primitive-returning functions—and move complex logic into ipcMain handlers.
Limitations and practical verification
- Behavior can differ between Electron major versions because default
webPreferencesvalues have changed. Always setcontextIsolation,nodeIntegration, andpreloadexplicitly rather than relying on defaults. contextBridge.exposeInMainWorldstill works whencontextIsolationisfalse, but the security boundary is weaker and not recommended for production.- Practical verification: reload the renderer, open DevTools, and run
console.log(window.electronAPI). Confirm it is an object and that calling one of its methods produces the expected result or a defined error, notundefined.
If none of the checks identify the cause, create a minimal reproduction with a single preload file exposing one function and a fresh BrowserWindow with no navigation. If the minimal case works, the issue is in your application's preload ordering, navigation timing, or renderer bundle, not in Electron's context bridge itself.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.