Your GA4 Tags Are Probably Duplicating Enhanced Measurement
GA4 Enhanced Measurement fires automatically. It tracks page views, scroll depth, outbound clicks, site search, file downloads, form interactions, and video engagement without any GTM tags. It's enabled by default on every GA4 data stream.
A GTM container with manual tags for these same events produces duplicate data. GA4 receives both the Enhanced Measurement event and the GTM-fired event, counts them separately, and has no built-in deduplication for most event types. The result is doubled page views, inflated scroll counts, and engagement metrics that don't reflect reality.
This is the most common GA4 data quality problem in containers that were migrated from Universal Analytics, because UA required manual tags for everything. GA4 handles many of those events automatically, but the manual tags from the migration survived.
What Enhanced Measurement tracks
Seven categories of events fire automatically when Enhanced Measurement is enabled in the GA4 data stream settings (Admin > Data Streams > Enhanced Measurement):
Page views fire on every navigation, including single-page application route changes detected via History API. This cannot be disabled independently of Enhanced Measurement. If EM is on, page_view events fire automatically.
Scroll fires once per page when the user scrolls past 90% of the page height. There is no configuration for other thresholds. It fires once, not continuously.
Outbound clicks fire when a user clicks a link pointing to a domain different from the current site. Internal link clicks are not tracked by EM.
Site search fires when a URL contains a query parameter matching Google's default list (q, s, search, query, keyword) or a custom parameter you configure. It sends a view_search_results event.
File downloads fire when a user clicks a link to a file with a matching extension. The default list includes .pdf, .xlsx, .docx, .csv, .pptx, and several others, matched via a regex pattern.
Form interactions fire form_start when a user first interacts with a form element, and form_submit when the form is submitted. This detection relies on native HTML form submission events and does not work reliably with AJAX form submissions or JavaScript-rendered forms (React, Vue, etc.).
Video engagement fires video_start, video_progress (at 10%, 25%, 50%, 75%), and video_complete for embedded YouTube videos. The video must have JavaScript API support enabled (enablejsapi=1 in the iframe URL). Non-YouTube videos are not tracked.
How duplication happens
A GTM container migrated from UA typically has manual tags for page views, scroll depth, form submissions, and click tracking. These tags were necessary in UA because nothing fired automatically. During migration, the tags were updated to GA4 format (switching from ua to gaawe tag type, changing the measurement ID), but their purpose wasn't reconsidered. The GA4 data stream has Enhanced Measurement enabled by default, so both systems start tracking the same events simultaneously.
The EM event and the GTM event both send to the same GA4 property. GA4 receives two page_view events per page load, two scroll events per scroll threshold, two form_submit events per form submission. Each is counted independently. Reports show approximately double the real numbers.
The duplication is invisible in the GTM Preview panel because Preview only shows tags that fire within GTM. Enhanced Measurement events fire at the gtag.js level, outside GTM's awareness. A practitioner debugging in Preview sees their manual page_view tag fire once and concludes tracking is working correctly. They don't see the EM page_view that fired alongside it because it's not a GTM tag.
Symptoms in GA4 reports
The most common indicators:
Doubled page view counts. GA4 reports show roughly twice the expected page views. If the site gets 50,000 real page views per month, GA4 shows 95,000-100,000. The doubling isn't exact because EM and GTM tags may not fire on exactly the same triggers (EM catches SPA navigations that GTM's All Pages trigger misses, or vice versa).
Artificially low bounce rate. GA4 calculates bounce rate as the inverse of engagement rate. A session with two page view events (one from EM, one from GTM) counts as an engaged session even if the user left immediately, because the session has multiple events. Bounce rates below 10% are a strong indicator of duplicate tracking.
Inflated event counts in DebugView. Open GA4 DebugView (Admin > DebugView) and navigate a page on your site. If page_view appears twice in quick succession with the same parameters, both EM and a manual tag are firing. The same test works for scroll, file_download, and form_submit events.
Network request duplication. In the browser's Network tab, filter for requests to google-analytics.com/g/collect. Two requests with the same measurement ID and event name within milliseconds of each other indicate duplicate tracking. The requests may have slightly different parameters (EM includes ep.engagement_time_msec automatically), but the event name and core parameters will match.
How to fix it
The fix depends on which source of tracking you want to keep. The options are:
Option 1: Keep GTM, disable EM. Go to GA4 Admin > Data Streams > select your stream > Enhanced Measurement. Toggle off the events you're tracking via GTM. If you have manual GTM tags for page views, scroll, clicks, forms, and video, toggle off all of those in EM. Keep EM enabled only for events you're NOT tracking via GTM.
This is the recommended approach for containers with established GTM tracking because GTM gives you more control over trigger conditions, parameter values, and consent settings. EM's form tracking doesn't work with AJAX forms, EM's scroll tracking only captures 90% depth, and EM's click tracking only captures outbound clicks. GTM tags can be configured for any threshold, any form type, and any click target.
Option 2: Keep EM, remove GTM tags. If your GTM tags for page views, scroll, and clicks are basic implementations that don't add custom parameters or use specific triggers, EM handles the same events with less maintenance. Remove the manual GTM tags and let EM do the work.
This approach is simpler but less flexible. You lose the ability to add custom parameters to these events (EM events have fixed parameter sets), and you can't control trigger conditions (EM fires on every page view, every 90% scroll, every outbound click, with no filtering).
Option 3: Check the "Ignore duplicate instances" setting. GA4 has a data stream setting called "More tagging settings > Ignore duplicate instances of on-page configuration." This prevents duplicate page_view events specifically when both gtag.js and GTM fire the GA4 configuration tag. It does NOT prevent duplication of other events (scroll, click, form, video). This setting addresses one symptom, not the root cause.
The Enhanced Measurement events worth replacing with GTM
Not all EM events are equal in reliability. Some are worth keeping, others are worth replacing with GTM implementations:
Replace with GTM: form_submit and form_start (EM misses AJAX forms, which are the majority of modern web forms), scroll (EM only captures 90% depth, no other thresholds), video events (EM requires enablejsapi=1 in the YouTube embed, which many sites don't have).
Keep EM for: page_view (EM handles SPA route changes via History API without additional configuration), file_download (EM's regex-based detection covers most file types reliably), view_search_results (reliable for standard query parameter patterns).
Keep EM or GTM for: click (EM tracks outbound clicks only; if you need internal click tracking, you need GTM regardless).
The decision should be per-event, not all-or-nothing. A container that uses EM for page views and file downloads while using GTM for form submissions, scroll depth (with custom thresholds), and video tracking across non-YouTube players gets the best of both systems without duplication.
After making changes, validate in DebugView that each event appears exactly once per interaction. The 48-hour window before standard reports update means DebugView is the only way to confirm the fix in real time.