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.
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
| Host | Compatibility evidence |
|---|---|
| Electron 28.3.3 | Retained Electron 28 host; native main, preload, renderer, and optional React checks. |
| Electron 44.3.0 | Tested 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:
| Process | Toggly responsibility |
|---|---|
| Main | Initializes the singleton client, fetches and caches definitions, and owns live updates. |
| Preload | Uses Electron's contextBridge to expose the package's limited window.toggly API. |
| Renderer | Evaluates 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 point | Use it for |
|---|---|
@ops-ai/electron-feature-flags-toggly/main | initToggly, direct main-process checks, setContext, lifecycle cleanup, and registerTogglyIpc. |
@ops-ai/electron-feature-flags-toggly/preload/entry | Compiled CommonJS entry for webPreferences.preload. |
@ops-ai/electron-feature-flags-toggly/preload | exposeToggly() included in a custom CommonJS preload bundle. |
@ops-ai/electron-feature-flags-toggly or /renderer | Renderer checks, snapshots, context changes, and update subscriptions. |
@ops-ai/electron-feature-flags-toggly/react | Feature, useFeatureFlag, useFeatureGate, and readFeatureFlag. |