Learn where an Electron app should store its data with app.getPath('userData'), what each special folder means on Windows, macOS and Linux, and what to avoid.

Bishal Biswas
Self Learner
The question every app must answer
Your app needs to save things: notes, settings, a small database, a log file. The first question is not “how do I write a file?” (you learned that in Reading and Writing Files from Your App) but “where should this file go?”
Every operating system has rules about that, and they are different on Windows, macOS and Linux. Luckily, Electron knows all of them. You ask app.getPath('userData'), and Electron answers with the right folder for the current system.
Think of it like staying in a hotel. You do not write on the walls of your room (that is the app’s own folder). You put your things in your own locker (the user data folder), and you can ask reception (app.getPath) where your locker is, in any hotel in the world.
Step 1: ask Electron where things are
Add this to main.js and run your app:
// main.js
const { app } = require('electron');
app.whenReady().then(() => {
console.log('userData :', app.getPath('userData'));
console.log('documents:', app.getPath('documents'));
console.log('temp :', app.getPath('temp'));
});
On Windows, for an app named “My Notes”, you see something like:
userData : C:\Users\asha\AppData\Roaming\My Notes
documents: C:\Users\asha\Documents
temp : C:\Users\asha\AppData\Local\Temp
On macOS:
userData : /Users/asha/Library/Application Support/My Notes
documents: /Users/asha/Documents
temp : /var/folders/xy/abc123/T/
On Linux:
userData : /home/asha/.config/My Notes
documents: /home/asha/Documents
temp : /tmp
Same code, three correct answers. That is why you should never type a path like C:\Users\... into your code.
The special folders
app.getPath(name) accepts these names. The ones you will use most are at the top:
| Name | What it is | Use it for |
|---|---|---|
userData |
Your app’s own private folder | Settings, notes, databases, caches you manage |
documents |
The user’s Documents folder | A sensible default in save dialogs |
downloads |
The user’s Downloads folder | Files the user downloads through your app |
desktop |
The user’s desktop | Rarely: only if the user asks |
pictures, music, videos |
The user’s media folders | Default folders for media apps |
temp |
The system’s temporary folder | Short-lived files; the system may delete them |
home |
The user’s home folder | Rarely needed directly |
appData |
The folder that holds all apps’ data folders | Rarely needed; userData sits inside it |
logs |
Your app’s log folder | Log files |
sessionData |
Where Chromium stores cookies and caches | Defaults to the same folder as userData |
exe |
The path of the running app’s executable | Advanced uses only |
If a name is not valid, or the folder cannot be found, getPath() throws an error.
Where userData lives on each system
| System | Base folder (appData) |
Your userData |
|---|---|---|
| Windows | %APPDATA%, usually C:\Users\<you>\AppData\Roaming |
...\Roaming\My Notes |
| macOS | ~/Library/Application Support |
~/Library/Application Support/My Notes |
| Linux | $XDG_CONFIG_HOME, or ~/.config if that is not set |
~/.config/My Notes |
~ means the user’s home folder. %APPDATA% and $XDG_CONFIG_HOME are environment variables: named settings the operating system provides to every program.
On macOS, the Library folder is hidden in Finder by default. Press Cmd + Shift + G in Finder and type the path to get there. On Linux, folders starting with a dot, like .config, are hidden; press Ctrl + H in most file managers to show them.
Where the folder name comes from
The last part of the path, “My Notes”, is your app name. Electron takes it from package.json:
{
"name": "my-notes",
"productName": "My Notes",
"version": "1.0.0",
"main": "main.js"
}
If productName exists, Electron uses it. Otherwise it uses name. You can check with app.getName().
Warning: Choose your app’s name early and keep it. If you rename
productNamelater,userDatapoints to a new, empty folder, and your users’ existing data seems to vanish. (It is still in the old folder, but your app no longer looks there.)
Note: On Windows, the name must not contain characters that are not allowed in folder names, such as
<,>,:,",/,\,|,?or*. Otherwise Electron cannot create the folder and your app may crash at startup.
What is already inside userData
Open the folder after running your app once and you will find things you did not create: folders with names like Cache, Local Storage or GPUCache. That is Chromium, the browser engine inside Electron, storing its own data there, because sessionData defaults to the same folder.
So be a good neighbour:
- Put your own files in clearly named files or a subfolder, such as
userData/notes/oruserData/settings.json. - Never delete the whole
userDatafolder from code to “reset” your app. Delete only your own files.
Create your subfolder before writing to it. recursive: true means “create any missing parent folders too, and do not complain if it already exists”:
// main.js
const fs = require('node:fs/promises');
const path = require('node:path');
const notesDir = path.join(app.getPath('userData'), 'notes');
await fs.mkdir(notesDir, { recursive: true }); // inside an async function
Always build paths with path.join(). It uses \ on Windows and / on macOS and Linux for you.
Where not to save
Not next to your code
__dirname (the folder of the current file) and app.getAppPath() (your app’s root folder) are for reading files you ship, like templates or images. Do not write there:
- During development it seems to work, which makes this mistake sneaky.
- After packaging, your code is inside an ASAR archive (a single read-only file that holds your app’s code), or in a protected folder such as
C:\Program Filesor/Applications. Writing fails, or every update wipes the data.
You will learn more about ASAR in Packaging and Making Installers with Forge.
Note: If you ever switch
main.jsto ES modules ("type": "module"inpackage.json, or a.mjsfile),__dirnamedoes not exist. Useimport.meta.dirnameinstead, which gives the same folder.
Not in temp for anything important
The temporary folder is for files you can lose, such as a file prepared for a drag-out. The operating system or cleanup tools may empty it at any time.
Not in Documents without asking
Documents belongs to the user. Save there only when the user chooses it in a save dialog. Your app’s internal files go in userData.
Showing the data folder to the user
A “Show data folder” button is handy for support (“please send me your settings file”). The page asks main, and main opens the folder with shell:
// main.js
const { app, ipcMain, shell } = require('electron');
ipcMain.handle('paths:get', () => ({
userData: app.getPath('userData'),
logs: app.getPath('logs'),
}));
ipcMain.handle('paths:open-user-data', async () => {
const error = await shell.openPath(app.getPath('userData'));
return error === '';
});
// preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('appPaths', {
get: () => ipcRenderer.invoke('paths:get'),
openUserData: () => ipcRenderer.invoke('paths:open-user-data'),
});
// renderer.js
const paths = await window.appPaths.get();
document.getElementById('where').textContent = `Your data is stored in ${paths.userData}`;
document.getElementById('show-folder').addEventListener('click', () => {
window.appPaths.openUserData();
});
Main returns the paths only for display. The page cannot use them to read anything, because no handler accepts a path.
Tip:
renderer.jsis a classic script, so top-levelawaitis not allowed there. Wrap the code inasync function start() { ... }and callstart(), or use.then().
Logs
app.getPath('logs') gives you a folder for log files. On macOS it is ~/Library/Logs/<App Name>. On Windows and Linux it is a logs folder inside userData. The folder is created for you the first time you ask for it.
Changing a path with setPath
You can move any of these folders with app.setPath(name, newPath). A common use is keeping development data separate from your real app data, so testing never damages your own notes:
// main.js (near the top, before anything uses userData)
const { app } = require('electron');
const path = require('node:path');
if (!app.isPackaged) {
app.setPath('userData', path.join(app.getPath('appData'), 'My Notes (development)'));
}
app.isPackaged is false while you run with npm start, and true in the installed app.
Call setPath() early, before the app is ready. Chromium decides where to keep cookies and caches (sessionData) during startup, so changing userData later is too late for those.
Common mistakes
Writing files next to main.js
It works on your machine and breaks after packaging. Use app.getPath('userData').
Hard-coding paths
'C:\\Users\\asha\\AppData\\Roaming\\My Notes' only works for Asha on Windows. Always ask app.getPath() and join with path.join().
Renaming the app after release
A new productName means a new userData folder. If you must rename, copy the old folder’s files on first start of the new version.
Forgetting to create subfolders
fs.writeFile() fails with ENOENT if the folder does not exist. Call fs.mkdir(dir, { recursive: true }) first.
Quick recap
app.getPath(name)returns the right folder on Windows, macOS and Linux. Never hard-code paths.userDatais your app’s private folder:%APPDATA%\<App Name>on Windows,~/Library/Application Support/<App Name>on macOS,~/.config/<App Name>on Linux.- The folder name comes from
productName(orname) inpackage.json. Do not change it after release. - Chromium also stores data in
userData, so keep your files clearly named or in a subfolder. __dirnameandapp.getAppPath()are read-only places for files you ship.tempis for files you can lose. Documents is for files the user chooses to save there.app.setPath()moves a folder; call it early, for example to separate development data.
// main.js: finding the right folders and showing them to the user
const { app, BrowserWindow, ipcMain, shell } = require('electron');
const path = require('node:path');
const fs = require('node:fs/promises');
// --- 1. Keep development data apart from the real app's data (do this early)
if (!app.isPackaged) {
app.setPath('userData', path.join(app.getPath('appData'), `${app.getName()} (development)`));
}
// --- 2. Our own subfolder inside userData, created on startup
const notesDir = () => path.join(app.getPath('userData'), 'notes');
async function prepareFolders() {
await fs.mkdir(notesDir(), { recursive: true });
console.log('userData :', app.getPath('userData'));
console.log('notes :', notesDir());
console.log('documents:', app.getPath('documents'));
console.log('logs :', app.getPath('logs'));
console.log('temp :', app.getPath('temp'));
}
// --- 3. The page may see the paths (for display) and ask to open the folder
ipcMain.handle('paths:get', () => ({
userData: app.getPath('userData'),
notes: notesDir(),
logs: app.getPath('logs'),
}));
ipcMain.handle('paths:open-user-data', async () => {
const error = await shell.openPath(app.getPath('userData'));
return error === '';
});
// --- 4. Window and lifecycle
function createWindow() {
const win = new BrowserWindow({
width: 900,
height: 600,
webPreferences: { preload: path.join(__dirname, 'preload.js') },
});
win.loadFile('index.html'); // __dirname is fine for READING files we ship
}
app.whenReady().then(async () => {
await prepareFolders();
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit();
});
Was this explanation helpful?
Your feedback helps us improve the content for everyone.
Thank you for your feedback!
We appreciate you helping us improve.
What could we improve? (Optional)
Read aloud
Ready