The 'l' in GCD: Fixing Unset Consent Signals in Manual gtag Implementations

4 min read
Mihai Brehar
google consent mode v2 gtag.js gcd parameter developer guide

[!NOTE] This article covers manual gtag.js implementations. If you are using Google Tag Manager (GTM), check out our GTM-specific troubleshooting guide.

If you’ve skipped the GTM route and implemented Google Consent Mode v2 manually using gtag.js, you have more control, but also more ways for things to go wrong.

One of the most common technical "red flags" we see at ConsentGuard is the lowercase "l" appearing in the gcd parameter.

What is 'l' Telling You?

In a GCD string (e.g., &gcd=13l3l3R2l5l1), the character "l" stands for "unset".

It means that while your tracking tags are firing, Google hasn't received a default or update command for that specific consent signal (like analytics_storage or ad_user_data).

For developers, this usually points to a race condition or a wrong order of execution in your script tags.


3 Reasons Why Your gtag implementation is sending 'l'

1. The 'default' Command is Missing

The most obvious reason is that you simply haven't defined the default consent state. In v2, you must include all four signals. If you only define ad_storage and analytics_storage, the others (ad_user_data and ad_personalization) will default to "l".

2. The Execution Order is Wrong

This is the "Silent Killer" of Consent Mode. The gtag('consent', 'default', ...) command must be executed before gtag('js', new Date()) and before any gtag('config', ...) calls.

If your code looks like this, it’s broken:

<!-- WRONG ORDER -->

<script async src="https://www.googletagmanager.com/gtag/js?id=AW-123456789"></script>

<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'AW-123456789');
  
  // This is too late!
  gtag('consent', 'default', {
    'ad_storage': 'denied',
    'analytics_storage': 'denied'
  });
</script>

3. Async Loading Issues

If you are loading your consent default settings via an external script or a CMP that loads asynchronously, the browser might execute your main gtag config before the consent defaults are loaded. Google then sees the tags firing without a consent state and marks them as "l".


How to Fix It

The fix is straightforward: Put your consent default command at the very top of your <head>, before any other tracking scripts.

Here is the correct template for a manual Consent Mode v2 implementation:

<script>
  // 1. Initialize dataLayer
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}

  // 2. Set DEFAULT consent (Must be before gtag('js', ...))
  gtag('consent', 'default', {
    'ad_storage': 'denied',
    'analytics_storage': 'denied',
    'ad_user_data': 'denied',
    'ad_personalization': 'denied',
    'wait_for_update': 2000 // Instructs Google tags to wait up to 2 seconds for a consent update
  });

  // 3. Load the library
  gtag('js', new Date());

  // 4. Configure your Google Ads tag
  gtag('config', 'AW-123456789');
</script>

<!-- 5. Now load the external script with your Google Ads ID -->
<script async src="https://www.googletagmanager.com/gtag/js?id=AW-123456789"></script>

[!TIP] The wait_for_update parameter is a "safety buffer". It tells the Google script to pause for up to 2000ms before sending any data. If your CMP sends the update command before that time is up, the data is sent immediately with the correct consent status.


Verifying the Fix

You can verify this in your browser console. Filter for gcd in the Network tab and look for the parameter. You want to see letters like p, r, q, or m - avoid the l.

Alternatively, let ConsentGuard do the heavy lifting. Our scanner detects "l" signals automatically and alerts you precisely which signals are missing.

ConsentGuard report showing GCD 'l' signal violation

Conclusion

In manual implementations, the "l" signal is almost always a sign that your default command is firing too late or is incomplete. By ensuring that your default state is set immediately - and includes all v2 signals - you'll ensure Google's modeling works as intended and your attribution stays accurate.

Published on February 28, 2026 by Mihai Brehar
Share: