Turning a web app into a desktop app with Electron
A web app that already works in the browser can become a Mac desktop app with Electron in an afternoon: one JavaScript file that opens a window and loads the existing URL, a packaging step, and a signed build. The afternoon is not the hard part. The hard part is the schedule that starts after it, because an Electron app carries its own copy of Chromium, and that copy has to be rebuilt and shipped again every time Chromium moves. Before writing any code, it helps to decide which of three things is actually wanted: a desktop app to distribute to customers, a private window for a site used every day, or something in between.
The two ways an Electron wrapper can load the app
Electron has a main process, which is Node.js and owns windows, menus and anything that touches the operating system, and one renderer process per window, which is Chromium and draws the page. The process model documentation describes this split in detail. For a wrapper around an existing web app, the main process is usually a single file, and the first real decision is what that file points the window at.
Pointing the window at the live URL keeps the desktop app thin. Every deploy of the web app is live in the desktop app immediately, because the desktop app is only a client. Nothing about the web release pipeline changes. The cost is that the app shows a blank or error screen without a network, unless an offline page is added.
Bundling the front end into the app makes it start offline and feel more like installed software. In exchange it gains a version number. Front end changes now need a desktop release, the API base URL has to be configurable, and requests that used to be same origin become cross origin, so the server must allow them. Login flows that relied on first party cookies may need rework.
For a web app that is already deployed and always online, the first form is almost always the right one. The second form is justified when offline use is a requirement, not a nice extra.
A minimal main process for the live URL form looks like this:
const { app, BrowserWindow, shell } = require('electron');
function createWindow() {
const win = new BrowserWindow({ width: 1280, height: 800 });
win.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});
win.loadURL('https://app.example.com');
}
app.whenReady().then(createWindow);
The window open handler matters more than it looks. Without it, links that open a new window create bare Electron windows with no address bar, which confuses users and widens the attack surface.
What Electron adds that a browser tab cannot
If the only goal is a separate window with its own Dock icon, macOS already provides that without code. Since macOS Sonoma 14, Safari can save any page as a web app with File, then Add to Dock, and Apple's support page for Safari web apps states that such an app shares no browsing history, cookies or website data with Safari itself. Electron earns its cost when the requirement list includes something a browser deliberately withholds from a web page:
- A full native application menu with keyboard shortcuts that do not collide with browser shortcuts.
- A global shortcut that works while another app is focused, for summoning a capture window or a timer.
- A menu bar or tray item, and the ability to keep running with no window open.
- A registered URL scheme, so a link in mail or chat opens the app directly, plus file type associations.
- Direct file system access from the main process, for watching a folder or writing to a fixed path.
- Fine control over the window: fixed sizes, always on top, frameless, kiosk mode.
Each of these is a genuine reason. If the honest list is empty, the rest of this guide describes costs with nothing on the other side, and a no code route will do the job.
Packaging, signing and notarizing on macOS
A packaged Electron app is a normal .app bundle. Electron Forge, which the packaging tutorial uses, turns the project into a .app and a .dmg or .zip with a couple of commands. The bundle contains the whole Chromium and Node.js runtime, which is why even a one window wrapper is large on disk and why two Electron apps do not share an engine the way two browser tabs do.
To run without warnings on other people's Macs, the build has to be signed with a Developer ID certificate and notarized by Apple. The Electron code signing guide covers the tooling. The certificate requires membership in the Apple Developer Program, which Apple lists at 99 US dollars per year for individuals and organizations, checked on October 18, 2026. Notarization is not a one time step. Every release goes through it, so it belongs in the build script from the first day.
An app used only on the builder's own Mac can skip all of this. It runs locally without notarization, and Gatekeeper only intervenes when a quarantined download is opened. That distinction is the first question to settle: whether anyone else will ever install the app.
Auto updates are the third piece. Electron has an autoUpdater module, but it needs a place to fetch signed releases from, which means a release server, a hosted feed, or a paid service. ToDesktop is one example of a hosted build and update service for Electron apps, and its home page listed plans at 100, 300 and 1,200 US dollars per month with a 7 day trial on October 18, 2026. Whether that is worth it depends on how many users there are, but it shows that update infrastructure is a real, recurring line item.
The update schedule nobody plans for
The cost that surprises teams most is not packaging. It is the runtime. According to Electron's release timeline page, a new major version ships every 8 weeks, each one tracks an even numbered Chromium release, and only the latest three stable major versions are supported. On October 18, 2026 the latest release on npm was 44.9.0, so the supported lines were 42, 43 and 44.
Put those numbers together and an app built today drops out of support in roughly six months unless it is rebuilt. A browser installed on the same Mac patches itself in the background within days of a security fix. A bundled engine patches itself never. It is fixed only when someone bumps the Electron version, tests the app, signs it, notarizes it and pushes the update to every installed copy.
For a wrapper around a live web app, this treadmill is the main ongoing cost. The web code does not change, yet the desktop app still needs a release every couple of months to stay on a supported engine. Teams that ship a wrapper once and forget it end up distributing an outdated browser with their logo on it.
Breaking changes come with the treadmill. Electron versions sometimes change defaults or remove deprecated APIs, so the bump is not always a one line change. Budget a short test pass per upgrade: sign in, sign out, file upload, download, print and notifications.
Security defaults that matter for a wrapper
Electron's security checklist has 20 items. For a window that loads a remote site, a handful carry most of the weight, and several are now the defaults:
| Recommendation | Default since | What it protects |
|---|---|---|
| Node.js integration off for remote content | Electron 5.0.0 | Page scripts cannot call Node.js APIs |
| Context isolation on | Electron 12.0.0 | Preload code and page code do not share globals |
| Process sandboxing on | Electron 20.0.0 | The renderer runs with OS level restrictions |
| Handle permission requests | Not a default | Camera, microphone and notification prompts |
| Limit navigation and new windows | Not a default | Links that would load other sites in the app |
The two items that are not defaults are where wrappers usually go wrong. Electron approves permission requests automatically unless a handler is set, so a wrapper should decide which permissions its own domain may use. Navigation should be restricted to the app's own origins, with everything else handed to the default browser through shell.openExternal, as in the code above.
Single sign on deserves a test of its own. Logins that bounce through an identity provider on another domain have to be allowed to navigate inside the window, or the user is sent to the browser mid login and the session lands in the wrong place. Allow the specific identity provider origins rather than opening navigation to everything.
Electron compared with the other routes
| Electron | Tauri | Safari Add to Dock | Site to app tool on an installed browser | |
|---|---|---|---|---|
| Engine | Bundled Chromium | System WebKit | System WebKit | The Chrome, Edge or Brave already installed |
| Code to maintain | Main process and build scripts | Rust core and build scripts | None | None |
| Bundle size | Large, includes the runtime | Can be under 600KB, per Tauri | Nothing extra | Small launcher |
| Native menus, tray, global shortcut | Yes | Yes | No | No |
| Offline start | Yes, if assets are bundled | Yes | No | No |
| Signing for distribution | Required | Required | Not applicable | Handled by the tool |
| Who updates the engine | The app's builder | macOS updates | macOS updates | The browser's own updater |
The size figure for Tauri comes from the Tauri documentation, which says a minimal app can be less than 600KB because it uses the system web view instead of bundling a browser. The trade is that rendering follows the WebKit version on each user's Mac, so a site tested only in Chrome may need fixes.
The last row decides most cases. Anything with a bundled engine hands browser security updates to whoever built the app. Anything on a system or installed browser engine leaves them with a component that already updates itself.
When a wrapper without code is the better answer
Electron is the right tool for a product team shipping a desktop client to customers, with native features on the roadmap and someone owning the release train. It is a heavy tool for an individual who wants Linear, a CRM or an internal admin panel out of the browser tabs and into its own Dock icon.
For that second case, the requirements are usually a window, an icon, its own login session, notifications, and Cmd+Tab. A site to app tool that builds a small launcher on top of an installed Chromium browser covers those without a repository or a build pipeline. One such tool keeps each app on a separate browser profile, so cookies and logins do not mix, and links the app to the installed browser so browser updates apply automatically. The feature list shows what such an app can and cannot do, and the supported services page lists the presets that can be created without entering a URL. It is free for up to 3 apps and a one time 24.99 US dollars beyond that, per its pricing, which is less than the first year of an Apple Developer membership.
The limits are the ones in the comparison table: no custom native menus, no tray items, no offline start. If those appear on the requirement list later, the Electron route is still there, and the web app has not changed.
What to change first
Write down the native features the desktop app must have. If the list is empty, try a no code wrapper for a week before writing a main process. If the list is real, start Electron with the live URL form, the security defaults above, and an upgrade reminder every 8 weeks. For the no code route, Kagemusha has the step by step setup.
Frequently asked questions
Can an existing web app be wrapped in Electron without changing its code?
Yes, if the wrapper loads the live URL. The page runs as it does in Chrome and does not need to know it is inside Electron. Code changes become necessary only when the front end is bundled into the app, because the API base URL then has to be configurable and requests become cross origin.
How often does an Electron app need to be updated?
Electron ships a new major version every 8 weeks and supports only the latest three, so an app falls out of support about six months after its last rebuild. Security fixes reach users only when the app is rebuilt on a newer Electron, signed, notarized and shipped.
Is an Apple Developer account required to make an Electron app for a Mac?
Not for personal use on the Mac that built it. To distribute the app to other people without Gatekeeper warnings, it must be signed with a Developer ID certificate and notarized, and that requires the Apple Developer Program at 99 US dollars per year.
Is Electron or Tauri better for wrapping a website?
Electron bundles Chromium, so rendering matches Chrome on every Mac but the app is large and the engine must be updated by the builder. Tauri uses the system WebKit, so a minimal app can be under 600KB and the engine updates with macOS, but the site has to render well in WebKit.
Why do links in an Electron wrapper open in a bare window?
Electron creates a new BrowserWindow for links that request one unless a window open handler is set. Setting setWindowOpenHandler to deny the request and pass the URL to shell.openExternal sends those links to the default browser instead.