This is the fourth post in my "My Open-Source Projects" series, where I go through some of the OSS projects I started or maintain and tell you the story behind them. So far we've had AngleSharp, MAGES, and Piral. This time: Electron.NET - and it's a bit different, because I didn't start this one. I kind of wandered into the maintainer seat and never left.
What Is Electron.NET, Actually?
Electron.NET lets you build cross-platform desktop apps with .NET - Razor Pages, MVC, Blazor, or even a plain console app - with Electron doing the window-and-pixels part.
If you've never touched Electron: it's the thing behind a scary number of desktop apps you use every day. It bundles Chromium and Node.js, gives you a window, and lets you build the UI with HTML, CSS, and JavaScript. Yes, that means you ship a browser with your app. No, I'm not going to apologize for that, and neither should you - it works, on Windows, macOS, and Linux, with one codebase.
The catch, if you're a .NET developer: Electron speaks JavaScript. Electron.NET is the bridge that lets you stay in C#.
var window = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions { Width = 1024, Height = 768 });
That's a native desktop window, created from C#. No main.js in sight.
Not My Project (Originally)
The other projects in this series all started with me having an idea and then regretting it slowly. Electron.NET is different: it was founded by Gregor Biswanger and Robert Mühsig.
Their goal will sound familiar if you've read the AngleSharp post: they wanted a cross-platform GUI framework with C# as the driver. The difference is that they had a big advantage I didn't have back then - they could look at what Electron had already accomplished for Node.js and JavaScript. Robert wrote up the origin story in a blog post from October 2017: Gregor asked if it was possible to build desktop apps with ASP.NET Core, they agreed that Electron-as-is plus an embedded ASP.NET Core was the best bet, and then Robert went to bed while Gregor stayed up and built a working prototype. That's the kind of division of labor I can respect.
Their take was refreshingly simple: let's use an ASP.NET Core server as the HTML/CSS/JS producer, and wrap the Electron API into an interface that is callable from that server.
How It Works: Three Layers, Two Bridges
Once you accept that idea, you end up with three layers:
- Rendering layer (HTML / CSS / JS) - the same as always. It talks to Electron through the standard globals and message passing.
- Server layer (C# / ASP.NET Core) - produces the UI and talks to Electron through an IPC bridge based on socket.io. (Yes, that's just another flavor of message passing.)
- Node.js layer (the original Electron trigger code) - can be extended with custom code, but is otherwise just the other end of the IPC bridge.
Your C# code calls something like Electron.WindowManager.CreateWindowAsync(...), that call travels over the bridge, the Node.js side makes the actual Electron call, and the result comes back the same way. Meanwhile the renderer happily loads whatever your server serves it. It's a little bit of plumbing, and it's very easy to forget about once it works - which is exactly the point.
The Hard Parts
Of course, the idea is the easy part. The journey was (and is) full of questions like these:
- How do you package an app correctly? Your app is now a Node.js app, an Electron runtime, and a .NET application that all need to end up in one installer that works on three operating systems.
- How do you expose the server - but not too much? There's now a web server running on the client's machine. It needs to be reachable by your own window and by nobody else.
- How do you debug this? Three layers, two runtimes, and a bridge in the middle. "Attach a debugger" turns out to be a much bigger sentence than it sounds like.
electronize, Growing Pains, and 5,000 Stars
To get started, Gregor and Robert invented a system of their own: electronize, a CLI tool that handled building and packaging. It was good enough to begin with, and the framework grew quickly - past 2,000 stars by November 2018, 4,000 by November 2019, and on to 5,000 after that.
But then the problems stacked up:
- Outdated versions of Electron (which were fixed through electronize, but always somewhat behind)
- Unresolved issues piling up
- Weird cross-platform bugs - the kind that only reproduce on the one OS you don't have at hand
- Missing features, mostly from newer Electron versions that the wrapper hadn't caught up with yet
Those things kept the project from growing further. And if you look at the classic NuGet package's release history, you can see it. Back then, the major version of Electron.NET was the Electron compatibility number: Electron.NET 20 matched Electron 20, Electron.NET 13 matched Electron 13, and so on. That's a nice, honest scheme - and it also means every gap in Electron.NET's release history is a gap in which Electron kept moving without you. The releases came with some regularity through 2019, 2020 and 2021 - and then there's nothing on NuGet for 20 months.
(Do the math on 13.5.1 followed by 23.6.1: that's ten Electron major versions in one go.)
The star curve kept climbing regardless - the repository passed 6,000 stars in January 2024. People clearly wanted this to work.
Stepping In
This is where I stepped in. I joined the team in March 2023 - Gregor announced it in issue #744 under the title "Electron.NET Reloaded". I'll be honest: I wasn't planning on becoming a maintainer of a project with thousands of stars. But I'd been in the "web meets .NET" space long enough that the problems looked solvable rather than scary, and together with a few other new maintainers we made a decision that was either brave or reckless: throw out the tooling and rebuild it from scratch.
The result - launched in November 2025 - is ElectronNET.Core: a completely new system, fully based on MSBuild and .NET, supporting essentially any version of Electron. It's also still pre-1.0 (the latest release is 0.6), and the version number no longer means anything about Electron - which is exactly the point.
What changed, in a nutshell:
-
No more CLI and no more
electron.manifest.json. Everything flows through MSBuild project properties, which means Visual Studio's own project system just works. A whole category of "why does my JSON config not do what I think it does" problems disappeared overnight. - .NET is in control. Instead of Electron launching first and babysitting the .NET process, your .NET app launches first and runs Electron as a child process. Better lifecycle management, more reliable shutdown. The Electron-first route still exists, though - there are eight launch scenarios in total (packaged/unpackaged × console/ASP.NET × .NET-first/Electron-first).
- Any Electron version. No more rigid coupling to whatever version a given release happened to bundle - the old "Electron.NET 20 = Electron 20" scheme is gone. You choose the Electron version with a single property in your project file, and the build validates that it's compatible.
- ASP.NET is optional. A plain console app is now enough if all you need is to load HTML from the file system or from a remote server.
- Debugging that feels like .NET. Press F5, get breakpoints and Hot Reload. You can even build and debug the Linux version of your app from Windows Visual Studio through WSL.
- Full API compatibility. Existing code keeps working - including custom host hooks - so migrating is mostly about the project setup, not a rewrite. There's a migration guide for exactly that.
The new package structure is intentionally modular:
-
ElectronNET.Core- the main package with the build logic and project system integration -
ElectronNET.Core.API- the pure API definitions -
ElectronNET.Core.AspNet- the ASP.NET-specific runtime pieces -
ElectronNET.Core.Templates- project templates to get started quickly
Getting Started
You'll need .NET 8 or 10 and Node.js 22 or newer. For an ASP.NET-based app, create a new ASP.NET Core project and add two packages:
dotnet add package ElectronNET.Core
dotnet add package ElectronNET.Core.AspNet
Then hook Electron into the startup of a minimal API (Razor Pages here, but MVC works the same way):
using ElectronNET;
using ElectronNET.API;
using ElectronNET.API.Entities;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddElectron(); // handy for dependency injection
builder.UseElectron(args, async () =>
{
var browserWindow = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions { Show = false, AutoHideMenuBar = true });
browserWindow.OnReadyToShow += () => browserWindow.Show();
});
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.MapRazorPages();
app.Run();
Notice the callback you pass to UseElectron() - it tells you the right moment to set up your UI, which used to be a source of mysterious race conditions.
Then press F5. That's it. You're running a desktop app.
Want a specific Electron version? It's just an MSBuild property in your .csproj:
<PropertyGroup Label="ElectronNetCommon">
<ElectronVersion>30.4.0</ElectronVersion>
</PropertyGroup>
(That's the default in the docs at the time of writing - the configuration page lists all the other settings.)
Blazor
For Blazor there's exactly one thing to remember - the IsRunningBlazor option, which sets up the renderer in a way that Blazor can run without interference (including hot module replacement during development):
builder.UseElectron(args, async () =>
{
var options = new BrowserWindowOptions
{
Show = false,
IsRunningBlazor = true, // <-- crucial
};
var browserWindow = await Electron.WindowManager.CreateWindowAsync(options);
browserWindow.OnReadyToShow += () => browserWindow.Show();
});
No ASP.NET? No Problem.
If you don't need a local web server at all, a console app is enough. Create one with dotnet new console, add dotnet add package ElectronNET.Core, and start the runtime yourself:
using ElectronNET.API;
using ElectronNET.API.Entities;
var runtimeController = ElectronNetRuntime.RuntimeController;
await runtimeController.Start();
await runtimeController.WaitReadyTask;
var browserWindow = await Electron.WindowManager.CreateWindowAsync(
new BrowserWindowOptions { Show = false });
// file system, remote URL, whatever you like
await browserWindow.WebContents.LoadURLAsync("https://example.com");
browserWindow.OnReadyToShow += () => browserWindow.Show();
await runtimeController.WaitStoppedTask;
(This is a trimmed version - the console app page in the wiki has the complete example including the project file settings you need.)
And the Rest of Electron?
The C# API covers a good chunk of what Electron offers: windows, menus, dialogs, the tray, notifications, the clipboard, global shortcuts, the auto-updater, native theme, power monitor, screens, ipcMain, and more. The wiki has a page for each of them.
Where It Shines
- You keep your stack. If you know ASP.NET Core - Razor Pages, MVC, or Blazor - you already know 90% of what it takes to build a desktop app. The web skills and the C# skills you have are the skills you need.
- Real cross-platform, one codebase. Windows, macOS, and Linux, with the packaging story handled by MSBuild instead of a separate tool.
- Flexible architecture. Full ASP.NET host, or a lightweight console app that loads local files or a remote URL. The setup fits the app, not the other way around.
- Modern debugging. .NET-first launch, Hot Reload, and WSL for Linux builds is a workflow that just feels like normal .NET development.
- The Electron ecosystem is right there. If it works in Electron, it works in Electron.NET - and if there's a custom bit of JavaScript you need, the Node.js layer can be extended with your own code.
Where It Struggles
- It's Electron. You're shipping Chromium and a .NET runtime and Node.js. Installers are big, memory usage is what you'd expect from a browser plus a server, and if your app is a small utility, a native UI toolkit might be a better fit.
- Two ecosystems to keep in your head. Even with .NET in the driver's seat, you still touch Node.js (which is a requirement), and now and then the JavaScript side of the story.
- The API is a wrapper. That's a strength (you get C# instead of JS) but also a limitation: a shiny new Electron feature has to be exposed in the C# API before you can use it comfortably. Keeping up with that is part of what stalled the classic line.
- Core is young. It launched in November 2025 and is still pre-1.0 (latest release: 0.6). It's moving fast - new pre-release builds show up almost daily - but "fast-moving" and "settled" are not the same thing.
- Migrating from classic takes some effort. Full API compatibility helps a lot, but the project setup is different, and you'll have to work through the migration guide.
- The team is small. This is open-source work done in our free time. More on that below.
People Behind It
The README lists four authors: Gregor Biswanger and Robert Mühsig (the founders), softworkz, and me.
The current changelog reads like a thank-you note to everyone who showed up: softworkz has shipped an enormous share of the Core work (infrastructure, build system, integration tests - the list is long), while agracio, AeonSake, Denny09310, NimbusFox, davidroth, markatosi, DYH1319, hilin and adityashirsatrao007 all appear with fixes, features, docs, and sample apps.
Since this is done in our free time, there's a donation page, and you can also sponsor the core maintainers on GitHub (Gregor, me) - or buy me a coffee. Donations can even be used to bump the priority of an issue. For questions you can use the GitHub discussions feature.
Where Electron.NET Shows Up Today
The numbers first: the repository has 7.6k stars and 741 forks. The classic ElectronNET.API package sits at over 650K downloads, and the much younger Core packages have already crossed 40K.
Electron.NET is widely used - for demos, within Microsoft, and within the .NET community. Some of the projects built on top of it:
- Papercut SMTP (3.3K stars), the simple desktop email server that developers use to catch outgoing mail during testing.
- Sidekick, a helper app for the game Path of Exile.
- SecureDNS, an all-in-one cross-platform DNS server.
- Ididit, a Blazor habit tracker that runs on the web and on desktop.
- Hyprism, an open-source Hytale launcher - built on the new ElectronNET.Core.
- AsteroidsWasm, a single C# project that runs as Blazor WASM, Blazor Server, Electron, WPF, WinForms, MAUI, and WinUI3 (which has to be some kind of record).
None of that was in the plan when Gregor stayed up to hack together a prototype, but "let .NET developers use the web stack they already know for desktop apps" turns out to be a pretty good idea.
What's Next for Electron.NET
The Core rewrite removed the technical debt that was holding the project back. The flexible Electron versioning, the integrated build system, and the cross-platform tooling are the foundation for more frequent updates, better tooling and IDE integration, easier community contributions, and support for more platforms. Newer Electron versions are already supported, and the release train is running.
The future certainly holds more treats. I'd tell you what, but I'm not great at keeping surprises - so I'll just say: watch the repository.
That's the Electron.NET story - from a late-night prototype in 2017, through 5,000 stars and a rough patch, to a complete rebuild on top of MSBuild and .NET. If you'd like to poke around, here are some links:
- GitHub project: ElectronNET/Electron.NET
- NuGet: ElectronNET.Core.API (new, rework) and ElectronNET.API (classic)
- Documentation / what's new: What's New
- Previous articles in this series: AngleSharp, Mages, and Piral
Next up in this series: another project, another origin story. Stay tuned.







Top comments (0)