Migrating from v26 to v27
This is the step-by-step walkthrough for upgrading a project from v26 to v27. For the authoritative catalogue of everything that changed, see v27 Breaking Changes →.
v27 migrates the entire electron-builder ecosystem to native ES modules, raises the minimum Node.js version to 22.12.0, and hard-deletes the deprecated APIs that accumulated since v22.
Every breaking change — what's removed, renamed, restructured, and what action each requires — is catalogued on the v27 Breaking Changes page. Skim it before you start so nothing surprises you.
Most projects need only a Node.js version bump plus one command. The build() API is unchanged and CJS require() continues to work without any code changes on supported Node.js versions. Configuration keys that were renamed or restructured (win.sign, mac.sign, electronGet, asar, nativeModules, …) are rewritten for you automatically by migrate-schema in Step 1 — they keep the same runtime behavior. A few type exports were renamed or removed; see the breaking changes.
The upgrade is three steps:
- Run the automated migrator — rewrites your config to v27 form
- Update Node.js — to >=22.12.0
- Apply the manual steps — the few things the migrator can't do
Then walk the summary checklist.
Step 1: Run the automated migrator
Run the built-in command to apply every config-level breaking change to your project automatically:
electron-builder migrate-schema # apply changes in place
electron-builder migrate-schema --dry-run # preview without writing (alias: -n)
This rewrites your electron-builder.json, electron-builder.yml, package.json (build key), or any other static config format in place. It also rewrites programmatic configs (.js/.ts/.cjs/.mjs) via an AST-located codemod that preserves comments, imports, functions, and formatting.
Pass --config <path> to point at a non-default config file, or --project-dir <dir> to specify the project root (default: current directory).
What it migrates automatically
| Config change | Before | After |
|---|---|---|
electronCompile removed | "electronCompile": true | (deleted) |
disableDefaultIgnoredFiles removed | "disableDefaultIgnoredFiles": true | (deleted) |
framework / nodeVersion / launchUiVersion removed | "framework": "electron" | (deleted) |
npmSkipBuildFromSource removed | "npmSkipBuildFromSource": true | "nativeModules": { "buildDependenciesFromSource": false } |
| Native-module options grouped | "npmRebuild": true | "nativeModules": { "npmRebuild": true } |
nativeRebuilder renamed | "nativeRebuilder": "parallel" | "nativeModules": { "rebuildMode": "parallel" } |
appImage.systemIntegration removed | "appImage": { "systemIntegration": "ask" } | (deleted) |
linux.syncDesktopName removed | "linux": { "syncDesktopName": true } | (deleted — always synced in v27) |
| Legacy asar keys | "asar-unpack": "**/*.node" | "asar": { "unpack": ["**/*.node"] } |
asarUnpack consolidated | "asarUnpack": ["**/*.node"] | "asar": { "unpack": ["**/*.node"] } |
disableSanityCheckAsar moved | "disableSanityCheckAsar": true | "asar": { "disableSanityCheck": true } |
disableAsarIntegrity moved | "disableAsarIntegrity": true | "asar": { "disableIntegrity": true } |
asar: true removed | "asar": true | (deleted — absence means enabled) |
macOS signing fields → sign | "mac": { "identity": "Developer ID...", "hardenedRuntime": true } | "mac": { "sign": { "identity": "Developer ID...", "hardenedRuntime": true } } |
signIgnore renamed | "mac": { "signIgnore": ["**/*.txt"] } | "mac": { "sign": { "ignore": ["**/*.txt"] } } |
mac.universal fields → universal | "mac": { "mergeASARs": true } | "mac": { "universal": { "mergeASARs": true } } |
electronDownload → electronGet | "electronDownload": { "mirror": "https://m/" } | "electronGet": { "mirrorOptions": { "mirror": "https://m/" } } |
GithubOptions.vPrefixedTagName | "vPrefixedTagName": false | "tagNamePrefix": "" |
win.azureSignOptions → win.sign | "win": { "azureSignOptions": { "endpoint": "…" } } | "win": { "sign": { "type": "azure", "endpoint": "…" } } |
win.signtoolOptions → win.sign | "win": { "signtoolOptions": { "certificateFile": "…" } } | "win": { "sign": { "type": "signtool", "certificateFile": "…" } } |
| Azure extra sign keys | "win": { "azureSignOptions": { "ExcludeCredentials": "X" } } | "win": { "sign": { "type": "azure", "additionalMetadata": { "ExcludeCredentials": "X" } } } |
win.signExecutable: false | "win": { "signExecutable": false } | "win": { "sign": false } |
win.signExecutable: true removed | "win": { "signExecutable": true } | (deleted — enabled by default) |
win.signAndEditExecutable: true removed | "win": { "signAndEditExecutable": true } | (deleted — always enabled in v27) |
snap → snapcraft | "snap": { "confinement": "strict", "base": "core22" } | "snapcraft": { "base": "core22", "core22": { "confinement": "strict" } } |
helper-bundle-id moved | "helper-bundle-id": "com.x.helper" | "mac": { "helperBundleId": "com.x.helper" } |
squirrelWindows.noMsi inverted | "squirrelWindows": { "noMsi": true } | "squirrelWindows": { "msi": false } |
Root-level directories moved | { "directories": { "output": "dist" } } (package.json root) | { "build": { "directories": { "output": "dist" } } } |
Each row links to its full explanation on the v27 Breaking Changes page.
Serialization caveats
- JSON5 → JSON: when
migrate-schemarewrites a.json5file it produces valid JSON (no comments, standard quoting) and prints a warning. If preserving comments matters, apply the changes manually. - TOML: the
tomlnpm package is read-only.migrate-schemadetects TOML configs, prints the required changes, and exits without writing. - YAML:
js-yamlpreserves key order when round-tripping but does not preserve comments. - Programmatic configs (
.js/.ts/.cjs/.mjs): rewritten in place when reducible to a single object literal; falls back to printing manual steps for dynamic function bodies, spreads, or computed keys, or whentypescriptis not installed. snapbase defaulting: when your oldsnapconfig has nobasefield, the tool assumes"core20"(v27's 1-to-1 migration target) and prints a warning so you can confirm or change it.
Step 2: Update Node.js
v27 requires Node.js 22.12.0 or later — the version where Node's require(esm) support was stabilized, allowing both CJS and ESM consumers to use these packages without code changes.
Update your local environment:
# nvm
nvm install 22 && nvm use 22
# fnm
fnm install 22 && fnm use 22
Update CI (GitHub Actions):
- uses: actions/setup-node@v4
with:
node-version: '22'
Update your package.json engines field if you declare one:
{ "engines": { "node": ">=22.12.0" } }
Confirm your CI and Docker images run Node.js 22.12 or later:
FROM node:22-bookworm-slim
electron-builder's own Docker images for Linux builds have been updated and are available in both node 22 and 24 flavors: https://hub.docker.com/r/electronuserland/builder/tags
ESM/CJS — no code changes needed on Node >=22.12
// CJS require() — still works
const { build } = require("electron-builder")
// ESM import — now the preferred style
import { build } from "electron-builder"
moduleResolution settings "node", "node16"/"nodenext", and "bundler" (recommended) all work. See Native ESM output for details.
Step 3: Apply the manual steps
migrate-schema handles every config-level change. The following require manual action — each links to its full explanation:
- Migrate off
electron-compileto a modern bundler (electron-vite / esbuild / webpack), if you usedelectronCompile. - Add
--publishexplicitly to your release scripts — auto-publish is gone. - Replace
devMetadata/extraMetadatain the programmatic API withconfig.extraMetadata. - Replace
--em.build/--em.directoriesCLI flags with-c/-c.directories. -
linux.syncDesktopNameis removed automatically bymigrate-schema— the behaviour is now always on. Only if you had it set tofalse: setdesktopNameto control the installed.desktopfilename (the migrator warns you). - Switch
<%= var %>→${var}in Linux maintainer scripts. - Update custom NSIS scripts that hard-code the old file-association ProgID.
- Replace
CI_BUILD_TAGwithCI_COMMIT_TAG. - Replace toolset env-var overrides (
APPIMAGE_TOOLS_PATH,ELECTRON_BUILDER_NSIS_DIR,USE_SYSTEM_WINE, …) withtoolsets.X: { url, checksum }. - Validate toolset pins — defaults now resolve to
"latest"(newest bundle); pin to"0.0.0"only if a legacy bundle is needed. - 32-bit builds:
arch: "all"now expands to x64 + arm64 (was x64 + ia32) — requestia32explicitly to keep it. Building Windowsia32/ Linuxarmv7lon Electron 44+ now fails fast; pinelectronVersionto43.xor earlier (supported until January 2027) or drop those targets. - macOS names:
productName/executableNameare now validated, not silently sanitized — if a build starts erroring on the name, choose one that needs no filename sanitization. - Bitbucket publishing: a token set without a username is now sent as Bearer auth — if
BITBUCKET_TOKENholds an app password / API token, also setBITBUCKET_USERNAMEso Basic auth is used; genuine access tokens need no username. - node_modules arch/os filtering: dependencies are now
cpu/os-filtered against the target on every build — a package whosecpu/osmismatches the target is excluded (was effectively universal-macOS-only). Re-include an intentionally cross-arch binary viaextraResources/files. - macOS DMG: the DMG
filesystemdefault is now APFS — setdmg.filesystem: "HFS+"only if you need pre-10.13 (High Sierra) compatibility. - electron-updater:
disableWebInstallernow defaults totrue— v27 warns but still downloads if you never set it; opt in withdisableWebInstaller: falsebefore v28 enforces it. - electron-updater:
latest*.ymlno longer includes legacy top-levelpath/sha512— no action for updater >=2.16; migrate any app code readinginfo.path/info.sha512toinfo.files[0], and setelectronUpdaterCompatibilityto a legacy-inclusive range only if you still ship 1.x–2.15 updaters. - Production dependencies: redundant production
dependenciesare excluded, not rejected —electron/electron-builderare excluded from the copiednode_modules(was a hard error) and theALLOW_ELECTRON_BUILDER_AS_PRODUCTION_DEPENDENCYenv var is removed (if you used it to bundleelectron-builder, overrideignoredProductionDependencieswithout that name); tune the set viaignoredProductionDependencies(e.g. add bundler-inlined deps likereact).electron-prebuilt/electron-rebuildno longer error and are NOT excluded — remove them fromdependencies(they are deprecated) or add them to the list. - Plugin/custom-target authors: replace
packager.info.XandplatformSpecificBuildOptionswith the new pass-through getters.
Summary checklist
Run electron-builder migrate-schema first — it handles the items marked ✓ automatically.
Runtime and CI
- Node.js runtime updated to >=22.12.0
- CI
node-versionupdated to'22' -
"engines"field inpackage.jsonupdated (if declared)
Build config — auto-migrated by migrate-schema
- ✓
electronCompileremoved (if present) - ✓
disableDefaultIgnoredFilesremoved (if present; re-include specific files via afilesglob such as**/*.obj) - ✓
framework,nodeVersion,launchUiVersionremoved (if present) - ✓
npmSkipBuildFromSource→nativeModules.buildDependenciesFromSource(if present) - ✓
buildDependenciesFromSource,nodeGypRebuild,npmRebuildmoved intonativeModules;nativeRebuilder→rebuildMode - ✓ Legacy asar keys +
asarUnpack→asar.unpack;disableSanityCheckAsar→asar.disableSanityCheck;disableAsarIntegrity→asar.disableIntegrity;asar: trueremoved - ✓ macOS signing fields →
mac.sign.*;signIgnore→sign.ignore(alsomas/masDev) - ✓
mac.universalfields →mac.universal.* - ✓
electronDownload→electronGet(verify droppedcache/customDir/customFilename/strictSSL/platform/arch/version) - ✓
appImage.systemIntegrationremoved (if present) - ✓
linux.syncDesktopNameremoved (if present; always synced in v27 — setdesktopNameif you had itfalse) - ✓
GithubOptions.vPrefixedTagName→tagNamePrefix(if present) - ✓
win.azureSignOptions/win.signtoolOptions→win.sign;win.signExecutable/win.signAndEditExecutablehandled - ✓
snaprestructured tosnapcraft(verify the assumedbaseif none was set) - ✓
helper-bundle-id→mac.helperBundleId;squirrelWindows.noMsi→msi; root-leveldirectories→build.directories
Runtime defaults & auto-update
- DMG
filesystemdefault is now APFS — setdmg.filesystem: "HFS+"only for pre-10.13 macOS compatibility - electron-updater
disableWebInstallerdefaults totrue— opt in withdisableWebInstaller: falsebefore v28 (v27 has a warn-only grace period) -
latest*.ymldrops legacy top-levelpath/sha512; defaultelectronUpdaterCompatibilityis now>=2.16— readfiles[]instead; pin a legacy-inclusive range only for legacy embedded updaters - Production
dependenciesthat electron-builder provides or that your bundler inlines (electron/electron-builder/react/…) are excluded from the copiednode_modulesinstead of erroring;ALLOW_ELECTRON_BUILDER_AS_PRODUCTION_DEPENDENCYremoved andelectron-prebuilt/electron-rebuildno longer guarded — tune viaignoredProductionDependenciesManual steps — see Step 3 above.
Plugin and custom-target authors — see the programmatic API changes.
Full breaking-changes reference
Every change above is documented in detail — with rationale (design notes) and before/after for each — on the v27 Breaking Changes page.