| name | electron-path-resolution |
| description | Critical path resolution patterns for Electron apps - avoid process.cwd() and __dirname pitfalls |
Electron Path Resolution Skill
Path resolution is the #1 cause of Electron app failures. This skill covers all the pitfalls and their solutions.
The Core Problem
When an Electron app is launched from Finder/Explorer (NOT terminal):
process.cwd() returns / on macOS, C:\Windows\System32 on Windows
__dirname in bundled code is resolved at bundle time, not runtime
- Node.js module resolution fails for bundled apps
Environment Setup Pattern
In your main process, set environment variables that ALL code can use:
import { app } from 'electron';
import path from 'path';
async function initializeEnvironment() {
const userDataDir = app.getPath('userData');
const appRoot = app.getAppPath();
process.env.ELECTRON_USER_DATA = userDataDir;
process.env.ELECTRON_APP_ROOT = appRoot;
process.env.ELECTRON_DB_PATH = path.join(userDataDir, 'app.db');
const fs = require('fs');
const dirs = ['uploads', 'generated', 'cache', 'logs'];
for (const dir of dirs) {
const dirPath = path.join(userDataDir, dir);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
}
Safe Path Helper Functions
Create utility functions that ALL code uses:
import path from 'path';
import os from 'os';
export function getUserDataDir(): string {
if (process.env.ELECTRON_USER_DATA) {
return process.env.ELECTRON_USER_DATA;
}
if (process.env.ELECTRON_DB_PATH) {
return path.dirname(process.env.ELECTRON_DB_PATH);
}
return path.resolve(process.cwd(), 'data');
}
export function getAppRoot(): string {
if (process.env.ELECTRON_APP_ROOT) {
return process.env.ELECTRON_APP_ROOT;
}
return process.cwd();
}
export function isElectron(): boolean {
return !!process.env.ELECTRON_USER_DATA || !!process.env.ELECTRON_DB_PATH;
}
export function getSafeWorkingDir(): string {
if (isElectron()) {
return getUserDataDir();
}
return process.cwd();
}
export function resolveFilePath(relativePath: string): string {
if (!relativePath.startsWith('/')) {
return relativePath;
}
const filename = path.basename(relativePath);
if (isElectron()) {
const userData = getUserDataDir();
if (relativePath.includes('/uploaded/') || relativePath.includes('/uploads/')) {
return path.join(userData, 'uploads', filename);
}
if (relativePath.includes('/generated/')) {
return path.join(userData, 'generated', filename);
}
if (relativePath.includes('/renders/')) {
return path.join(userData, 'renders', filename);
}
return path.join(getAppRoot(), 'public', relativePath);
}
return path.join(process.cwd(), 'public', relativePath);
}
Fixing Existing Code
Database Location
const dbPath = path.resolve(process.cwd(), 'data', 'app.db');
import { getUserDataDir } from './electron-paths';
const dbPath = process.env.ELECTRON_DB_PATH || path.join(getUserDataDir(), 'app.db');
File Uploads
const uploadsDir = path.join(process.cwd(), 'public', 'uploads');
function getUploadsDir(): string {
if (process.env.ELECTRON_DB_PATH) {
const userDataDir = path.dirname(process.env.ELECTRON_DB_PATH);
return path.join(userDataDir, 'uploads');
}
return path.join(process.cwd(), 'public', 'uploads');
}
Generated Files
const GENERATED_DIR = path.resolve(process.cwd(), 'public/assets/generated');
function getGeneratedDir(): string {
if (process.env.ELECTRON_DB_PATH) {
const userDataDir = path.dirname(process.env.ELECTRON_DB_PATH);
return path.join(userDataDir, 'generated');
}
return path.resolve(process.cwd(), 'public/assets/generated');
}
const GENERATED_DIR = getGeneratedDir();
Temp Directories
const tempDir = path.resolve(process.cwd(), '.temp');
function getTempDir(): string {
if (process.env.ELECTRON_DB_PATH) {
const userDataDir = path.dirname(process.env.ELECTRON_DB_PATH);
return path.join(userDataDir, '.temp');
}
return path.resolve(process.cwd(), '.temp');
}
Static File Serving in Express
import express from 'express';
import path from 'path';
const app = express();
const isElectron = !!process.env.ELECTRON_DB_PATH;
if (isElectron) {
const userDataDir = path.dirname(process.env.ELECTRON_DB_PATH!);
app.use('/assets/uploads', express.static(path.join(userDataDir, 'uploads')));
app.use('/assets/generated', express.static(path.join(userDataDir, 'generated')));
app.use('/assets/renders', express.static(path.join(userDataDir, 'renders')));
const publicPath = process.env.ELECTRON_APP_ROOT
? path.join(process.env.ELECTRON_APP_ROOT, '..', 'public')
: path.resolve(__dirname, '../../public');
app.use('/assets', express.static(publicPath));
} else {
app.use('/assets', express.static(path.join(process.cwd(), 'public/assets')));
}
Preload Path in BrowserWindow
import { app, BrowserWindow } from 'electron';
import path from 'path';
const isDev = process.env.NODE_ENV === 'development';
export function createMainWindow() {
const preloadPath = isDev
? path.join(__dirname, 'preload.cjs')
: path.join(app.getAppPath(), 'electron-dist', 'preload.cjs');
const win = new BrowserWindow({
webPreferences: {
preload: preloadPath,
contextIsolation: true,
nodeIntegration: false,
},
});
return win;
}
Anti-Patterns
const bad1 = process.cwd();
const bad2 = __dirname;
const bad3 = require.resolve('./config.json');
const bad4 = './data/file.txt';
Validation Checklist
Before packaging:
Integration
Used by:
electron-converter agent
electron-build-config skill