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.
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 this first
- Breaking changes at a glance — the full table
- Runtime changes — Node.js and ESM
- Removed configuration options
- Restructured configuration
- CLI changes
- Toolsets
- Programmatic and plugin-author API changes
- Summary checklist
- Internal changes (non-user-facing) — for transparency
- Design notes — rationale behind the breaking changes
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 change | Before | After |
|---|---|---|
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" } } |
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" } } } |
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.
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. Abase: "custom"config is moved verbatim without per-base nesting.
Breaking changes at a glance
| Change | Auto-migrated | Action required |
|---|---|---|
| Node.js >=22.12.0 required | — | Update your runtime and CI node version |
| All packages are native ESM | — | None — require() still works on Node >=22.12 |
electronCompile config option removed | ✓ | Remove from build config; migrate off electron-compile |
electron-forge-maker-* are now ESM | — | None — same API, same export shape |
Implicit --publish removed | — | Pass --publish explicitly |
snap config key removed | ✓ | Restructured to snapcraft with an explicit base |
GithubOptions.vPrefixedTagName removed | ✓ | Use tagNamePrefix instead |
GitlabOptions.vPrefixedTagName removed | ✓ | Removed automatically |
framework, nodeVersion, launchUiVersion removed | ✓ | Removed automatically |
| ProtonFramework and LibUiFramework removed | — | Migrate to an Electron-based build setup |
devMetadata, extraMetadata in PackagerOptions removed | — | Use config / config.extraMetadata instead |
--em.build, --em.directories CLI flags removed | — | Use -c / -c.directories instead |
npmSkipBuildFromSource removed | ✓ | Replaced by nativeModules.buildDependenciesFromSource |
buildDependenciesFromSource, nodeGypRebuild, npmRebuild, nativeRebuilder moved | ✓ | Grouped under nativeModules; nativeRebuilder → rebuildMode |
appImage.systemIntegration removed | ✓ | Removed automatically |
| Legacy asar keys removed | ✓ | asar-unpack, asar-unpack-dir, asar.unpackDir replaced by asar.unpack |
asarUnpack moved into asar object | ✓ | Use asar.unpack |
disableSanityCheckAsar moved | ✓ | Use asar.disableSanityCheck |
disableAsarIntegrity moved | ✓ | Use asar.disableIntegrity |
asar: true removed | ✓ | Removed automatically (absence means enabled) |
directories at package.json root removed | ✓ | Specify under build.directories |
build.helper-bundle-id removed | ✓ | Moved to mac.helperBundleId |
squirrelWindows.noMsi removed | ✓ | Replaced by msi (inverted) |
<%= var %> EJS template syntax in Linux scripts removed | — | Use ${var} |
CI_BUILD_TAG environment variable removed | — | Use CI_COMMIT_TAG |
win.azureSignOptions index-signature keys moved | ✓ | Extra keys moved into additionalMetadata |
| Toolset env-var overrides removed | — | Replace APPIMAGE_TOOLS_PATH, ELECTRON_BUILDER_NSIS_DIR, etc. with toolsets.X: { url, checksum } |
| Toolset default versions bumped | — | Optionally pin to "0.0.0" to restore legacy bundle if needed |
PlatformPackager.info deprecated; platformSpecificBuildOptions protected | — | Plugin 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:
moduleResolution | Works |
|---|---|
"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-webpackfor 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 key | Replacement |
|---|---|
asar-unpack | asar.unpack |
asar-unpack-dir | asar.unpack |
asar.unpackDir | asar.unpack |
asarUnpack | asar.unpack |
// Before
{
"build": {
"asarUnpack": ["**/*.node", "resources/**"]
}
}
// After
{
"build": {
"asar": {
"unpack": ["**/*.node", "resources/**"]
}
}
}
Integrity and sanity-check flags (disableSanityCheckAsar, disableAsarIntegrity):
| Removed key | Replacement |
|---|---|
disableSanityCheckAsar | asar.disableSanityCheck |
disableAsarIntegrity | asar.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.vPrefixedTagName → tagNamePrefix
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 vPrefixedTagName — tagNamePrefix 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.
snap → snapcraft
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 flag | Replacement |
|---|---|
--em.build | -c (pass build config inline) |
--em.directories | -c.directories |
New command: migrate-schema
Toolsets
Toolset defaults updated
All built-in binary toolsets have been upgraded to modern default versions in v27:
| Toolset | v26 default | v27 default | Key improvement |
|---|---|---|---|
winCodeSign | 0.0.0 (winCodeSign 2.6.0) | "1.1.0" | Windows Kits 10.0.26100.0; modern signtool.exe and osslsigncode |
nsis | 0.0.0 (NSIS 3.0.4.1, split bundle) | "1.2.1" | NSIS 3.12; unified single-archive bundle |
appimage | 0.0.0 (FUSE2 runtime) | "1.0.3" | FUSE3-compatible static runtime (runs without host FUSE install) |
wine | 0.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 var | Toolset it controlled |
|---|---|
APPIMAGE_TOOLS_PATH | AppImage build tools (mksquashfs, runtime) |
LINUX_TOOLS_MAC_PATH | Linux-tools-mac bundle (ar, lzip, gtar) |
CUSTOM_FPM_PATH | FPM executable |
ELECTRON_BUILDER_NSIS_DIR | NSIS compiler bundle directory |
ELECTRON_BUILDER_NSIS_RESOURCES_DIR | NSIS resources/plugins directory |
CUSTOM_NSIS_RESOURCES | Alternate NSIS resources bundle |
ELECTRON_BUILDER_WINE_TOOLSET_DIR | Wine 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-liband access thepackagerorplatformPackagerobjects 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.tempDirManager | packager.tempDirManager |
packager.info.metadata | packager.metadata |
packager.info.framework | packager.framework |
packager.info.cancellationToken | packager.cancellationToken |
packager.info.repositoryInfo | packager.repositoryInfo |
packager.info.relativeBuildResourcesDirname | packager.relativeBuildResourcesDirname |
packager.info.stageDirPathCustomizer | packager.stageDirPathCustomizer |
packager.info.areNodeModulesHandledExternally | packager.areNodeModulesHandledExternally |
packager.info.isPrepackedAppAsar | packager.isPrepackedAppAsar |
packager.info.appDir | packager.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 pattern | v27 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-versionupdated to'22' -
"engines"field inpackage.jsonupdated (if declared)
Build config — auto-migrated by migrate-schema
- ✓
electronCompileremoved (if present) - ✓
framework,nodeVersion,launchUiVersionremoved (if present) - ✓
npmSkipBuildFromSource→nativeModules.buildDependenciesFromSource(if present) - ✓
buildDependenciesFromSource,nodeGypRebuild,npmRebuildmoved intonativeModules(if present) - ✓
nativeRebuilder→nativeModules.rebuildMode(if present) - ✓ Legacy asar keys (
asar-unpack,asar-unpack-dir,asar.unpackDir) →asar.unpack(if present) - ✓
asarUnpack→asar.unpack(if present) - ✓
disableSanityCheckAsar→asar.disableSanityCheck(if present) - ✓
disableAsarIntegrity→asar.disableIntegrity(if present) - ✓
asar: true→ removed (absence means enabled) (if present) - ✓
appImage.systemIntegrationremoved (if present) - ✓
GithubOptions.vPrefixedTagName→tagNamePrefix(if present) - ✓
win.azureSignOptionsextra keys →additionalMetadata(if present) - ✓
snaprestructured tosnapcraft(verify the assumedbaseif none was set) - ✓
helper-bundle-id→mac.helperBundleId(if present) - ✓
squirrelWindows.noMsi→msi(if present) - ✓ Root-level
directories→build.directories(package.json only)
Build config / scripts — manual steps required
- Migrated off
electron-compileto a modern bundler (if you usedelectronCompile) -
--publishflag added explicitly to release scripts (no more auto-publish) -
devMetadata/extraMetadatain programmatic API →config.extraMetadata(if applicable) -
--em.build/--em.directoriesCLI flags →-c/-c.directories(if applicable) -
<%= var %>Linux script syntax →${var}(if applicable) -
CI_BUILD_TAG→CI_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.Xchains with directpackager.Xgetters (if applicable) - Replace
packager.platformSpecificBuildOptionswithplatformOptions/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
tscto native ESM withexportsmaps and full type declarations. - Code-quality modernization. Production code paths adopt native Node.js APIs and modern TypeScript patterns:
sleep()fromtimers/promisesreplaces hand-rolledsetTimeoutpromise wrappers; native process signal handlers replaceasync-exit-hook;Error.causepreserves full stack traces inExecError;Reflect.get/Reflect.setreplaceas anyProxy traps; the WHATWGURLAPI replaces legacyurl.format(); SHA-256 replaces MD5 for WiX directory-ID generation;console.*calls were eliminated from production code paths. - Dead-code removal.
ProtonFramework,LibUiFramework, andbinDownload.tswere deleted; all binary downloads were consolidated into a singledownloadBuilderToolsetpath. - Flags consolidation. All boolean
process.envflags were consolidated into a singleflags.ts, andvalidateShellEmbeddablemoved tobuilder-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 inapp-builder-libinternals changed accordingly; the public API surface is unchanged. - Toolset module split. Toolset getters were split into single-purpose files alongside the new
ToolsetCustomsupport. - NSIS internals. Arch package-file resolution and async resource loaders were extracted from
buildInstaller/configureDefinesfor 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-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 (parse 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): 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.