npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@capawesome/capacitor-electron

v0.2.2

Published

Capacitor platform for building desktop apps with Electron.

Downloads

11,005

Readme

Capacitor Electron Platform

Capacitor platform to build, run, and package desktop apps for macOS, Windows, and Linux with Electron.[^1]

Features

The Capacitor Electron platform brings your web app and your Capacitor plugins to the desktop. Here are some of the key features:

  • 🖥️ Cross-platform: Build desktop apps for macOS, Windows, and Linux from one codebase.
  • ⚡ Familiar workflow: cap add, cap sync, cap run — the same commands as iOS and Android.
  • 🔌 Plugin support: Capacitor plugins with an Electron implementation work out of the box; plugins with a web implementation work automatically via fallback.
  • ⚡ Live Updates: Deliver web-bundle updates over the air with the Capacitor Live Update plugin and Capawesome Cloud — the same workflow as on Android and iOS.
  • 🔒 Security-first: Sandboxed renderer, context isolation, strict Content-Security-Policy, and validated IPC — enabled by default and not configurable.
  • 🔗 Deep links: Custom URL schemes delivered through the standard @capacitor/app plugin's appUrlOpen event.
  • 📱 App lifecycle: appStateChange, pause, and resume events, exactly like on mobile.
  • ♻️ Live reload: Develop against your web dev server with full HMR.
  • 📦 Packaging: Create installers with electron-builder, including automatic dependency vendoring.
  • 🪟 Window management: Typed window options, state persistence, and hooks for trays and menus.
  • 🧩 Minimal scaffold: You own a handful of small, stable files; all platform logic ships in the package and updates via npm update.
  • 🔁 Up-to-date: Always supports the latest Capacitor and Electron versions.
  • ⭐️ Support: Priority support from the Capawesome Team.
  • ✨ Handcrafted: Built from the ground up with care and expertise, not forked or AI-generated.

Missing a feature? Just open an issue and we'll take a look!

Use Cases

The Electron platform is typically used to bring an existing Capacitor app to the desktop, for example:

  • Desktop companion apps: Ship your mobile app's functionality to macOS, Windows, and Linux without a rewrite.
  • Offline-first desktop tools: Combine the platform with plugins like SQLite for fully offline desktop applications.
  • Instant desktop updates: Ship fixes to the web layer without building and distributing a new installer, using Live Updates.
  • Internal business tools: Distribute apps directly to your team without app stores.
  • Kiosk and point-of-sale apps: Run your web app full screen on dedicated desktop hardware.
  • Deep-link driven workflows: Handle custom URL schemes on the desktop exactly like on mobile.

Compatibility

| Platform Version | Capacitor Version | Electron Version | Status | | ---------------- | ----------------- | ---------------- | -------------- | | 0.x | >=6.x.x | >=28.x.x | Active support |

[!NOTE] On Capacitor 6 and 7, the Capacitor CLI ignores the exit code of platform hooks, so a failing npx cap sync still reports success — check the log output for [capacitor-electron] errors. Capacitor 8 fails the command properly.

Guides

Installation

You can use our AI-Assisted Setup to add the platform. Add the Capawesome Skills to your AI tool using the following command:

npx skills add capawesome-team/skills --skill capacitor-platforms

Then use the following prompt:

Use the `capacitor-platforms` skill from `capawesome-team/skills` to add the `@capawesome/capacitor-electron` platform to my project.

If you prefer Manual Setup, add the platform by running the following commands:

npm install @capawesome/capacitor-electron
npx cap add @capawesome/capacitor-electron
cd electron && npm install && cd ..

We recommend adding a postinstall script to your root package.json so the Electron dependencies are always installed together with your app dependencies:

{
  "scripts": {
    "postinstall": "cd electron && npm ci && cd .."
  }
}

The initial cd electron && npm install generates electron/package-lock.json — commit it so that npm ci works for every future install.

[!NOTE] Always use the full package name with Capacitor CLI commands (e.g. npx cap sync @capawesome/capacitor-electron). A bare npx cap sync electron resolves to the electron npm package and silently does nothing.

The scaffolded electron/ project contains only files you own:

| File | Purpose | | ------------------------------ | ------------------------------------------------------- | | main.ts | ~5 lines: imports the runtime and starts the app | | capacitor.electron.config.ts | Typed platform options (window, CSP, deep links, hooks) | | electron-builder.config.js | Packaging configuration | | assets/ | App icons |

Everything with logic lives in the versioned npm package and updates via npm update — your platform project never rots.

Configuration

Platform options are configured in electron/capacitor.electron.config.ts with full type safety:

import { defineConfig } from '@capawesome/capacitor-electron/config';

export default defineConfig({
  window: {
    width: 1200,
    height: 800,
    minWidth: 800,
    minHeight: 600,
  },
  deepLinks: {
    scheme: 'myapp',
  },
});

Extension happens through typed options and hooks (windowFactory, beforeReady, onWindowCreated, CSP overrides) — never by owning runtime code.

Plugin Configuration

Some plugins support platform-specific configuration on Android and iOS — for example, the Capacitor Live Update plugin reads a default channel from an Android string resource or an iOS Info.plist key so the build system can inject a value derived from the app version. The plugins section of electron/capacitor.electron.config.ts fills that role on Electron — generically, for every plugin. Because the file is executable TypeScript evaluated in the main process, values can be computed in code — no template syntax required.

It is merged over the plugins section of the Capacitor config shallowly, per plugin key, and this section wins: for each plugin, its keys override the matching keys in the Capacitor config while unmentioned keys survive; plugins present in only one of the two configs pass through unchanged. Everything outside plugins is untouched. The merge runs once at startup, before plugins receive their config.

import { defineConfig } from '@capawesome/capacitor-electron/config';
import packageJson from './package.json';

export default defineConfig({
  plugins: {
    LiveUpdate: {
      defaultChannel: `production-${packageJson.version}`,
    },
  },
});

Importing ./package.json requires resolveJsonModule in electron/tsconfig.json (already enabled in the scaffold).

Demo

A working example can be found here: capawesome-team/capacitor-electron

Usage

# Copy web assets + regenerate the plugin manifest
npx cap sync @capawesome/capacitor-electron

# Run the app (uses server.url for live reload when configured)
npx cap run @capawesome/capacitor-electron

# Open the built app
npx cap open @capawesome/capacitor-electron

Live Reload

Set server.url in your Capacitor config to your dev server and run the app — the window loads the dev server with full HMR, and the plugin bridge works exactly as in production:

// capacitor.config.ts
const config: CapacitorConfig = {
  // ...
  server: {
    url: 'http://localhost:5173',
  },
};
npx vite &                                       # your web dev server
npx cap run @capawesome/capacitor-electron       # dev mode

In dev mode a documented, dev-only CSP relaxation is applied (inline scripts, eval, websockets — required by HMR runtimes), and the window automatically reconnects when the dev server restarts. Remove server.url (or use a production config) to serve the built web assets from the app bundle again.

Deep Links

Declare the custom URL scheme in the platform configuration (see Configuration) and listen to the standard @capacitor/app event:

import { App } from '@capacitor/app';

await App.addListener('appUrlOpen', ({ url }) => {
  console.log('App opened with URL:', url);
});

Deep links opened while the app is running are routed to the running instance (single instance is enforced by default); the URL that launched the app is available via App.getLaunchUrl().

Splash Screen

Booting a desktop app is not instant: the platform runs every plugin's load() lifecycle hook (e.g. the Live Update plugin verifying and activating a bundle) before the main window is shown. A splash screen covers that gap so the app never appears frozen or blank.

A splash screen is shown automatically when a splash file exists in the electron app directory — no configuration required. Two files are looked up, in order:

  1. electron/assets/splash.html
  2. electron/assets/splash.png

The scaffold ships a neutral, theme-aware assets/splash.html by default. Migrating from @capacitor-community/electron? Its assets/splash.png is picked up unchanged.

Configure the splash screen in electron/capacitor.electron.config.ts:

import { defineConfig } from '@capawesome/capacitor-electron/config';

export default defineConfig({
  splashScreen: {
    // Custom file, relative to the electron app directory. Either an `.html`
    // file or an image (`.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp`).
    path: 'assets/splash.html',
    width: 400,
    height: 300,
    backgroundColor: '#ffffff',
    // Keep the splash visible for at least this long, even on fast startups.
    minimumDurationMs: 0,
  },
});

| Option | Type | Default | Description | | ------------------- | --------- | ----------------------------------------- | ------------------------------------------------------------------------------------------ | | enabled | boolean | shown when a splash file exists | Set false to disable. Set true to require a splash file — boot fails if none is found. | | path | string | assets/splash.html, assets/splash.png | Splash file relative to the electron app directory. | | width | number | 400 | Window width in pixels. | | height | number | 300 | Window height in pixels. | | backgroundColor | string | '#ffffff' | Window background and the canvas behind an image splash. | | minimumDurationMs | number | 0 | Minimum time the splash stays visible. |

HTML vs. image: an .html file is loaded directly, so you get full control over layout, fonts, and animation. An image is centered (object-fit: contain) on a backgroundColor canvas — convenient for a logo, but static.

The splash window is deliberately kept outside the plugin bridge: it is frameless, sandboxed, has no preload and no access to the app scheme, and navigation is blocked.

Packaging note: the splash files live under assets/, which is also the electron-builder buildResources directory. The scaffolded electron-builder.config.js includes assets/**/* in files so the splash ships inside the packaged app (app.asar). If you replace the config, keep that entry — otherwise the splash works in development but silently disappears from packaged binaries.

Debugging

The platform keeps Electron's default application menu, so the Chromium DevTools can be opened at any time via View → Toggle Developer Tools or the keyboard shortcut:

| Operating System | Shortcut | | ---------------- | ------------------------------------------------- | | macOS | Cmd + Option + I | | Windows | Ctrl + Shift + I | | Linux | Ctrl + Shift + I |

To open the DevTools automatically on launch (e.g. to catch logs from early app startup), use the onWindowCreated hook in electron/capacitor.electron.config.ts:

import { defineConfig } from '@capawesome/capacitor-electron/config';

export default defineConfig({
  // ...
  hooks: {
    onWindowCreated: window => {
      window.webContents.openDevTools();
    },
  },
});

Tray Icon

Use the onWindowCreated hook together with Electron's Tray and Menu APIs to add a tray icon and context menu:

import { defineConfig } from '@capawesome/capacitor-electron/config';
import { app, Menu, Tray } from 'electron';
import { join } from 'path';

export default defineConfig({
  // ...
  hooks: {
    onWindowCreated: window => {
      const tray = new Tray(join(app.getAppPath(), 'assets', 'tray-icon.png'));
      tray.setContextMenu(
        Menu.buildFromTemplate([
          { label: 'Show', click: () => window.show() },
          { label: 'Hide', click: () => window.hide() },
          { type: 'separator' },
          { label: 'Quit', click: () => app.quit() },
        ]),
      );
    },
  },
});

Place the tray icon under electron/assets/, which already ships inside the packaged app (see the Splash Screen packaging note). To start the app minimized to the tray, set window.showOnLaunch: false so the main window is created hidden until the user picks Show.

Migrating from @capacitor-community/electron? This recipe replaces its trayIconAndMenuEnabled and hideMainWindowOnLaunch options — build the tray via the hook above and use window.showOnLaunch: false for start-in-tray behavior.

Migration

You can use our AI-Assisted Migration to migrate from @capacitor-community/electron. Add the Capawesome Skills to your AI tool using the following command:

npx skills add capawesome-team/skills --skill capacitor-platforms

Then use the following prompt:

Use the `capacitor-platforms` skill from `capawesome-team/skills` to migrate my project from `@capacitor-community/electron` to `@capawesome/capacitor-electron`.

If you prefer Manual Migration, perform the following steps:

  1. Back up anything you customized in your existing electron/ directory (icons, electron-builder configuration, and any code you added to the generated runtime files).
  2. Remove the old platform and its electron/ directory:
    npm uninstall @capacitor-community/electron
    rm -rf electron
  3. Add this platform:
    npm install @capawesome/capacitor-electron
    npx cap add @capawesome/capacitor-electron
    cd electron && npm install && cd ..
  4. Re-apply your customizations through the typed options in electron/capacitor.electron.config.ts (window options, deep-link scheme, CSP overrides, tray/menu via hooks) instead of editing runtime code, and restore your icons to electron/assets/ and your electron-builder settings in electron/electron-builder.config.js.
  5. Sync and run (always with the full package name):
    npx cap sync @capawesome/capacitor-electron
    npx cap run @capawesome/capacitor-electron

Notes:

  • Deep links no longer require hand-written runtime code — declare the scheme in the platform config and listen to @capacitor/app's appUrlOpen event.
  • Splash screens are picked up automatically from electron/assets/. Keep assets/splash.png and it just works; if you used a custom splashScreenImageName: 'x.gif', either rename it to assets/splash.png or point the config at it via splashScreen: { path: 'assets/x.gif' } (see Splash Screen).
  • Files in Electron's userData directory (e.g. SQLite databases) are stored in a directory named after productName (or name, if productName is missing) in electron/package.json. The new electron/package.json sets productName to your appName, which the old platform used as name by default. If your old electron/package.json used a different productName or name (e.g. because you renamed it to package your app), set productName in the new electron/package.json to that value to keep your existing data.
  • Web storage such as localStorage and IndexedDB is tied to the page's origin. The old platform served your app from capacitor-electron://-, this platform serves it from capacitor-electron://localhost. To keep your existing web storage, set hostname: '-' in electron/capacitor.electron.config.ts.
  • Plugins must provide an electron implementation for this platform's contract (see Plugin Development); implementations written for the old platform are not loaded. Plugins whose web implementation is sufficient continue to work unchanged via the automatic fallback.

Plugin Development

Plugins declare their electron implementation via package.json:

{
  "capacitor": {
    "electron": { "src": "electron" }
  }
}

src is optional and defaults to electron, like android/ios in the Capacitor CLI. The implementation is an ES module at <src>/dist/plugin.mjs exporting plugin classes. A plugin class declares its Capacitor registration name and its public API via static metadata — the static property is the contract, so a build-time dependency on this package is not required.

Recommended: extend ElectronPlugin

Mirroring how Android/iOS plugins extend Capacitor's Plugin and override load(), the recommended path is to extend the ElectronPlugin base class. Add @capawesome/capacitor-electron as a devDependency (for the types) and an optional peerDependency (for the runtime value), then:

import { ElectronPlugin, defineElectronPlugin } from '@capawesome/capacitor-electron/plugin';

class SqliteImpl extends ElectronPlugin {
  // `this.context` (config, services, notifyListeners) is stored by the base constructor.

  // Optional lifecycle hook. Awaited by the platform before the first window loads.
  async load() { ... }

  async open(options) { ... }
  async query(options) { ... }

  // Not declared below, therefore never bridged.
  resolvePath(path) { ... }
}

export const Sqlite = defineElectronPlugin(
  { name: 'Sqlite', methods: ['open', 'query'] },
  SqliteImpl,
);

Zero-dependency: marker-only

The base class is optional sugar — the discovery contract is the static __capacitorElectronPlugin metadata, and the lifecycle hook is detected structurally (never via instanceof, which would break across duplicated copies of this package). So a plugin can ship with no dependency on this package at all, implementing a structural load() if it needs the hook:

class SqliteImpl {
  constructor({ config, services, notifyListeners }) { ... }

  // Optional. Structural lifecycle hook, detected by name.
  async load() { ... }

  async open(options) { ... }
  async query(options) { ... }

  // Not declared below, therefore never bridged.
  resolvePath(path) { ... }
}

// Equivalent to defineElectronPlugin, without importing this package.
SqliteImpl.__capacitorElectronPlugin = {
  name: 'Sqlite',
  methods: ['open', 'query'],
};

export { SqliteImpl as Sqlite };

The declared methods array is the plugin's entire bridged surface: anything not listed stays main-process-internal, and a declared method that is missing on the class fails loudly at boot. Each class is instantiated once in the main process (full Node and Electron API access) and exposed under its registration name through Capacitor's native plugin path — registerPlugin('Sqlite', { web: ... }) just works, with the web implementation as the automatic fallback for platforms the plugin doesn't cover. No electron key in the plugin's registerPlugin wiring is needed.

The constructor context (this.context on an ElectronPlugin subclass, or the constructor argument otherwise) provides:

  • config — the app's Capacitor configuration.
  • notifyListeners(eventName, data) — emits a plugin event, mirroring Capacitor's native notifyListeners. Web listeners use the standard addListener(eventName, callback) / PluginListenerHandle API.
  • services — platform primitives (currently services.bundles: switch the served web-bundle directory and reload the app windows).

The load() lifecycle hook

load() runs once after the plugin is constructed and is awaited before the first application window loads, so async setup — including repointing the active bundle via services.bundles.setActiveBundle() — takes effect on first paint (no default-bundle flash). It is a lifecycle hook, not a bridged method: load is reserved and is never bridged to the renderer. It must not be listed in methods — doing so is rejected at boot, because bridging it would let web content invoke the lifecycle hook arbitrarily. A rejected or thrown load() fails the app boot loudly, the same way a declared-but-missing method does. On an ElectronPlugin subclass the default load() is a no-op, so overriding it is optional.

At sync time the platform statically scans the app's dependencies and generates a plugin manifest — no plugin code runs outside Electron. Results, thrown Errors, and their code properties cross the bridge with Capacitor semantics.

Packaging

The scaffolded electron/ project packages with electron-builder:

cd electron && npm run pack

The pack script runs three steps: compile (tsc), vendor, and electron-builder. The vendor step (capacitor-electron vendor) copies the platform runtime, every plugin's electron implementation, and their dependency closure (production, peer, and installed optional dependencies) into electron/vendor/, which the scaffolded electron-builder config maps to node_modules inside the packaged app — so module resolution works identically in development (from your app root) and in the package, without a second npm install and without version drift.

Code signing, notarization, targets (dmg/msi/nsis/AppImage/deb), and icons are standard electron-builder configuration in the user-owned electron-builder.config.js — see https://www.electron.build. Electron Forge is a supported alternative: run capacitor-electron vendor before packaging and include vendor/node_modules as the app's node_modules.

App Updates

Two independent update layers, matching mobile:

  • Binary updates (Electron itself, the runtime, native modules): use electron-updater — the desktop analog of an app-store update. Wire it in your main.ts; it operates on the packaged artifacts produced above.
  • Web-bundle updates: use the Capacitor Live Update plugin with Capawesome Cloud — download, verification, channels, and failed-boot rollback, exactly like on mobile. The platform itself only ships the serving primitive the plugin builds on: services.bundles (setActiveBundle(dir | null) switches the served directory in memory and reloads the app windows; getActiveBundlePath() returns it). The platform persists nothing and never rolls back; the plugin re-applies its active bundle in its load() hook.

Electron Support Policy

  • Floor: Electron 28 (required for the runtime's serving APIs). No ceiling — the scaffold uses a caret range you control, and the platform releases only when Electron actually breaks an API it uses.
  • Tested: CI runs the example app against the latest stable Electron and the floor on every release.
  • Electron ships a new major roughly every 8 weeks. This platform's wide peer range means you can adopt new Electron majors immediately, without waiting for a platform release.

Limitations

Read this before committing to the platform — these are inherent trade-offs, not bugs:

  • Binary size and memory. Every app ships its own Chromium and Node: expect roughly 80–150 MB installed and a matching memory footprint. If minimal footprint is the priority, Electron is the wrong tool.
  • Plugins need an electron implementation. Only Capacitor plugins that ship an electron implementation (or whose web implementation is sufficient — the automatic fallback) work. iOS/Android native code does not translate.
  • Native Node addons are not rebuilt automatically. If a plugin's electron implementation depends on native Node modules, capacitor-electron vendor detects and reports them, but you must rebuild them against Electron's ABI yourself (e.g. @electron/rebuild) before packaging.
  • No web-bundle OTA product included. The platform provides the serving primitive (services.bundles) only; update delivery, channels, and verification are a separate product layer.
  • Always pass the platform name to the Capacitor CLI. A bare npx cap sync only processes iOS/Android/web; npx cap sync electron resolves to the electron npm package and silently does nothing. Use the full package name (see the note under Installation).
  • Desktop is not mobile. APIs like geolocation permission prompts, status bars, or app-store review flows have no desktop equivalent; plugins whose web implementation assumes a mobile browser may behave differently on desktop Chromium.

FAQ

Can I use any Capacitor plugin with this platform?

Plugins with a dedicated Electron implementation or a sufficient web implementation work. Plugins that only ship iOS/Android native code do not.

How do I update Electron in my app?

Update the electron version in electron/package.json and run npm install there. The platform supports Electron 28 and later with no upper bound.

Does Capacitor.getPlatform() return electron?

Yes. Capacitor.getPlatform() returns 'electron' and Capacitor.isNativePlatform() returns true, so you can branch platform-specific code the same way as on iOS and Android.

Can I use Live Updates on Electron?

Yes. The Capacitor Live Update plugin (8.5.0 or later) has a native Electron implementation and uses the same Capawesome Cloud app as your mobile app. See Update Electron Apps.

Where does my app store its data?

Electron stores app data, such as databases created by plugins, in the userData directory. Its name comes from productName in electron/package.json, or from name if productName is missing. If you re-add the platform to an app that was added with version 0.2.0 or earlier, set productName to the previous name (<app-name-slug>-electron) to keep using the existing data.

Related Plugins

Newsletter

Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our Capawesome Newsletter.

Breaking Changes

See BREAKING.md.

Changelog

See CHANGELOG.md.

License

See LICENSE.

[^1]: This project is not affiliated with, endorsed by, sponsored by, or approved by the OpenJS Foundation or any of its affiliates. Electron is a trademark of the OpenJS Foundation.