There are no prebuilt downloads of the desktop app at the moment, so installing it means building it from the repository. The build is driven by electron-vite, and packaging into an installer by electron-builder; both are already in devDependencies, so there’s nothing to install beyond npm install.
What you’ll need#
- Node.js 22.12 or later, with npm.
- Hugo on your
PATH, if you want the live preview. The app runs without it; you just won’t get the Hugo server. - A Hugo site with
admin/site.yamlandadmin/cms.yamlin it. If yours doesn’t have them yet, running the web dev server against it once (HUGO_ROOT=/path/to/site npm run dev, see Installation) writes both.
Running from source#
Clone and install as for the web version, then start Electron in development mode:
git clone https://github.com/dmyates/emetcms.git
cd emetcms
npm install
HUGO_ROOT=/path/to/your/hugo/site npm run dev:desktopdev:desktop runs electron-vite dev, which compiles the main process and preload script, starts a Vite dev server for the renderer and opens an Electron window pointed at it. Changes to the renderer hot-reload like they do in the browser; changes to src/main/ or src/preload/ aren’t picked up until you stop and rerun the command.
With HUGO_ROOT set, the app opens that site as soon as the window appears. Without it you get the splash screen and pick a folder yourself. You should see your site’s sections in the sidebar either way.
InfoBoth config files are required. The desktop app readsadmin/site.yamlandadmin/cms.yamlfrom the folder you open and currently fails with “Failed to load config from admin/cms.yaml” if the second one is missing, even thoughcms.yamlis optional everywhere else. An emptycms.yamlis enough to get past this.
Building#
npm run build:desktopThis is electron-vite build, configured in electron.vite.config.ts, and produces three outputs under out/: out/main/ for the main process, out/preload/ for the bridge script and out/renderer/ for the React app. The main and preload builds use externalizeDepsPlugin() so Node built-ins and Electron itself aren’t bundled.
The renderer build is close to the web build but not the same thing. It has its own, simpler chunking (an editor chunk for Tiptap and a codemirror chunk for CodeMirror), it doesn’t inject the Content Security Policy that the web build does and it only includes the local-filesystem Vite plugins when HUGO_ROOT is set. Without HUGO_ROOT the renderer relies entirely on IPC to read the site, which is what you want for a packaged app.
Packaging an installer#
npm run package:desktopThis runs the build above and then electron-builder, which reads its configuration from the build block of package.json. The output lands in build/ (not dist/, which is the web build), as:
| Platform | Target |
|---|---|
| macOS | .dmg |
| Windows | NSIS installer (.exe) |
| Linux | AppImage |
electron-builder packages for the platform you’re running on. To target another one explicitly, run it directly, i.e. npx electron-builder --mac, --win or --linux. Note that macOS builds need a Mac, and Windows builds on Linux need Wine; the electron-builder documentation has the details. The app ID is com.emet.cms and the product name is “Emet CMS”.
The desktop-specific files#
Most of the source is shared with the web version. The parts that only exist for Electron are:
src/main/
index.ts window creation, menu, IPC registration, Hugo server lifecycle
ipc.ts filesystem, dialog, shell and Hugo IPC handlers
hugo-server.ts the HugoServerManager class
menu.ts the application menu
path-validation.ts keeps IPC file access inside the opened site
src/preload/
index.ts exposes window.electronAPI via contextBridge
src/shared/
ipc-types.ts channel names and types shared by all threeOn the renderer side, src/content/backends/electron.ts is the content backend that routes reads and writes through window.electronAPI, and src/hooks/useHugoServer.ts is the hook behind the server log panel.
If the window is blank#
Open the developer tools with View > Toggle Developer Tools and look at the console. The usual causes are a HUGO_ROOT that doesn’t exist, a failed renderer build (check the terminal running electron-vite) or dependencies that need a fresh npm install. If the app opens but the preview pane is empty, that’s the Hugo server rather than the app; see Local Hugo server.
Where next?#
- Local Hugo server for how the preview works and what to do when it doesn’t.
- Desktop app for what the app does and doesn’t do compared to the web version.