DEV Community

Sangmin Lee
Sangmin Lee

Posted on • Originally published at claudeguide.io

Build Electron Desktop Apps with Claude Code (2026)

Originally published at claudeguide.io/claude-code-electron-desktop-app

Build Electron Desktop Apps with Claude Code (2026)

To build an Electron desktop app with Claude Code, scaffold the project with npm create electron-vite, then prompt Claude Code to wire the main process, preload script, and renderer using TypeScript. Claude Code understands Electron's security model — contextIsolation, nodeIntegration, and IPC channels — and generates correctly structured code for both sides of the process boundary in 2026. This guide covers project setup, IPC communication, packaging, auto-updates, and when to choose Tauri instead.


Electron Project Scaffolding

Start a new project using electron-vite, which gives you Vite's fast HMR in the renderer:

npm create electron-vite@latest my-ai-app -- --template vue-ts
# or for React:
npm create electron-vite@latest my-ai-app -- --template react-ts
cd my-ai-app && npm install
Enter fullscreen mode Exit fullscreen mode

Then open Claude Code in the project root and use this prompt to establish the architecture:

I'm building an Electron desktop app with electron-vite and TypeScript.
The app will call the Claude API from the main process and display
results in a React renderer.

Set up the following structure:
- src/main/index.ts — main process entry point
- src/preload/index.ts — context bridge (no direct nodeIntegration)
- src/renderer/src/App.tsx — React renderer entry

Requirements:
- contextIsolation: true, nodeIntegration: false (secure by default)
- IPC channels: invoke/handle pattern for async Claude API calls
- TypeScript types shared between main and renderer via src/shared/types.ts
- Vite aliases so renderer can import from @shared/

Show all four files with the correct electron-vite configuration.
Enter fullscreen mode Exit fullscreen mode

Claude Code generates a working scaffold with proper webPreferences security settings and a typed channel map — the most common source of bugs in new Electron projects.


IPC Communication Patterns

Electron's IPC bridge is where most developers hit friction. Claude Code handles the three canonical patterns:

1. Renderer → Main (invoke/handle) — for Claude API calls:

// src/shared/types.ts
export interface ClaudeRequest {
  prompt: string;
  model: 'claude-opus-4-5' | 'claude-sonnet-4-5' | 'claude-haiku-4-5';
}

export interface ClaudeResponse {
  text: string;
  inputTokens: number;
  outputTokens: number;
}

export type IpcChannels = {
  'claude:ask': [ClaudeRequest, ClaudeResponse];
};
Enter fullscreen mode Exit fullscreen mode
// src/main/index.ts
import { app, BrowserWindow, ipcMain } from 'electron';
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

ipcMain.handle('claude:ask', async (_event, req: ClaudeRequest): Promise<ClaudeResponse

---

## Tauri as a Lightweight Alternative

Tauri uses the OS WebView instead of bundling Chromium, resulting in a dramatically smaller binary. For AI-powered desktop apps, the trade-off is mostly in ecosystem maturity.

| Feature | Electron | Tauri |
|---|---|---|
| **Binary size** | 80150 MB | 310 MB |
| **Memory usage** | 150400 MB | 3080 MB |
| **Language** | Node.js (main) + JS (renderer) | Rust (backend) + JS (renderer) |
| **IPC** | ipcMain/ipcRenderer (mature) | Commands + Events (v2 stable) |
| **Native APIs** | electron APIs + node modules | Tauri plugins + Rust crates |
| **Claude API calls** | From main process (Node.js) | From Rust backend or renderer |
| **Auto-updates** | electron-updater (mature) | tauri-plugin-updater (v2) |
| **Build tooling** | electron-builder / electron-vite | Cargo + Vite (tauri-cli) |
| **macOS notarization** | Manual or electron-builder | Built into tauri-cli |
| **Learning curve** | Low (JS/TS throughout) | High (requires Rust) |
| **Best for** | Teams with JS/TS skills, complex integrations | Minimal footprint, security-critical apps |

For a Claude-powered desktop app where the team knows TypeScript, Electron remains the pragmatic choice. Choose Tauri if binary size or memory footprint is a hard requirement (e.g., distributing to corporate environments with strict size policies).

**Tauri scaffold with Claude Code:**

Enter fullscreen mode Exit fullscreen mode

Scaffold a Tauri v2 desktop app with:

  • Vite + React + TypeScript renderer
  • Rust backend that calls the Claude API via reqwest
  • A Tauri command: async fn ask_claude(prompt: String) -

Frequently Asked Questions

How do I securely use the Anthropic API key in an Electron app?

Never put the API key in the renderer process or expose it via the preload script. Load it in the main process only — either from an environment variable set at launch, from a user-configurable encrypted store using electron-store with encryptionKey, or from a local config file in app.getPath('userData'). The renderer should call an IPC handler like claude:ask and receive only the response, never the key itself.

Can the renderer process make direct Claude API calls in Electron?

Technically yes if you enable nodeIntegration, but this is a serious security risk — any XSS in the renderer gains full Node.js access. The correct pattern is to keep contextIsolation: true and nodeIntegration: false, make all API calls from the main process via ipcMain.handle, and expose only the results through the context bridge. Claude Code will warn you if you try to set up direct renderer API calls.

How do I handle streaming Claude API responses in Electron IPC?

Use webContents.send for streaming: the main process streams tokens from client.messages.stream() and sends each chunk to the renderer via mainWindow.webContents.send('claude:chunk', { text, done }). The preload exposes onChunk(callback) and offChunk(callback) methods via the context bridge. The renderer subscribes to chunks and updates state incrementally. Claude Code generates this full pattern on request.

Is Electron or Tauri better for an AI-powered desktop app?

For most teams, Electron is the better starting point because the entire stack is TypeScript — no Rust required. The main downside is binary size (80–150 MB vs. 3–10 MB for Tauri) and higher memory usage. If you need the smallest possible distributable or are working in a security-sensitive environment, Tauri is worth the Rust learning curve. Claude Code supports scaffolding both — see the comparison table above for a full breakdown.

How do I add native OS features (tray icon, notifications, file dialogs) with Claude Code?

Describe what you need and Claude Code generates the Electron API calls: Tray, nativeImage, Notification, and dialog.showOpenDialog. For example: "Add a system tray icon that shows the last Claude response as a tooltip and has a menu with 'New Chat' and 'Quit'." Claude Code knows these APIs well and produces working code including the correct nativeImage loading pattern for macOS retina displays.

Top comments (0)