Skip to main content

Migrating from v26 to v27

v27 migrates the entire electron-builder package ecosystem to native ES modules (ESM), raises the minimum Node.js version to 22.12.0, and hard-deletes the deprecated APIs that accumulated since v22.

Most projects need only a Node.js version bump. The build() API, all runtime configuration options, and all exported types are unchanged. CJS require() continues to work without any code changes on supported Node.js versions.

Run the automated migrator first

Before reading further, run the built-in command to automatically rewrite your config:

electron-builder migrate-schema

Use --dry-run to preview changes without writing. This handles every config-level breaking change automatically — see what it migrates for the full list.

This guide is organized by purpose:


Automated migration tool

Run the built-in command to automatically apply the config-level breaking changes to your project:

electron-builder migrate-schema

This rewrites your electron-builder.json, electron-builder.yml, package.json (build key), or any other static config format in place. Use --dry-run (-n) to preview changes without writing:

electron-builder migrate-schema --dry-run

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 changeBeforeAfter
electronCompile removed"electronCompile": 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)
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)
GithubOptions.vPrefixedTagName"vPrefixedTagName": false"tagNamePrefix": ""
GitlabOptions.vPrefixedTagName"vPrefixedTagName": true(deleted)
Azure extra sign keys"azureSignOptions": { "ExcludeCredentials": "X" }"azureSignOptions": { "additionalMetadata": { "ExcludeCredentials": "X" } }
snapsnapcraft"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" } } }

Note: Programmatic configs (.js, .ts, .cjs, .mjs) and TOML configs cannot be auto-rewritten. The command prints the required manual steps instead. See serialization caveats.

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. A base: "custom" config is moved verbatim without per-base nesting.


Breaking changes at a glance

ChangeAuto-migratedAction required
Node.js >=22.12.0 requiredUpdate your runtime and CI node version
All packages are native ESMNone — require() still works on Node >=22.12
electronCompile config option removedRemove from build config; migrate off electron-compile
electron-forge-maker-* are now ESMNone — same API, same export shape
Implicit --publish removedPass --publish explicitly
snap config key removedRestructured to snapcraft with an explicit base
GithubOptions.vPrefixedTagName removedUse tagNamePrefix instead
GitlabOptions.vPrefixedTagName removedRemoved automatically
framework, nodeVersion, launchUiVersion removedRemoved automatically
ProtonFramework and LibUiFramework removedMigrate to an Electron-based build setup
devMetadata, extraMetadata in PackagerOptions removedUse config / config.extraMetadata instead
--em.build, --em.directories CLI flags removedUse -c / -c.directories instead
npmSkipBuildFromSource removedReplaced by nativeModules.buildDependenciesFromSource
buildDependenciesFromSource, nodeGypRebuild, npmRebuild, nativeRebuilder movedGrouped under nativeModules; nativeRebuilderrebuildMode
appImage.systemIntegration removedRemoved automatically
Legacy asar keys removedasar-unpack, asar-unpack-dir, asar.unpackDir replaced by asar.unpack
asarUnpack moved into asar objectUse asar.unpack
disableSanityCheckAsar movedUse asar.disableSanityCheck
disableAsarIntegrity movedUse asar.disableIntegrity
asar: true removedRemoved automatically (absence means enabled)
directories at package.json root removedSpecify under build.directories
build.helper-bundle-id removedMoved to mac.helperBundleId
squirrelWindows.noMsi removedReplaced by msi (inverted)
<%= var %> EJS template syntax in Linux scripts removedUse ${var}
CI_BUILD_TAG environment variable removedUse CI_COMMIT_TAG
win.azureSignOptions index-signature keys movedExtra keys moved into additionalMetadata
Toolset env-var overrides removedReplace APPIMAGE_TOOLS_PATH, ELECTRON_BUILDER_NSIS_DIR, etc. with toolsets.X: { url, checksum }
Toolset default versions bumpedOptionally pin to "0.0.0" to restore legacy bundle if needed
PlatformPackager.info deprecated; platformSpecificBuildOptions protectedPlugin authors: use the new pass-through getters

Runtime changes

Update Node.js to >=22.12.0

v27 requires Node.js 22.12.0 or later. This is the version where Node's require(esm) support was stabilized (no flags needed), which allows both CJS and ESM consumers to use these packages without any 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"
}
}

ESM output — what changes for your project

electron-builder packages now ship as native ES modules. On Node >=22.12.0 both styles continue to work:

CJS require() — still works

const { build, Platform } = require("electron-builder")

ESM import — now the preferred style

import { build, Platform } from "electron-builder"

TypeScript

import { build, Configuration, Platform } from "electron-builder"

const config: Configuration = {
appId: "com.example.myapp",
mac: { target: "dmg" },
}

await build({ targets: Platform.MAC.createTarget(), config })

If your project uses "type": "module" or ESM imports, no changes are needed. If it uses CJS on Node >=22.12, require() continues to work as before.

The build() function signature, all configuration options, and all exported types are otherwise unchanged.

electron-forge-maker-* plugins

The four Forge maker plugins (electron-forge-maker-appimage, electron-forge-maker-nsis, electron-forge-maker-nsis-web, electron-forge-maker-snap) are now native ES modules. The public API is identical — Electron Forge loads them via dynamic import() internally, so no config changes are required.

TypeScript moduleResolution settings

v27 packages include exports maps and full TypeScript declarations. The following tsconfig.json settings all work:

moduleResolutionWorks
"node" (legacy)
"node16" / "nodenext"
"bundler"✓ (recommended)

CI and Docker environments

Confirm your CI and Docker images run Node.js 22.12 or later.

# GitHub Actions
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v4
with:
node-version: '22'
# Docker
FROM node:22-bookworm-slim

electron-builder's own Docker images for Linux builds have been updated as well and are available in both node 22 and 24 flavors. https://hub.docker.com/r/electronuserland/builder/tags


Removed configuration options

electronCompile

The electronCompile configuration option has been removed. electron-compile is an unmaintained library with no releases since 2019.

{
"build": {
"electronCompile": true // ← delete this line
}
}

If your project relies on electron-compile for source compilation, migrate to a modern build tool before upgrading to v27:

  • electron-vite — recommended; fast, first-class ESM and Electron integration
  • esbuild — extremely fast, minimal configuration
  • webpack — mature, widely used; electron-webpack for Electron-specific setup

Quick start with electron-vite:

npm create electron-vite@latest my-app

framework, nodeVersion, launchUiVersion

These three fields are removed. Only Electron is supported as a target framework — proton/proton-native and libui support has been removed.

{
"build": {
"framework": "electron", // ← delete; "electron" was and remains the default
"nodeVersion": "current", // ← delete; no effect
"launchUiVersion": "0.1.0" // ← delete; no effect
}
}

appImage.systemIntegration

Removed. Desktop integration is handled automatically by AppImageLauncher.

npmSkipBuildFromSource

Removed. Use buildDependenciesFromSource (now under nativeModules — see Restructured configuration). It is the logical inverse:

// v26
{ "npmSkipBuildFromSource": true }

// v27 equivalent
{ "nativeModules": { "buildDependenciesFromSource": false } }

ASAR options consolidated under asar

All ASAR-related configuration is now nested under a single asar key. Flat root-level properties are removed.

Unpack patterns (asarUnpack and the older hyphenated aliases):

Removed keyReplacement
asar-unpackasar.unpack
asar-unpack-dirasar.unpack
asar.unpackDirasar.unpack
asarUnpackasar.unpack
// Before
{
"build": {
"asarUnpack": ["**/*.node", "resources/**"]
}
}

// After
{
"build": {
"asar": {
"unpack": ["**/*.node", "resources/**"]
}
}
}

Integrity and sanity-check flags (disableSanityCheckAsar, disableAsarIntegrity):

Removed keyReplacement
disableSanityCheckAsarasar.disableSanityCheck
disableAsarIntegrityasar.disableIntegrity
// Before
{
"build": {
"disableSanityCheckAsar": true,
"disableAsarIntegrity": true
}
}

// After
{
"build": {
"asar": {
"disableSanityCheck": true,
"disableIntegrity": true
}
}
}

asar: true is no longer valid. Simply omit the asar key entirely to enable ASAR with defaults, or specify an object:

// Before — enabling ASAR with defaults
{ "build": { "asar": true } }

// After — omit entirely (ASAR is enabled by default) or use an object
{ "build": { "asar": {} } }

When asar: false all the sub-options are irrelevant and the migrator skips them.

The asar type is now AsarOptions | false | null. All options combined:

{
"build": {
"asar": {
"unpack": ["**/*.node"],
"smartUnpack": true,
"ordering": "packing-order.txt",
"disableSanityCheck": false,
"disableIntegrity": false
}
}
}

Root-level directories in package.json

Specifying directories at the root of package.json is removed. Move it under the build key.

// Before
{
"name": "my-app",
"directories": { "output": "dist" }
}

// After
{
"name": "my-app",
"build": {
"directories": { "output": "dist" }
}
}

build.helper-bundle-id

The hyphenated root-level helper-bundle-id is removed. Use mac.helperBundleId.

// Before
{ "build": { "helper-bundle-id": "com.example.helper" } }

// After
{ "build": { "mac": { "helperBundleId": "com.example.helper" } } }

squirrelWindows.noMsi

The noMsi boolean is removed in favor of its inverse, msi.

// Before
{ "build": { "squirrelWindows": { "noMsi": true } } }

// After
{ "build": { "squirrelWindows": { "msi": false } } }

GithubOptions.vPrefixedTagNametagNamePrefix

The vPrefixedTagName boolean on GithubOptions is removed. Use tagNamePrefix to control the tag prefix.

// Before
{ "publish": { "provider": "github", "vPrefixedTagName": false } }

// After
{ "publish": { "provider": "github", "tagNamePrefix": "" } } // empty string = no prefix

To keep the default v prefix, simply remove vPrefixedTagNametagNamePrefix defaults to "v".

vPrefixedTagName on GitlabOptions is also removed. GitLab releases now always use the v-prefixed tag (e.g. v1.2.3). There is no replacement option; update your release pipeline if your GitLab tags do not follow this convention.

devMetadata, extraMetadata (programmatic PackagerOptions)

These fields in the programmatic PackagerOptions API are removed (they have thrown InvalidConfigurationError since v22).

// Before
await build({
targets: Platform.MAC.createTarget(),
devMetadata: { ... }, // ← removed
extraMetadata: { ... }, // ← removed
})

// After
await build({
targets: Platform.MAC.createTarget(),
config: {
extraMetadata: { ... },
},
})

Linux script EJS template syntax

The legacy <%= varName %> EJS interpolation in Linux maintainer scripts (e.g. FPM after-install/after-remove) is removed. Use shell-style ${varName} instead.

CI_BUILD_TAG environment variable

Removed. Use CI_COMMIT_TAG (the standard GitLab CI variable) to provide the release tag.


Restructured configuration

Native-module options grouped under nativeModules

Four root-level configuration properties are moved into a new nativeModules sub-key, and nativeRebuilder is renamed to rebuildMode.

// Before (v26)
{
"build": {
"buildDependenciesFromSource": true,
"nodeGypRebuild": false,
"npmRebuild": true,
"nativeRebuilder": "parallel"
}
}

// After (v27)
{
"build": {
"nativeModules": {
"buildDependenciesFromSource": true,
"nodeGypRebuild": false,
"npmRebuild": true,
"rebuildMode": "parallel"
}
}
}

npmArgs is not affected — it controls the package-manager install phase and remains at the root level.

npmSkipBuildFromSource (deprecated in v26) is removed; the migrate-schema command converts it to its inverse, nativeModules.buildDependenciesFromSource.

snapsnapcraft

The top-level snap configuration key is removed. Use snapcraft with an explicit base field and per-base options nested under a sub-key named after the base.

// Before
{
"build": {
"snap": {
"confinement": "strict",
"stagePackages": ["libfoo"],
"base": "core22"
}
}
}

// After
{
"build": {
"snapcraft": {
"base": "core22",
"core22": {
"confinement": "strict",
"stagePackages": ["libfoo"]
}
}
}
}

Supported base values: "core18", "core20", "core22", "core24", and "custom". The migrate-schema command performs this restructuring; when the old config has no base, it assumes "core20" and warns so you can confirm. A base: "custom" config (inline/path snapcraft.yaml) is moved verbatim.

win.azureSignOptions — extra keys moved to additionalMetadata

In v26, WindowsAzureSigningConfiguration used an index signature ([k: string]: string) that allowed arbitrary extra keys alongside the typed fields. These were passed as additional arguments to Invoke-TrustedSigning.

In v27, the PowerShell-based Invoke-TrustedSigning integration is replaced by signtool.exe /dlib Azure.CodeSigning.Dlib.dll /dmdf metadata.json. Extra fields must now be placed inside an explicit additionalMetadata object, forwarded verbatim into metadata.json.

// Before
{
"win": {
"azureSignOptions": {
"endpoint": "https://weu.codesigning.azure.net/",
"codeSigningAccountName": "my-account",
"certificateProfileName": "my-profile",
"publisherName": "CN=My Company",
"ExcludeCredentials": "ManagedIdentityCredential"
}
}
}

// After
{
"win": {
"azureSignOptions": {
"endpoint": "https://weu.codesigning.azure.net/",
"codeSigningAccountName": "my-account",
"certificateProfileName": "my-profile",
"publisherName": "CN=My Company",
"additionalMetadata": {
"ExcludeCredentials": "ManagedIdentityCredential"
}
}
}
}

The migrate-schema command moves any unrecognized keys into additionalMetadata automatically.


CLI changes

--publish is no longer implicit

v27 no longer auto-publishes based on the presence of CI tag environment variables. Pass --publish <always|onTag|onTagOrDraft|never> explicitly in your release scripts.

Removed flags: --em.build, --em.directories

These flags are removed (they have thrown since v22).

Removed flagReplacement
--em.build-c (pass build config inline)
--em.directories-c.directories

New command: migrate-schema

See Automated migration tool.


Toolsets

Toolset defaults updated

All built-in binary toolsets have been upgraded to modern default versions in v27:

Toolsetv26 defaultv27 defaultKey improvement
winCodeSign0.0.0 (winCodeSign 2.6.0)"1.1.0"Windows Kits 10.0.26100.0; modern signtool.exe and osslsigncode
nsis0.0.0 (NSIS 3.0.4.1, split bundle)"1.2.1"NSIS 3.12; unified single-archive bundle
appimage0.0.0 (FUSE2 runtime)"1.0.3"FUSE3-compatible static runtime (runs without host FUSE install)
wine0.0.0 (Wine 4.0.1, macOS only)"1.0.1"Wine 11.0; macOS + Linux; ia32 via WoW64

No action required for most projects — the new bundles are drop-in replacements and produce identical output. If you hit a regression introduced by a newer bundle, pin back to the legacy bundle by setting the toolset version to "0.0.0":

{
"build": {
"toolsets": {
"winCodeSign": "0.0.0", // restore winCodeSign 2.6.0
"nsis": "0.0.0", // restore NSIS 3.0.4.1
"appimage": "0.0.0", // restore FUSE2 AppImage runtime
"wine": "0.0.0" // restore Wine 4.0.1 (macOS only)
}
}
}

This escape hatch is intended as a short-term workaround. The "0.0.0" alias may be removed in a future major release.

Toolset env-var overrides replaced by ToolsetCustom

This is a breaking change if you used env-var toolset overrides. The following environment variables are removed:

Removed env varToolset it controlled
APPIMAGE_TOOLS_PATHAppImage build tools (mksquashfs, runtime)
LINUX_TOOLS_MAC_PATHLinux-tools-mac bundle (ar, lzip, gtar)
CUSTOM_FPM_PATHFPM executable
ELECTRON_BUILDER_NSIS_DIRNSIS compiler bundle directory
ELECTRON_BUILDER_NSIS_RESOURCES_DIRNSIS resources/plugins directory
CUSTOM_NSIS_RESOURCESAlternate NSIS resources bundle
ELECTRON_BUILDER_WINE_TOOLSET_DIRWine bundle directory

Replace env-var overrides with the new ToolsetCustom config object on the relevant toolsets key. The url accepts an https:// URL (downloaded and cached automatically) or a file:// path (used as-is).

// Remote bundle (URL)
{
"build": {
"toolsets": {
"nsis": {
"url": "https://example.com/my-nsis-bundle-1.0.tar.gz",
"checksum": "sha256:abc123...",
"version": "my-custom-1.0"
}
}
}
}
// Local directory (no checksum required)
{
"build": {
"toolsets": {
"appimage": {
"url": "file:///path/to/my-appimage-tools-dir"
}
}
}
}

The bundle must mirror the directory layout of the corresponding built-in bundle. See electron-builder-binaries/packages for the expected structure.

Supported archive formats: .zip, .7z, .tar.gz, .tar.xz. Exception for sevenZip: because 7-Zip is used to extract .7z and .tar.xz archives, a custom sevenZip bundle can only be supplied as a .tar.gz, .zip, or bare file:// directory.


Programmatic and plugin-author API changes

This section only affects you if you import from app-builder-lib and access the packager or platformPackager objects directly. Standard project configurations are unaffected.

info: Packager is now @deprecated

PlatformPackager has always exposed a public info: Packager field giving access to the whole-build orchestrator. In v27 this field is deprecated and will become protected in a future major release. TypeScript emits a TS6385 hint anywhere your code chains through .info.:

// deprecated — produces a TS6385 hint
const tmpDir = packager.info.tempDirManager
await packager.info.emitArtifactCreated(event)

All commonly-needed properties are now directly accessible on PlatformPackager. Migrate by dropping the .info. chain:

Before (deprecated)After
packager.info.tempDirManagerpackager.tempDirManager
packager.info.metadatapackager.metadata
packager.info.frameworkpackager.framework
packager.info.cancellationTokenpackager.cancellationToken
packager.info.repositoryInfopackager.repositoryInfo
packager.info.relativeBuildResourcesDirnamepackager.relativeBuildResourcesDirname
packager.info.stageDirPathCustomizerpackager.stageDirPathCustomizer
packager.info.areNodeModulesHandledExternallypackager.areNodeModulesHandledExternally
packager.info.isPrepackedAppAsarpackager.isPrepackedAppAsar
packager.info.appDirpackager.appDir
packager.info.getWorkspaceRoot()packager.getWorkspaceRoot()
packager.info.emitArtifactBuildStarted(e)packager.emitArtifactBuildStarted(e)
packager.info.emitArtifactBuildCompleted(e)packager.emitArtifactBuildCompleted(e)
packager.info.emitArtifactCreated(e)packager.emitArtifactCreated(e)
packager.info.emitMsiProjectCreated(p)packager.emitMsiProjectCreated(p)
packager.info.emitAppxManifestCreated(p)packager.emitAppxManifestCreated(p)

The getters already on PlatformPackager in v26 are unchanged (config, projectDir, buildResourcesDir, packagerOptions, appInfo, debugLogger).

platformSpecificBuildOptions is now protected

External consumers should use the two new public helpers instead:

Access patternv27 replacement
packager.platformSpecificBuildOptions (direct read)packager.platformOptions
deepAssign({}, packager.platformSpecificBuildOptions, config.X)packager.getOptionsForTarget<T>("X")

platformOptions is a typed getter returning the same platform-level config object. getOptionsForTarget<T>(key) performs the standard merge of platform options with the named per-target key (e.g. "appx", "msi") and returns the result as T. All built-in targets (AppImage, Flatpak, Fpm, Snap, Appx, Msi, MsiWrapped, SquirrelWindows, DMG, NSIS, Archive) have been migrated; custom targets extending PlatformPackager must update any direct .platformSpecificBuildOptions access.

Removed exports

ProtonFramework, LibUiFramework, and SnapOptions are removed from the public exports of app-builder-lib and electron-builder. Use the Electron framework and the snapcraft config shape respectively.


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)
  • framework, nodeVersion, launchUiVersion removed (if present)
  • npmSkipBuildFromSourcenativeModules.buildDependenciesFromSource (if present)
  • buildDependenciesFromSource, nodeGypRebuild, npmRebuild moved into nativeModules (if present)
  • nativeRebuildernativeModules.rebuildMode (if present)
  • ✓ Legacy asar keys (asar-unpack, asar-unpack-dir, asar.unpackDir) → asar.unpack (if present)
  • asarUnpackasar.unpack (if present)
  • disableSanityCheckAsarasar.disableSanityCheck (if present)
  • disableAsarIntegrityasar.disableIntegrity (if present)
  • asar: true → removed (absence means enabled) (if present)
  • appImage.systemIntegration removed (if present)
  • GithubOptions.vPrefixedTagNametagNamePrefix (if present)
  • win.azureSignOptions extra keys → additionalMetadata (if present)
  • snap restructured to snapcraft (verify the assumed base if none was set)
  • helper-bundle-idmac.helperBundleId (if present)
  • squirrelWindows.noMsimsi (if present)
  • ✓ Root-level directoriesbuild.directories (package.json only)

Build config / scripts — manual steps required

  • Migrated off electron-compile to a modern bundler (if you used electronCompile)
  • --publish flag added explicitly to release scripts (no more auto-publish)
  • devMetadata / extraMetadata in programmatic API → config.extraMetadata (if applicable)
  • --em.build / --em.directories CLI flags → -c / -c.directories (if applicable)
  • <%= var %> Linux script syntax → ${var} (if applicable)
  • CI_BUILD_TAGCI_COMMIT_TAG (if applicable)
  • Toolset env-var overrides → toolsets.X: { url, checksum } (if applicable)
  • Toolset version pins validated — defaults are winCodeSign: "1.1.0", nsis: "1.2.1", appimage: "1.0.3", wine: "1.0.1"; pin to "0.0.0" only if needed

Plugin and custom-target authors

  • Replace packager.info.X chains with direct packager.X getters (if applicable)
  • Replace packager.platformSpecificBuildOptions with platformOptions / getOptionsForTarget (if applicable)

Internal changes (non-user-facing)

These changes have no impact on your build configuration or the public API. They are listed here for transparency about what shipped in v27.

  • Native ESM build pipeline. Babel was removed entirely; packages are compiled by tsc to native ESM with exports maps and full type declarations.
  • Code-quality modernization. Production code paths adopt native Node.js APIs and modern TypeScript patterns: sleep() from timers/promises replaces hand-rolled setTimeout promise wrappers; native process signal handlers replace async-exit-hook; Error.cause preserves full stack traces in ExecError; Reflect.get/Reflect.set replace as any Proxy traps; the WHATWG URL API replaces legacy url.format(); SHA-256 replaces MD5 for WiX directory-ID generation; console.* calls were eliminated from production code paths.
  • Dead-code removal. ProtonFramework, LibUiFramework, and binDownload.ts were deleted; all binary downloads were consolidated into a single downloadBuilderToolset path.
  • Flags consolidation. All boolean process.env flags were consolidated into a single flags.ts, and validateShellEmbeddable moved to builder-util/envUtil.
  • Source reorganization. Platform-specific files were split into per-platform subdirectories (targets/mac|win|linux/, vm/mac|win/, codeSign/mac|win/, electron/mac|win/, util/mac|win/, toolsets/win|linux/). Import paths in app-builder-lib internals changed accordingly; the public API surface is unchanged.
  • Toolset module split. Toolset getters were split into single-purpose files alongside the new ToolsetCustom support.
  • NSIS internals. Arch package-file resolution and async resource loaders were extracted from buildInstaller/configureDefines for clarity (no behavioral change).
  • Test suite. Duplicated test files were replaced by runtime-generated tests (via TestSuite) that fan out across toolset version combinations.

Design notes

Rationale behind the user-facing breaking changes, for those who want full transparency.

Why native ESM?

electron-builder historically shipped CommonJS. The move to native ESM was driven by the ecosystem: major dependencies (chalk, figures, ora, etc.) have dropped CJS support, and Node.js 22.12's stabilized require(esm) makes it safe to ship ESM without breaking CJS consumers. The minimum was set to 22.12.0 specifically because earlier Node.js versions require --experimental-require-module to require() an ESM package.

Why move native-module options under nativeModules?

In v26, options like buildDependenciesFromSource, nodeGypRebuild, npmRebuild, and nativeRebuilder were scattered at the root of Configuration alongside unrelated properties. They were grouped under nativeModules for the same reason directories and toolsets are sub-keys: related options should be co-located. The rename nativeRebuilder → rebuildMode makes the field name consistent with other enum-style selectors ("sequential" | "parallel" is a "mode").

Why replace [k: string] in WindowsAzureSigningConfiguration?

The v27 Azure integration switches from PowerShell Invoke-TrustedSigning to signtool.exe /dlib /dmdf, which reads a metadata.json file. An explicit additionalMetadata: Record<string, string> field is cleaner than an index signature because it makes intent visible in IntelliSense and prevents accidental shadowing of the typed fields.

Why restructure snap into snapcraft?

The snapcraft shape makes the base explicit and nests per-base options under a base-named sub-key, so a single config can describe multiple bases unambiguously and core24 (which uses the snapcraft CLI directly) can coexist with legacy bases. migrate-schema automates the move but assumes core20 when no base is present, because that is the v27 1-to-1 migration target — verify the assumption for your project.

migrate-schema serialization caveats

  • JSON5 → JSON: JSON5 supports comments and trailing commas. 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 (parse 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): Cannot be safely transformed — the command prints manual steps instead.

Why consolidate ASAR options under asar?

In v26, asarUnpack lived alongside asar on PlatformSpecificBuildOptions, and disableSanityCheckAsar / disableAsarIntegrity lived at the root of Configuration. This was confusing: asarUnpack is meaningless when asar: false, yet nothing in the type system expressed that relationship. Moving all options under a single asar key makes the dependency explicit — if asar is absent or false, none of the sub-options apply. It also brings per-platform asar control in line with other per-platform sub-objects (nativeModules, toolsets, directories).

The asar: true sentinel was removed because having both true (enable with defaults) and {} (same meaning, but object) was redundant. Absent asar key means enabled; asar: false means disabled. migrate-schema removes bare asar: true automatically.

PlatformPackager.info deprecation strategy

The public info: Packager field created a hard coupling: any internal Packager refactor became a breaking change for all plugins. In v27, direct pass-through getters and methods were added for the properties plugin authors actually need, and info was marked @deprecated (a soft deprecation — it still works in v27 but emits a TS6385 hint). It will become protected in a future major release.