Skip to content

feat: add id, groupId, and groupTitle support for Windows notifications#50328

Merged
VerteDinde merged 9 commits intoelectron:mainfrom
bitdisaster:notification-id-for-windows
Apr 10, 2026
Merged

feat: add id, groupId, and groupTitle support for Windows notifications#50328
VerteDinde merged 9 commits intoelectron:mainfrom
bitdisaster:notification-id-for-windows

Conversation

@bitdisaster
Copy link
Copy Markdown
Contributor

@bitdisaster bitdisaster commented Mar 18, 2026

Description of Change

This PR extends the Windows notification implementation to support the id, groupId, and groupTitle properties, bringing feature parity with macOS and adding Windows-specific header grouping support.

Changes

New Features:
  • id property now works on Windows — maps directly to the toast notification’s Tag property for deduplication and removal.
  • groupId property now works on Windows — it serves two purposes:
    • together with id it uniquely identifies a notification in memory
    • maps to the toast notification’s header id property for grouping in Action Center
  • groupTitle property (Windows only) — when both groupId and groupTitle are specified, a <header> element is generated in the toast XML to display a visual group header.
Validation:
  • Added length validation for id and groupId on Windows (max 64 UTF-16 characters) with descriptive error messages.
Internal Changes:
  • Removed FastHash encoding of notification IDs — the raw id is now used directly as the Windows toast Tag.
  • Added debug logging for group and tag values when ELECTRON_DEBUG_NOTIFICATIONS is set.
Tests:
  • Extended existing macOS tests to also run on Windows (id, groupId properties).
  • Added Windows-specific tests for groupTitle.
  • Added validation tests for length limits.

Example Usage

const { Notification } = require('electron')

// Basic usage with id and groupId
const n = new Notification({
  id: 'msg-123',
  groupId: 'chat-channel-1',
  title: 'New Message',
  body: 'Hello!'
})

// With header grouping (Windows only)
const n2 = new Notification({
  id: 'msg-456',
  groupId: 'camping-trip',
  groupTitle: 'Camping Trip 🏕️',
  title: 'New Message',
  body: 'Are you coming?'
})
image

Checklist

Release Notes

Notes: Added id, groupId, and groupTitle support for Windows notifications

@electron-cation electron-cation Bot added the new-pr 🌱 PR opened recently label Mar 18, 2026
@VerteDinde VerteDinde added semver/minor backwards-compatible functionality target/42-x-y PR should also be added to the "42-x-y" branch. labels Mar 18, 2026
@bitdisaster bitdisaster force-pushed the notification-id-for-windows branch from fa28467 to 6644704 Compare March 20, 2026 18:34
@electron-cation electron-cation Bot removed the new-pr 🌱 PR opened recently label Mar 25, 2026
Comment thread docs/api/notification.md Outdated
Copy link
Copy Markdown
Member

@erickzhao erickzhao left a comment

Choose a reason for hiding this comment

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

API LGTM

Copy link
Copy Markdown
Member

@itsananderson itsananderson left a comment

Choose a reason for hiding this comment

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

API LGTM

Copy link
Copy Markdown
Member

@codebytere codebytere left a comment

Choose a reason for hiding this comment

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

generally lgtm with a few comments

Comment thread shell/browser/notifications/win/windows_toast_notification.cc
return {};
}
}
#endif
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We're parsing the options dict twice — once here via PeekNext + ConvertFromV8 to validate, and then again inside the Notification constructor. Imo that's a bit redundant and fragile since the two parse sites could diverge.

The gin::Dictionary(nullptr) + the IsEmpty() || IsUndefined() || ConvertFromV8(...)` conditional is also a bit hard to follow — the first two branches fall through into the body with a null-initialized dictionary, which feels like it shouldn't work.

Could we instead construct the Notification first and then validate the already-parsed members before returning the handle? Something like:

auto handle = gin_helper::CreateHandle(thrower.isolate(), new Notification(args));

#if BUILDFLAG(IS_WIN)
  constexpr size_t kMaxTagLength = 64;
  auto* notif = handle.get();
  if (!notif->id_.empty() &&
      base::UTF8ToWide(notif->id_).length() > kMaxTagLength) {
    thrower.ThrowError(
        "Notification id exceeds Windows limit of 64 UTF-16 characters");
    return {};
  }
  if (!notif->group_id_.empty() &&
      base::UTF8ToWide(notif->group_id_).length() > kMaxTagLength) {
    thrower.ThrowError(
        "Notification groupId exceeds Windows limit of 64 UTF-16 characters");
    return {};
  }
#endif

return handle;

That way we validate against the actual values that'll be used at Show() time, and there's a single parse path for the options.

I recognize the downside here that we construct the object before potentially discarding it on the error path, but the constructor is side-effect-free, and CreateHandle + return {} is already the pattern used for the "app not ready" early return above, so it should be safe?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Comment thread shell/browser/notifications/win/windows_toast_notification.cc
@bitdisaster bitdisaster requested a review from codebytere April 9, 2026 14:55
@bitdisaster bitdisaster force-pushed the notification-id-for-windows branch from 4013227 to 8cd36bc Compare April 9, 2026 22:11
@VerteDinde VerteDinde merged commit 20ed34a into electron:main Apr 10, 2026
62 of 63 checks passed
@release-clerk
Copy link
Copy Markdown

release-clerk Bot commented Apr 10, 2026

Release Notes Persisted

Added id, groupId, and groupTitle support for Windows notifications

@trop
Copy link
Copy Markdown
Contributor

trop Bot commented Apr 10, 2026

I have automatically backported this PR to "42-x-y", please check out #50895

@trop trop Bot added in-flight/42-x-y merged/42-x-y PR was merged to the "42-x-y" branch. and removed target/42-x-y PR should also be added to the "42-x-y" branch. in-flight/42-x-y labels Apr 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api-review/approved ✅ merged/42-x-y PR was merged to the "42-x-y" branch. semver/minor backwards-compatible functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants