{"id":951,"date":"2022-05-16T17:45:26","date_gmt":"2022-05-16T16:45:26","guid":{"rendered":"https:\/\/organicdigital.co\/blog\/?p=951"},"modified":"2026-09-21T12:04:23","modified_gmt":"2026-09-21T11:04:23","slug":"how-to-set-up-google-analytics-and-ga4-on-multiple-environments","status":"publish","type":"post","link":"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/","title":{"rendered":"How To: \u00a0Set Up GA4 on Multiple Environments"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>TL;DR:<\/strong> <br>If you use GA4 across production, staging and development environments, you need to prevent test activity from contaminating your live reporting without losing the ability to test analytics before launch. Depending on what you need, you can filter staging traffic, use separate GA4 properties or data streams, or use Google Tag Manager to send each hostname to a different measurement ID. This guide explains the approaches I use and when each one makes sense.<\/p>\n\n\n\n<div id=\"ez-toc-container\" class=\"ez-toc-v2_0_89 counter-hierarchy ez-toc-counter ez-toc-grey ez-toc-container-direction\">\n<div class=\"ez-toc-title-container\">\n<p class=\"ez-toc-title\" style=\"cursor:inherit\">Table of Contents<\/p>\n<span class=\"ez-toc-title-toggle\"><a href=\"#\" class=\"ez-toc-pull-right ez-toc-btn ez-toc-btn-xs ez-toc-btn-default ez-toc-toggle\" aria-label=\"Toggle Table of Content\"><span class=\"ez-toc-js-icon-con\"><span class=\"\"><span class=\"eztoc-hide\" style=\"display:none;\">Toggle<\/span><span class=\"ez-toc-icon-toggle-span\"><svg style=\"fill: #999;color:#999\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" class=\"list-377408\" width=\"20px\" height=\"20px\" viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 6H4v2h2V6zm14 0H8v2h12V6zM4 11h2v2H4v-2zm16 0H8v2h12v-2zM4 16h2v2H4v-2zm16 0H8v2h12v-2z\" fill=\"currentColor\"><\/path><\/svg><svg style=\"fill: #999;color:#999\" class=\"arrow-unsorted-368013\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"10px\" height=\"10px\" viewBox=\"0 0 24 24\" version=\"1.2\" baseProfile=\"tiny\"><path d=\"M18.2 9.3l-6.2-6.3-6.2 6.3c-.2.2-.3.4-.3.7s.1.5.3.7c.2.2.4.3.7.3h11c.3 0 .5-.1.7-.3.2-.2.3-.5.3-.7s-.1-.5-.3-.7zM5.8 14.7l6.2 6.3 6.2-6.3c.2-.2.3-.5.3-.7s-.1-.5-.3-.7c-.2-.2-.4-.3-.7-.3h-11c-.3 0-.5.1-.7.3-.2.2-.3.5-.3.7s.1.5.3.7z\"\/><\/svg><\/span><\/span><\/span><\/a><\/span><\/div>\n<nav><ul class='ez-toc-list ez-toc-list-level-1 ' ><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-1\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#Why_GA4_needs_to_be_handled_differently_across_environments\" >Why GA4 needs to be handled differently across environments<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-2\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#How_to_set_up_GA4_across_staging_development_and_live_environments\" >How to set up GA4 across staging, development and live environments<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-3\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#Should_GA4_track_your_staging_and_development_sites\" >Should GA4 track your staging and development sites?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-4\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#Dont_just_remove_analytics_from_staging\" >Don&#8217;t just remove analytics from staging<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-5\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#Can_you_use_one_GA4_property_across_multiple_environments\" >Can you use one GA4 property across multiple environments?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-6\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#Should_you_create_a_separate_GA4_data_stream_for_staging\" >Should you create a separate GA4 data stream for staging?<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-7\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#How_to_filter_staging_or_development_hostnames_in_GA4\" >How to filter staging or development hostnames in GA4<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-8\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#Test_your_hostname_filter_before_making_it_active\" >Test your hostname filter before making it active<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-9\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#How_to_check_whether_your_GA4_hostname_filter_is_working\" >How to check whether your GA4 hostname filter is working<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-10\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#What_happens_to_data_GA4_has_already_collected\" >What happens to data GA4 has already collected?<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-11\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#Should_you_use_separate_GA4_properties_for_staging_and_production\" >Should you use separate GA4 properties for staging and production?<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-12\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#Separate_data_stream_or_separate_GA4_property\" >Separate data stream or separate GA4 property?<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-13\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#Send_different_environments_to_different_GA4_properties_with_Google_Tag_Manager\" >Send different environments to different GA4 properties with Google Tag Manager<\/a><ul class='ez-toc-list-level-3' ><li class='ez-toc-heading-level-3'><a class=\"ez-toc-link ez-toc-heading-14\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#How_to_use_a_Lookup_Table_for_different_GA4_measurement_IDs\" >How to use a Lookup Table for different GA4 measurement IDs<\/a><\/li><\/ul><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-15\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#What_about_Google_Tag_Manager_environments\" >What about Google Tag Manager environments?<\/a><\/li><li class='ez-toc-page-1 ez-toc-heading-level-2'><a class=\"ez-toc-link ez-toc-heading-16\" href=\"https:\/\/daveashworth.co\/blog\/how-to-set-up-google-analytics-and-ga4-on-multiple-environments\/#Which_GA4_setup_should_you_use_for_multiple_environments\" >Which GA4 setup should you use for multiple environments?<\/a><\/li><\/ul><\/nav><\/div>\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Why_GA4_needs_to_be_handled_differently_across_environments\"><\/span>Why GA4 needs to be handled differently across environments<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When I&#8217;m working as an <a>analytics and tracking consultant<\/a>, one issue I regularly need to account for is <strong>how GA4 should be configured across development, staging and production environments<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You want analytics available on your test environments so you can check page views, events, ecommerce and conversions before a site goes live.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What you don&#8217;t want is developers and testers generating data on a staging site that ends up mixed into your live reporting.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There are several ways to handle this in GA4 and <a href=\"https:\/\/daveashworth.co\/skills\/google-tag-manager-consultant\/\">Google Tag Manager<\/a>, and the right approach depends on whether you want to <strong>filter non-production traffic, send it to a separate GA4 property, or manage different measurement IDs based on the hostname<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In this guide I&#8217;ll show you the approaches I use and when each one makes sense.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"How_to_set_up_GA4_across_staging_development_and_live_environments\"><\/span>How to set up GA4 across staging, development and live environments<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;re running GA4 across multiple environments, the aim is straightforward, you want to be able to test your analytics implementation on staging or development without contaminating the data you&#8217;re collecting from the live website.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, you might have:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>www.example.com<\/code> &#8211; live<br><\/li>\n\n\n\n<li><code>staging.example.com<\/code> &#8211; staging<br><\/li>\n\n\n\n<li><code>dev.example.com<\/code> &#8211; development<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">There are several ways to manage this in GA4 and Google Tag Manager. You can use the same GA4 property and control which hostname data you retain, use separate GA4 properties for live and test environments, or use GTM to send data to different measurement IDs depending on the hostname.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Which approach is best depends on how your tracking is configured and what you need to test.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Should_GA4_track_your_staging_and_development_sites\"><\/span>Should GA4 track your staging and development sites?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Yes, in most cases I want GA4 and Google Tag Manager working on staging and development environments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;re building a new website, changing an ecommerce checkout or implementing new events, waiting until the site reaches production before testing the tracking is asking for trouble.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ve seen sites launch with missing analytics, broken ecommerce tracking or events that appeared to work during development but weren&#8217;t actually being sent to the correct GA4 property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Having tracking available before launch means you can test page views, events, conversions and ecommerce data while the site is still being developed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The problem isn&#8217;t having GA4 on a staging site. The problem is <strong>allowing staging and development activity to contaminate your live reporting.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So rather than removing analytics from your test environments altogether, I prefer to keep the tracking implementation consistent and control <strong>where the data is sent or how it&#8217;s separated from production data<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This also means you&#8217;re testing something much closer to the tracking implementation that will actually be running when the site goes live.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Dont_just_remove_analytics_from_staging\"><\/span>Don&#8217;t just remove analytics from staging<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">It can be tempting to solve the problem by simply disabling analytics on staging altogether.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I generally don&#8217;t recommend that.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Staging is often exactly where you need analytics most: it&#8217;s where you can confirm that your events, ecommerce data, consent configuration and other tracking are working correctly before the changes reach real users.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;d rather deliberately separate or control the data than discover after launch that something wasn&#8217;t being tracked.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Can_you_use_one_GA4_property_across_multiple_environments\"><\/span>Can you use one GA4 property across multiple environments?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Yes. You can use the same GA4 property across your live, staging and development environments<\/strong>, but that doesn&#8217;t necessarily mean you should send everything into the same reporting dataset without considering how you&#8217;ll separate the traffic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One of the confusing things here is the difference between a <strong>GA4 property, data stream and website environment<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A GA4 property is the main container for your analytics data. Within that property you can have web and app data streams, each with its own measurement ID.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Your staging and development sites, however, don&#8217;t automatically need their own data streams simply because they&#8217;re different environments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, if the same web data stream is installed on:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>www.example.com<\/code><br><\/li>\n\n\n\n<li><code>staging.example.com<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">GA4 can receive data from both hostnames.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Whether that&#8217;s desirable depends on what you&#8217;re trying to achieve.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you simply want to test that GA4 is working before deployment, using the same implementation can make life easier. But if you want to retain test data without mixing it with genuine customer activity, you&#8217;ll need a way of separating the two.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s where <strong>hostname filtering, separate GA4 properties or routing the measurement ID through Google Tag Manager<\/strong> become useful.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Should_you_create_a_separate_GA4_data_stream_for_staging\"><\/span>Should you create a separate GA4 data stream for staging?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You can create another web data stream for a staging site, but I wouldn&#8217;t automatically create a new data stream for every environment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A separate stream gives you a different measurement ID, but the data still belongs to the same GA4 property. So if your objective is complete separation between production and test data, a separate stream isn&#8217;t necessarily the same thing as having a completely separate test property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"822\" height=\"195\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/data-stream.png\" alt=\"Data Stream\" class=\"wp-image-2303\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/data-stream.png 822w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/data-stream-300x71.png 300w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/data-stream-150x36.png 150w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/data-stream-767x182.png 767w\" sizes=\"(max-width: 822px) 100vw, 822px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I tend to start with the question <strong>\u201cwhat do I want to happen to the test data?\u201d<\/strong> rather than \u201chow many data streams should I create?\u201d<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If I don&#8217;t need to retain it, filtering may be enough. If I want a clean test dataset I can inspect independently, a separate property may make more sense. And if I want the same GTM implementation to decide automatically where data should be sent, I&#8217;ll usually handle that through GTM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"How_to_filter_staging_or_development_hostnames_in_GA4\"><\/span>How to filter staging or development hostnames in GA4<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If your live and staging environments are sending data into the same GA4 property, one option is to use a <strong>Web hostname traffic data filter<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Note:<\/strong> Google introduced Web hostname traffic filters in August 2026 and the feature is still being rolled out. If you don&#8217;t currently see the option in your property, it may not have reached your account yet.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">GA4 automatically collects the hostname for web traffic, so it can distinguish between visits to URLs such as:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>www.example.com<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>staging.example.com<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>dev.example.com<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A hostname filter can then be used to control which of those hostnames are included in or excluded from the data GA4 processes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, if I only wanted the production website included in a GA4 property, I could configure the filter around the live hostname rather than allowing staging and development traffic into the reporting data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In GA4, go to:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Admin \u2192 Data collection and modification \u2192 Data filters \u2192 Create filter<\/strong><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"824\" height=\"186\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/data-filters.png\" alt=\"Data Filters\" class=\"wp-image-2304\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/data-filters.png 824w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/data-filters-300x68.png 300w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/data-filters-150x34.png 150w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/data-filters-766x173.png 766w\" sizes=\"(max-width: 824px) 100vw, 824px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Select <strong>Web hostname traffic<\/strong> as the filter type.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can then choose whether to <strong>include or exclude<\/strong> matching traffic, select how the hostname should be matched and enter the hostname you want the rule to apply to.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example, if your staging environment is:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>staging.example.com<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">you could create an <strong>Exclude<\/strong> filter that exactly matches that hostname, as shown below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"876\" height=\"897\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/create-data-filter-1.png\" alt=\"Create Data Filter\" class=\"wp-image-2306\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/create-data-filter-1.png 876w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/create-data-filter-1-146x150.png 146w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/create-data-filter-1-293x300.png 293w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/create-data-filter-1-768x786.png 768w\" sizes=\"(max-width: 876px) 100vw, 876px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You&#8217;ll also notice that GA4 gives you three filter states: <strong>Testing, Active and Inactive<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;d start with <strong>Testing<\/strong> rather than immediately making a new hostname filter active.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Test_your_hostname_filter_before_making_it_active\"><\/span>Test your hostname filter before making it active<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">This is one setting I&#8217;d be particularly careful with.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">GA4 data filters affect <strong>incoming data from the point they&#8217;re applied<\/strong>. Once an exclude filter is active, the excluded data isn&#8217;t processed by GA4 and can&#8217;t simply be restored later. It also doesn&#8217;t retrospectively remove traffic you&#8217;ve already collected.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">While the filter is in <strong>Testing<\/strong>, you can check that it&#8217;s identifying the traffic you expect before changing its state to Active.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s particularly important if you&#8217;re creating an <strong>include filter for your live hostname<\/strong>. Get the rule wrong and you could potentially stop legitimate production data from being processed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I wouldn&#8217;t activate a hostname filter and assume it&#8217;s working. I&#8217;d verify the result before relying on it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"How_to_check_whether_your_GA4_hostname_filter_is_working\"><\/span>How to check whether your GA4 hostname filter is working<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">When a data filter is in <strong>Testing<\/strong>, GA4 doesn&#8217;t permanently exclude the matching traffic. Instead, it adds a test data filter dimension that you can use to check whether the filter is identifying the traffic you expect.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a staging hostname filter, I&#8217;d generate some activity on the staging site and then check that GA4 is identifying those events as matching the test filter.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can also use the <strong>Hostname<\/strong> dimension in an Exploration to confirm which hostname the traffic is coming from.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Once you&#8217;re satisfied that the filter is matching <code>staging.example.com<\/code> without affecting your live hostname, you can change the filter from <strong>Testing<\/strong> to <strong>Active<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Remember that once the filter is active, the excluded traffic will no longer be processed, so this isn&#8217;t something I&#8217;d switch on without testing first.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"What_happens_to_data_GA4_has_already_collected\"><\/span>What happens to data GA4 has already collected?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>A GA4 data filter isn&#8217;t retrospective.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If staging traffic has already been collected, creating a hostname data filter now won&#8217;t remove that historical data from the property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can still use the <strong>Hostname<\/strong> dimension in reports or Explorations to inspect or isolate traffic from particular hostnames, but that&#8217;s different from preventing the data from being processed in the first place. GA4 automatically populates the Hostname dimension for web traffic.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s another reason I prefer to think about environment handling when the tracking is initially configured rather than after staging traffic has already started appearing in the live property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Should_you_use_separate_GA4_properties_for_staging_and_production\"><\/span>Should you use separate GA4 properties for staging and production?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you want to keep your test data <strong>completely separate from your live analytics data<\/strong>, another option is to use a separate GA4 property for staging or development.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>Example Website \u2013 Production<\/code> \u2192 live website<br><code>Example Website \u2013 Test<\/code> \u2192 staging\/development<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This gives you a clean test property where you can check events, ecommerce tracking, conversions and other changes without those interactions appearing in your production property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I tend to prefer this approach when the staging environment is used regularly for analytics testing and I actually want to <strong>retain and inspect the test data<\/strong>, rather than simply exclude it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The trade-off is that you&#8217;re now maintaining more than one GA4 property. Any relevant configuration \u2014 such as events, key events, custom dimensions or other property-level settings \u2014 may need to be kept consistent between them.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s why I wouldn&#8217;t create a separate property just because a staging site exists. If all I need to do is stop occasional staging traffic contaminating live reporting, a hostname filter may be enough.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If I want a persistent analytics testing environment, however, a separate property gives me much clearer separation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Separate_data_stream_or_separate_GA4_property\"><\/span>Separate data stream or separate GA4 property?<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">These aren&#8217;t quite the same thing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A <strong>separate web data stream<\/strong> gives your staging site its own measurement ID, but the stream still sits within the same GA4 property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A <strong>separate GA4 property<\/strong> keeps the staging data in an entirely separate property from your production analytics.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So my decision usually comes down to:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>I don&#8217;t want the staging data:<\/strong> filter it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>I want staging identifiable within the same property:<\/strong> consider a separate data stream.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>I want staging data completely separated and retained for testing:<\/strong> use a separate property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There&#8217;s then another option if you want the <strong>same Google Tag Manager container deployed across all of these environments<\/strong>: make GTM decide which GA4 measurement ID should receive the data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Send_different_environments_to_different_GA4_properties_with_Google_Tag_Manager\"><\/span>Send different environments to different GA4 properties with Google Tag Manager<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;re using separate GA4 properties or data streams for your live and staging environments, you don&#8217;t necessarily need separate Google Tag Manager containers as well.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can use the <strong>same GTM container across multiple environments<\/strong> and have GTM determine which GA4 measurement ID should receive the data based on the hostname.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For example:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>www.example.com<\/code> \u2192 production GA4 measurement ID<br><code>staging.example.com<\/code> \u2192 staging GA4 measurement ID<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is an approach I like because the tracking implementation itself can remain consistent across environments. Your tags, triggers and event configuration can be tested on staging before deployment without having to maintain two completely separate GTM containers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One way to do this is with a <strong>Lookup Table variable<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"How_to_use_a_Lookup_Table_for_different_GA4_measurement_IDs\"><\/span>How to use a Lookup Table for different GA4 measurement IDs<span class=\"ez-toc-section-end\"><\/span><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">I love Lookup Tables because they have lots of uses for changing an output based on a particular input.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s say I have:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>www.example.com<\/code> \u2192 <code>G-PRODUCTION<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>staging.example.com<\/code> \u2192 <code>G-STAGING<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In Google Tag Manager, create a new variable and select <strong>Lookup Table<\/strong> as the variable type.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Set the <strong>Input Variable<\/strong> to:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><code>{{Page Hostname}}<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then add each hostname and the corresponding Google tag ID as its output.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In my own setup, for example, I have separate IDs for <code>daveashworth.co<\/code> and <code>staging.daveashworth.co<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"864\" height=\"329\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/lookup-table.png\" alt=\"\" class=\"wp-image-2307\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/lookup-table.png 864w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/lookup-table-300x114.png 300w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/lookup-table-150x57.png 150w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/lookup-table-767x292.png 767w\" sizes=\"(max-width: 864px) 100vw, 864px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Once the Lookup Table is set up, I can use the variable in my Google tag rather than hard-coding a single ID.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In the example below, <code>{{LookUp - GA4 Tracking ID}}<\/code> is used as the <strong>Tag ID<\/strong>, with the tag firing across the site:<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"862\" height=\"433\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/ga4-config.png\" alt=\"\" class=\"wp-image-2308\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/ga4-config.png 862w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/ga4-config-300x151.png 300w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/ga4-config-150x75.png 150w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/ga4-config-766x385.png 766w\" sizes=\"(max-width: 862px) 100vw, 862px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">GTM checks the current <code>{{Page Hostname}}<\/code>, the Lookup Table returns the corresponding ID, and the Google tag sends the data to the appropriate GA4 setup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This means I can use the <strong>same GTM container and tracking implementation on both my live and staging sites<\/strong>, while keeping control over where the analytics data is sent.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It also reduces the risk of somebody copying a staging site to production and forgetting to replace a hard-coded analytics ID.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"What_about_Google_Tag_Manager_environments\"><\/span>What about Google Tag Manager environments?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Google Tag Manager also has an <strong>Environments<\/strong> feature specifically designed for testing changes across environments such as development, staging and production.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is different from the Lookup Table approach I&#8217;ve shown above.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The <strong>Lookup Table determines which Google tag ID is used based on the hostname<\/strong>, whereas <strong>GTM Environments allow different versions of a container to be deployed to different environments<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You can find the feature under <strong>Admin \u2192 Environments<\/strong> in Google Tag Manager.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" width=\"968\" height=\"556\" src=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/gtm-admin.png\" alt=\"\" class=\"wp-image-2309\" srcset=\"https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/gtm-admin.png 968w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/gtm-admin-150x86.png 150w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/gtm-admin-300x172.png 300w, https:\/\/daveashworth.co\/blog\/wp-content\/uploads\/2022\/05\/gtm-admin-768x441.png 768w\" sizes=\"(max-width: 968px) 100vw, 968px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">GTM includes <strong>Live<\/strong> and <strong>Latest<\/strong> as default environments, and you can create additional custom environments for development or staging.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A custom environment uses its own GTM container snippet, allowing a particular version of the container to be tested on that environment before the same changes are published to Live.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That&#8217;s potentially useful for more controlled development and release workflows, but it&#8217;s not something you need simply to send staging and production analytics to different GA4 destinations. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the setup I&#8217;ve shown above, I&#8217;m using the hostname and Lookup Table to control that.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><span class=\"ez-toc-section\" id=\"Which_GA4_setup_should_you_use_for_multiple_environments\"><\/span>Which GA4 setup should you use for multiple environments?<span class=\"ez-toc-section-end\"><\/span><\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">There isn&#8217;t one GA4 setup I&#8217;d recommend for every staging, development and live environment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It depends mainly on what you want to happen to the test data.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If I don&#8217;t need to retain staging data, a hostname filter can be enough to stop it contaminating the production property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If I want to keep staging data completely separate so I can test events, ecommerce and other tracking without affecting live reporting, I&#8217;d consider a separate GA4 property.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the same Google Tag Manager container is running across multiple environments, a Lookup Table based on <code>{{Page Hostname}}<\/code> is a useful way of automatically sending each environment to the appropriate Google tag ID.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And if you have a more controlled development and release process, GTM Environments provide another option for deploying different container versions to development, staging and live websites.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The important thing is to make the decision deliberately. You don&#8217;t want to discover after a launch that your production property contains weeks of staging activity \u2014 or, worse, that the tracking you tested on staging wasn&#8217;t actually the tracking running on the live site.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If you&#8217;re setting up GA4 across multiple environments and need help getting the tracking right, I can help with both <a href=\"https:\/\/daveashworth.co\/skills\/analytics-and-ad-tracking-consultant\/\">analytics implementation<\/a> and <a href=\"https:\/\/daveashworth.co\/skills\/google-tag-manager-consultant\/\">Google Tag Manager<\/a>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That can include checking an existing implementation, separating staging and production data, configuring events and ecommerce tracking, or making sure everything is working correctly before a new website goes live.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Please feel free to get in touch &#8211; I&#8217;m always happy to help.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Find out various ways to ensure your test\/staging data doesn\u2019t dilute your live site data using Google Analytics views and Google Tag Manager.<\/p>\n","protected":false},"author":1,"featured_media":965,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[15],"tags":[],"class_list":["post-951","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-analytics"],"_links":{"self":[{"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/posts\/951","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/comments?post=951"}],"version-history":[{"count":0,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/posts\/951\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/media\/965"}],"wp:attachment":[{"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/media?parent=951"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/categories?post=951"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/daveashworth.co\/blog\/wp-json\/wp\/v2\/tags?post=951"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}