@electron-forge/core, electron-builder, and electron-packager are tools used to bundle, package, and distribute Electron applications. electron-packager is a low-level utility that creates executable app folders without installers. electron-builder is a comprehensive solution that handles packaging, installer creation (DMG, NSIS, etc.), code signing, and publishing. @electron-forge/core serves as the underlying engine for the Electron Forge ecosystem, providing a unified workflow for scaffolding, bundling, and packaging with a focus on developer experience and plugin extensibility.
Building a desktop app with Electron is only half the battle. Getting that app into the hands of users requires packaging, signing, and distributing it across Windows, macOS, and Linux. @electron-forge/core, electron-builder, and electron-packager tackle this problem with different levels of abstraction and scope. Let's look at how they handle the critical steps of shipping software.
How you define your build rules sets the tone for the rest of your project. The tools differ in where and how they store settings.
@electron-forge/core uses a JavaScript or TypeScript config file.
// forge.config.js
module.exports = {
packagerConfig: {
asar: true,
icon: './src/assets/icon'
},
makers: [
{
name: '@electron-forge/maker-squirrel',
config: { name: 'my_app' }
}
]
};
electron-builder typically uses a build key in package.json or a separate YAML file.
// package.json
{
"build": {
"appId": "com.example.app",
"mac": {
"target": "dmg"
},
"win": {
"target": "nsis"
}
}
}
electron-packager relies on command-line arguments or a JS API call.
# CLI usage
npx electron-packager . MyApp --platform=win32 --arch=x64
// Or via JS API
const packager = require('electron-packager');
await packager({ dir: '.', platform: 'win32' });
Users expect an installer, not just a folder of files. The tools vary significantly in their ability to create setup wizards.
@electron-forge/core delegates installer creation to "makers".
// forge.config.js
makers: [
{
name: '@electron-forge/maker-wix',
config: { name: 'MyApp' }
}
]
electron-builder has installer generation built into the core.
build section without extra plugins.// package.json
"build": {
"win": {
"target": ["nsis", "portable"]
}
}
electron-packager does not create installers.
.app or .exe).electron-installer-windows to create a setup file.# Packager only creates the folder
npx electron-packager . MyApp
# You must run a second tool for installers
npx electron-installer-windows --src dist/MyApp-win32-x64
Keeping apps up to date is critical for security and features. The level of built-in support varies.
@electron-forge/core works with electron-updater via plugins.
@electron-forge/plugin-auto-update simplifies the setup.// forge.config.js
plugins: [
{
name: '@electron-forge/plugin-auto-update',
config: { provider: 'github' }
}
]
electron-builder has deep integration with electron-updater.
publish config in package.json defines where updates are hosted.// package.json
"build": {
"publish": [
{
"provider": "github",
"owner": "my-org",
"repo": "my-app"
}
]
}
electron-packager has no update logic.
electron-updater.// Manual implementation required
const { autoUpdater } = require('electron-updater');
autoUpdater.checkForUpdatesAndNotify();
// You must upload files yourself
Modern Electron apps use bundlers like Webpack or Vite. The tools handle this differently.
@electron-forge/core supports bundlers via plugins.
// forge.config.js
plugins: [
{
name: '@electron-forge/plugin-vite',
config: { build: ['vite.config.js'] }
}
]
electron-builder expects your app to be pre-built.
npm run build) before running builder.dist or out).// package.json scripts
"scripts": {
"build": "vite build",
"dist": "npm run build && electron-builder"
}
electron-packager also expects a pre-built directory.
# Run bundler first, then package
npm run build
npx electron-packager ./dist MyApp
Distributing on macOS and Windows requires code signing to avoid security warnings.
@electron-forge/core manages signing through maker config.
// forge.config.js
makers: [
{
name: '@electron-forge/maker-squirrel',
config: { certificateFile: 'cert.pfx' }
}
]
electron-builder automates signing based on environment variables.
CSC_LINK or WIN_CSC_LINK automatically.// package.json
"build": {
"mac": {
"identity": "Developer ID Application: My Name"
}
}
electron-packager requires manual signing flags.
osx-sign or sign options explicitly.# Manual signing flags
npx electron-packager . MyApp --osx-sign --osx-notarize
These tools are powerful, but they are not always the right fit:
electron-packager for public consumer apps. The lack of installer generation and auto-update support creates too much manual work for production releases.@electron-forge/core directly if you just want a standard app. Use the CLI instead. Direct core usage is for tooling authors, not app developers.electron-builder if you need a highly custom build pipeline that doesn't fit its opinionated structure. In that case, scripting electron-packager might offer more freedom.| Feature | @electron-forge/core | electron-builder | electron-packager |
|---|---|---|---|
| Config Format | JS/TS (forge.config.js) | JSON/YAML (package.json) | CLI Args / JS API |
| Installers | Via Maker Plugins | Built-In (NSIS, DMG, etc.) | None (Manual Required) |
| Auto-Update | Via Plugin | Built-In Integration | Manual Implementation |
| Bundler | Via Plugin (Vite/Webpack) | External (Pre-build) | External (Pre-build) |
| Signing | Maker Config | Env Var / Config | Manual Flags |
| Best For | Forge Ecosystem Users | Production Distribution | Internal/Portable Tools |
electron-packager is the raw engine 🏗️. It does one thing: make the app folder. Use it if you want to build everything else yourself or need a portable binary without installation.
electron-builder is the complete factory 🏭. It takes your code and ships a signed, installable, auto-updating product. It is the standard choice for serious commercial Electron apps.
@electron-forge/core is the custom workshop 🛠️. It powers the Electron Forge experience. Use it if you are building tools within the Forge ecosystem, but for most app developers, the @electron-forge/cli is the practical entry point.
Final Thought: For most teams shipping a product, electron-builder offers the best balance of power and convenience. If you prefer a plugin-based workflow and are already using Forge for scaffolding, stick with the Forge ecosystem. Avoid electron-packager for end-user distribution unless you have a specific reason to manage your own installers.
Choose @electron-forge/core if you are building a custom Electron tooling pipeline or plugin that requires programmatic control over the Forge build process. It is best suited for teams already invested in the Electron Forge ecosystem who need to extend its functionality beyond the standard CLI. For standard app development, most teams should use the @electron-forge/cli which wraps this core logic.
Choose electron-builder if you need a robust, all-in-one solution for creating installers, handling code signing, and managing auto-updates without writing custom scripts. It is ideal for production applications targeting multiple platforms where you need fine-grained control over the distribution artifacts and publishing workflow.
Choose electron-packager only if you need a simple, low-level way to convert your source code into an Electron app folder without installers or auto-update logic. It is suitable for internal tools, portable apps, or scenarios where you will build your own installer and distribution pipeline on top of the raw executable.
This module contains the core logic of Electron Forge and exposes the base API as a number of simple JS functions.
import { api } from '@electron-forge/core';
// Package the current directory as an Electron app
api.package(__dirname);
The named export api has it's methods documented over at ForgeAPI.
All the methods are async and expose the core forge methods, please note that all
user-side configuration is still done through your forge config file or the "config.forge"
section of your package.json. This API simply let's you call the methods in
node land without using the CLI.
As all methods return a promise you should handle all rejections, you should note
that rejections will not always be errors, in fact we commonly reject our
promises with just strings so do not assume that properties such as stack or
message will exist on thrown errors.