site-kit-wp
site-kit-wp copied to clipboard
Add Google Analytics page views count to WP Admin's Posts list view.
Feature Description
Considering adding a total Google Analytics view count on both the pages and posts tab, as per the screenshot below, once Site Kit is set up with Analytics connected. This could be a sortable column, as some of the other columns are within the same list views.

This is a feature request on behalf of one user in the support forums.
Do not alter or remove anything below. The following sections will be managed by moderators only.
Acceptance criteria
- Given GA4 is connected, when an admin visits the Posts list view, then they should see a new column as follows:
- Column title: GA Page Views (translatable string)
- Column data: Metric should be
screenPageViewsand dimension should bepagePathPlusQueryStringfor the Post's URL. (Use existing GA4 datastore infrastructure to fetch this data). - The column should appear after the "Author" column (if it exists), otherwise it can appear as the column before the "Date".
- The column should not be sortable, see: https://github.com/google/site-kit-wp/issues/5418#issuecomment-1420564334
Implementation Brief
For overview and clarity
The approach will use transient data to store post IDs, which maps to associative array holding itβs permalinks and views. ID will be used to match the data. Transient should be stored for 24 hours to minimize calls to generate the report only when necessary - otherwise it will be updated earlier in case when new post is added, or usage of pagination/filter. Visible posts permalinks on admin list view will be used with inListFilter dimension filter to make 1 batch request to pull the data. Transient will be allways replaced with new data, so at most it will hold 100 posts if user set 100 rows per table in the view which in most cases will be default 20. This data will be matched with $post_id in new post column to display views.
New class to handle transient data and post columns
- Create new class, e.g.
includes/Modules/Analytics_4/Post_List_View_Analytics_Columnthat will handle the report data and analytics column modification- The class constructor will accept a
$transientsinstance. - Add methods to interact with transient data, you can see example in
is_data_availableandset_data_availablemethods ofCustom_Dimensions_Data_Availableclass. You can name themget_page_views_column_dataandset_page_views_column_datafor example, and include method for deleting the transientdelete_page_views_column_data. And transient can be namedga4_page_views.
- The class constructor will accept a
Manage columns
- Create a method, say
manage_columns- Hook into
manage_posts_columns, this hook passes$columnsparam. Define new variablenew_columnsas an empty array, which should hold the columns. - Iterate through
$columnsto find thecommentskey. Once found, add a new column, represented by the keyga4_page_views, to the array. Use translatable stringGA Page Viewsas value. - If no column with
commentskey is not found, check fordateto pushga4_page_viewsbefore it - After the checks just push original column to maintain the original array like
$new_columns[$column_key] = $column_value;. - In case no
commentsanddatecolumns were found, addga4_page_viewsat the end as last to ensure column is present
- Hook into
- Hook into
manage_posts_custom_columnwhich will populate the data, it accepts 2 params$column_nameand,$post_id- Check if current
$column_nameis notga4_page_viewsto return early - Otherwise collect transient data from
get_page_views_column_datamethod and using$post_idextract the current post data (as post IDs are the keys of stored data arrays) - If data is found,
$post_idexists in stored data array, output theviewsvalue - In case analytics is in
gathering data state, returnN/A
- Check if current
Analytics_4 class
- Define new
$post_list_view_analytics_columnprotected variable, that will hold the instance ofPost_List_View_Analytics_Column - In
__constructmethod set$post_list_view_analytics_column, by assigning it a new instance ofPost_List_View_Analytics_Column, you can follow this example - Create a new
protectedmethod, saygenerate_posts_report, which should accept$pathsarray param (which will hold flat list of posts permalinks)- Re-use part of the logic from this block
- Instead of returning error return
nullfor example to exit early - Replicate the
reportOptionsin php array, like this one. WithdimensionFilterforpagePathPlusQueryString, and keep sameinListFilter, forvalueit will accept passed$pathsarrays consisting of posts links. - Handle potential errors or failed API requests
- Instead of returning error return
- Re-use part of the logic from this block
Register method updates
- Call register method on
$post_list_view_analytics_column - Hook into
pre_get_postsand utilize the$queryparameter passed to it - Check if
! is_admin(), to return early - Check for
$query->is_main_queryandget_current_screen()->base !== 'edit-post'to return early- Then check if analytics module is connected, e.q
$this->is_connected(), and if not, return early - Otherwise means it is on the
All Postspage in the main query, with analytics 4 module being connected and it can be proceed - Continue by hooking into
posts_selection(when query will be setup), and pass the received$queryparam from previous hook- Define new variable
$current_posts_datawith empty array as value. - Then do a loop on
$query->poststo get the posts permalinks withget_permalinkfunction. Add these to the$current_posts_dataarray, using the post ID as the key. Each post ID will map to an associative array with two keys:path, holding the permalink, andviews, initially set to0to represent page views. - After the loop check if
$current_posts_datais empty to return early - Check also for
gathering data state, to return early, using$this->is_data_available() - Retrieve transient data using
get_page_views_column_datamethod of$post_list_view_analytics_columnand assign it to the variable, say$stored_data - Define
$make_new_requestvariable with valuefalse, which will be used to determine if analytics request should be made or not - If there is no
$stored_data,$make_new_requestshould be true. - Compare
$current_posts_datakeys, with$stored_datakeys (as permalinks can change), and in case of mismatch, set$make_new_requestto true - If
$make_new_requestistrue- Extract flat array of
pathfrom$current_posts_dataand use it as param ingenerate_posts_reportmethod to get report data - Update
viewsvalue in$current_posts_datafrom report response to hold the metric value (screenPageViews) for all posts, by matching theirpathwith received dimensionpagePathPlusQueryStringvalue - Save data into transient (for 24h) using
set_page_views_column_datamethod of$post_list_view_analytics_column - If there is no data returned from report, assign value
N/Ato the page views
- Extract flat array of
- Define new variable
- Then check if analytics module is connected, e.q
- When
propertyIDchanges, transient should be emptied, follow the example from this filter- Add it without feature flag
- In case values mismatch, call
delete_page_views_column_datamethod of$post_list_view_analytics_column
Test Coverage
- Add PHP unit test case for the
Analytics_4->generate_posts_reportmethod - Add tests for new
Post_List_View_Analytics_Columnclass
QA Brief
Changelog entry
following
Thank you and following!
please implement this. This feature would be very helpful both for bloggers and SEOs.
This would be a VERY useful thing for WordPress.
That's exactly on point! I hope Google implements it soon, it would make our lives so much easier ;)
+1
An option/setting/filter to save these values as post_meta would be equally as important as showing those values in the admin.
I'd love to see this implemented as well. Keenly following!
+1 Please!
+1 This would be incredibly helpful.
This would be really nice
@aaemnnosttv @marrrmarrr Have added some preliminary ACs. It makes sense to only implement this for GA4. Just need to add a bit more clarity on the "position" of this column.
I have also split this issue and created #6503 to implement the same column for the Pages list view separately.
The above will require Site Kit's assets to be loaded on the Posts wp-admin page similar to how we load them for the WP Dashboard.
In this case, wouldn't it make more sense to load the data in PHP and then serve it directly in the UI? I suppose that means a request on every page load since we don't cache data in PHP, but it seems very excessive to load an entire React app for every table row and then make a getReport call for each.
I know this is venturing into implementation, but I wonder why this needs to be part of the ACs here, as it's more a "how" than a "what" for this issue. π€
@tofumatt I completely agree - we can discuss this at the IB level and happy to remove this point from the ACs.
Cool, in that case ACs here are good; moving to IB ππ»
@jimmymadon althought I think I'm not able to write the IB, as I'm not sure about how the PHP classes should be structured, some thoughts from my side:
I don't think the sorting functionality should be included in this issue. It's one thing load the data for the shown posts on every pageload, but its something completely different to add this sorting.
The sorting is done by the database query. The post lists are paginated to 10 posts / page (can be increased to 100 posts/page), so client side sorting won't work. To implement sorting, we would need to have the post views already in the database. Therefore we would need to somehow continuously sync the page view data from Analytics to our database and save the page views as a postmeta.
We currently don't store/sync analytics data to the WordPress database. If we want to have this sorting function, we should think in depth about how such a sync could look like and create a separate issue for it.
Just for the sake of completeness: instead of keeping the postview data in the WordPress database and synchronising it continuously, we could solve the pageview sorting "on the fly" with the help of "ORDER BY CASE". But that would still mean that we would have to load the pageviews of all posts. For WordPress installations with a large number of posts, this would mean quite a performance loss. Also, this would probably lead to compatibility problems with other plugins as we would have to edit the sql query directly instead of going via WP_Query. So this approach does not make sense.
@aaemnnosttv @tofumatt Any thoughts on how to implement sorting here based on what @derweili has raised?
Oh, actually, that's a great point and sometimes I didn't fully consider π
The filed issue says:
This could be a sortable column, as some of the other columns are within the same list views.
But really only "Title" and "Date" are sortable. Sorting by page views might be useful but adds a lot of complexity and overhead for minimal value, especially as you can view top-performing pages elsewhere in Analytics views.
It's not the primary purpose of this screen to view pages by page views, so I've updated the ACs and omitted the sortable requirement from it.
Can one make an input? I think it should display the pageviews (all time or if there is a way change it between days like the sitekit page) then the sorting should be HIGHEST and LOWEST.
Currently our theme has this, views shows all time pageviews to that particular URL.
@nfmohit As @derweili pointed out above, sorting will be hard here unless you figure out something completely different. If it is too cumbersome like we predict, I can remove sorting by page views from the AC and we can continue without it for now.
Apologies, @tofumatt already made that change! π
Putting in a vote for some API to (optionally?) use CRUD functions via PHP to get/set view counts in post_meta and/or options so we can use WP_Query to get the most popular or currently trending posts according to GA.
Hi @nfmohit, thanks for drafting this IB. I have a minor point, and a more substantial aspect that we need to consider.
The minor point is that we need to ensure the column is placed in the right order as per the AC, and can refer to this comment on the linked blog post for an example on how this can be achieved.
More substantially though, while the IB as is stands would work in terms of functionality, I see a problem with the approach.
By following this approach we'd end up with a synchronous series of GA4 report requests being made on the backend before the page is even rendered. This could be any number of requests - the default number of posts per page is 20.
This could introduce an unacceptably high delay to the page loading, which I am sure our users would not appreciate.
We need to batch the Analytics metric retrieval so that only one GA4 report request is made. However, having spent some time looking into it, I don't see this being realistically achievable on the PHP side, due to the way the code is structured for the post list page (for reference, here's the entry point to the loop where the table rows are rendered). Plus - even if we were to find a way, just adding one GA4 report request can introduce a significant delay, with a variety of factors in play.
I think that a better approach here would be to add the column with a placeholder for the data in PHP, and then use JS to make the GA4 report request and populate the data on the client-side. I'm not sure exactly how we'd do this without looking into it, but I'm sure it's achievable. What do you think?
Hi
Nice work guys, but how is this coming up? its been over a year now.
Nice work guys, but how is this coming up? its been over a year now.
Hi, @iamkingsleyf. Thank you for checking in. Unfortunately, this had to fall back a little since we had some other priorities that came up. We acknowledge this feature has received quite a good number of +1s, and we will work on putting this back in the pipeline again.
Nice work guys, but how is this coming up? its been over a year now.
Hi, @iamkingsleyf. Thank you for checking in. Unfortunately, this had to fall back a little since we had some other priorities that came up. We acknowledge this feature has received quite a good number of +1s, and we will work on putting this back in the pipeline again.
Alright thanks.
Hi @nfmohit, thanks for drafting this IB. I have a minor point, and a more substantial aspect that we need to consider.
Thank you for the kind IB review, @techanvil! Unfortunately, I had to leave this one hanging and unassigned myself as I had to adhere to other priorities. Apologies for not leaving a comment earlier, I remember to have left one but don't see it now, so it might have not gone through.
Since the approach needs some more thought, I'll unassign myself from here so that someone who has the capacity can pick up the IB review feedback. Thank you!
CC: @bethanylang
Thanks, @zutigrm. This is a good start, but we need to make some improvements to IB:
- Create new class, e.g.
Post_List_View_Analytics_Columnthat will handle the report data and analytics column modification
Where should it be created? Which namespace should it use? I believe it should live in the Google\Site_Kit\Modules\Analytics_4 namespace, right?
- The class constructor will accept a
$modulesinstance. This will be injected to provide access to the analytics module using itβsget_module( $module_slug )method- Then using module instance check if analytics 4 module is available, e.q
$this->modules->get_module(Analytics_4::MODULE_SLUG), and if not, return early- ... and use it as param in
$this->modules->get_module(Analytics_4::MODULE_SLUG)->generate_posts_report()method to get report data- In
includes/Plugin.phpon init hook, instantiatePost_List_View_Analytics_Columnand pass$modulesinstance
- Call
registermethod
This is not how it should work. The new class is part of the Analytics_4 functionality. Thus it should be instantiated and registered in the Analytics_4 class. In this case, we don't need to check whether the Analytics 4 module is available. The only thing that we will need to check is that the module is connected.
Also this will let us pass anything that we need from the module class to the column class. For example, we will need to pass the reference to the transients class to be able to read and manage transients. Use the Custom_Dimensions_Data_Available class as an example.
@eugene-manuilov Thanks for the feedback. Originally I though it might be more handy to make this class more decoupled for flexibility as it would have access to the $modules which would be easier to extend in the future for some other module, hence the proposal to instantiate it that way. But on the second thought, it does make more sense to group it with Analytics module for instantiation as you suggested, as this will only rely on analytics data.
Updated IB per suggestions. I also included reseting of transient upon property change (something I missed originally), and showing N/A as a value if there is no report data, and check for gathering data state.
@eugene-manuilov Reminder on this one please. :)
@bethanylang this one i want to discuss on our round table call that is scheduled for the next week. Planned to discuss it in Dec on the same call but we skipped those calls.