You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Build the wp-admin menu count badge + its global count bundle #13359
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.
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 featureDiscoveryHub feature 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-features screen, add the following to the menu_title:
In register() add a filter to googlesitekit_connected_modules that returns $this->get_connected_modules().
In includes/Core/Assets/Assets.php:
Add a get_inline_features_badge_data() method returning:
connectedModules – Slugs of modules from Modules::get_connected_modules() by applying the googlesitekit_connected_modules filter added above to an empty array.
pluginVersion – GOOGLESITEKIT_VERSION.
resetSession – Whether the googlesitekit_reset_session URL parameter is true.
userID – get_current_user_id().
In get_assets(), if the user has Permissions::MANAGE_OPTIONS and featureDiscoveryHub is enabled:
Add googlesitekit-features-badge-data as a Script_Data:
Add assets/js/googlesitekit-features-badge.ts as a Script:
dependencies – googlesitekit-i18n and googlesitekit-features-badge-data.
load_contexts – Asset::CONTEXT_ADMIN_GLOBAL.
In register():
Add an action to admin_enqueue_scripts that enqueues scripts with Asset::CONTEXT_ADMIN_GLOBAL, like the existing enqueue_block_assets and enqueue_block_editor_assets examples.
In assets/webpack/basicModules.config.js
Add an entry for googlesitekit-features-badge.
In assets/js/types/globals.d.ts
Type _googlesitekitFeaturesBadgeData.
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\Assets to check get_inline_features_badge_data() returns the expected data.
QA Brief
Set up a new Site Kit site and enable the featureDiscoveryHub feature 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.php again, 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.
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
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).featureDiscoveryHubfeature flag is enabled — so only users who have an Add Features item to badge carry any of this.Implementation Brief
includes/Core/Admin/Screens.php:googlesitekit-featuresscreen, add the following to themenu_title:__( 'Add Features %s', 'google-site-kit' )andsprintf()for the appending, for consistency with core's example.assets/js/util/features-badge.ts:FEATURE_COUNT_CACHE_KEY–googlesitekit::feature-count.FEATURE_COUNT_CHANNEL_NAME–googlesitekit::feature-count.FEATURE_COUNT_CHANNEL_MESSAGES.UPDATED–'updated'FeatureCountFingerprint–{ connectedModules: string[], pluginVersion: string, userID: number }.FeatureCountCache–extends FeatureCountFingerprint { count: number }.isFeatureCountCache( value: unknown ): value is FeatureCountCache: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.getFeatureCountCache(): FeatureCountCache | null:FEATURE_COUNT_CACHE_KEY.isFeatureCountCache().nullon parsing error or check failure.clearFeatureCountCache():FEATURE_COUNT_CACHE_KEY.FEATURE_COUNT_CHANNEL_MESSAGES.UPDATEDas a message on aBroadcastChannelforFEATURE_COUNT_CHANNEL_NAME.setFeatureCountCache( featureCountCache: FeatureCountCache ):isFeatureCountCache()and stringify.FEATURE_COUNT_CACHE_KEY.FEATURE_COUNT_CHANNEL_MESSAGES.UPDATEDas a message onFEATURE_COUNT_CHANNEL_NAME.renderFeaturesBadge( count: number, showCount: boolean ):.googlesitekit-features-badgeelement..countelement tocountifshowCount, otherwise an empty string.count-*class withcount-${count}.screen-reader-textelement with:showCount–_n( '%d new feature', '%d new features', count, 'google-site-kit' )__( 'new features', 'google-site-kit' ).renderFeaturesBadgeFromCache( fingerprint: FeatureCountFingerprint ):getFeatureCountCache().clearFeatureCountCache()and return iffingerprint.userIDdoesn't match the cache'suserID.renderFeaturesBadge()with the cache count, and passshowCountastrueif the fingerprint version and connected modules match the cache's.renderFeaturesBadge( 0, false ).try/catchwithlocalStorageandBroadcastChannel, failing silently.assets/js/components/feature-discovery/useFeatureCountCache.ts:useFeatureCountCache():getModuleshas finished resolution.connectedModulesby selectingCORE_MODULES.getModules()and usingCORE_MODULES.isModuleConnected().countfromCORE_FEATURE_DISCOVERY.getNewFeatureCount().pluginVersionfromglobal.GOOGLESITEKIT_VERSION.userIDfromCORE_USER.getID().setFeatureCountCache()with those values in an effect if selectors are resolved andcountis notundefined.useFeatureCountCache():assets/js/components/DashboardMainApp.jsassets/js/components/DashboardEntityApp.jsassets/js/components/feature-discovery/FeatureDiscoveryApp.tsxassets/js/components/wp-dashboard/WPDashboardApp.jsassets/js/googlesitekit-features-badge.ts:clearFeatureCountCache,FEATURE_COUNT_CHANNEL_NAME,renderFeaturesBadgeFromCache.connectedModules,pluginVersion,resetSession,userIDfrom_googlesitekitFeaturesBadgeData.featureCountChannel–new BroadcastChannel( FEATURE_COUNT_CHANNEL_NAME ).onMessage( event: MessageEvent ):FEATURE_COUNT_CHANNEL_MESSAGES.UPDATED, callrenderFeaturesBadgeFromCache({ connectedModules, pluginVersion, userID }).setupFeaturesBadge():onMessageto themessageevent on aBroadcastChannelforFEATURE_COUNT_CHANNEL_NAME.clearFeatureCountCache()ifresetSession.renderFeaturesBadgeFromCache({ connectedModules, pluginVersion, userID }).setupFeaturesBadge().includes/Core/Modules/Modules.php:register()add a filter togooglesitekit_connected_modulesthat returns$this->get_connected_modules().includes/Core/Assets/Assets.php: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().get_assets(), if the user hasPermissions::MANAGE_OPTIONSandfeatureDiscoveryHubis enabled:googlesitekit-features-badge-dataas aScript_Data:global–_googlesitekitFeaturesBadgeData.data_callback– Returnget_inline_features_badge_data().assets/js/googlesitekit-features-badge.tsas aScript:dependencies–googlesitekit-i18nandgooglesitekit-features-badge-data.load_contexts–Asset::CONTEXT_ADMIN_GLOBAL.register():admin_enqueue_scriptsthat enqueues scripts withAsset::CONTEXT_ADMIN_GLOBAL, like the existingenqueue_block_assetsandenqueue_block_editor_assetsexamples.assets/webpack/basicModules.config.jsgooglesitekit-features-badge.assets/js/types/globals.d.ts_googlesitekitFeaturesBadgeData.Test Coverage
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.Core\Assetsto checkget_inline_features_badge_data()returns the expected data.QA Brief
featureDiscoveryHubfeature flag.1.84.0./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"./wp-admin/edit.phpagain, but log in as the second admin user. The badge should not appear.googlesitekit.data.dispatch( 'core/feature-discovery' ).markFeaturesSeen( [ 'email-reports' ] );. The number should update to "7" in both tabs.Changelog entry