Getting Started
Create your app
Use degit to copy the template without the git history of this repository:
npx degit nick-keller/electron-appstack/template my-app
cd my-appInstall dependencies
Requires Node.js 22.12 or later.
npm installThe postinstall script downloads the Electron binary for your platform and
prepares native modules (SQLite) for Electron.
The template ships a package-lock.json and its GitHub workflows use npm ci.
To use another package manager, delete the lockfile and update the workflows.
Start the app
npm run devAn Electron window opens with a small todo list (stored in SQLite) and a settings page (theme and language). See Development for how hot reload works.
Make it yours
- Rename the app:
nameandproductNameinpackage.json,appIdandpublishinelectron-builder.ts,<title>insrc/renderer/index.html(see What each name controls) - Replace the icon and DMG background in
build/(see Icons and Images) - Set up the release secrets (see Releasing)
What each name controls
The app name is set in several places, and each one shows up somewhere different:
| Setting | File | Used for |
|---|---|---|
productName | package.json | The name people see: app.getName(), the macOS app menu and Dock, the .app bundle and .exe names, the Windows Start Menu shortcut and “Installed apps” entry, the Linux desktop entry, installer file names, and the data folder |
name | package.json | The npm package name, used when productName is missing. On Linux, also the executable and .deb package name. Lowercase, no spaces |
<title> | src/renderer/index.html | The window title: the title bar, the Windows and Linux taskbar button, the macOS Window menu and Mission Control |
appId | electron-builder.ts | The unique ID of the app: the macOS bundle identifier (used for code signing, notarization and permissions) and the Windows app ID (used for taskbar grouping, notifications and the uninstaller entry). Reverse domain, e.g. com.acme.notes |
publish.owner, publish.repo | electron-builder.ts | The GitHub repository where releases are uploaded and where the auto updater looks for new versions |
copyright | electron-builder.ts | The macOS Get Info window and the Windows file properties |
description, author | package.json | Package metadata, shown by Linux package managers |
appName | src/shared/locales/*/common.json | A translated name for your own UI, used by the license activation window |
The window title only comes from <title>, not from productName. Keep them
in sync, or set the title from code with document.title in the renderer.
In development, the app runs inside the Electron binary: the macOS menu bar,
Dock and app switcher show “Electron” with the Electron icon. Run
npm run dist:dir to see the real name and icon.
The data folder
The database and the config file are stored in app.getPath('userData'), a
folder named after productName:
| Platform | Folder |
|---|---|
| macOS | ~/Library/Application Support/<productName> |
| Windows | %APPDATA%\<productName> |
| Linux | ~/.config/<productName> |
Choose productName and appId before your first release. Changing
productName later moves the data folder, so existing users start with an
empty database and default settings. Changing appId makes the new version a
different app to the operating system: on Windows it installs next to the old
one instead of replacing it, and on macOS users are asked again for
permissions such as notifications.
Remove the todo example
The todo list shows how the database, tRPC and TanStack Query fit together. To start from a clean slate, give this prompt to your coding agent:
Remove the todo example from this app so I can start from a clean slate:
- Delete `src/main/trpc/routers/todos.ts` and remove `todosRouter` from `src/main/trpc/router.ts`.
- In `src/main/db/schema.ts`, delete the `todos` table and the imports it used. Keep the comment.
- Delete `drizzle/0000_init.sql` and `drizzle/meta/0000_snapshot.json`, and set `entries` to `[]`
in `drizzle/meta/_journal.json`. My first table will get a fresh migration with `npm run db:generate`.
- Replace `src/renderer/src/routes/index.tsx` with an empty home page: keep the route, render a
`Card` with `t('home.title')` and nothing else. Remove unused imports.
- In every locale in `src/shared/locales/`, set `home.title` to "Home" (translated) and delete the
other `home.*` keys.
- In `AGENTS.md` and `src/renderer/src/lib/trpc.ts`, replace the `trpc.todos.*` examples with a
generic `trpc.items.*`.
- Run `npm run db:reset`, `npm run typecheck` and `npm run check:fix`, and fix any errors.Scripts
| Script | Description |
|---|---|
npm run dev | Start the app in development mode, with hot reload |
npm run build | Type check and build the app into out/ |
npm run preview | Run the production build locally |
npm run typecheck | Type check the main, preload and renderer code |
npm run check | Lint and check formatting with Biome |
npm run check:fix | Fix lint and formatting issues |
npm run lint | Lint only |
npm run format | Format only |
npm run db:generate | Generate a SQL migration from the database schema |
npm run db:reset | Delete the development database |
npm run dist | Build installers for the current platform into dist/ |
npm run dist:dir | Build an unpacked app into dist/, faster, for testing |
npm run changeset | Describe a change for the next release |