Patch fixing workflow when not in active git am conflict
When modifying an existing patch outside of an active `git am` conflict, edit the `.patch` file directly. When creating a new patch (rare, avoid when possible), commit in the node repo and then run `e patches node`. Fix existing patches 99% of the time rather than creating new ones.
Electron Node.js Upgrade Phase One: Patch Conflict Resolution
Phase One of Node.js upgrades focuses on applying patches to the Node.js source. Run `e sync --3` repeatedly, fixing patch conflicts as they arise in `../third_party/electron_node`, until it succeeds with exit code 0. When a patch fails, analyze the failure, fix conflicts in the node repo, run `git am --continue`, then export fixes with `e patches node` before running `e sync --3` again. Phase One is complete when `e sync --3` exits with code 0 and all changes are committed per phase-one-commit-guidelines.md.
Never skip and recreate patches during Node.js upgrades
Do not run `git am --skip` and then manually recreate a patch by making a new commit, as this destroys the original patch's authorship, commit message, and position in the series. If `git am --continue` reports 'No changes', the changes were likely absorbed by a prior conflict resolution's 3-way merge. Investigate why and present the situation to the user rather than skipping and recreating. This is critical for maintaining patch integrity.
Preserve patch authorship when fixing Node.js patches
When fixing existing patches, preserve the original author in TODO comments from the patch `From:` field. Never change TODO assignees—`TODO(name)` must retain the original name. Update descriptions only if upstream changed APIs or macros, and update the patch commit message to reflect the current state.
Clear git rerere cache before Node.js upgrade session
Run `git rerere clear` in both the electron and `../third_party/electron_node` repos at the start of each upgrade session. Stale recorded resolutions from a prior attempt can silently apply wrong merges, causing unintended patch conflicts or applications.
Verify pre-commit hooks are installed for Node.js upgrades
Ensure `.git/hooks/pre-commit` exists in the electron repo. If not, run `yarn husky` to install it. The hook runs `lint-staged` which handles clang-format for C++ files during patch work.
Electron Node.js Upgrade Phase Two: Build Fixes
Phase Two focuses on fixing build issues after patches apply. Run `e build -k 999 -- --quiet` repeatedly, fixing build issues as they arise by adapting Electron's code for changes in Node.js, until it exits with code 0. After building just the failed target with `e build -t {target}.o`, commit fixes following phase-two-commit-guidelines.md. After any commit, run `git status` in the electron repo to check for dependent patches with index/hunk header-only changes and commit them with `git commit -am "chore: update patches (trivial only)"`. When build succeeds, run `e start --version` to validate Electron launches, then check for pending changes in `../third_party/electron_node` and commit them into the appropriate patch file. Phase Two is complete when build succeeds and Electron launches.
Do not modify Node.js source to fix Electron build issues
When fixing build issues during Phase Two, adapt Electron's code in the electron repo (shell/, electron/, etc.) to account for changes in Node.js. Do not touch code in `../third_party/electron_node` to fix build issues—only read it to obtain context. Node.js APIs, especially internal V8 integration and build system files, frequently change between versions, and Electron code must be updated to account for these changes.
Do not run tests directly with ELECTRON_RUN_AS_NODE during upgrades
When running Node.js tests during Phase Three upgrades, do not run tests directly with `ELECTRON_RUN_AS_NODE`—use the `script/node-spec-runner.js` runner instead. The runner handles environment setup such as temporarily switching `package.json` from ESM to CommonJS.
Electron Node.js Upgrade Phase Three: Test Suite Fixes
Phase Three focuses on running and fixing the Node.js test suite. Run `node script/node-spec-runner.js --default` from the electron repo. When tests fail, identify the failing test file(s), analyze each failure, fix the test in `../third_party/electron_node/test/...`, re-run the specific failing test with `node script/node-spec-runner.js {test-path}` (without `--default`), and commit the fix using the fixup workflow and phase-three-commit-guidelines.md. Do not run tests directly with `ELECTRON_RUN_AS_NODE`—the runner handles environment setup. Phase Three is complete when `node script/node-spec-runner.js --default` exits with zero failures and all changes are committed.
Never delete or skip patches unless certain they are no longer needed
During Node.js upgrades, do not delete or skip patches unless 100% certain the patch is no longer needed. For major version upgrades, patches that shim deprecated V8 APIs or backport upstream changes are often deletable because the new Node.js version already incorporates them, but verify before removing. When patches that shim newer V8 APIs are deleted, it is because Node.js has caught up to Chromium's V8 version. Complicated conflicts or hard to resolve issues should be presented to the user after exhausting all other options. Do not delete a patch just because it is difficult to resolve.
Electron Node.js Upgrade workflow commands
Key commands for Node.js upgrades: `e sync --3` (clone deps and apply patches with 3-way merge), `git am --continue` (continue after resolving conflict in node repo), `e patches node` (export commits from node repo to patch files), `e patches all` (export all patches from all targets), `e patches node --commit-updates` (export patches and auto-commit trivial changes), `e patches --list-targets` (list targets and config paths), `e build -k 999 -- --quiet` (build Electron, continue on errors, suppress status lines), `e build -t {target}.o` (build just one specific target), `e start --version` (validate Electron launches), `node script/node-spec-runner.js --default` (run full Node.js test suite), `node script/node-spec-runner.js test/path/to/test.js` (run a single test), `NODE_REGENERATE_SNAPSHOTS=1 node script/node-spec-runner.js test/test-runner/test-foo.mjs` (regenerate snapshot for snapshot-based test).
BoringSSL incompatibilities in Node.js tests
Electron builds Node.js against Chromium's BoringSSL instead of Node.js's bundled OpenSSL. Upstream Node.js now supports building against BoringSSL natively via `node_openssl_path = "//third_party/boringssl"` in `build/args/all.gn`. Most historical BoringSSL workarounds have been eliminated; the `fix_crypto_tests_to_run_with_bssl.patch` now mainly skips a handful of still-unsupported test files rather than containing large test modifications. Only add a guard when a feature is genuinely still missing from BoringSSL. The preferred guard is a file-level `common.skip()` when the feature is unsupported, added to `fix_crypto_tests_to_run_with_bssl.patch`. For tests that cannot be cleanly guarded inline, add the file to `script/node-disabled-tests.json` instead.
BoringSSL features still unsupported in Chromium as of v24.18.0
As of Node.js v24.18.0, the following features remain unsupported in Chromium's BoringSSL: RSA-PSS keygen (deprecation path, file-level skip in test-crypto-keygen-deprecation), ML-DSA keys (file-level skip in test-crypto-pqc-key-objects-ml-dsa), ML-KEM keys (disabled in node-disabled-tests.json), FIPS mode (disabled), Secure heap (disabled), Stateless DH (disabled), Assorted keygen and WebCrypto keygen (disabled in node-disabled-tests.json: test-crypto-keygen, test-webcrypto-keygen, wpt/test-webcrypto). When guarding tests, prefer a precise capability check (e.g. `ciphers.includes('aes-128-ccm')`) over a blanket `process.features.openssl_is_boringssl` check when the feature can be probed directly.
Snapshot test regeneration during Node.js upgrades
Some Node.js tests compare output against committed `.snapshot` files using `assert.strictEqual`—these are not wildcard comparisons. When Chromium's V8 produces different output (e.g. different stack traces), regenerate snapshots with `NODE_REGENERATE_SNAPSHOTS=1 node script/node-spec-runner.js test/test-runner/test-foo.mjs`. Inspect the diff to verify the changes are expected, then commit the updated snapshot into the appropriate patch.
High-churn patches in Node.js upgrades
These patches consistently require the most work during Node.js upgrades: (1) `fix_handle_boringssl_and_openssl_incompatibilities.patch`—Electron uses BoringSSL while Node.js expects OpenSSL; historically large but greatly reduced once Node.js gained native BoringSSL support via GN arg, though it still shims C++-level differences when Node.js/ncrypto APIs change. (2) `fix_crypto_tests_to_run_with_bssl.patch`—Adapts Node.js crypto tests for BoringSSL; greatly reduced now that upstream tests self-skip under BoringSSL, mainly skipping a handful of still-unsupported test files. (3) `support_v8_sandboxed_pointers.patch`—V8 sandbox pointer support requires careful adaptation when V8 APIs change. (4) `build_add_gn_build_files.patch`—The GN build file patch is large and touches many build targets; upstream build system changes frequently conflict.
Major Node.js version upgrades expectations
Major Node.js version transitions (e.g., v22 → v24) are significantly more involved than patch bumps. Expect patch deletions—many patches exist to bridge gaps between Chromium's V8 and Node.js's bundled V8; when Node.js bumps to a newer major version, its V8 catches up and those bridge patches can be deleted (in the v22 → v24 upgrade, 17 patches were deleted). Update `@types/node` in `package.json` to match the new major version. Post-upgrade regressions are expected even after the upgrade lands; follow-up fix PRs for edge cases (ESM path handling, certificate loading, platform-specific issues) are normal.
Two types of Node.js version updates in Electron
There are two types of Node.js version updates: (1) Bumps (patch/minor) are automated by `electron-roller[bot]` with commit title `chore: bump node to v{version}`; trivial patch index updates are handled automatically by `patchup[bot]`; these often land cleanly but may require manual patch fixes. (2) Major upgrades (e.g., v22 → v24) are manual, large PRs with commit title `chore: upgrade Node.js to v{X}.{Y}.{Z}`; these typically involve deleting obsolete patches, adapting many others, and updating `@types/node` in `package.json`.
Permanently disabled tests should not be modified
Tests listed in `script/node-disabled-tests.json` are permanently disabled and should not be run or modified. Only add a test to this list as a last resort when the test is fundamentally incompatible with Electron's architecture, not just a BoringSSL difference that can be guarded. Tests disabled here are completely skipped and never run.
dialog module runs in main process only
The dialog module only runs in the main process. It cannot be used from the renderer process.
Extensions class runs in main process only
The Extensions class is available only in the main process. It is not exported from the 'electron' module directly but is only available as a return value of other methods in the Electron API. Instances are accessed via the extensions property of a Session.
globalShortcut process requirement
The globalShortcut module runs in the Main process only.
nativeTheme module process context
The nativeTheme module runs in the main process only.
process.type property values
process.type is a readonly string representing the current process's type. Possible values are: 'browser' (the main process), 'renderer' (a renderer process), 'service-worker' (in a service worker), 'worker' (in a web worker), or 'utility' (in a node process launched as a service).
Electron multi-process architecture
Electron inherits its multi-process architecture from Chromium. Each Electron app developer controls two types of processes: main and renderer. These are analogous to Chrome's browser and renderer processes. Each tab or window renders in its own process, limiting the harm that buggy or malicious code on a web page could cause to the app as a whole.
Single main process per Electron app
Each Electron app has a single main process, which acts as the application's entry point. The main process runs in a Node.js environment, meaning it has the ability to require modules and use all of Node.js APIs.
Main process primary purpose: window management
The main process' primary purpose is to create and manage application windows with the BrowserWindow module. Each instance of the BrowserWindow class creates an application window that loads a web page in a separate renderer process. You can interact with this web content from the main process using the window's webContents object.
BrowserWindow creates renderer process
When a BrowserWindow instance is created, a separate renderer process is spawned. When the BrowserWindow instance is destroyed, its corresponding renderer process gets terminated as well.
BrowserWindow webContents object example
The following example creates a BrowserWindow, loads a URL, and accesses the webContents object:
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('https://github.com')
const contents = win.webContents
console.log(contents)
BrowserWindow is EventEmitter
BrowserWindow is an EventEmitter, so you can add handlers for various user events such as minimizing or maximizing your window.
Renderer process responsibility
Each Electron app spawns a separate renderer process for each open BrowserWindow and for each web embed. A renderer is responsible for rendering web content. Code run in renderer processes should behave according to web standards as Chromium does.
Renderer process web standards
All user interfaces and app functionality within a single browser window should be written with the same tools and paradigms that you use on the web. An HTML file is the entry point for the renderer process, UI styling is added through CSS, and executable JavaScript code is added through script elements.
Renderer process no direct Node.js access
The renderer process has no direct access to require or other Node.js APIs. In order to directly include NPM modules in the renderer, you must use the same bundler toolchains (such as webpack or parcel) that you use on the web.
Preload scripts contain renderer initialization code
Preload scripts contain code that executes in a renderer process before its web content begins loading. These scripts run within the renderer context but are granted more privileges by having access to Node.js APIs.
Preload script configuration in BrowserWindow
A preload script can be attached to the main process in the BrowserWindow constructor's webPreferences option.
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
webPreferences: {
preload: 'path/to/preload.js'
}
})
Preload script purposes
Preload scripts are useful for two main purposes: (1) By exposing ipcRenderer helpers to the renderer, you can use inter-process communication to trigger main process tasks from the renderer and vice versa. (2) If developing an Electron wrapper for an existing web app hosted on a remote URL, you can add custom properties onto the renderer's window global that can be used for desktop-only logic.
Utility process for child processes
Each Electron app can spawn multiple child processes from the main process using the UtilityProcess API. The utility process runs in a Node.js environment, meaning it has the ability to require modules and use all of Node.js APIs.
Utility process use cases
The utility process can be used to host untrusted services, CPU intensive tasks, or crash prone components which would have previously been hosted in the main process or process spawned with Node.js child_process.fork API.
Utility process vs child_process.fork
The primary difference between the utility process and process spawned by Node.js child_process module is that the utility process can establish a communication channel with a renderer process using MessagePorts. An Electron app should prefer the UtilityProcess API over Node.js child_process.fork API when there is need to fork a child process from the main process.
TypeScript process-specific module aliases
Electron's npm package exports subpaths that contain a subset of Electron's TypeScript type definitions: electron/main includes types for all main process modules, electron/renderer includes types for all renderer process modules, and electron/common includes types for modules that can run in main and renderer processes.
Using TypeScript process-specific aliases
Process-specific module aliases have no impact on runtime but can be used for typechecking and autocomplete:
const { shell } = require('electron/common')
const { app } = require('electron/main')
Web embeds create renderer processes
A renderer process is also created for web embeds such as the BrowserView module. The webContents object is also accessible for embedded web content.
pushNotifications module location
The pushNotifications module runs in the main process only.