Deno 2.9 shipped a deno desktop command on June 25, 2026, and it does something Electron never managed to make simple: it turns a plain TypeScript file into a native, cross-platform desktop app with no Chromium bundle and no Rust toolchain. Here is the whole workflow, step by step.
The pitch is straightforward. deno desktop main.ts compiles your code, the Deno runtime, and a rendering backend into one redistributable binary per platform. By default it renders through the operating system's own WebView, so the output is a fraction of an Electron app's size. In one macOS test, the WebView build came in at 68.5 MB versus 308.9 MB for the same app bundled with Chromium.
One command, one binary, no
node_modules, no packaging config. That is the entire value proposition — and for once it holds up.
Note up front: deno desktop is experimental in 2.9. The surface below is stabilizing, but expect flags and platform behavior to shift before it settles.
1. Upgrade to Deno 2.9
The command only exists in 2.9.0 and later, so start there:
deno upgrade
deno --versionIf you are installing fresh, curl -fsSL https://deno.land/install.sh | sh gets you the latest. Anything older than 2.9 will not recognize the desktop subcommand.
2. Write a single-file app
You do not need a framework. A desktop entrypoint is just a Deno server — Deno.serve() inside a desktop build automatically binds to the port the WebView opens, so you serve HTML and the window loads it:
// main.ts
Deno.serve((_req) =>
new Response(
`<!doctype html>
<html>
<body style="font-family: system-ui; padding: 2rem">
<h1>Hello from Deno Desktop</h1>
<p>No Electron. No Rust. Just TypeScript.</p>
</body>
</html>`,
{ headers: { "content-type": "text/html" } },
),
);3. Run it live with hot reload
Before you build anything distributable, iterate with --hmr. It launches the app with hot module replacement so edits to your UI show up without a rebuild:
deno desktop --hmr main.tsAttach a debugger to both the runtime and the renderer with --inspect, --inspect-wait, or --inspect-brk when you need to step through code.
4. Choose a backend
--backend selects the rendering engine, and this is the decision that drives your app's size and consistency:
- `webview` (default) — delegates to the OS engine: WebKit on macOS and Linux, WebView2 on Windows. Tiny output, but you inherit each platform's quirks.
- `cef` — bundles a full copy of Chromium. Hundreds of megabytes larger, but pixel-identical rendering everywhere.
deno desktop --backend webview main.tsPick webview unless you have a hard requirement for identical rendering across every OS. A third raw backend exists for advanced cases and is selected through deno.json.
5. Grant permissions and build a distributable
deno desktop takes the same permission flags as deno run, and whatever you grant at build time is baked into the binary. Grant narrowly:
deno desktop --allow-read --allow-net --output ./dist/MyApp.dmg main.tsThe file extension you pass to --output decides the format: .app or .dmg on macOS, .exe or an .msi installer on Windows, and .AppImage, .deb, or .rpm on Linux. Set the window icon with --icon (.icns/.png on macOS, .ico on Windows), and trim the bundle with --include / --exclude.
6. Cross-compile for every platform
You do not need a Mac to ship a .dmg or Windows to ship an .exe. One --target builds for another platform; --all-targets builds for all of them at once, no platform-specific toolchain required:
deno desktop --target aarch64-apple-darwin main.ts
deno desktop --all-targets main.tsThat single line is the part CI pipelines have wanted from Electron for a decade.
7. Move settings into deno.json
Once the flags pile up, park them in a desktop block so every build is just deno desktop:
{
"desktop": {
"app": {
"name": "MyApp",
"identifier": "com.example.myapp",
"icons": { "macos": "./icons/icon.icns", "windows": "./icons/icon.ico" }
},
"backend": "webview",
"release": { "baseUrl": "https://updates.example.com" }
}
}The release.baseUrl field wires up auto-update, and an errorReporting.url field forwards crash reports. If your project already uses Next.js, Astro, Fresh, TanStack Start, or Vite SSR, a bare deno desktop detects the framework and builds it with no code changes at all.
That is the full loop: upgrade, write a file, --hmr to iterate, choose a backend, build a signed artifact per OS, and cross-compile the rest. For a first experimental release, deno desktop covers more of the real packaging problem than most 1.0 tools ever do.