Skip to main content

Electron SDK

Use Toggly in Electron by keeping the definitions client in the main process. A small, explicit bridge carries feature evaluations to the renderer. This keeps the renderer from needing direct access to the definitions client or its configuration.

Grab the printable Electron cheat sheet (download PDF) — main process, preload bridge, renderer gates.

The runnable Electron sample walks through this three-process setup, offline defaults, user context, entity context, and live updates in a complete application.

Frontend App Key

Use your Toggly App Key (the frontend definitions key). It is not a management API credential and must never be used to authorize sensitive actions. Keep the SDK configuration in the main process; expose only the renderer operations that your application needs.

Install​

npm install @ops-ai/electron-feature-flags-toggly

The declared Electron peer range is 28 or later. The optional React entry point requires React 18 or later.

Requirements​

HostCompatibility evidence
Electron 28.3.3Retained Electron 28 host; native main, preload, renderer, and optional React checks.
Electron 44.3.0Tested current host; the same native process and React checks.

These exact hosts were independently exercised on macOS using the packed SDK, with public npm dependencies, including the signature verifier. The checks cover public TypeScript imports, synchronous defaults and asynchronous snapshots over real IPC, React Feature (including negation), and useFeatureFlag. They do not establish that every version in the open peer range has been tested.

The package declares Node 18+. Electron supplies the Node runtime used by its main process; choose build tools compatible with your installed Electron release. Native host checks need an environment that can launch Electron windows. On Linux, provide a display server such as Xvfb; the macOS checks do not establish Windows or Linux host results.

How the processes work​

Electron separates trusted Node.js code from page code:

ProcessToggly responsibility
MainInitializes the singleton client, fetches and caches definitions, and owns live updates.
PreloadUses Electron's contextBridge to expose the package's limited window.toggly API.
RendererEvaluates UI gates through that bridge and redraws when the main process reports a flag update.

The main process must initialize Toggly and register the IPC handlers before a renderer can use the bridge. contextIsolation: true and nodeIntegration: false keep renderer code separate from Node.js APIs.

Set up the main process​

Initialize after Electron is ready, then register the IPC handlers. The registerTogglyIpc call is required for the preload and renderer helpers.

import { app, BrowserWindow, ipcMain } from 'electron'
import {
closeToggly,
initToggly,
isFeatureOn,
registerTogglyIpc,
} from '@ops-ai/electron-feature-flags-toggly/main'

await app.whenReady()

await initToggly({
appKey: process.env.TOGGLY_APP_KEY,
environment: 'Production',
userDataPath: app.getPath('userData'),
flagDefaults: {
NewDashboard: false,
},
// Include identity, groups, and claims here when they are already known.
// They are used by this first definitions request.
// identity: 'user-123',
// groups: ['beta'],
// claims: { plan: 'pro' },
onError: (message, error) => {
console.warn('Toggly error:', message, error)
},
})

const unregisterTogglyIpc = registerTogglyIpc(
ipcMain,
() => BrowserWindow.getAllWindows(),
)

// Gate trusted main-process behavior such as menus or windows.
if (isFeatureOn('NewDashboard')) {
// ...
}

app.on('before-quit', () => {
unregisterTogglyIpc()
closeToggly()
})

userDataPath is required. Pass Electron's app.getPath('userData') so the SDK can keep last-known-good definitions on disk for the current app, environment, and user context.

Set up the preload bridge​

Use the package's compiled CommonJS preload entry. It calls exposeToggly() and exposes window.toggly through Electron's contextBridge. Resolve its absolute path from the main process; an ESM main entry can use createRequire:

import { createRequire } from 'node:module'

const require = createRequire(import.meta.url)
const togglyPreload = require.resolve(
'@ops-ai/electron-feature-flags-toggly/preload/entry',
)

const window = new BrowserWindow({
webPreferences: {
preload: togglyPreload,
contextIsolation: true,
nodeIntegration: false,
},
})

Create the window after the main-process initialization and IPC registration shown above. Keep context isolation enabled and renderer Node integration disabled; the renderer communicates through the bridge instead of importing Electron or the main-process client.

If your application needs additional preload APIs, bundle a custom preload as CommonJS and include the package's /preload surface in that bundle. Call exposeToggly() from the bundled preload. Do not externalize that dependency: Electron's sandboxed preload loader cannot resolve arbitrary packages with require() at runtime. Point webPreferences.preload to the compiled output, not an unbundled source file containing ESM imports.

exposeToggly() makes window.toggly immutable and exposes checks, getFlags, context changes, and flag-update subscriptions. Use it only for renderer content you trust. If a window displays remote or otherwise untrusted content, expose a narrower application-specific preload API instead.

Evaluate flags in the renderer​

Import the renderer surface from the package root (or @ops-ai/electron-feature-flags-toggly/renderer). These functions call the preload bridge; they do not create a second SDK client in Chromium.

import {
evaluateFeatureGate,
getFlags,
isFeatureOn,
onFlagsUpdated,
} from '@ops-ai/electron-feature-flags-toggly'

function render() {
const canShowDashboard = isFeatureOn('NewDashboard')
const canShowExperiment = evaluateFeatureGate(
['new-dashboard', 'beta-access'],
'any',
)

// Update your UI from these booleans.
console.log({ canShowDashboard, canShowExperiment })
}

render()

// `getFlags` returns the current plain boolean snapshot.
getFlags().then((flags) => console.log('Current flags:', flags))

// Live updates from main cause the UI to re-evaluate.
const unsubscribe = onFlagsUpdated(() => render())
// Call unsubscribe() when this view is destroyed.

If the preload bridge is missing, synchronous renderer checks fail closed: isFeatureOn returns false and evaluateFeatureGate returns false unless you requested negation. getFlags, setContext, and clearContext reject with an error that explains that exposeToggly() is missing.

React (optional)​

The React entry point subscribes to the same update events. Use <Feature> for declarative UI and useFeatureFlag or useFeatureGate when your component needs a boolean.

import {
Feature,
useFeatureFlag,
useFeatureGate,
} from '@ops-ai/electron-feature-flags-toggly/react'

function Dashboard() {
const { isEnabled: showDashboard } = useFeatureFlag('NewDashboard')
const { isEnabled: showExperiment } = useFeatureGate(
['new-dashboard', 'beta-access'],
{ requirement: 'any' },
)

return (
<>
<Feature featureKey="NewDashboard">
<NewDashboard />
</Feature>
{showDashboard && showExperiment ? <Badge /> : null}
</>
)
}

Feature gate options​

evaluateFeatureGate(keys, requirement, negate, entityContext, kind) evaluates one or more keys. The requirement defaults to all.

// Every key must be on (the default requirement).
evaluateFeatureGate(['A', 'B'], 'all')

// At least one key must be on.
evaluateFeatureGate(['A', 'B'], 'any')

// Invert the result.
evaluateFeatureGate(['LegacyUi'], 'all', true)

User context and rollouts​

identity, groups, and claims are the user targeting context for percentage, targeting, and User Claims filters. If your desktop app already knows them at startup, pass them to initToggly as shown above. That lets the initial evaluated request use the right context instead of fetching once and then refreshing.

When login, logout, or membership changes later, call setContext or clearContext from main or through the renderer bridge:

import { setContext } from '@ops-ai/electron-feature-flags-toggly'

await setContext({
identity: 'user-123',
groups: ['beta'],
claims: { plan: 'pro' },
})

Both methods refresh evaluated definitions. Electron uses one Toggly client per main process, so the new snapshot is shared by its renderer windows; do not use this API for per-window state. Context is sent on the /evaluated-signed request for targeting and rollout evaluation. Use opaque IDs and coarse claims, since query-string context can appear in client and edge logs.

Entity context​

Use entity context for a specific domain object, such as an Order. Pass it to each check; do not put order properties in global user context. An entity-gated flag fails closed when its entity context is missing.

import { isFeatureOn } from '@ops-ai/electron-feature-flags-toggly'

const order = {
kind: 'Order',
key: 'order-9001',
attributes: {
Vip: true,
Total: 149,
},
}

const showExpressCheckout = isFeatureOn('ExpressCheckout', order)

getFlags() is a plain boolean snapshot. Use isFeatureOn or evaluateFeatureGate with entity context for features that use a Context Property filter.

Offline behavior and defaults​

flagDefaults supplies values when there is no App Key or when the first remote refresh cannot produce definitions. With an App Key, the main process also stores last-known-good evaluated definitions under userData and loads the entry matching the app, environment, and user context before refreshing.

await initToggly({
userDataPath: app.getPath('userData'),
flagDefaults: { NewDashboard: true },
})

Omitting appKey runs an offline, defaults-only setup. It does not contact Toggly.

Signed definitions​

Set verifySignatures: true to verify ES256 evaluated-signed envelopes in the main process before they are cached or sent to renderer windows.

await initToggly({
appKey: process.env.TOGGLY_APP_KEY,
environment: 'Production',
userDataPath: app.getPath('userData'),
verifySignatures: true,
// allowedKeyIds: ['kid-1'],
// maxSignatureAgeSeconds: 300,
})

Live updates​

When appKey is set, enableLiveUpdates defaults to true. The main process opens a WebSocket, refreshes definitions when it receives an update, and registerTogglyIpc broadcasts the resulting boolean snapshot to the current renderer windows. Set enableLiveUpdates: false when your application should use only the refresh interval.

API surfaces​

Entry pointUse it for
@ops-ai/electron-feature-flags-toggly/maininitToggly, direct main-process checks, setContext, lifecycle cleanup, and registerTogglyIpc.
@ops-ai/electron-feature-flags-toggly/preload/entryCompiled CommonJS entry for webPreferences.preload.
@ops-ai/electron-feature-flags-toggly/preloadexposeToggly() included in a custom CommonJS preload bundle.
@ops-ai/electron-feature-flags-toggly or /rendererRenderer checks, snapshots, context changes, and update subscriptions.
@ops-ai/electron-feature-flags-toggly/reactFeature, useFeatureFlag, useFeatureGate, and readFeatureFlag.

Next steps​