Skip to main content

Package Channels

Switch a package (GEP, Overlay, Recorder, or a custom package) to a different release channel, for example to pull a QA or dev build, with the Package Channels API on app.overwolf.packages.

PropertyValue
SurfaceOverwolf Electron (ow-electron)
Minimum versionow-electron@38.9.12
PlatformWindows
When to useSwitching a package to a QA, dev, or other named channel at runtime or at launch

What package channels replace

important

--owepm-packages-url is no longer required. Use a channel instead to get a dev build, per package, without pointing your app at a different endpoint.

Previously, testing a package's QA build meant pointing your app directly at the QA package endpoint with a launch argument:

--owepm-packages-url=https://electronapi-qa.overwolf.com/v2/packages

The Package Channels API replaces this. You request a channel (for example dev) per package, and the package manager fetches that channel's build from the same production endpoint. You no longer point your app at a different server to get a QA build.

If your launch.json currently sets --owepm-packages-url, replace it with --owepm-package-channel (see below). To do the same thing from your app's code instead of the command line, see Switching a package's channel.

CLI channel override

Set a package's channel at launch time, without calling setChannel() from your code:

# Single package
my-app.exe --owepm-package-channel=gep:dev

# Multiple packages (comma-separated)
my-app.exe --owepm-package-channel=gep:dev,overlay:dev

This is the direct replacement for pointing launch.json at a QA endpoint URL: set the channel per package instead of the endpoint. The override stores the same localStorage preference setChannel() would, before the first update check (see Switching a package's channel for the code equivalent). An invalid channel name falls back to the public release on the first update check.

Setup

Import the interface and cast the app.overwolf.packages object once at startup:

import {
IOverwolfPackageApi,
OverwolfPackageApiEvents,
} from 'ow-electron-package-manager/src/browser/api/overwolf-package-api.interface';
import { ChannelPackageInfo } from 'ow-electron-package-manager/src/common/package-info';

const api = app.overwolf.packages as unknown as IOverwolfPackageApi;

Switching a package's channel

setChannel() switches a package to a named release channel and downloads that channel's version immediately.

setChannel(
packageName: string,
channel?: string | 'public',
ready?: (packageInfo: ChannelPackageInfo) => void,
): Promise<SetChannelResult>

The channel preference is stored in localStorage and survives restarts. If the requested channel differs from what is installed, setChannel() triggers a download, including downgrading to an older version on that channel. The ready callback fires once the download completes, and your app must restart to apply it.

const result = await api.setChannel('overlay', 'dev', (pkg) => {
console.log(`overlay v${pkg.version} ready on dev, restart required`);
api.relaunch();
});

if (!result.success) {
console.error('setChannel failed:', result.error);
// result.error: 'invalid-package' | 'invalid-channel'
}

If the package is already at the requested channel version, ready never fires and no download starts.

Restoring the public release

'public', an empty string, undefined, and omitting the argument are equivalent. All four clear the stored channel preference and check for the public version:

await api.setChannel('overlay');
await api.setChannel('overlay', undefined);
await api.setChannel('overlay', '');
await api.setChannel('overlay', 'public');

Error cases

Return valueMeaning
{ success: false, error: 'invalid-package' }The package was not found on the server.
{ success: false, error: 'invalid-channel' }The channel does not exist for this package.
Throws ErrorpackageName is not listed in your app's package.json under overwolf.packages.

Waiting for the ready callback

setChannel() resolves as soon as the preference is stored and the download starts, not once the download finishes. The ready callback fires later. Wrap it in a Promise when you need to wait for it:

function setChannelAndWait(
packageName: string,
channel: string,
timeoutMs = 60_000,
): Promise<ChannelPackageInfo | null> {
return new Promise((resolve) => {
let settled = false;

const settle = (value: ChannelPackageInfo | null) => {
if (settled) return;
settled = true;
clearTimeout(timer);
resolve(value);
};

// If the package is already at this channel version, ready never fires,
// so the timeout prevents the Promise from hanging forever.
const timer = setTimeout(() => settle(null), timeoutMs);

api
.setChannel(packageName, channel, (pkg) => settle(pkg))
.then((result) => {
if (!result.success) settle(null);
})
.catch(() => settle(null));
});
}

setChannelAndWait resolves with a ChannelPackageInfo once the download finishes, or null if setChannel() returned { success: false }, the package was already at the requested version, or timeoutMs elapsed.

Switching multiple packages concurrently

const [overlayPending, gepPending] = await Promise.all([
setChannelAndWait('overlay', 'dev'),
setChannelAndWait('gep', 'dev'),
]);

const needsRestart = [overlayPending, gepPending].filter(
(p): p is ChannelPackageInfo => p !== null,
);

if (needsRestart.length > 0) {
showRestartPrompt(
`Updates ready: ${needsRestart.map(p => `${p.name}@${p.version}`).join(', ')}`,
() => api.relaunch(),
);
}

Applying the update

setChannel() resolves once the preference is stored and the download starts, not once the download finishes. Wait for the ready callback, or listen for PackageUpdatePending, then call app.relaunch() to apply it.

Checking channels

getChannel() returns the active channel for one or more packages.

getChannel(...packageNames: string[]): Promise<CurrentChannelsResult>
// CurrentChannelsResult = Record<string, string>, 'public' is the default release

All registered packages:

const current = await api.getChannel();
// { overlay: 'dev', gep: 'public', utility: 'public' }

for (const [pkg, channel] of Object.entries(current)) {
console.log(`${pkg}: ${channel}`);
}

Specific packages:

const result = await api.getChannel('overlay', 'gep');
console.log('overlay channel:', result.overlay); // 'dev'
console.log('gep channel:', result.gep); // 'public'

getChannel() returns 'public' for a package on the default release. An unknown package name is silently omitted from the result, it does not throw. The result also includes any package that has a non-public channel stored, even if that package isn't listed in your package.json.

getAvailableChannels() returns the channels the server exposes for one or more packages.

getAvailableChannels(...packageNames: string[]): Promise<AvailableChannelsResult>
// AvailableChannelsResult = Record<string, string[]>

All registered packages:

const channels = await api.getAvailableChannels();
// { overlay: ['dev', 'beta'], gep: ['dev'], utility: [] }

for (const [pkg, available] of Object.entries(channels)) {
if (available.length > 0) {
console.log(`${pkg} channels: ${available.join(', ')}`);
} else {
console.log(`${pkg}: no release channels available`);
}
}

Specific packages:

const { overlay, gep } = await api.getAvailableChannels('overlay', 'gep');
console.log('overlay channels:', overlay); // ['dev', 'beta']
console.log('gep channels:', gep); // ['dev']

A package with no channels defined returns an empty array. Unlike getChannel(), getAvailableChannels() throws if you pass a package name that is not registered.

Stale channel auto-recovery

If a stored channel is deleted from the server, the package manager falls back to the public release on the next update check and clears the stale preference. No action is required in your code.

Types

import {
ChannelPackageInfo,
SetChannelResult,
AvailableChannelsResult,
CurrentChannelsResult,
} from 'ow-electron-package-manager/src/common/package-info';

type SetChannelResult = { success: boolean; error?: string };
type ChannelPackageInfo = { name: string; version: string };
type AvailableChannelsResult = Record<string, string[]>;
type CurrentChannelsResult = Record<string, string>;
  • Dev Mode, to run packages locally before your app is signed.
  • App Signing, to sign your app so packages load once distributed.