Skip to main content
Build hooks allow you to execute custom code at various stages of the electron-builder build process. Hooks can be defined as functions directly in the configuration or as paths to external modules.
All examples assume you are using Node.js 8.11.x or higher.

Hook Configuration

Hooks can be specified in two ways:

As a Function (JS/TS config only)

// electron-builder.config.js
module.exports = {
  beforePack: async (context) => {
    console.log('Before pack:', context.appOutDir)
    // Your custom code
  },
  afterPack: async (context) => {
    console.log('After pack:', context.appOutDir)
    // Your custom code
  }
}

As a Path to Module

When using JSON or YAML configuration, specify the path to a file that exports the hook function:
{
  "build": {
    "beforePack": "./build-hooks/beforePack.js",
    "afterPack": "./build-hooks/afterPack.js"
  }
}
The hook file should export the function as the default export:
// build-hooks/beforePack.js
exports.default = async function(context) {
  // Your custom code
  console.log('Platform:', context.electronPlatformName)
  console.log('Architecture:', context.arch)
}

Available Hooks

beforePack

beforePack
function | string
The function (or path to file or module id) to be run before pack.Function signature:
(context: BeforePackContext): Promise<void> | void
Context properties:
  • outDir - The output directory
  • appOutDir - The application output directory
  • packager - The platform packager instance
  • electronPlatformName - The Electron platform name (e.g., ‘darwin’, ‘win32’, ‘linux’)
  • arch - The architecture being built
  • targets - Array of build targets
Example:
// electron-builder.config.js
module.exports = {
  beforePack: async (context) => {
    console.log('Starting build for', context.electronPlatformName)
    console.log('Output directory:', context.appOutDir)
    
    // Example: Copy additional files
    const fs = require('fs-extra')
    await fs.copy('extra-resources', context.appOutDir)
  }
}
As external file:
{
  "build": {
    "beforePack": "./hooks/beforePack.js"
  }
}
// hooks/beforePack.js
exports.default = async function(context) {
  // Custom pre-pack logic
}

afterExtract

afterExtract
function | string
The function (or path to file or module id) to be run after the prebuilt Electron binary has been extracted to the output directory.Function signature:
(context: AfterExtractContext): Promise<void> | void
This hook is useful for modifying the Electron binary or adding custom files before the app is packaged.Example:
module.exports = {
  afterExtract: async (context) => {
    console.log('Electron extracted to:', context.appOutDir)
    
    // Example: Replace FFmpeg with custom build
    const path = require('path')
    const fs = require('fs-extra')
    
    const ffmpegPath = path.join(context.appOutDir, 'libffmpeg.so')
    await fs.copy('custom-ffmpeg/libffmpeg.so', ffmpegPath)
  }
}

afterPack

afterPack
function | string
The function (or path to file or module id) to be run after pack (but before pack into distributable format and sign).Function signature:
(context: AfterPackContext): Promise<void> | void
This is one of the most commonly used hooks, allowing you to modify the packaged app before it’s signed and distributed.Example:
module.exports = {
  afterPack: async (context) => {
    const fs = require('fs-extra')
    const path = require('path')
    
    // Example: Add a file to the packaged app
    const licenseFile = path.join(context.appOutDir, 'LICENSE.txt')
    await fs.writeFile(licenseFile, 'Your license text here')
    
    // Example: Modify package.json
    const packageJsonPath = path.join(
      context.appOutDir,
      'resources',
      'app.asar.unpacked',
      'package.json'
    )
    if (await fs.pathExists(packageJsonPath)) {
      const packageJson = await fs.readJson(packageJsonPath)
      packageJson.custom = 'value'
      await fs.writeJson(packageJsonPath, packageJson)
    }
  }
}

afterSign

afterSign
function | string
The function (or path to file or module id) to be run after pack and sign (but before pack into distributable format).Function signature:
(context: AfterPackContext): Promise<void> | void
This hook is particularly useful for notarizing macOS applications.Example - macOS Notarization:
module.exports = {
  afterSign: async (context) => {
    // Only notarize on macOS
    if (context.electronPlatformName !== 'darwin') {
      return
    }

    const { notarize } = require('@electron/notarize')
    const appName = context.packager.appInfo.productFilename

    await notarize({
      appBundleId: 'com.example.app',
      appPath: `${context.appOutDir}/${appName}.app`,
      appleId: process.env.APPLE_ID,
      appleIdPassword: process.env.APPLE_ID_PASSWORD,
      teamId: process.env.APPLE_TEAM_ID
    })
  }
}
As external file:
{
  "build": {
    "afterSign": "./hooks/notarize.js"
  }
}
// hooks/notarize.js
const { notarize } = require('@electron/notarize')

exports.default = async function notarizing(context) {
  const { electronPlatformName, appOutDir } = context
  if (electronPlatformName !== 'darwin') {
    return
  }

  const appName = context.packager.appInfo.productFilename

  return await notarize({
    appBundleId: 'com.example.app',
    appPath: `${appOutDir}/${appName}.app`,
    appleId: process.env.APPLE_ID,
    appleIdPassword: process.env.APPLE_ID_PASSWORD
  })
}

artifactBuildStarted

artifactBuildStarted
function | string
The function (or path to file or module id) to be run when artifact build starts.Function signature:
(context: ArtifactBuildStarted): Promise<void> | void
Example:
module.exports = {
  artifactBuildStarted: async (context) => {
    console.log('Building artifact:', context.file)
    console.log('Target:', context.target)
  }
}

artifactBuildCompleted

artifactBuildCompleted
function | string
The function (or path to file or module id) to be run when artifact build completes.Function signature:
(context: ArtifactCreated): Promise<void> | void
Context properties:
  • file - The output file path
  • target - The build target
  • arch - The architecture
  • packager - The platform packager instance
Example:
module.exports = {
  artifactBuildCompleted: async (context) => {
    const fs = require('fs-extra')
    const crypto = require('crypto')
    
    // Generate checksum for the artifact
    const fileBuffer = await fs.readFile(context.file)
    const hashSum = crypto.createHash('sha256')
    hashSum.update(fileBuffer)
    
    const hex = hashSum.digest('hex')
    await fs.writeFile(`${context.file}.sha256`, hex)
    
    console.log('Artifact completed:', context.file)
    console.log('SHA256:', hex)
  }
}

afterAllArtifactBuild

afterAllArtifactBuild
function | string
The function (or path to file or module id) to be run after all artifacts are built.Function signature:
(buildResult: BuildResult): Promise<string[]> | string[]
This hook can return an array of additional files to publish.Example:
module.exports = {
  afterAllArtifactBuild: async (buildResult) => {
    console.log('All artifacts built!')
    console.log('Output directory:', buildResult.outDir)
    console.log('Artifacts:', buildResult.artifactPaths)
    
    // Example: Generate release notes
    const fs = require('fs-extra')
    const path = require('path')
    
    const releaseNotesPath = path.join(buildResult.outDir, 'RELEASE_NOTES.md')
    await fs.writeFile(releaseNotesPath, '# Release Notes\n\n...')
    
    // Return additional files to publish
    return [releaseNotesPath]
  }
}
As external file:
// hooks/afterAllArtifactBuild.js
exports.default = async function(buildResult) {
  // Generate additional files
  // Return array of file paths to publish
  return ['/path/to/additional/file']
}

beforeBuild

beforeBuild
function | string
The function (or path to file or module id) to be run before dependencies are installed or rebuilt.Works when npmRebuild is set to true. Resolving to false will skip dependencies install or rebuild.Function signature:
(context: BeforeBuildContext): Promise<boolean | void> | boolean | void
If provided and node_modules are missing, it will not invoke production dependencies check.
Example:
module.exports = {
  beforeBuild: async (context) => {
    console.log('Before building dependencies')
    console.log('Platform:', context.platform)
    console.log('Arch:', context.arch)
    
    // Return false to skip dependency rebuild
    // return false
    
    // Or return true/undefined to continue
    return true
  }
}

onNodeModuleFile

onNodeModuleFile
function | string
The function (or path to file or module id) to be run on each node module file.Returning true will force include the file, false will exclude it, and undefined will use the default copier logic.Function signature:
(file: string): void | boolean
Example:
module.exports = {
  onNodeModuleFile: (file) => {
    // Example: Force include specific files
    if (file.endsWith('.node')) {
      console.log('Including native module:', file)
      return true
    }
    
    // Example: Exclude test files
    if (file.includes('/test/') || file.includes('/__tests__/')) {
      return false
    }
    
    // Use default logic
    return undefined
  }
}

msiProjectCreated

msiProjectCreated
function | string
The function (or path to file or module id) to be run after MSI project is created on disk (not packed into .msi package yet).Function signature:
(path: string): Promise<void> | void
Example:
module.exports = {
  msiProjectCreated: async (projectPath) => {
    console.log('MSI project created at:', projectPath)
    // Modify MSI project files if needed
  }
}

appxManifestCreated

appxManifestCreated
function | string
The function (or path to file or module id) to be run after Appx manifest is created on disk (not packed into .appx package yet).Function signature:
(path: string): Promise<void> | void
Example:
module.exports = {
  appxManifestCreated: async (manifestPath) => {
    console.log('Appx manifest created at:', manifestPath)
    
    // Example: Modify the manifest
    const fs = require('fs-extra')
    let manifest = await fs.readFile(manifestPath, 'utf8')
    // Modify manifest content
    await fs.writeFile(manifestPath, manifest)
  }
}

electronDist

electronDist
function | string
The function (or path to file or module id) to be run when staging the Electron artifact environment.Returns the path to custom Electron build (e.g., ~/electron/out/R) or folder of Electron zips.Function signature:
(options: PrepareApplicationStageDirectoryOptions): Promise<string> | string
Zip files must follow the pattern electron-v${version}-${platformName}-${arch}.zip, otherwise it will be assumed to be an unpacked Electron app directory.
Example:
module.exports = {
  electronDist: async (options) => {
    // Return path to custom Electron build
    return '/path/to/custom/electron/build'
    
    // Or return path to folder containing Electron zips
    // return '/path/to/electron/zips'
  }
}

Common Hook Use Cases

Custom File Processing

module.exports = {
  afterPack: async (context) => {
    const path = require('path')
    const fs = require('fs-extra')
    
    // Minify or obfuscate JavaScript files
    const appPath = path.join(context.appOutDir, 'resources', 'app')
    // Your minification/obfuscation logic
  }
}

Environment-Specific Configuration

module.exports = {
  beforePack: async (context) => {
    const fs = require('fs-extra')
    const path = require('path')
    
    // Copy environment-specific config
    const configSrc = process.env.NODE_ENV === 'production'
      ? 'config/production.json'
      : 'config/development.json'
    
    const configDest = path.join(context.appOutDir, 'config.json')
    await fs.copy(configSrc, configDest)
  }
}

Generating Checksums

module.exports = {
  artifactBuildCompleted: async (context) => {
    const fs = require('fs-extra')
    const crypto = require('crypto')
    
    const hash = crypto.createHash('sha256')
    const fileBuffer = await fs.readFile(context.file)
    hash.update(fileBuffer)
    
    const checksum = hash.digest('hex')
    await fs.writeFile(`${context.file}.sha256`, checksum)
  }
}

Platform-Specific Logic

module.exports = {
  afterPack: async (context) => {
    if (context.electronPlatformName === 'darwin') {
      // macOS-specific logic
    } else if (context.electronPlatformName === 'win32') {
      // Windows-specific logic
    } else if (context.electronPlatformName === 'linux') {
      // Linux-specific logic
    }
  }
}

Best Practices

  1. Keep hooks fast - Hooks run during the build process, so keep them as efficient as possible
  2. Handle errors properly - Always use try-catch blocks and provide meaningful error messages
  3. Use async/await - Prefer async/await over callbacks for better readability
  4. Log important actions - Use console.log to track what your hooks are doing
  5. Test thoroughly - Test hooks with different platforms and configurations
Be careful when modifying files in hooks, especially signed files. Modifying signed binaries will invalidate signatures.

Debugging Hooks

To debug hooks, add verbose logging:
module.exports = {
  beforePack: async (context) => {
    console.log('BeforePackContext:', JSON.stringify({
      outDir: context.outDir,
      appOutDir: context.appOutDir,
      electronPlatformName: context.electronPlatformName,
      arch: context.arch
    }, null, 2))
  }
}
Run electron-builder with the --verbose flag for additional output:
electron-builder --verbose

Build docs developers (and LLMs) love