Files
sveltekit-adapters/packages/adapter-electron

@sveltejs/adapter-electron

A SvelteKit adapter for Electron desktop apps that uses native protocol handling for production and seamless Vite dev server integration for development.

Features

  • Native Protocol Handling: Uses Electron's protocol.handle() API for production
  • Development Integration: Seamless Vite dev server integration with HMR
  • No HTTP Server: Bypasses Node.js HTTP servers entirely in production
  • Full SvelteKit Support: SSR, API routes, static assets, prerendered pages, form actions
  • Clean Architecture: All Electron integration code is encapsulated
  • Production Ready: Works with electron-builder and similar tools
  • TypeScript Support: Full type definitions included
  • Proper Error Handling: User-friendly error reporting with GitHub issue links

Installation

npm install @sveltejs/adapter-electron

Quick Start

1. Configure SvelteKit

In your svelte.config.js:

import adapter from '@sveltejs/adapter-electron';

/** @type {import('@sveltejs/kit').Config} */
const config = {
	kit: {
		adapter: adapter({
			// All options are optional with sensible defaults
			out: 'out',                    // Output directory (default: 'out')
			assets: true,                  // Include static assets (default: true)
			fallback: undefined,           // Fallback page for client-side routing (default: undefined)
			precompress: false,            // Precompress assets (default: false)
			strict: true                   // Strict mode (default: true)
		})
	}
};

export default config;

2. Set up Vite Configuration

In your vite.config.ts:

import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { electronPlugin } from 'adapter-electron';

export default defineConfig({
	plugins: [
		sveltekit(),
		electronPlugin({
			// Auto-detects src/main.ts and src/preload.ts by default
			// Override paths if needed:
			// mainEntry: 'src/main.ts',        // Main process entry (default: 'src/main.ts')
			// preloadEntry: 'src/preload.ts',  // Preload script entry (default: 'src/preload.ts')
			// mainOut: 'out/main/index.js',    // Main output (default: 'out/main/index.js')
			// preloadOut: 'out/preload/index.js' // Preload output (default: 'out/preload/index.js')
		})
	]
});

3. Create Electron Main Process

Create src/main.ts:

import { app, BrowserWindow } from 'electron';
import { setupHandler, getPreloadPath, registerAppScheme } from 'adapter-electron/functions/setupHandler';

let mainWindow: BrowserWindow | null = null;
let stopIntercept: (() => void) | undefined;

// IMPORTANT: Register the app scheme before app.ready
registerAppScheme();

async function createWindow() {
	mainWindow = new BrowserWindow({
		width: 1200,
		height: 800,
		webPreferences: {
			preload: getPreloadPath(),  // Auto-configured preload path
			contextIsolation: true,
			nodeIntegration: false,
			webSecurity: true
		}
	});

	mainWindow.on('closed', () => {
		mainWindow = null;
		stopIntercept?.();
	});

	// Setup the protocol handler (handles dev vs prod automatically)
	stopIntercept = await setupHandler(mainWindow);
}

app.on('ready', createWindow);

app.on('window-all-closed', () => {
	if (process.platform !== 'darwin') {
		app.quit();
	}
});

app.on('activate', async () => {
	if (BrowserWindow.getAllWindows().length === 0 && !mainWindow) {
		await createWindow();
	}
});

4. Create Preload Script

Create src/preload.ts:

// Your preload script content
console.log('Preload loaded');

// Example: Expose APIs to renderer process
import { contextBridge } from 'electron';

contextBridge.exposeInMainWorld('electronAPI', {
	// Your APIs here
});

5. Build and Run

# Development (uses Vite dev server with HMR)
npm run dev

# Production build
npm run build

# Run built Electron app
npm start  # or your preferred Electron launcher

How It Works

Development Mode

  • Uses Vite dev server (http://localhost:5173 by default)
  • Full hot module replacement (HMR) support
  • No protocol interception needed
  • Set VITE_DEV_SERVER environment variable to customize dev server URL

Production Mode

The adapter uses Electron's native protocol.handle() API to intercept http://127.0.0.1 requests:

  1. Static Assets: Serves files from the client/ directory
  2. Prerendered Pages: Serves static HTML from the prerendered/ directory
  3. SSR/API Routes: Calls SvelteKit's Server.respond() directly
  4. Form Actions: Full support for SvelteKit form actions
  5. Cookie Handling: Automatic cookie synchronization with Electron session

Build Output Structure

After running npm run build, you'll have:

out/
├── client/                  # SvelteKit client assets (JS, CSS, images)
├── server/                  # SvelteKit server files for SSR
│   ├── index.js            # SvelteKit server
│   ├── manifest.js         # App manifest
│   └── chunks/             # Server chunks
├── prerendered/            # Prerendered static HTML pages
├── functions/              # Protocol handler code
│   ├── setupHandler.js     # Main protocol handler
│   └── setupHandler.d.ts   # TypeScript definitions
├── main/                   # Compiled main process
│   └── index.js
└── preload/                # Compiled preload script
    └── index.js

Configuration Options

SvelteKit Adapter Options

adapter({
	out: 'out',              // Output directory
	assets: true,            // Include static assets from /static
	fallback: undefined,     // Fallback page for client-side routing
	precompress: false,      // Precompress assets with gzip/brotli
	strict: true             // Enable strict mode
})

Electron Plugin Options

electronPlugin({
	mainEntry: 'src/main.ts',           // Main process entry point
	preloadEntry: 'src/preload.ts',     // Preload script entry point  
	mainOut: 'out/main/index.js',       // Main process output
	preloadOut: 'out/preload/index.js'  // Preload script output
})

Environment Variables

Variable Description Default
VITE_DEV_SERVER Development server URL http://localhost:5173
VITE_APP_URL Production app URL http://127.0.0.1

API Reference

Protocol Handler Functions

setupHandler(mainWindow: BrowserWindow): Promise<() => void>

Sets up the protocol handler and loads the appropriate URL based on environment.

  • Development: Loads Vite dev server
  • Production: Sets up protocol interception and loads app
  • Returns: Cleanup function to stop protocol interception

registerAppScheme(): void

Registers the HTTP scheme as privileged. Must be called before app.ready.

getPreloadPath(): string

Returns the correct preload script path for current environment.

  • Development: Points to source preload script
  • Production: Points to built preload script

Request Flow

Development:
Electron Window → Vite Dev Server (http://localhost:5173)
                     ↓
                 Hot Module Replacement

Production:
Electron Request (http://127.0.0.1/page)
    ↓
Protocol Handler
    ↓
1. Check static files (client/)
2. Check prerendered pages (prerendered/)  
3. Handle SSR/API (server.respond())
    ↓
Response with Cookie Sync

Advanced Usage

Custom Error Handling

The adapter includes built-in error reporting. Errors are:

  • Logged to console in development
  • Shown as dialog boxes in production
  • Include GitHub issue reporting instructions

Cookies are automatically synchronized between SvelteKit and Electron's session:

  • Request cookies are extracted from Electron session
  • Response Set-Cookie headers are applied to Electron session
  • Supports all cookie attributes (secure, httpOnly, etc.)

Security Features

  • Path traversal protection for static files
  • Automatic CORS handling
  • Secure cookie handling
  • Context isolation enforced

Production Packaging

With electron-builder

{
  "scripts": {
    "build": "vite build",
    "dist": "npm run build && electron-builder"
  },
  "build": {
    "directories": {
      "output": "dist",
      "buildResources": "out"
    },
    "files": [
      "out/**/*",
      "package.json"
    ],
    "mac": {
      "icon": "assets/icon.icns"
    },
    "win": {
      "icon": "assets/icon.ico"
    }
  }
}

With electron-forge

// forge.config.js
module.exports = {
  packagerConfig: {
    dir: './out'
  },
  makers: [
    {
      name: '@electron-forge/maker-squirrel',
      config: {}
    }
  ]
};

Troubleshooting

Common Issues

Protocol not working in production:

  • Ensure registerAppScheme() is called before app.ready
  • Check that setupHandler() is called after window creation

Development server not loading:

  • Verify Vite dev server is running on expected port
  • Set VITE_DEV_SERVER environment variable if using custom port

Form actions not working:

  • Ensure you're using proper Web API Request objects (handled automatically)
  • Check that cookies are being synchronized properly

Build errors:

  • Verify all dependencies are installed
  • Check that TypeScript configuration includes Electron types

Debug Mode

Enable verbose logging:

// In main process
process.env.ELECTRON_ENABLE_LOGGING = 'true';

Getting Help

If you encounter issues:

  1. Check the console for error messages
  2. Verify your configuration matches the examples
  3. File an issue with error details and reproduction steps

License

MIT