hooks
Build Hooks
Build hooks let you run custom code at specific points in the electron-builder build lifecycle. Use them to modify the app bundle, perform additional signing, upload symbols, or integrate with external services.
Build Lifecycle
Hooks fire in this order during every build. Pick the hook that runs at the right stage for your task — modifying app files before signing, triggering external tools after all artifacts are ready, etc.
| Hook | When it fires | Common use |
|---|---|---|
beforeBuild | Before native deps are installed/rebuilt | Skip or customize native module rebuild |
beforePack | Before files are copied into the app bundle | Inject generated files, modify source before pack |
afterExtract | After Electron binary is extracted | Modify the Electron binary or its resources |
afterPack | After files are packaged, before signing | Modify app bundle structure, add unpacked files |
afterSign | After signing, before distributable is created | Custom post-sign steps, extra notarization workflows |
artifactBuildStarted | When each installer/image starts building | Per-artifact logging or tracking |
artifactBuildCompleted | When each installer/image finishes | Checksum capture, per-artifact upload |
afterAllArtifactBuild | After all artifacts are done | Upload debug symbols, generate changelogs, return extra publish paths |
Hook execution is serial within each platform/arch combination. Async hooks are awaited before the build proceeds.
Target-specific hooks (only fire for their respective target):
electronDist— supply a custom Electron binarymsiProjectCreated— edit WiX XML before MSI compilationappxManifestCreated— edit AppX manifest XML before packagingonNodeModuleFile— filter whichnode_modulesfiles are included in the bundle
Defining Hooks
Hooks can be defined in three ways:
1. Inline Function (JS/TS config only)
// electron-builder.config.js
module.exports = {
afterPack: async (context) => {
console.log("afterPack:", context.appOutDir)
}
}
2. Path to File
Use a file path string when using JSON/YAML config, or when you want to keep hooks in separate files:
# electron-builder.yml
afterPack: ./scripts/afterPack.js
// scripts/afterPack.js
exports.default = async function(context) {
console.log("afterPack:", context.appOutDir)
}
The function must be the default export of the module.
3. Module ID
Reference a node module:
afterPack: my-electron-builder-plugin
Hook Reference
beforeBuild
Runs before native dependencies are installed or rebuilt (before npm rebuild). Resolving to false skips dependency installation entirely.
beforeBuild: async (context) => {
// context: BeforeBuildContext
// { appDir, electronVersion, platform, arch }
console.log("Building for:", context.platform.name, context.arch)
// return false to skip dependency install
}
beforePack
Runs before files are copied into the app bundle.
beforePack: async (context) => {
// context: BeforePackContext
// { outDir, appOutDir, packager, electronPlatformName, arch, targets }
}
afterExtract
Runs after the Electron binary has been extracted into the output directory, before app files are placed. Useful for manipulating the Electron binary or its resources.
afterExtract: async (context) => {
// context: AfterExtractContext (same shape as PackContext)
// { outDir, appOutDir, packager, electronPlatformName, arch, targets }
}
afterPack
Runs after the app is fully packaged but before code signing. This is the ideal hook for:
- Modifying the app bundle structure
- Adding files that should not be ASAR-archived
- Running post-processing on the packaged app
afterPack: async (context) => {
// context: AfterPackContext
// { outDir, appOutDir, packager, electronPlatformName, arch, targets }
const path = require("path")
const fs = require("fs")
// Example: add a VERSION file to the app bundle
const versionFile = path.join(context.appOutDir, "VERSION")
fs.writeFileSync(versionFile, context.packager.appInfo.version)
}
afterSign
Runs after the app is signed but before the distributable (DMG, NSIS installer, etc.) is created. Commonly used for macOS notarization in custom workflows.
afterSign: async (context) => {
// context: AfterPackContext
// { outDir, appOutDir, packager, electronPlatformName, arch, targets }
}
electron-builder has built-in notarization support via mac.notarize: true. Only use afterSign for notarization if you need a custom notarization workflow. See Notarization.
artifactBuildStarted
Runs when an individual artifact (e.g., a DMG, an NSIS installer) starts being built.
artifactBuildStarted: async (context) => {
// context: ArtifactBuildStarted
// { targetPresentableName, file, safeArtifactName, packager, arch }
console.log("Building artifact:", context.targetPresentableName)
}
artifactBuildCompleted
Runs when an individual artifact finishes building.
artifactBuildCompleted: async (context) => {
// context: ArtifactCreated
// { file, safeArtifactName, target, packager, publishConfig, isWriteUpdateInfo, updateInfo, sha2, sha512, artifactName, arch }
console.log("Built:", context.file, "SHA512:", context.sha512)
}
afterAllArtifactBuild
Runs after all artifacts have been built. Can return an array of additional file paths to publish.
afterAllArtifactBuild: async (buildResult) => {
// buildResult: BuildResult
// { outDir, artifactPaths, platformToTargets, configuration }
// Example: upload debug symbols to Sentry
for (const artifactPath of buildResult.artifactPaths) {
if (artifactPath.endsWith('.dSYM') || artifactPath.endsWith('.pdb')) {
await uploadSymbols(artifactPath)
}
}
// Return additional files to publish
return ["/path/to/additional-artifact.zip"]
}
electronDist
Provide a custom Electron binary instead of the standard downloaded one. The hook receives PrepareApplicationStageDirectoryOptions and should return the path to a custom Electron build or a directory of Electron zip files.
electronDist: async (context) => {
// Return path to custom Electron directory
return "/path/to/custom/electron-dist"
}
msiProjectCreated
Runs after the WiX project has been created on disk but before it is compiled into an MSI. Receives the path to the WiX project directory.
msiProjectCreated: async (wixProjectPath) => {
// Modify WiX XML files at wixProjectPath
}
appxManifestCreated
Runs after the AppX manifest has been created but before the package is assembled. Receives the path to the manifest file.
appxManifestCreated: async (manifestPath) => {
// Modify the AppX manifest XML
const fs = require("fs")
let manifest = fs.readFileSync(manifestPath, "utf8")
// ... modify manifest ...
fs.writeFileSync(manifestPath, manifest)
}
onNodeModuleFile
Called for each file in node_modules during packaging. Return true to force-include, false to force-exclude, or undefined/void to use default behavior.
onNodeModuleFile: async (filePath) => {
// Exclude test files from all node_modules
if (filePath.includes("__tests__") || filePath.endsWith(".test.js")) {
return false
}
}
Common Hook Patterns
Modifying Info.plist After Pack
const path = require("path")
const plist = require("plist")
const fs = require("fs")
afterPack: async (context) => {
if (context.electronPlatformName !== "darwin") return
const plistPath = path.join(
context.appOutDir,
`${context.packager.appInfo.productFilename}.app`,
"Contents",
"Info.plist"
)
const plistData = plist.parse(fs.readFileSync(plistPath, "utf8"))
plistData.LSUIElement = true // Hide from dock
fs.writeFileSync(plistPath, plist.build(plistData))
}
Custom Code Signing
mac:
sign: "./scripts/sign.js"
// scripts/sign.js
exports.default = async function(configuration) {
// configuration: CustomMacSign
// { path, packager, entitlements, ... }
const { execSync } = require("child_process")
execSync(`codesign --deep --sign "${configuration.identity}" "${configuration.path}"`)
}
Uploading Symbols to Sentry
afterAllArtifactBuild: async (buildResult) => {
const { execSync } = require("child_process")
const dsymPaths = buildResult.artifactPaths.filter(p => p.endsWith(".dSYM"))
for (const dsym of dsymPaths) {
execSync(`sentry-cli upload-dif ${dsym}`)
}
}
Generating a Changelog File
afterAllArtifactBuild: async (buildResult) => {
const fs = require("fs")
const changelogPath = `${buildResult.outDir}/CHANGELOG.txt`
fs.writeFileSync(changelogPath, generateChangelog())
return [changelogPath] // include in publish
}
Async and Error Handling
All hooks support async functions. If a hook throws an error, the build fails:
afterPack: async (context) => {
try {
await doSomething()
} catch (err) {
console.error("afterPack failed:", err)
throw err // rethrow to fail the build
}
}
Debugging Hooks
Add console.log or console.error inside your hooks — output appears in the electron-builder terminal output.
Enable verbose electron-builder logging to see the full build lifecycle:
DEBUG=electron-builder electron-builder build
Interface Reference
Interface: Hooks
Lifecycle hooks that run at specific points during the electron-builder pipeline.
Each hook can be specified as:
- An inline function (JavaScript/TypeScript config files only):
// electron-builder.config.jsmodule.exports = { beforePack: async (context) => { /* ... */ } }
- A path to a CommonJS/ESM module (relative to the project directory) that exports the
hook as its default export:
{ "build": { "beforePack": "./scripts/myHook.js" } }// scripts/myHook.jsexport default async function (context) { /* ... */ }
Extended by
Properties
afterAllArtifactBuild?
readonlyoptionalafterAllArtifactBuild?:string|Hook<BuildResult,string[]> |null
Called after all artifacts across all platforms/architectures have been built.
May return an array of additional file paths that should be treated as publish targets (i.e. uploaded to the configured publisher).
Receives a BuildResult.
Example
export default async function afterAllArtifactBuild(result) {
return ["/path/to/extra-file.txt"]
}
afterExtract?
readonlyoptionalafterExtract?:string|Hook<PackContext,void> |null
Called after the prebuilt Electron binary has been extracted to the staging directory but before the app files have been copied into it.
Use this hook to patch or augment the Electron binary itself (e.g. rename helper processes, inject native libraries) before the app payload is laid down on top.
Receives an AfterExtractContext.
afterPack?
readonlyoptionalafterPack?:string|Hook<PackContext,void> |null
Called after the app files have been copied into the staging directory (and ASAR created if applicable) but before signing and before the output archive/installer is built.
Use this hook to post-process the staged app bundle (e.g. strip debug symbols, inject platform-specific resources).
Receives an AfterPackContext.
afterSign?
readonlyoptionalafterSign?:string|Hook<PackContext,void> |null
Called after code signing but before the signed app is packed into the final
distributable format (e.g. before dmg is created from the signed .app).
Use this hook to perform post-sign validation or to add signed resources that are not part of the main app bundle.
Receives an AfterPackContext.
appxManifestCreated?
readonlyoptionalappxManifestCreated?:string|Hook<string,void> |null
Called after the AppX/MSIX manifest XML has been written to the staging directory but before
makeappx packages it.
The hook receives the path to the manifest file as a string. Use it to inject custom manifest extensions (capabilities, protocol handlers, etc.) not exposed through AppXOptions.
artifactBuildCompleted?
readonlyoptionalartifactBuildCompleted?:string|Hook<ArtifactCreated,void> |null
Called when an individual artifact has been successfully built and written to disk.
Use this hook to collect artifact paths, upload to custom destinations, or trigger post-processing steps.
Receives an ArtifactCreated.
artifactBuildStarted?
readonlyoptionalartifactBuildStarted?:string|Hook<ArtifactBuildStarted,void> |null
Called when an individual artifact build starts (e.g. just before a .dmg or .exe is
written to disk).
Receives an ArtifactBuildStarted.
beforeBuild?
readonlyoptionalbeforeBuild?:string|Hook<BeforeBuildContext,boolean|void> |null
Called before dependencies are installed or native modules are rebuilt.
Return false (or a Promise that resolves to false) to skip the install/rebuild step
entirely — useful when you manage node_modules externally (e.g. in a CI pipeline that
restores them from cache). When not set, electron-builder proceeds with the normal
install/rebuild flow.
If this hook is provided and node_modules is missing, electron-builder will not perform
the production-dependencies check.
Receives a BeforeBuildContext.
beforePack?
readonlyoptionalbeforePack?:string|Hook<PackContext,void> |null
Called immediately before electron-builder begins packing the app for each platform/arch combination.
Use this hook to perform last-minute modifications to source files or build resources before they are read by the packager.
Receives a BeforePackContext.
electronDist?
readonlyoptionalelectronDist?:string|Hook<PrepareApplicationStageDirectoryOptions,string> |null
Override the source of the Electron distribution to stage.
Should return the path to either:
- A directory containing a pre-built, unpacked Electron app (e.g.
~/electron/out/R). - A directory containing Electron zip archives following the naming convention
electron-v${version}-${platformName}-${arch}.zip.
When not set, electron-builder downloads the official Electron release from GitHub (or the
mirror configured via electronDownload).
Receives a PrepareApplicationStageDirectoryOptions.
msiProjectCreated?
readonlyoptionalmsiProjectCreated?:string|Hook<string,void> |null
Called after the WiX MSI project XML has been written to a temporary directory but before
candle.exe / light.exe compile it into a .msi package.
The hook receives the path to the project directory as a string. Use it to patch the generated
.wxs files for customizations not exposed through electron-builder's options.
onNodeModuleFile?
readonlyoptionalonNodeModuleFile?:string|Hook<string,boolean|void> |null
Called for every file inside each node_modules package directory as electron-builder
decides whether to include it in the packaged app.
Return true to force-include the file regardless of the default exclusion rules.
Return false or undefined to let electron-builder apply its normal filtering logic.
Receives the absolute path of the file as a string.