Pfade in Electron sind doppelt heikel: Plattform-Unterschiede (/ vs. \) und User-spezifische Verzeichnisse (~/Library/... vs. %APPDATA%\...). Das path-Modul aus Node abstrahiert die Trennzeichen, app.getPath liefert die richtigen User-Verzeichnisse. Hier alle Standard-Pfade und Patterns.

Standard-Pfade von app.getPath

KeymacOSWindowsLinux
userData~/Library/Application Support/<App>%APPDATA%\<App>\~/.config/<App>/
home~C:\Users\<user>~
appData~/Library/Application Support/%APPDATA%~/.config/
temp/tmp/%TEMP%/tmp/
downloads~/Downloads/%USERPROFILE%\Downloads\~/Downloads/
documents~/Documents/~\Documents\~/Documents/
desktop~/Desktop/~\Desktop\~/Desktop/
pictures~/Pictures/~\Pictures\~/Pictures/
music~/Music/~\Music\~/Music/
videos~/Movies/~\Videos\~/Videos/
logs~/Library/Logs/<App>/%APPDATA%\<App>\logs\~/.config/<App>/logs/
exeApp-ExecutableApp-ExecutableApp-Executable
JavaScript
import { app } from 'electron';

const userData = app.getPath('userData');
const downloads = app.getPath('downloads');
const logsDir = app.getPath('logs');

userData — der App-spezifische Speicher

userData ist der wichtigste Pfad: hier speichert deine App alles, was zur App gehört (Settings, SQLite-DB, Cache, Logs).

JavaScript
import path from 'node:path';
import { app } from 'electron';
import fs from 'node:fs/promises';

const settingsPath = path.join(app.getPath('userData'), 'settings.json');
await fs.writeFile(settingsPath, JSON.stringify(settings));

userData wird automatisch nach productName aus package.json benannt — My App → Ordner My App. Setze productName sorgfältig (mit Spaces, sauber capitalisiert), denn er taucht in Pfaden und Menüs auf.

Mit app.setPath('userData', '/custom/path') (vor app.ready) kann man's überschreiben — selten nötig.

path-Modul — plattformsicheres Joinen

JavaScript
import path from 'node:path';

// path.join — gibt richtigen Trenner pro Plattform
const file = path.join(app.getPath('userData'), 'cache', 'image.png');
// macOS/Linux: '/Users/anna/Library/Application Support/MyApp/cache/image.png'
// Windows:     'C:\\Users\\anna\\AppData\\Roaming\\MyApp\\cache\\image.png'

// path.resolve — absolut, inkl. cwd
const abs = path.resolve('cache/image.png');

// path.dirname / basename / extname
path.dirname('/foo/bar/baz.txt');  // '/foo/bar'
path.basename('/foo/bar/baz.txt'); // 'baz.txt'
path.extname('/foo/bar/baz.txt');  // '.txt'

// Plattform-spezifische Constants
path.sep;     // '/' auf macOS/Linux, '\\' auf Windows
path.delimiter; // ':' vs. ';'

Niemals Pfade per String-Konkatenation bauen (a + '/' + b) — auf Windows brichst du das. path.join ist der einzige sichere Weg.

Pfad-Traversal verhindern

Wenn der Renderer einen Pfad mitschickt, kann er Path-Traversal versuchen (../../etc/passwd). Schutz:

JavaScript
const ROOT = app.getPath('userData');

function safeUserDataPath(input) {
    // resolve + Whitelist
    const target = path.resolve(ROOT, input);
    if (!target.startsWith(ROOT + path.sep)) {
        throw new Error('Path traversal detected');
    }
    return target;
}

ipcMain.handle('files:read', async (_event, name) => {
    const p = safeUserDataPath(name);
    return fs.readFile(p, 'utf-8');
});

path.resolve löst .. und . auf — danach prüfen, dass der Pfad noch im erlaubten Bereich liegt. Mit path.sep als Suffix verhindert false-positives wie /data/userdata matched /data/userda.

Pfade aus dem App-Bundle

In Production sind Assets im App-Bundle gepackt — typisch in app.asar. Pfade dorthin baust du relativ zur __dirname oder app.getAppPath():

JavaScript
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));

// Asset im selben Ordner wie main.js
const iconPath = path.join(__dirname, 'assets', 'icon.png');

// App-Root (wo package.json liegt)
const appRoot = app.getAppPath();
const templatePath = path.join(appRoot, 'templates', 'invoice.html');

app.getAppPath() liefert den App-Root — bei electron-builder typisch <install>/resources/app.asar/. In Dev: das Projekt-Verzeichnis.

Spezialpfad: process.resourcesPath

Für Assets, die NICHT in der app.asar liegen (z. B. Native Binaries, große Files):

JavaScript
// electron-builder „extraResources" liegen in resources/
const ffmpegPath = path.join(process.resourcesPath, 'ffmpeg');

In electron-builder.yml:

YAML
extraResources:
  - from: 'binaries/ffmpeg'
    to: 'ffmpeg'

Das landet in resources/ffmpeg — über process.resourcesPath zugänglich.

Besonderheiten

NIEMALS Pfade per String-Konkatenation bauen.

path + '/' + name bricht auf Windows. Immer path.join(path, name). Bei jeder Code-Review der Klassiker, der gefunden werden muss.

userData ist DER Pfad für App-Daten.

Settings, SQLite-Datenbank, Cache, Logs — alles in app.getPath('userData'). NICHT in __dirname (read-only nach Packaging) und NICHT in app.getPath('home') (User würde Müll sehen).

productName bestimmt den userData-Ordner.

package.json productName: 'My App' → Ordner heißt My App. Bei späterer Umbenennung verlieren User ihre Settings, weil der neue Name einen neuen Ordner anlegt. Migration vorher planen.

Path-Traversal-Schutz mit resolve + startsWith.

path.resolve(root, userInput) löst .. auf. Danach startsWith(root + path.sep) prüft, dass das Resultat im erlaubten Bereich liegt. Sonst: kompromittierter Renderer kann beliebige Dateien lesen.

app.getPath braucht app.ready.

Vor app.whenReady().then(...) darf getPath für manche Keys nicht aufgerufen werden — nur die System-Pfade (home, temp) sind früh verfügbar, userData etc. braucht ggf. die Init.

process.resourcesPath für Native Binaries.

Wenn deine App externe Tools (ffmpeg, eigene Binaries) braucht: in extraResources packen, mit process.resourcesPath zugreifen. NICHT in app.asar — dort sind sie als Read-Only und nicht direkt ausführbar.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Native APIs & Filesystem

Zur Übersicht