Skip to content

How to add license keys to an Electron app ​

Short answer: npm install sunlicense, check the key in Electron's main process when the app is ready, open your activation window when SunLicense says no, and keep working when it can't be reached (the last good check or an offline license file). Never check from the renderer: the key and your API key stay out of web content.

js
// main.js (main process)
import { app, BrowserWindow, dialog } from 'electron';
import { SunLicense } from 'sunlicense';

const sunlicense = new SunLicense({ baseUrl: 'https://api.sunlicense.com', token: API_KEY }); // from your build

app.whenReady().then(async () => {
  const key = readSavedKey();                     // saved under app.getPath('userData') after activation
  if (!key) return showActivationWindow();
  try {
    const r = await sunlicense.validate(key, { host: machineId() });
    if (r.status === 400) {                       // EXPIRED, REVOKED, TOO_MANY_MACHINES…
      dialog.showErrorBox('License', `This license can't be used: ${r.data?.code}`);
      return showActivationWindow();
    }
  } catch {
    // offline: carry on (or require a recent good check / an offline license file)
  }
  new BrowserWindow({ width: 1000, height: 700 }).loadFile('index.html');
});

validate resolves to { valid, status, data } and throws only when SunLicense can't be reached.

Or let your AI agent do it: paste the setup prompt into Claude Code, Cursor or Codex in your app's folder. It adds the check and the activation flow and proves them with a test license.

Free plan: 3 products, 100 licenses, no credit card. Create a free account.

Step by step ​

  1. In the dashboard: a product, a policy (for example 3 machines: a buyer's desktop and laptop) and an API key, which can only validate licenses. For trials, add a trial policy; for subscriptions, connect your store and ended subscriptions suspend the key on their own.

  2. Activation window. A small window with one text field; it sends the key to the main process over IPC (ipcMain.handle('activate', …)), the main process validates it and saves it. Save it in app.getPath('userData'), encrypted with safeStorage if you like; it's the buyer's key, not a secret from them.

  3. Your API key goes into the build (a generated module from SUNLICENSE_API_KEY at packaging time), never git.

  4. Machine id: a hash of OS, architecture and host name, sent as host:

    js
    import { createHash } from 'node:crypto';
    import os from 'node:os';
    const machineId = () => createHash('sha256').update(`${os.platform()}|${os.arch()}|${os.hostname()}`).digest('hex');

    Choosing a machine id. Let buyers move to a new computer by freeing a machine on the license (dashboard, or the customer portal on Lite and Standard).

  5. Offline. Desktop apps travel. Either accept a check from the last few days (store its date with the key) or give buyers a signed offline license file that the app verifies with your public key and no network.

  6. Results: 200 opens the app, 400 shows why (result codes) and the activation window, anything else carries on. Mask keys in logs and UI (AX8P-XXXX-XXXX-XXXX).

  7. Re-check now and then (at start-up and every few hours while open), so refunds and revokes reach running copies.

Packaging honestly ​

Electron apps ship JavaScript that anyone can read inside app.asar. A license check stops casual sharing and lets you revoke leaked keys; it doesn't make the code secret. Run the main-process code through obfuscation (javascript-obfuscator) and keep a piece of the product behind the license with protection or protected files (Standard).

Selling it ​

Gumroad, Lemon Squeezy, Payhip, itch.io and Stripe issue a key per purchase and revoke it on a refund. More on licensing desktop apps.

Send feedback

Found something broken, or have an idea? It goes straight to our board.

The first line becomes the title.
Images (optional, up to 5: paste, drop or capture)
Check each image before sending, and remove any that show something private.

We also receive this page's address and your browser version.

Ctrl Enter to send