Project Showcases · 8 min read
Sketchora: An Unofficial Desktop App for Excalidraw (Electron, Windows, macOS, Linux)
Sketchora wraps Excalidraw in an Electron shell so the whiteboard runs as a native desktop app with real file handling, double-click .excalidraw opening, and recent-files integration on Windows, macOS, and Linux.

TL;DR
Sketchora is an unofficial cross-platform desktop app for Excalidraw, the open-source hand-drawn style whiteboard. It wraps the Excalidraw web app in an Electron shell so you get a native application instead of a browser tab: real File > New / Open / Save / Save As actions that write .excalidraw files to disk, double-click opening of .excalidraw files in your OS file manager, recent-files integration with the operating system, a single-instance window, and one codebase that builds installers for Windows, macOS, and Linux. Sketchora is not affiliated with the Excalidraw team; it is an independent packaging of their editor, licensed MIT like upstream, and it adds no new drawing features of its own. Downloads for all three platforms are published automatically on every tagged release through GitHub Actions. If you want Excalidraw as a real desktop document you can open, edit, and save like any other file, Sketchora is that wrapper.
The Problem
Excalidraw is excellent, but it lives in a browser tab. That is fine for quick sketches, but it breaks down the moment you want a whiteboard to behave like a real document. A browser tab has no OS-level file association, no recent-files list in your dock or taskbar, and no way to double-click a .excalidraw file on your desktop and have it open in the editor. Saving and opening also depend on what your browser supports, so you can end up downloading JSON files and re-opening them by hand, or keeping a tab pinned forever.
I wanted the full Excalidraw editing experience, shapes, freehand drawing, text, arrows, libraries, dark mode, but living as a first-class desktop document. The goal was simple: a native app where a .excalidraw file is just a file on disk, opened and saved like any other document, while reusing all of Excalidraw's rendering and editing code instead of reimplementing a whiteboard.
What I Built
A thin Electron wrapper around the existing Excalidraw app:
- Full Excalidraw editor: the editor itself, including shape libraries and dark mode, runs unchanged inside the Electron window. Features that depend on Excalidraw's hosted backend services, like real-time collaboration and share links, are not part of what Sketchora ships.
- Native file handling:
File > New / Open / Save / Save As, writing real.excalidrawfiles to disk. - File association: double-clicking a
.excalidrawfile opens it directly in Sketchora. - Recent files: saved paths are registered with the OS recent-documents list.
- Single instance: a second launch focuses the existing window instead of opening a second copy.
- Cross-platform builds: Windows (nsis installer), macOS (dmg), and Linux (AppImage and deb) from one codebase.
- Unsaved-change awareness: the window title gets a
*prefix when the document is dirty.
Architecture
Sketchora does not fork Excalidraw's editor. It reuses the upstream excalidraw-app build output and adds an Electron main process on top.
- Renderer: the standard Excalidraw web app, loaded from
http://localhost:3001in development and fromexcalidraw-app/build/index.htmlin a packaged build. - Main process:
electron/main.cjs, written in CommonJS because Electron's main process loads it directly. It owns theBrowserWindow, the application menu, file dialogs, and IPC handlers. - Preload:
electron/preload.cjsbridges the renderer and main process withcontextIsolationandsandboxenabled, so the renderer never gets direct Node access. - Packaging:
electron-builderwithappIdcom.sketchora.app, producingrelease/output.
The Electron layer is intentionally small. The main process only handles OS integration; all drawing logic stays in Excalidraw.
Native file handling
The main process exposes four IPC handlers: file:open, file:save, file:save-as, and file:get-current-path. file:open shows a native open dialog filtered to .excalidraw files, reads the file, validates that it parses as JSON, and returns { filePath, content } to the renderer. file:save writes to the current path if one exists, otherwise falls through to file:save-as, which shows a save dialog, appends .excalidraw if the user did not type it, and writes the file.
Every successful read or write calls app.addRecentDocument(filePath), so the file shows up in the OS recent-files list. The window title is updated through a window:set-title IPC message carrying { filePath, dirty }, producing Untitled - Sketchora or *drawing.excalidraw - Sketchora.
File association and double-click opening
The build section of package.json declares a file association for the excalidraw extension:
"fileAssociations": [
{
"ext": "excalidraw",
"name": "Sketchora Drawing",
"role": "Editor",
"mimeType": "application/vnd.excalidraw+json"
}
]
At runtime, two OS entry points are handled. On Windows and Linux, a second instance receives the file path through the second-instance event and forwards it with file:open-result. On macOS, the open-file event fires, and if the window is not ready yet the path is stored in pendingOpenPath and opened after did-finish-load. The renderer then loads the returned JSON into the Excalidraw scene.
Single instance lock
app.requestSingleInstanceLock() prevents multiple windows. If a second launch happens, the first instance restores and focuses its window and opens the passed file if there is one. This matters because double-clicking several .excalidraw files in a row should reuse one editor, not spawn five.
Hard Parts / What I Learned
Unsigned builds and OS trust warnings. Sketchora is not code-signed, so Windows SmartScreen shows "Windows protected your PC" and macOS Gatekeeper blocks the first launch. The workarounds are documented (More info -> Run anyway on Windows; right-click -> Open on macOS), but this is the single biggest friction for new users. Code signing is the next thing to fix; until then the install notes have to be loud and explicit.
Dev versus packaged loading. In development the window loads http://localhost:3001 and waits for the Vite dev server; in production it loads a local file from the packaged excalidraw-app/build directory. Getting electron:dev to wait for the dev server with wait-on before launching Electron, and to keep both processes alive with concurrently, took more tuning than expected. The packaged path also has to resolve correctly under asar, which is why assets are referenced relative to __dirname.
File path state is easy to lose. The current file path lives in the main process, not the renderer, because the renderer is Excalidraw's own app and does not know about OS files. Bridging that through IPC without leaking Node into the renderer, while keeping the dirty flag in sync with Excalidraw's own change events, was the fiddliest part of the integration.
Honest scope. Sketchora is a packaging layer, not a new editor. Being explicit about that in the README and credits avoids implying affiliation with the Excalidraw team, which matters for trust and for not overpromising.
How does Sketchora handle saving?
Saving is path-aware. If a document has never been saved, Save behaves like Save As and prompts for a location. If it has a path, Save writes in place without a dialog. Save As always prompts and lets you pick a new name or location. The .excalidraw extension is enforced: if you type a name without it, Sketchora appends it. After every save the path becomes the current file and is added to the OS recent-documents list.
How does Sketchora open a file from the OS?
Double-clicking a .excalidraw file in Finder, Explorer, or a Linux file manager launches Sketchora (or focuses the running instance) and sends the path through open-file on macOS or second-instance on Windows and Linux. The main process reads and validates the file, then sends file:open-result to the renderer, which loads the JSON into the Excalidraw scene. If the app is still starting, the path is queued in pendingOpenPath and replayed after the page finishes loading.
What platforms are supported and how do I install?
| OS | File | Notes |
|---|---|---|
| Windows | Sketchora-Setup-x.x.x.exe |
nsis installer; SmartScreen may warn because the build is not code-signed |
| macOS | Sketchora-x.x.x.dmg |
Universal build; Gatekeeper blocks first launch, right-click Open to allow |
| Linux | Sketchora-x.x.x.AppImage or .deb |
AppImage needs chmod +x and runs without install |
Install commands for Linux:
chmod +x Sketchora-x.x.x-linux-x86_64.AppImage
./Sketchora-x.x.x-linux-x86_64.AppImage
sudo dpkg -i Sketchora-x.x.x-linux-amd64.deb
sudo apt-get install -f
Every tagged release is built automatically for all three platforms by the GitHub Actions workflow in .github/workflows/release.yml, so the installers on the Releases page are always fresh off the latest tag.
What does Sketchora not do yet?
Stated plainly:
- It is not code-signed, so OS trust warnings appear on first install.
- There is no auto-updater; updates are manual downloads from Releases.
- There is no cloud sync or collaboration; files are local
.excalidrawdocuments. - The editor is Excalidraw as-is; Sketchora does not add new drawing features beyond the wrapper.
- Linux builds are AppImage and deb only; no rpm or flatpak yet.
Development
Requires Node.js 18+ and Yarn.
git clone https://github.com/MalahimHaseeb/Sketchora.git
cd Sketchora
yarn install
yarn build:packages
Run the web dev server:
yarn start
In a separate terminal, run the Electron shell pointed at it:
yarn electron:dev
Checks before opening a pull request:
yarn prettier --check electron excalidraw-app/App.tsx excalidraw-app/types/electron.d.ts
yarn test:typecheck
yarn test:code
Building a release yourself:
yarn build:packages
yarn build:app
yarn desktop:linux
Swap desktop:linux for desktop:win or desktop:mac depending on the target. Packaged output lands in release/.
Credits and license
Sketchora is built entirely on top of Excalidraw, created and maintained by the Excalidraw team and community. All whiteboard functionality, the hand-drawn rendering style, and the core editor come from their work. Sketchora is an independent, unofficial packaging of that project and is not affiliated with or endorsed by the Excalidraw team.
- Original project: github.com/excalidraw/excalidraw
- Official web app: excalidraw.com
- License: MIT, same as upstream Excalidraw. See LICENSE.
Try It / Links
- Download: github.com/MalahimHaseeb/Sketchora/releases/latest
- Source: github.com/MalahimHaseeb/Sketchora
- Report issues: github.com/MalahimHaseeb/Sketchora/issues/new
- Stack: Electron, React, TypeScript, Excalidraw, electron-builder, Node.js
Related Reading
- mini-redis: Building Redis From Scratch: another small, self-contained project where I rebuilt a tool to understand it.
- AI Code Reviewer: A Self-Hostable GitHub PR Reviewer: a GitHub-integrated tool with a similar "wrap an existing ecosystem" philosophy.