---
title: "Electron"
description: "Track an Electron app with CanaryLytics. Remote URLs work as-is; bundled apps need a custom protocol scheme, because file:// origins can never be trusted."
last_updated: "2026-07-30"
---

# Electron

Electron apps track like websites as long as the page has a real origin. A `BrowserWindow` loading a remote URL needs nothing special; a bundled app needs a custom protocol scheme, because `file://` pages have no origin to trust.

## Loading a remote URL

If your `BrowserWindow` loads a real `https://` URL (for example `win.loadURL("https://myapp.com")`), it works exactly like a normal web page. The page's origin is `https://myapp.com`, which just needs to be a trusted domain like any other site. Nothing Electron-specific to configure.

## Bundled apps

<Aside type="danger" title="Never load via file://">
  Don't `win.loadFile("index.html")` and expect tracking to work. A `file://` page's origin is the opaque string `"null"`, which can never match a trusted domain, so every event is dropped by design. This isn't a bug to work around; `file://` origins are unsupportable for a domain-locked ingest model.
</Aside>

Instead, register a **standard, secure custom protocol scheme** before the app is ready, and load your bundled app through it:

```js title="main.js"
const { app, protocol, BrowserWindow } = require("electron");

protocol.registerSchemesAsPrivileged([
  {
    scheme: "app",
    privileges: {
      standard: true,
      secure: true,
      supportFetchAPI: true,
      corsEnabled: true,
    },
  },
]);

app.whenReady().then(() => {
  // ... register a protocol.handle("app", ...) that serves your bundled files ...
  const win = new BrowserWindow({ /* ... */ });
  win.loadURL("app://myapp/index.html");
});
```

A page served over `app://myapp/...` has a real, non-opaque origin: `app://myapp`. Add `myapp` as a trusted domain in the dashboard (single-label domains, like `localhost`, are allowed) and point the snippet at it:

```html
<script src="https://cdn.analytics.canarycoders.es/lytics.js" data-domain="myapp" defer></script>
```

With that in place, `/ingest`, the widget, and the browser SDK all work exactly as they would on the web.

## Tracking from the main process

The [`@canarylytics/sdk`](/docs/api/sdk/) client is isomorphic, so in Electron's main process it behaves like any Node server calling `/v1`. There is no `Origin` header and no CORS check there (that only applies to browser reads), just a Bearer-authed request:

```ts title="main.js"

const lytics = createClient({ apiKey: process.env.CANARYLYTICS_KEY });
await lytics.events.track("app_launched", { version: app.getVersion() });
```

## Related

<CardGrid>
  <LinkCard title="Capacitor & Ionic" description="The mobile-hybrid equivalent of this page." href="/docs/environments/capacitor/" />
  <LinkCard title="Custom events & goals" description="Server-side event constraints for main-process tracking." href="/docs/tracking/events-and-goals/" />
</CardGrid>

## Sitemap

- [Docs sitemap](https://analytics.canarycoders.es/docs/sitemap.md)
- [Full documentation as a single file](https://analytics.canarycoders.es/llms-full.txt)
