Build Configuration
This guide covers all configuration options available in electrobun.config for building and distributing your Electrobun applications.
Configuration File
Electrobun uses electrobun.config.ts in your project root to control how your application is built and packaged. The config file uses TypeScript with ESM syntax, providing type safety and modern JavaScript features.
Basic Structure
// electrobun.config.ts
import type { ElectrobunConfig } from "electrobun";
export default {
app: {
name: "MyApp",
identifier: "com.example.myapp",
version: "1.0.0",
},
runtime: {
exitOnLastWindowClosed: true,
},
build: {
bun: {
entrypoint: "src/bun/index.ts",
},
},
} satisfies ElectrobunConfig; Bun Bundler Options
Both build.bun and each entry in build.views accept all Bun.build() options as pass-through properties. The only required field is entrypoint — everything else is optional.
Electrobun controls entrypoints (derived from your entrypoint), outdir, and target ("bun" for the bun process, "browser" for views) automatically. All other Bun bundler options are passed through directly.
Available Options
Some commonly used options include:
| Option | Type | Description |
|---|---|---|
plugins | BunPlugin[] | Bundler plugins (e.g., for CSS modules, SVG imports, etc.) |
external | string[] | Modules to exclude from bundling |
sourcemap | "none" | "linked" | "inline" | "external" | Source map generation |
minify | boolean | { whitespace, identifiers, syntax } | Minification options |
splitting | boolean | Enable code splitting for shared modules |
define | Record<string, string> | Global identifier replacements at build time |
loader | Record<string, Loader> | Custom file extension loaders |
format | "esm" | "cjs" | "iife" | Output module format |
naming | string | { chunk, entry, asset } | Output file naming patterns |
banner | string | Prepend text to output (e.g., "use client") |
drop | string[] | Remove function calls (e.g., ["console", "debugger"]) |
env | "inline" | "disable" | "PREFIX_*" | Environment variable handling |
jsx | { runtime, importSource, factory, fragment } | JSX transform configuration |
packages | "bundle" | "external" | Whether to bundle or externalize all packages |
For the full list of options, see the Bun Bundler documentation.
Example: Using Plugins
import type { ElectrobunConfig } from "electrobun";
import myPlugin from "./plugins/my-plugin";
export default {
app: {
name: "MyApp",
identifier: "com.example.myapp",
version: "1.0.0",
},
build: {
bun: {
entrypoint: "src/bun/index.ts",
plugins: [myPlugin()],
},
views: {
mainview: {
entrypoint: "src/mainview/index.ts",
plugins: [myPlugin()],
sourcemap: "linked",
},
},
},
} satisfies ElectrobunConfig;
Example: Minification and Source Maps
import type { ElectrobunConfig } from "electrobun";
export default {
app: {
name: "MyApp",
identifier: "com.example.myapp",
version: "1.0.0",
},
build: {
views: {
mainview: {
entrypoint: "src/mainview/index.ts",
minify: true,
sourcemap: "linked",
define: {
"process.env.NODE_ENV": '"production"',
},
drop: ["console"],
},
},
},
} satisfies ElectrobunConfig;
Note: Since electrobun.config.ts is a real TypeScript module, you can dynamically construct plugins and configuration. Plugins are JavaScript objects, so they work natively — no serialization required.
URL Schemes (Deep Linking)
Electrobun supports registering custom URL schemes for your application, enabling deep linking. When users click a link like myapp://some/path, your app will open and receive the URL.
Platform support:
- macOS: Fully supported. App must be in
/Applications folder for URL scheme registration to work reliably. - Windows: Not yet supported
- Linux: Not yet supported
Configuration
Add URL schemes to the app section of your config:
const config: ElectrobunConfig = {
app: {
name: "MyApp",
identifier: "com.example.myapp",
version: "1.0.0",
urlSchemes: ["myapp", "myapp-dev"], // Register multiple schemes
},
// ...
};
Handling URL Opens
Listen for the open-url event in your Bun process to handle incoming URLs:
import Electrobun from "electrobun";
Electrobun.events.on("open-url", (e) => {
console.log("Opened with URL:", e.data.url);
// Parse the URL to extract information
const url = new URL(e.data.url);
console.log("Protocol:", url.protocol); // "myapp:"
console.log("Pathname:", url.pathname); // "/some/path"
// Route to appropriate part of your app
if (url.pathname.startsWith("/login")) {
// Handle login deep link
}
});
How It Works on macOS
When you build your app with URL schemes configured, Electrobun automatically adds the CFBundleURLTypes entry to your app's Info.plist. The operating system registers these URL schemes when your app is placed in the /Applications folder.
Important notes:
- The app must be in
/Applications (or ~/Applications) for macOS to register the URL schemes - During development, URL schemes won't work unless you build and install to Applications
- If another app has already registered the same URL scheme, macOS will use whichever was installed first
- Notarization is recommended for production apps to ensure a smooth user experience
Dynamic Configuration
TypeScript config files support dynamic configuration with full type safety:
// electrobun.config.ts
import type { ElectrobunConfig } from "electrobun";
import { readFileSync } from 'fs';
// Read version from package.json
const packageJson = JSON.parse(readFileSync('./package.json', 'utf8'));
export default {
app: {
name: "MyApp",
identifier: process.env.APP_ID || "com.example.myapp",
version: packageJson.version,
},
build: {
bun: {
entrypoint: "src/bun/index.ts",
},
},
release: {
baseUrl: process.env.RELEASE_BASE_URL || "",
},
} satisfies ElectrobunConfig;
ASAR Packaging
Electrobun supports packaging your application resources into an ASAR archive. ASAR (Atom Shell Archive) is an archive format that combines multiple files into a single file, providing faster file access and improved security for production builds.
Configuration Options
const config: ElectrobunConfig = {
build: {
useAsar: true,
asarUnpack: ["*.node", "*.dll", "*.dylib", "*.so"],
// ... rest of config
},
};
useAsar
Type: boolean
Default: false
Enables ASAR packaging for your application resources. When enabled, the entire app/ directory is packed into a single app.asar file.
asarUnpack
Type: string[]
Default: ["*.node", "*.dll", "*.dylib", "*.so"]
Glob patterns for files and folders that should be excluded from the ASAR archive. These files will remain unpacked in the app.asar.unpacked directory alongside the archive.
Common use cases for unpacking:
- Native modules (
*.node, *.dll, *.dylib, *.so) - Files that need to be accessed directly by external processes
- Files that need to be executed or dynamically loaded
- Large binary files that don't benefit from archiving
Example with ASAR Configuration
import type { ElectrobunConfig } from "electrobun";
export default {
app: {
name: "MyApp",
identifier: "com.example.myapp",
version: "1.0.0",
},
build: {
useAsar: true,
asarUnpack: [
"*.node", // Native modules
"*.dll", // Windows DLLs
"*.dylib", // macOS dynamic libraries
"*.so", // Linux shared objects
"data/large/**/*", // Large data files
],
bun: {
entrypoint: "src/bun/index.ts",
},
},
} satisfies ElectrobunConfig;
Benefits of ASAR Packaging
- Performance: Faster file access and reduced I/O operations
- Security: App code is extracted to randomized temp files with automatic cleanup
- Distribution: Fewer files to manage and distribute
- Integrity: Single archive is easier to verify and protect
Renderer Configuration
Electrobun supports multiple webview renderers. By default, it uses the system's native webview (WKWebKit on macOS, WebView2 on Windows, GTK WebKit on Linux). You can also bundle CEF (Chromium Embedded Framework) for a consistent cross-platform experience.
Platform-specific Renderer Options
Each platform (mac, linux, win) supports the following renderer options:
bundleCEF
Type: boolean
Default: false
When true, CEF (Chromium Embedded Framework) is bundled with your application. This adds approximately 100MB+ to your app bundle but provides a consistent Chromium-based rendering experience across all platforms.
defaultRenderer
Type: 'native' | 'cef'
Default: 'native'
Sets the default renderer for all BrowserWindow and BrowserView instances when no explicit renderer option is specified. This allows you to bundle CEF and use it by default without having to specify renderer: 'cef' on every window/view.
Note: Setting defaultRenderer: 'cef' only affects the default. You can still override it per-window or per-view by explicitly passing renderer: 'native' or renderer: 'cef' in the options.
Example: CEF as Default Renderer
const config: ElectrobunConfig = {
// ...
build: {
mac: {
bundleCEF: true,
defaultRenderer: 'cef', // All webviews use CEF by default
},
linux: {
bundleCEF: true,
defaultRenderer: 'cef',
},
win: {
bundleCEF: true,
defaultRenderer: 'cef',
},
},
};
With this configuration, when you create a window without specifying a renderer:
// Uses CEF (the configured default)
const mainWindow = new BrowserWindow({
title: "My App",
url: "views://main/index.html",
});
// Explicitly use native renderer for this specific window
const settingsWindow = new BrowserWindow({
title: "Settings",
url: "views://settings/index.html",
renderer: 'native', // Override the default
});
Accessing Build Configuration at Runtime
You can access the build configuration at runtime using the BuildConfig API. This is useful for knowing which renderers are available and what the default is. See the BuildConfig API documentation for details.
Full example from the Electrobun Playground app
// electrobun.config.ts
import type { ElectrobunConfig } from "electrobun";
export default {
app: {
name: "Electrobun (Playground)",
identifier: "dev.electrobun.playground",
version: "0.0.1",
},
build: {
bun: {
entrypoint: "src/bun/index.ts",
},
views: {
mainview: {
entrypoint: "src/mainview/index.ts",
},
myextension: {
entrypoint: "src/myextension/preload.ts",
},
webviewtag: {
entrypoint: "src/webviewtag/index.ts",
},
},
copy: {
"src/mainview/index.html": "views/mainview/index.html",
"src/mainview/index.css": "views/mainview/index.css",
"src/webviewtag/index.html": "views/webviewtag/index.html",
"src/webviewtag/electrobun.png": "views/webviewtag/electrobun.png",
"assets/electrobun-logo-32-template.png": "views/assets/electrobun-logo-32-template.png",
},
mac: {
codesign: true,
notarize: true,
bundleCEF: true,
defaultRenderer: 'cef',
entitlements: {
"com.apple.security.device.camera": "This app needs camera access for video features",
"com.apple.security.device.microphone": "This app needs microphone access for audio features",
},
icons: "icon.iconset",
},
linux: {
bundleCEF: true,
defaultRenderer: 'cef',
},
win: {
bundleCEF: true,
defaultRenderer: 'cef',
},
},
scripts: {
postBuild: "./buildScript.ts",
},
release: {
baseUrl: "https://static.electrobun.dev/playground/",
},
} satisfies ElectrobunConfig;
Custom Bun Version
Each Electrobun release ships with a specific tested Bun version. You can override this with the bunVersion option in your build configuration to use a different version from Bun's GitHub releases.
Why Override the Bun Version
- Use a newer version: If a newer Bun release includes a performance improvement, bug fix, or API you need, you can adopt it immediately without waiting for Electrobun to update its default.
- Pin an older version: If a newer Bun release introduces a regression that affects your app, you can pin to the version that works while you wait for a fix.
Configuration
Set the bunVersion field in the build section of your electrobun.config.ts. The value is a semver version string matching a Bun release tag:
// electrobun.config.ts
import type { ElectrobunConfig } from "electrobun";
export default {
app: {
name: "MyApp",
identifier: "com.example.myapp",
version: "1.0.0",
},
build: {
bunVersion: "1.4.2",
bun: {
entrypoint: "src/bun/index.ts",
},
},
} satisfies ElectrobunConfig;
How It Works
When bunVersion is set, the Electrobun CLI downloads the specified Bun binary from GitHub releases and caches it locally. The cached binary is stored in node_modules/.electrobun-cache/bun-override/ so it survives both dist rebuilds and bun install (which replaces node_modules/electrobun).
Unlike the CEF version override, no compilation or restructuring is needed — Bun is a single standalone binary with no external dependencies.
Caching
The downloaded Bun binary is cached per platform and version. If you change bunVersion, the CLI detects the mismatch and re-downloads automatically. Removing the override restores the default Bun version shipped with your Electrobun release.
See also: For overriding the CEF (Chromium) version, see Bundling CEF — Custom CEF Versions.
Chromium Flags
You can pass custom Chromium command-line flags to CEF during initialization. This is useful for enabling debugging features, overriding browser behavior, or setting values like a custom user agent.
Flags are defined per-platform in the chromiumFlags option. Keys are flag names without the -- prefix. Use true for switch-only flags, or a string for flags that take a value.
chromiumFlags
Type: Record<string, string | true>
Default: undefined (no custom flags)
const config: ElectrobunConfig = {
// ...
build: {
mac: {
bundleCEF: true,
chromiumFlags: {
// Switch-only flag (no value)
"show-paint-rects": true,
// Flag with a value
"user-agent": "MyApp/1.0 (custom)",
},
},
linux: {
bundleCEF: true,
chromiumFlags: {
"user-agent": "MyApp/1.0 (custom)",
},
},
win: {
bundleCEF: true,
chromiumFlags: {
"user-agent": "MyApp/1.0 (custom)",
},
},
},
};
How It Works
Flags defined in chromiumFlags are written into Resources/build.json during the build. At runtime, the native wrapper reads them and applies them to CEF's command line via AppendSwitch / AppendSwitchWithValue.
User flags are applied after Electrobun's internal flags. If a user flag duplicates an internal one, CEF's last-write-wins behavior applies for value switches. Each applied flag is logged at startup:
[CEF] Applying user chromium flag: show-paint-rects
[CEF] Applying user chromium flag: user-agent=MyApp/1.0 (custom)
Note: Chromium flags are powerful and can change browser behavior in unexpected ways. Only use flags you understand. Electrobun does not validate flag names or values — they are passed directly to CEF.
Common Flags
Flag Type Description user-agent string Override the default user agent string show-paint-rects true Flash green rectangles over repainted areas (useful for debugging) show-composited-layer-borders true Show colored borders around GPU-composited layers
For a full list of Chromium command-line flags, see the Chromium Command Line Switches reference.
Runtime Configuration
The runtime section defines settings that affect your application's behaviour at runtime. These values are copied into build.json during the build and are accessible via the BuildConfig API.
exitOnLastWindowClosed
Type: boolean
Default: true
When true, the application automatically quits when the last BrowserWindow is closed. This is the most common expected behaviour for desktop applications.
Set to false if your app should keep running without any open windows (e.g., menu bar apps, or apps that stay in the system tray).
import type { ElectrobunConfig } from "electrobun";
export default {
app: {
name: "MyApp",
identifier: "com.example.myapp",
version: "1.0.0",
},
runtime: {
exitOnLastWindowClosed: true, // default
},
build: {
// ...
},
} satisfies ElectrobunConfig;
Custom Runtime Values
You can add arbitrary keys to the runtime section. The entire object is copied into build.json and available at runtime via BuildConfig:
// electrobun.config.ts
export default {
// ...
runtime: {
exitOnLastWindowClosed: true,
myCustomSetting: "hello",
},
} satisfies ElectrobunConfig;
// src/bun/index.ts
import { BuildConfig } from "electrobun/bun";
const config = await BuildConfig.get();
console.log(config.runtime?.myCustomSetting); // "hello"
Build Lifecycle Hooks
Electrobun provides lifecycle hooks that let you run custom scripts at various stages of the build process. These hooks are configured in the scripts section of your electrobun.config.ts.
Available Hooks
Hooks are executed in the following order during a build:
Hook When it runs Use case preBuild Before the build starts Validation, environment setup, generating files, cleanup postBuild After the inner app bundle is complete (before ASAR/signing) Modify app bundle contents, add resources postWrap After self-extracting bundle created, before signing (non-dev only) Add files to the wrapper bundle (e.g., for macOS features like Liquid Glass) postPackage After all build artifacts are created Custom distribution steps, upload, notifications, cleanup
Configuration
Specify hooks as paths to TypeScript or JavaScript files that will be executed with Bun:
const config: ElectrobunConfig = {
// ... app and build config
scripts: {
preBuild: "./scripts/pre-build.ts",
postBuild: "./scripts/post-build.ts",
postWrap: "./scripts/post-wrap.ts",
postPackage: "./scripts/post-package.ts",
},
};
Environment Variables
All hook scripts receive the following environment variables:
Variable Description ELECTROBUN_BUILD_ENV Build environment: dev, canary, or stable ELECTROBUN_OS Target OS: macos, linux, or win ELECTROBUN_ARCH Target architecture: x64 or arm64 ELECTROBUN_BUILD_DIR Path to the build output directory ELECTROBUN_APP_NAME Application name with environment suffix ELECTROBUN_APP_VERSION Application version from config ELECTROBUN_APP_IDENTIFIER Bundle identifier from config ELECTROBUN_ARTIFACT_DIR Path to the artifacts output directory
The postWrap hook receives an additional variable:
ELECTROBUN_WRAPPER_BUNDLE_PATH - Path to the self-extracting wrapper bundle
Example: Adding files for Liquid Glass (macOS)
The postWrap hook is ideal for adding files to the self-extracting wrapper before it's code signed. This is useful for macOS features like Liquid Glass that require specific files in the app bundle:
// scripts/post-wrap.ts
import { join } from "path";
import { cpSync, existsSync, mkdirSync } from "fs";
const wrapperPath = process.env.ELECTROBUN_WRAPPER_BUNDLE_PATH;
const buildEnv = process.env.ELECTROBUN_BUILD_ENV;
if (!wrapperPath) {
console.error("ELECTROBUN_WRAPPER_BUNDLE_PATH not set");
process.exit(1);
}
// Only add Liquid Glass assets for production builds
if (buildEnv !== "dev") {
const resourcesPath = join(wrapperPath, "Contents", "Resources");
const liquidGlassAssets = "./assets/liquid-glass";
if (existsSync(liquidGlassAssets)) {
console.log("Adding Liquid Glass assets to wrapper bundle...");
cpSync(liquidGlassAssets, join(resourcesPath, "liquid-glass"), {
recursive: true
});
}
}
console.log("postWrap hook completed");
Example: Build validation with preBuild
// scripts/pre-build.ts
import { existsSync } from "fs";
const buildEnv = process.env.ELECTROBUN_BUILD_ENV;
// Ensure required environment variables are set for production builds
if (buildEnv === "stable") {
const requiredVars = [
"ELECTROBUN_DEVELOPER_ID",
"ELECTROBUN_APPLEID",
"ELECTROBUN_APPLEIDPASS",
"ELECTROBUN_TEAMID",
];
const missing = requiredVars.filter(v => !process.env[v]);
if (missing.length > 0) {
console.error("Missing required environment variables for stable build:");
missing.forEach(v => console.error(` - ${v}`));
process.exit(1);
}
}
// Validate required files exist
const requiredFiles = ["src/bun/index.ts", "src/mainview/index.html"];
for (const file of requiredFiles) {
if (!existsSync(file)) {
console.error(`Required file not found: ${file}`);
process.exit(1);
}
}
console.log("preBuild validation passed");
Example: Post-package notifications
// scripts/post-package.ts
const buildEnv = process.env.ELECTROBUN_BUILD_ENV;
const version = process.env.ELECTROBUN_APP_VERSION;
const artifactDir = process.env.ELECTROBUN_ARTIFACT_DIR;
console.log(`Build complete: ${buildEnv} v${version}`);
console.log(`Artifacts: ${artifactDir}`);
// Send Slack notification for production builds
if (buildEnv === "stable" && process.env.SLACK_WEBHOOK_URL) {
await fetch(process.env.SLACK_WEBHOOK_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
text: `New stable release v${version} built successfully!`,
}),
});
}
Note: Hook scripts are run using the host machine's Bun binary, not the bundled one. This ensures scripts always run regardless of the target platform being built.