Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
385 changes: 385 additions & 0 deletions doc/specs/#147 - Release Channels.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,385 @@
---
author: Demitrius Nelon denelon, GitHub Copilot Copilot
created on: 2026-06-17
last updated: 2026-06-17
issue id: 147
---

# Release Channels

For [#147](https://github.com/microsoft/winget-cli/issues/147).

## Abstract

Add support for release channels (stable, beta, dev, canary, LTS, etc.) to the WinGet package model. Users subscribe to a channel for a package and receive upgrades only within that channel. This eliminates accidental cross-channel upgrades and enables proper management of packages with multiple concurrent release streams.

## Inspiration

Many popular packages publish multiple concurrent release streams:

- **Google Chrome** β€” Stable, Beta, Dev, Canary
- **Microsoft Edge** β€” Stable, Beta, Dev, Canary
- **VS Code** β€” Stable, Insiders
- **Node.js** β€” Current, LTS (multiple active LTS lines)
- **Firefox** β€” Release, Beta, Developer Edition, Nightly

Today in `winget-pkgs`, these are handled inconsistently:
- Some use separate package IDs (`Google.Chrome` vs `Google.Chrome.Beta` vs `Google.Chrome.Dev`)
- Some publish only the stable channel, with beta users unable to track updates
- `winget upgrade --all` may cross channel boundaries (beta→stable "downgrade")
- No way to express "keep me on LTS" without pinning to a specific version

This is one of the oldest open feature requests ([#147](https://github.com/microsoft/winget-cli/issues/147), opened 2020) and relates to side-by-side package support ([#2129](https://github.com/microsoft/winget-cli/issues/2129)).

## Solution Design

### Manifest Schema Extension (v1.29.0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove version information


Add a `Channel` field to the version manifest:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This already exists in the installer manifest at the installer or root level, allowing for the possibility of multiple channels in a single manifest


```yaml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to assume that a package version can only exist in a single channel at the same time - which conflicts with real flighting rules where the same version could be in Dev, Canary, and Insiders all at the same time.

Need more detail on how versions move between channels and multi-channel support. Keeping the field at the installer level may make sense, as new installer nodes can be added to the manifest as the flighting progresses

PackageIdentifier: Google.Chrome
PackageVersion: 127.0.6533.0
Channel: Dev

@pl4nty pl4nty Jun 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could a version have multiple channels? This happens a lot with runtimes - a developer might install OpenJS.NodeJS 24.x (current LTS), for an app they're working on. If they subscribed to LTS instead of 24.x, they would receive 26.x when it becomes LTS in October, potentially breaking their app.

Speaking of dependencies, would packages be able to depend on a specific channel of a different package?

ManifestType: version
ManifestVersion: 1.29.0
```

**Schema:**

| Property | Type | Required | Default | Description |
|----------|------|----------|---------|-------------|
| `Channel` | string | No | (empty = stable) | Channel name for this version |

- If `Channel` is omitted or empty, the version belongs to the implicit `stable` channel
- Channel names are case-insensitive, publisher-defined strings
- Recommended vocabulary: `stable`, `beta`, `dev`, `canary`, `lts`, `preview`, `insiders`, `nightly`, `rc`
- Maximum length: 32 characters
- Allowed characters: `[a-zA-Z0-9._-]`

### Version Resolution Logic

The version resolver gains channel awareness:

**Current behavior (no channel specified by user):**
1. Only versions with no `Channel` field (or `Channel: stable`) are considered
2. Backwards compatible β€” existing behavior unchanged

**Explicit channel subscription:**
1. `winget install Google.Chrome --channel dev` β€” installs from the `dev` channel
2. The channel subscription is recorded in the local tracking database
3. Subsequent `winget upgrade Google.Chrome` only considers versions in the `dev` channel

**Channel switching:**
1. `winget install Google.Chrome --channel stable` while `dev` is subscribed β†’ prompts: "Switch from Dev to Stable? This may install an older version."
2. With `--force`, switches without prompting

### Tracking Database

WinGet stores channel subscriptions in the installed packages database:

| Column | Type | Description |
|--------|------|-------------|
| `PackageId` | TEXT | Package identifier |
| `Channel` | TEXT | Subscribed channel (NULL = stable/default) |
| `SubscribedOn` | DATETIME | When the subscription was set |

This is stored alongside existing installation tracking data (ARP correlation, source, installed version).

### CLI Commands Affected

#### `winget install`

```
winget install <package> [--channel <channel>]
```

- New `--channel` argument selects the channel to install from
- If the package is already installed on a different channel, prompt for confirmation (or require `--force`)
- Records channel subscription on successful install

#### `winget upgrade`

```
winget upgrade [<package>] [--channel <channel>] [--include-channel]
```

- Default: only shows upgrades within subscribed channel
- `--channel` temporarily overrides for this operation (does not change subscription)
- `--include-channel` adds a Channel column to output

Behavior of `winget upgrade --all`:
- Each package is upgraded within its subscribed channel only
- Packages without channel subscription only see stable versions
- No cross-channel upgrades occur

#### `winget show`

```
winget show <package> [--channels] [--channel <channel>] [--versions]
```

- `--channels` lists all available channels with their latest version
- `--channel <channel>` shows versions within that channel
- `--versions` combined with `--channel` lists all versions in that channel

#### `winget list`

```
winget list [--include-channel]
```

- `--include-channel` adds Channel column showing subscribed channel per package

#### `winget export` / `winget import`

Export includes channel information:

```json
{
"Sources": [{
"Packages": [{
"PackageIdentifier": "Google.Chrome",
"Channel": "Dev"
}]
}]
}
```

Import respects channel β€” installs from the specified channel.

### Settings

```json
{
"installBehavior": {
"defaultChannel": "",
"showChannelInList": false,
"showChannelInUpgrade": true
}
}
```

| Setting | CLI Argument | Description |
|---------|-------------|-------------|
| `defaultChannel` | `--channel` | Default channel for new installations (empty = stable) |
| `showChannelInList` | `--include-channel` | Always show channel column in `winget list` |
| `showChannelInUpgrade` | `--include-channel` | Always show channel column in `winget upgrade` |

### Group Policy

| Policy | Type | Default | Description |
|--------|------|---------|-------------|
| `AllowedChannels` | MultiString | Empty (all allowed) | Restrict which channels users can subscribe to |
| `DefaultChannel` | String | Empty (stable) | Override default channel for managed devices |
| `PreventChannelSwitching` | Bool | False | Block users from changing subscriptions |

### COM API Surface

```idl
// Extended PackageCatalogReference
interface IPackageVersionInfo2 : IPackageVersionInfo
{
String Channel { get; };
}

// Install options
interface IInstallOptions2 : IInstallOptions
{
String PreferredChannel { get; set; };
}

// Package channel enumeration
interface IPackageChannelInfo
{
String Name { get; };
PackageVersionId LatestVersion { get; };
UInt32 VersionCount { get; };
}
```

### PowerShell Cmdlets

```powershell
# Install on a channel
Install-WinGetPackage -Id "Google.Chrome" -Channel "Dev"

# List with channel info
Get-WinGetPackage -IncludeChannel

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a separate switch? Users can use Select-Object -Property for showing specific properties


# See available channels
Show-WinGetPackage -Id "Google.Chrome" -Channels

# Upgrade within channel (automatic)
Update-WinGetPackage -Id "Google.Chrome" # uses subscribed channel
```

### Migration Path for Existing Packages

Packages currently using separate IDs for channels (e.g., `Google.Chrome.Beta`):

**Phase 1 β€” Introduction (v1.29.0):**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove version information

- Add `Channel` field to the unified package ID in parallel with existing separate-ID packages
- Both `Google.Chrome` with `Channel: Beta` AND `Google.Chrome.Beta` exist simultaneously

**Phase 2 β€” Transition (v1.30.0):**
- `Google.Chrome.Beta` marked as deprecated alias pointing to `Google.Chrome` channel `Beta`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no alias function currently. This would rely on package tombstones

- Installing `Google.Chrome.Beta` shows: "This package ID is deprecated. Use 'winget install Google.Chrome --channel beta' instead."
- Upgrades of `Google.Chrome.Beta` transparently resolve to `Google.Chrome` channel `Beta`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need more detail on how this would be achieved - no mechanism exists today


**Phase 3 β€” Deprecation (v1.31.0+):**

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove version information

- `Google.Chrome.Beta` stops receiving new version submissions
- Existing installs continue to work (alias resolution)
- `winget upgrade` for aliased packages offers migration

> [!IMPORTANT]
> WinGet 1.X does not make breaking changes. Aliased packages continue to resolve indefinitely. The community repository schema version policy (n/n-1) drives publisher adoption of the unified model.

### WinGet Configuration / DSC

```yaml
resources:
- resource: Microsoft.WinGet/Package
settings:
id: Google.Chrome
channel: Dev
ensure: latest
```

The `channel` property is added to the `Microsoft.WinGet/Package` resource schema.

### Validation Pipeline Impact

- `winget-pkgs` validation must handle manifests with `Channel` field
- Channel names are validated against the allowed character set
- Multiple versions of the same package with different channels can exist (this is the point)
- Existing submission workflows unchanged for packages not using channels

### Cross-Repository Impact

- **winget-cli** β€” Channel-aware version resolver, tracking database, CLI arguments, settings, GPO
- **winget-pkgs** β€” Schema validation for `Channel` field, parallel versions per channel allowed
- **winget-create** β€” Support `Channel` field in manifest creation/update
- **winget-cli-restsource** β€” REST API filter/query by channel

### Schema Version

Requires manifest schema version 1.29.0 for the `Channel` field.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove version-specific information


## UI/UX Design

### `winget show --channels`:

```
> winget show Google.Chrome --channels
Google Chrome [Google.Chrome]

Available channels:
Channel Latest Version Versions
─────────────────────────────────────────
Stable 127.0.6478.0 152
Beta 127.0.6500.0 48
Dev 127.0.6533.0 51
Canary 128.0.6550.0 203
```

### `winget upgrade` with channels:

```
> winget upgrade --include-channel
The following packages have updates available:

Name Id Channel Installed Available Source
──────────────────────────────────────────────────────────────────────────────────
Google Chrome Google.Chrome Dev 127.0.6510.0 127.0.6533.0 winget
Node.js OpenJS.NodeJS LTS 20.13.0 20.14.0 winget
VS Code Microsoft.VS.. Insiders 1.91.0 1.92.0 winget

3 upgrades available (within subscribed channels).
```

### Channel switch prompt:

```
> winget install Google.Chrome --channel stable
Google Chrome [Google.Chrome] is currently installed from the Dev channel (127.0.6533.0).
The latest Stable version is 127.0.6478.0 (older than currently installed).

Switch from Dev to Stable? This will not downgrade the current installation,
but future upgrades will come from the Stable channel. [y/N]:
```

### `--disable-interactivity` with channel switch:

Channel switch fails with error β€” explicit user consent required. Use `--force` to override.

## Capabilities

### Accessibility

- Channel information is text-based, screen-reader accessible
- No color-only indicators for channel status
- Channel names are simple strings β€” no special characters that screen readers might misread

### Security

- Channels do not bypass security validation β€” all versions undergo the same SmartScreen/hash/signature checks
- GPO `AllowedChannels` prevents users from subscribing to unstable channels on managed devices
- No security regression from supporting multiple channels

### Reliability

- Eliminates accidental cross-channel upgrades (the primary reliability improvement)
- `winget upgrade --all` becomes safe for users on non-stable channels
- Channel subscription persists across WinGet updates

### Compatibility

- **Fully backwards compatible** β€” packages without `Channel` field behave exactly as today
- Older clients ignore the `Channel` field and see all versions in a flat list (they may offer cross-channel "upgrades" β€” existing behavior)
- The separate-ID pattern (`Google.Chrome.Beta`) continues to work indefinitely
- Schema version 1.28.x manifests without `Channel` are implicitly on `stable`

### Performance, Power, and Efficiency

- Channel filtering is a lightweight in-memory operation on the version list
- No additional network calls β€” channel is part of manifest metadata in the source index
- Tracking database addition is a single column β€” negligible storage impact

## Potential Issues

1. **Publisher adoption** β€” Requires publishers to add `Channel` fields. Until they do, separate-ID patterns persist. Community PRs can backfill.
2. **Channel name inconsistency** β€” Publishers may use `beta` vs `Beta` vs `preview`. Case-insensitive matching helps; recommended vocabulary in docs provides guidance.
3. **ARP correlation complexity** β€” Detecting which channel is installed from registry entries is hard (Chrome Dev and Chrome Stable have different ARP entries, but some apps don't differentiate). May need manifest-level `AppsAndFeaturesEntries` per channel.
4. **Version ordering** β€” A Dev version (128.0.x) is numerically "higher" than Stable (127.0.x). Channel filtering prevents cross-channel comparison, but UI must make this clear.
5. **REST source compatibility** β€” REST sources need schema updates to support channel-filtered queries efficiently. Until updated, REST sources serve all versions and client filters locally.
6. **Side-by-side installation** β€” This spec does NOT address installing multiple channels simultaneously (that's [#2129](https://github.com/microsoft/winget-cli/issues/2129)). A user subscribed to Dev cannot also have Stable installed via WinGet.

## Deprecation Path

For packages migrating from separate IDs to unified ID + Channel:

| Phase | Timeframe | Behavior |
|-------|-----------|----------|
| Introduction | v1.29.0 | Both old (separate ID) and new (Channel) exist |
| Transition | v1.30.0 | Old ID shows deprecation notice, alias resolution added |
| Deprecation | v1.31.0+ | Old ID stops receiving new submissions |
| Removal | Never in 1.X | Old IDs continue resolving via alias |

Adoption will be driven by the winget-pkgs schema version policy (n/n-1) which encourages publishers to use the latest schema.

## Future Considerations

- **Channel-aware pinning** β€” `winget pin add --channel stable` prevents channel drift
- **Side-by-side channels** β€” Install both Stable and Dev simultaneously (separate feature, requires different approach)
- **Channel notifications** β€” Alert when a new channel becomes available
- **Automatic channel fallback** β€” If a channel stops publishing, suggest switching to next-stable
- **REST API channel filtering** β€” Server-side filtering for efficiency

## Resources

- Original issue: https://github.com/microsoft/winget-cli/issues/147
- Side-by-side packages: https://github.com/microsoft/winget-cli/issues/2129
- npm dist-tags (similar concept): https://docs.npmjs.com/cli/dist-tag
- Chrome release channels: https://www.chromium.org/getting-involved/dev-channel/
- Node.js release schedule: https://nodejs.org/en/about/releases/
Loading