Skip to main content

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.

Don't upgrade without reading the breaking changes

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:

  1. Run the automated migrator — rewrites your config to v27 form
  2. Update Node.js — to >=22.12.0
  3. 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.

Without --config, it looks for a build object in package.json and then for electron-builder.{yml,yaml,json,json5,toml,js,cjs,mjs,ts} in the project root. If your config file has another name (for example one your build scripts pass as electron-builder --config my-config.cjs), pass the same path: electron-builder migrate-schema --config my-config.cjs. Pass --project-dir <dir> to specify the project root (default: current directory).

What it migrates automatically​

Config changeBeforeAfter
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" }
nativeRebuilder: "legacy" removed"nativeRebuilder": "legacy"(dropped with a warning — @electron/rebuild "sequential" applies)
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)
Platform asarUnpack moved"asarUnpack": ["**/*.node"], "mac": { "asarUnpack": ["x/**"] }"asar": { "unpack": ["**/*.node"] }, "mac": { "asar": { "unpack": ["**/*.node", "x/**"] } } (root options merged in — a platform asar replaces the root one)
Keys with no effect under asar: false"asar": false, "asarUnpack": "x""asar": false
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.type moved"mac": { "type": "development" }"mac": { "sign": { "type": "development" } }
gatekeeperAssess removed"mac": { "gatekeeperAssess": false }(deleted — @electron/osx-sign 2.x has no spctl --assess step)
v26 null = unset on mac options"mac": { "provisioningProfile": null }(deleted — the v27 option rejects null; identity: null is kept)
mac.universal fields → universal"mac": { "mergeASARs": true }"mac": { "universal": { "mergeASARs": true } }
electronDownload → electronGet"electronDownload": { "mirror": "https://m/" }"electronGet": { "mirrorOptions": { "mirror": "https://m/" } }
electronDownload fields with no v5 equivalent"electronDownload": { "strictSSL": false, "force": true }(dropped with a warning)
GithubOptions.vPrefixedTagName"vPrefixedTagName": false"tagNamePrefix": ""
GitHub empty tagNamePrefix (ignored by v26){ "provider": "github", "tagNamePrefix": "" }{ "provider": "github", "tagNamePrefix": "v" } (keeps your tag names; warns)
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)
win.signAndEditExecutable: false"win": { "signAndEditExecutable": false }"win": { "sign": false } (resource editing now always runs; warns)
Signing options under disabled signing"win": { "signExecutable": false, "signtoolOptions": { … } }"win": { "sign": false } (the unused block is dropped; warns)
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" } } }
toolsets.*: null rejected"toolsets": { "wine": null }(deleted — unset resolves to "latest")
Retired appimage pin"toolsets": { "appimage": "1.0.2" }"toolsets": { "appimage": "1.0.3" }

It also warns without rewriting where there is no mechanical equivalent — squirrelWindows.customSquirrelVendorDir and a custom NSIS bundle in nsis.customNsisBinary / customNsisResources (see Step 3; it does move customNsisBinary.debugLogging → installerDebugLogging), a custom mac.sign signer next to sibling signing options, and, in JS/TS configs only, a platform-level asarUnpack next to root-level ASAR options — and prints advisories (never a rewrite) for an nsis-web target, nsis.perMachine or nsisWeb.perMachine set to true, a custom win.sign.sign hook without win.sign.publisherName, a generic publish configuration whose url has a query string, and a mac/mas/masDev section that names no entitlements file. See what migrate-schema does and does not do.

Each row links to its full explanation on the v27 Breaking Changes page.

Serialization caveats​

  • JSON5 → JSON: when migrate-schema rewrites a .json5 file it produces valid JSON (no comments, standard quoting) and prints a warning. If preserving comments matters, apply the changes manually.
  • TOML: the toml npm package is read-only. migrate-schema detects TOML configs, prints the required changes, and exits without writing.
  • YAML: js-yaml preserves 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 when typescript is not installed.
  • snap base defaulting: when your old snap config has no base field, 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 except squirrelWindows.customSquirrelVendorDir and a custom NSIS bundle in nsis.customNsisBinary / customNsisResources. The following require manual action — each links to its full explanation:


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-version updated to '22'
  • "engines" field in package.json updated (if declared)

Build config — auto-migrated by migrate-schema

  • ✓ electronCompile removed (if present)
  • ✓ disableDefaultIgnoredFiles removed (if present; re-include specific files via a files glob such as **/*.obj)
  • ✓ framework, nodeVersion, launchUiVersion removed (if present)
  • ✓ npmSkipBuildFromSource → nativeModules.buildDependenciesFromSource (if present)
  • ✓ buildDependenciesFromSource, nodeGypRebuild, npmRebuild moved into nativeModules; nativeRebuilder → rebuildMode
  • ✓ Legacy asar keys + asarUnpack → asar.unpack (platform-level too); disableSanityCheckAsar → asar.disableSanityCheck; disableAsarIntegrity → asar.disableIntegrity; asar: true removed
  • ✓ macOS signing fields → mac.sign.*; signIgnore → sign.ignore (also mas/masDev)
  • ✓ mac.universal fields → mac.universal.*
  • ✓ electronDownload → electronGet (verify dropped cache/customDir/customFilename/strictSSL/platform/arch/version/force)
  • ✓ appImage.systemIntegration removed (if present)
  • ✓ linux.syncDesktopName removed (if present; always synced in v27 — set desktopName if you had it false)
  • ✓ GithubOptions.vPrefixedTagName → tagNamePrefix (if present)
  • ✓ win.azureSignOptions / win.signtoolOptions → win.sign; win.signExecutable / win.signAndEditExecutable handled
  • ✓ snap restructured to snapcraft (verify the assumed base if none was set)
  • ✓ helper-bundle-id → mac.helperBundleId; squirrelWindows.noMsi → msi; root-level directories → build.directories
  • ✓ toolsets.*: null removed; toolsets.appimage: "1.0.2" → "1.0.3"; nativeRebuilder: "legacy" dropped

Runtime defaults & auto-update

  • DMG filesystem default is now APFS — set dmg.filesystem: "HFS+" only for pre-10.13 macOS compatibility
  • macOS default entitlements now grant only allow-jit — add allow-unsigned-executable-memory / disable-library-validation back in build/entitlements.mac.plist (and .inherit.plist for nested binaries) only if your app loads foreign-signed or unsigned binaries; MAS builds without build/entitlements.mas.plist now get osx-sign's sandboxed default
  • electron-updater disableWebInstaller defaults to true — web-installer updates are rejected unless it is false; v27+ nsis-web installs opt in automatically, set disableWebInstaller: false for older web-installer installs or when switching from nsis to nsis-web; the nsis-web installer verifies its app package (nsisWeb.allowUnverifiedAppPackage opts out)
  • A code-signed Windows build that writes app-update.yml needs a publisher name — set win.sign.publisherName for a custom win.sign.sign hook or a certificate without a CN, or win.verifyUpdateCodeSignature: false if your updates are not Authenticode-signed
  • NSIS per-machine builds set isAdminRightsRequired — their updates are started with elevate.exe and not installed automatically at launch; with autoInstallEvent: "onNextLaunch", call installPendingUpdateIfAvailable()
  • Update credentials stay on the feed's origin — requestHeaders / addAuthHeader credentials and the feed URL's query are not sent to downloads on another origin
  • latest*.yml drops legacy top-level path/sha512; default electronUpdaterCompatibility is now >=2.16 — read files[] instead; pin a legacy-inclusive range only for legacy embedded updaters
  • Production dependencies that electron-builder provides or that your bundler inlines (electron/electron-builder/react/…) are excluded from the copied node_modules instead of erroring; ALLOW_ELECTRON_BUILDER_AS_PRODUCTION_DEPENDENCY removed and electron-prebuilt/electron-rebuild no longer guarded — tune via ignoredProductionDependencies
  • Building Windows targets on macOS uses the host Wine (toolsets.wine defaults to "system") — install Wine on macOS build machines

Manual 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.