Repository navigation
Build the wp-admin menu count badge + its global count bundle #13359
Description
Feature Description
WordPress has a long-established way of telling an admin there's something waiting for them: a small count bubble on the menu item, as plugin updates and pending comments use. The hub's Add Features menu item (#13244) uses the same convention — a count of the features that are new to this user — and, crucially, shows it wherever the user happens to be in wp-admin, not only on Site Kit's own pages. That's the point of it: it reaches a user who isn't looking at Site Kit at all.
What makes this more than a menu tweak is where the number comes from. What counts as new is per user, and whether a feature is already set up is a live check against the user's services (#13246, #13247), so the count can only be worked out where Site Kit's data layer is running. So Site Kit works it out on the pages where it already runs — the hub, both dashboards, and the Site Kit widget on the WordPress dashboard, which is the page WordPress lands an admin on when they sign in — remembers it in the browser, and a very small script on every admin screen renders what was remembered. One place decides the number, one place draws it, and no rule about newness is duplicated anywhere.
The trade-off is deliberate: the count is as fresh as the last time the user was on a Site Kit page. When something has happened that could have changed it — Site Kit updating and bringing new features with it, or another administrator connecting a service — the remembered number can no longer be trusted, and the menu item shows a plain dot with no number until it's worked out again. A dot still says there's something new here, without asserting a number that might be wrong.
For reference, see The global count bundle and Entry points & new-feature indicators in the design doc, and the menu badge in Figma.
Do not alter or remove anything below. The following sections will be managed by moderators only.
Acceptance criteria
- The Add Features item in the Site Kit menu (Stand up the hub page:
Screen, entrypoint & view context #13244) carries a count bubble showing how many features are new to this user and not yet seen (Implement the newness model & derived selectors #13246) — the same number behind the dashboard header pill's dot (Build the dashboard header pill (AddFeaturesButton) #13358). - The bubble uses WordPress's own menu count styling, as plugin updates and pending comments do, and its meaning is available to screen readers.
- The bubble is shown on every wp-admin screen, not only Site Kit's own pages.
- When the count is zero, no bubble is shown.
- The count is worked out and remembered whenever the user loads a page where Site Kit derives it: the hub, the main dashboard, the entity dashboard, and the Site Kit widget on the WordPress dashboard.
- The remembered count is used only while it still matches the plugin version and the site's service connection state it was worked out against. If either has changed since — Site Kit was updated, or another administrator connected a service — the item shows a plain dot with no number, until the count is next worked out on one of those pages.
- Visiting What's new? (Build the "What's new?" tab #13321) clears the count, and any other wp-admin tabs the user has open update to match without needing to be reloaded.
- Logging out clears the remembered count, so the next person to use that browser doesn't see it.
- The count is remembered per browser, so a user signing in on a second browser sees no bubble there until they load a Site Kit page.
- The badge is loaded only for administrators, and only when the
featureDiscoveryHubfeature flag is enabled — so only users who have an Add Features item to badge carry any of this. - What's added to every admin screen is deliberately minimal: it reads the remembered count and renders the bubble. It loads no Site Kit data stores, no React, and makes no API requests.
- If the browser can't store the count, no bubble is shown and nothing else is affected.
Implementation Brief
- In
includes/Core/Admin/Screens.php:- For the
googlesitekit-featuresscreen, add the following to themenu_title:<span class="menu-counter googlesitekit-features-badge count-0"> <span aria-hidden="true" class="count"></span> <span class="screen-reader-text"></span> </span>
- Use
__( 'Add Features %s', 'google-site-kit' )andsprintf()for the appending, for consistency with core's example.
- For the
- Create
assets/js/util/features-badge.ts:- Define const
FEATURE_COUNT_CACHE_KEY–googlesitekit::feature-count. - Define const
FEATURE_COUNT_CHANNEL_NAME–googlesitekit::feature-count. - Define const
FEATURE_COUNT_CHANNEL_MESSAGES.UPDATED–'updated' - Define interface
FeatureCountFingerprint–{ connectedModules: string[], pluginVersion: string, userID: number }. - Define interface
FeatureCountCache–extends FeatureCountFingerprint { count: number }. - Export function
isFeatureCountCache( value: unknown ): value is FeatureCountCache:- Check
valueis an object with properties consistent with aFeatureCountCache. value is FeatureCountCacheis a type predicate, which helps TypeScript recognise that anyvaluethat passes the check is aFeatureCountCache.
- Check
- Define function
getFeatureCountCache(): FeatureCountCache | null:- Get cache from local storage with
FEATURE_COUNT_CACHE_KEY. - Parse JSON and check
isFeatureCountCache(). - Return result or
nullon parsing error or check failure.
- Get cache from local storage with
- Export function
clearFeatureCountCache():- Clear cache from local storage with
FEATURE_COUNT_CACHE_KEY. - Post
FEATURE_COUNT_CHANNEL_MESSAGES.UPDATEDas a message on aBroadcastChannelforFEATURE_COUNT_CHANNEL_NAME.
- Clear cache from local storage with
- Export function
setFeatureCountCache( featureCountCache: FeatureCountCache ):- Check
isFeatureCountCache()and stringify. - Return on error or check failure.
- Set cache in local storage with
FEATURE_COUNT_CACHE_KEY. - Post
FEATURE_COUNT_CHANNEL_MESSAGES.UPDATEDas a message onFEATURE_COUNT_CHANNEL_NAME.
- Check
- Export function
renderFeaturesBadge( count: number, showCount: boolean ):- Find
.googlesitekit-features-badgeelement. - Return if it doesn't exist.
- Set text content of the
.countelement tocountifshowCount, otherwise an empty string. - Replace the existing
count-*class withcount-${count}. - Set text content of the
screen-reader-textelement with:- If
showCount–_n( '%d new feature', '%d new features', count, 'google-site-kit' ) - Otherwise
__( 'new features', 'google-site-kit' ).
- If
- Find
- Export function
renderFeaturesBadgeFromCache( fingerprint: FeatureCountFingerprint ):- Get the cache with
getFeatureCountCache(). - If one is found:
- Call
clearFeatureCountCache()and return iffingerprint.userIDdoesn't match the cache'suserID. - Call
renderFeaturesBadge()with the cache count, and passshowCountastrueif the fingerprint version and connected modules match the cache's.
- Call
- Otherwise call
renderFeaturesBadge( 0, false ).
- Get the cache with
- Make sure to
try/catchwithlocalStorageandBroadcastChannel, failing silently.
- Define const
- Create
assets/js/components/feature-discovery/useFeatureCountCache.ts:- Export hook
useFeatureCountCache():- Keep track of whether
getModuleshas finished resolution. - Get
connectedModulesby selectingCORE_MODULES.getModules()and usingCORE_MODULES.isModuleConnected(). - Get
countfromCORE_FEATURE_DISCOVERY.getNewFeatureCount(). - Get
pluginVersionfromglobal.GOOGLESITEKIT_VERSION. - Get
userIDfromCORE_USER.getID(). - Call
setFeatureCountCache()with those values in an effect if selectors are resolved andcountis notundefined.
- Keep track of whether
- Export hook
- In each of the following components, import and call
useFeatureCountCache():assets/js/components/DashboardMainApp.jsassets/js/components/DashboardEntityApp.jsassets/js/components/feature-discovery/FeatureDiscoveryApp.tsxassets/js/components/wp-dashboard/WPDashboardApp.js
- Create
assets/js/googlesitekit-features-badge.ts:- Import
clearFeatureCountCache,FEATURE_COUNT_CHANNEL_NAME,renderFeaturesBadgeFromCache. - Get
connectedModules,pluginVersion,resetSession,userIDfrom_googlesitekitFeaturesBadgeData. - Define
featureCountChannel–new BroadcastChannel( FEATURE_COUNT_CHANNEL_NAME ). - Define function
onMessage( event: MessageEvent ):- If the message is
FEATURE_COUNT_CHANNEL_MESSAGES.UPDATED, callrenderFeaturesBadgeFromCache({ connectedModules, pluginVersion, userID }).
- If the message is
- Define function
setupFeaturesBadge():- Bind
onMessageto themessageevent on aBroadcastChannelforFEATURE_COUNT_CHANNEL_NAME. - Call
clearFeatureCountCache()ifresetSession. - Call
renderFeaturesBadgeFromCache({ connectedModules, pluginVersion, userID }).
- Bind
- Call
setupFeaturesBadge().
- Import
- In
includes/Core/Modules/Modules.php:- In
register()add a filter togooglesitekit_connected_modulesthat returns$this->get_connected_modules().
- In
- In
includes/Core/Assets/Assets.php:- Add a
get_inline_features_badge_data()method returning:connectedModules– Slugs of modules fromModules::get_connected_modules()by applying thegooglesitekit_connected_modulesfilter added above to an empty array.pluginVersion–GOOGLESITEKIT_VERSION.resetSession– Whether thegooglesitekit_reset_sessionURL parameter is true.userID–get_current_user_id().
- In
get_assets(), if the user hasPermissions::MANAGE_OPTIONSandfeatureDiscoveryHubis enabled:- Add
googlesitekit-features-badge-dataas aScript_Data:global–_googlesitekitFeaturesBadgeData.data_callback– Returnget_inline_features_badge_data().
- Add
assets/js/googlesitekit-features-badge.tsas aScript:dependencies–googlesitekit-i18nandgooglesitekit-features-badge-data.load_contexts–Asset::CONTEXT_ADMIN_GLOBAL.
- Add
- In
register():- Add an action to
admin_enqueue_scriptsthat enqueues scripts withAsset::CONTEXT_ADMIN_GLOBAL, like the existingenqueue_block_assetsandenqueue_block_editor_assetsexamples.
- Add an action to
- Add a
- In
assets/webpack/basicModules.config.js- Add an entry for
googlesitekit-features-badge.
- Add an entry for
- In
assets/js/types/globals.d.ts- Type
_googlesitekitFeaturesBadgeData.
- Type
Test Coverage
- Add unit test coverage for:
features-badge.ts, checking cache validation, rendering results and fingerprint comparisons.useFeatureCountCache.ts, checking the cache updates when expected.googlesitekit-features-badge.ts, checking event handling.
- Update unit test coverage for
Core\Assetsto checkget_inline_features_badge_data()returns the expected data.
QA Brief
- Set up a new Site Kit site and enable the
featureDiscoveryHubfeature flag. - Connect a second admin account to Site Kit. Log in as the original account before proceeding.
- In the Site Kit admin sub-menu an Add Features item should be visible, without any count or badge.
- With the tester plugin set Force initial Site Kit plugin version to
1.84.0. - Navigate to Pages > All Pages. Hover over the Site Kit menu item, no badge should appear.
- Navigate to Site Kit > Dashboard. A "5" badge should appear next to _Add Features.
- Navigate back to Pages > All Pages. The "5" badge should persist.
- Keep that page open and in an incognito window, or another browser, log in and connect Analytics. Once Analytics is fully connected the badge should read "8".
- Return to the original browser and refresh. Hover over the Site Kit admin menu item. In the sub menu a dot should be shown next to Add Features with no number.
- Navigate to Posts > All Posts. The numberless dot should persist.
- Navigate to Dashboard > Home, the WordPress dashboard. The number should update to "8".
- Open a new a tab in the same window. In that window disconnect Analytics. The badge should become a numberless dot. Navigate to Site Kit > Dashboard and it should return to "5".
- Return to the original tab and hover over the Site Kit admin menu. The badge should have changed to a numberless dot. Refresh and it should update to "5".
- Navigate to Site Kit > Dashboard, open a new tab and re-connect Analytics, when returning to the dashboard the number should update to "8". Return to the original tab and the number should also have updated to "8".
- Log out of WordPress and attempt to visit
/wp-admin/edit.php(to ensure a login redirect to a non-Site Kit page). Log in and hover over the Site Kit admin menu and the badge should read "8". - Log out and and attempt to visit
/wp-admin/edit.phpagain, but log in as the second admin user. The badge should not appear. - Navigate to Site Kit > Dashboard and the number should update to "8".
- Open a second tab to Posts > All Posts. Return to the tab on the Site Kit dashboard.
- In the browser console, run
googlesitekit.data.dispatch( 'core/feature-discovery' ).markFeaturesSeen( [ 'email-reports' ] );. The number should update to "7" in both tabs. - Log in as the original admin and return to the Site Kit dashboard. The number should be "8".
Changelog entry
- Show the new feature count in the "Add Features" admin menu item.
Activity
JakePT commentedon Sep 2, 2026
Moving to IBR, just flagging a couple of things:
storageevent I opted to use aBroadcastChannel. This way all live rendering of the badge can happen in a single message handler in the standalone file, rather than in separate handlers for other tabs and the current tab, or rendering from both the cache setter in the hook and a storage event handler from different files.admin_enqueue_scriptswon't enqueue the script on the logout URL, and listening for logout link clicks seemed messy, so instead I include a user ID in the cache and inline data and clear the cache before attempting to render if they don't match. So the key is cleared when somebody else logs in, rather than when the original person logs out.techanvil commentedon Sep 9, 2026
Thanks @JakePT. This is looking good (although somewhat verbose, as per my note below). A few points:
googlesitekit_connected_modulesfilter toModules(similar togooglesitekit_module_existsetc), in order to expose the list of modules toAssets.10 remaining items
kelvinballoo commentedon Sep 28, 2026
QA Update⚠️
Hi @JakePT , I tested this and it's looking good across all.
I just have 2 clarifications:
ITEM 1:
Log out of WordPress and attempt to visit /wp-admin/edit.php (to ensure a login redirect to a non-Site Kit page). Log in and hover over the Site Kit admin menu and the badge should read "8".When I log in as the same admin in incognito it will not show any number or dot, until I go to the SK dashboard
If I do it in the original browser that I was in (ie. not in incognito), then it will show the updated number.
ITEM 2:
Instead of having number '8', I had '9' and instead of '5', I had '6'.
I assume that's fine as it depends on the number of features and that could change.
But just in case, could you help to confirm before I move it to approval.
JakePT commentedon Sep 28, 2026
@kelvinballoo
kelvinballoo commentedon Sep 28, 2026
QA Update ✅
Thanks for confirming @JakePT
Moving to approval as this is verified good.
Badge behaves as expected: On Pages > All Pages, hovering over the Site Kit menu item showed no badge. On Site Kit > Dashboard, a "6" badge appeared next to Add Features, and it was still there after going back to Pages > All Pages.
Details
13359.-.01.-.Features.show.up.pages.dashboard.pages.mov
Badge updates as expected: With the original page still open, I logged in from an incognito window and connected Analytics, and after that the badge read "9". Back in the original browser, after a refresh, hovering over the Site Kit menu item showed a dot with no number next to Add Features, and it stayed that way on Posts > All Posts. On Dashboard > Home, the badge changed to "9".
Details
13359.-.02.-.Connect.analytics.+.return.to.main.480p.mov
Badge updates as expected: After disconnecting Analytics in a new tab, the badge turned into a dot with no number, then read "6" again on Site Kit > Dashboard. Back in the original tab, hovering over the Site Kit menu showed the numberless dot, and after a refresh it read "6".
Details
13359.-.03.-.disconnect.analytics.in.new.tab.mov
Badge updates as expected: Starting from Site Kit > Dashboard, I reconnected Analytics in a new tab, and the badge read "9" back on the dashboard. The original tab also read "9" when I went back to it.
Details
13359.-.04.-.Reconnect.ana.mov
Badge shows after login: I logged out of WordPress and went to /wp-admin/edit.php, which sent me to the login page and then back to a non-Site Kit page. After logging in, hovering over the Site Kit menu showed a badge reading "9". Note that in the image attached, I've put this as fail but it's actually pass as per clarified with Jake Above.
Details
13359.-.05.-.log.out.and.log.in.as.same.admin.-.not.ok.-.8.doesn.t.appear.480p.mov
Badge is tracked separately for each user: I logged out, went to /wp-admin/edit.php, and logged in as the second admin. No badge appeared at first, and it read "9" on Site Kit > Dashboard. With Posts > All Posts open in a second tab, I ran markFeaturesSeen( [ 'email-reports' ] ) from the dashboard tab, and both tabs changed to "8". Logged back in as the original admin, the Site Kit dashboard still read "9".
Details
13359.-.06.last.ok.480p.mov