Using Google Consent Mode v2 on a Django Website

2025-07-06

A decade ago, adding website analytics was simple: you’d just paste a JavaScript snippet from Google Analytics, and that was it.

But things changed. As people became more concerned about their privacy, countries introduced strict privacy laws—like GDPR in the European Union, PIPEDA in Canada, and APPI in Japan. These laws gave users more control over their data and placed new responsibilities on us developers.

One of those responsibilities is showing Cookie Consent banners and respecting users’ choices before loading any tracking scripts.

Today, if you want to add Google Analytics to your website, it’s not just about copying a script. You need to understand Google Tag Manager (GTM)—a tool that lets you manage what scripts run on your site and when, using a web-based dashboard.

When you add Google Analytics as a Google tag through GTM, it doesn’t automatically start collecting data. It waits for a signal called Consent Mode, which tells Google whether the user has accepted or denied tracking. This signal must be sent from your website to GTM as an event.

That’s where your Cookie Consent widget comes in. For Django websites, I created Django GDPR Cookie Consent, which lets you manage user consent and send the proper signals to Google.

In this article, I’ll show you how to make all three—Django GDPR Cookie Consent, Google Tag Manager, and Google Consent Mode v2—work together smoothly.

1. Set up Google Analytics

Go to Google Analytics and set up a property for your website. You'll need to know your Google Analytics 4 Measurement ID, e.g., G-XXXXXXX for Google Tag Manager.

GA4 Measurement ID

2. Set up Google Tag Manager

You’ll use the Google Tag Manager (GTM) web interface — to add and manage all your tracking scripts (called tags). You won’t have to edit your website code every time you want to change something — GTM handles it all from one place.

In your container settings, check Enable consent overview to make sure that all your tags have cookie consent setting configured.

Check the GTM container id bounded to your website, e.g., GTM-XXXXXXX. You'll need it later for the scripts.

GTM Container ID

Here’s how to configure GTM to load scripts only after the user gives consent, using the signals from Google Consent Mode.

GTM Tags

GA4 Configuration Tag

This tag initializes Google Analytics 4 (GA4) tracking.

  • Tag Type: Google Tag
  • Measurement ID: Your GA4 ID (G-XXXXXXX)
  • Consent Settings: Require additional consent for tag to fire: analytics_storage
  • Trigger: Consent Initialization – All Pages (this runs very early, before other tags)

This setup allows GA4 to start in a consent-aware way. It reads the default denied state at page load and will automatically adjust when the user accepts cookies later.

GA4 Event Tags

These are additional GA4 tags to track specific actions (like form submissions or button clicks).

  • Tag Type: Google Analytics: GA4 Event
  • Consent Settings: Require additional consent for tag to fire: analytics_storage
  • Trigger: Choose based on the action (e.g., form submit, button click)

You don’t need to check for consent manually here — GA4 automatically tracks or holds data based on the user's consent provided through Google Consent Mode.

Google Ads Tags

If you are using Google Ads, set these for conversion tracking and remarketing (showing ads to users who visited your site):

  • Tag Type: Google Ads Conversion Tracking or Google Ads Remarketing
  • Consent Settings: Require additional consent for tag to fire: ad_storage, ad_personalization, ad_user_data
  • Trigger: Set this to fire after a successful action (like a purchase or sign-up)

These tags respect consent choices, like whether the user allowed ad_storage.

3. Set up Django GDPR Cookie Consent

Download and install the package

Get Django GDPR Cookie Consent from Gumroad.

Create a directory private_wheels/ in your project's repository and add the wheel file of the app there.

Link to this file in your requirements.txt:

Django==5.2
file:./private_wheels/django_gdpr_cookie_consent-4.1.2-py2.py3-none-any.whl

Install the pip requirements from the requirements.txt file into your project's virtual environment:

(venv)$ pip install -r requirements.txt

Add the app to INSTALLED_APPS

INSTALLED_APPS = [
    # …
    "gdpr_cookie_consent",
    # …
]

Add the context processor

Add gdpr_cookie_consent to context processors in your Django settings:

TEMPLATES = [
    {
        # …
        "OPTIONS": {
            "context_processors": [
                # …
                "gdpr_cookie_consent.context_processors.gdpr_cookie_consent",
            ],
        },
    },
]

Add URL path to urlpatterns

from django.urls import path, include

urlpatterns = [
    # …
    path(
        "cookies/",
        include("gdpr_cookie_consent.urls", namespace="cookie_consent"),
    ),
    # …
]

Prepare cookie consent configuration

Create COOKIE_CONSENT_SETTINGS configuration in your Django project settings with these cookie sections:

  • Essential (strictly necessary) for cookies related to Django sessionid, CSRF token, and cookie consent,
  • Analytics (optional) for website usage statistics with Google Analytics,
  • Marketing (optional) for tracking cross-website ad statistics with Google Ads.
from django.utils.translation import gettext_lazy as _

COOKIE_CONSENT_SETTINGS = {
    "base_template_name": "base.html",
    "description_template_name": "gdpr_cookie_consent/descriptions/what_are_cookies.html",
    "dialog_position": "center",
    "consent_cookie_max_age": 60 * 60 * 24 * 30 * 6,
    "sections": [
        {
            "slug": "essential",
            "title": _("Essential Cookies"),
            "required": True,
            "preselected": True,
            "summary": _(
                "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided."),
            "description": _(
                "These cookies are always on, as they’re essential for making this website work, and making it safe. Without these cookies, services you’ve asked for can’t be provided."),
            "providers": [
                {
                    "title": _("This website"),
                    "cookies": [
                        {
                            "cookie_name": "sessionid",
                            "duration": _("2 Weeks"),
                            "description": _(
                                "Session ID used to authenticate you and give permissions to use the site."),
                            "domain": ".example.com",
                        },
                        {
                            "cookie_name": "csrftoken",
                            "duration": _("Session"),
                            "description": _(
                                "Security token used to ensure that no hackers are posting forms on your behalf."),
                            "description_template_name": "",
                            "domain": ".example.com",
                        },
                        {
                            "cookie_name": "cookie_consent",
                            "duration": _("6 Years"),
                            "description": _("Settings of Cookie Consent preferences."),
                            "description_template_name": "",
                            "domain": ".example.com",
                        },
                    ]
                },
            ],
        },
        {
            "slug": "analytics",
            "title": _("Analytics Cookies"),
            "required": False,
            "summary": _(
                "These cookies help us analyse how many people are using this website, where they come from and how they're using it. If you opt out of these cookies, we can’t get feedback to make this website better for you and all our users."),
            "description": _(
                "These cookies help us analyse how many people are using this website, where they come from and how they're using it. If you opt out of these cookies, we can’t get feedback to make this website better for you and all our users."),
            "providers": [
                {
                    "title": _("Google Analytics"),
                    "description": _("Google Analytics is used to track website usage statistics."),
                    "description_template_name": "",
                    "cookies": [
                        {
                            "cookie_name": "_ga",
                            "duration": _("2 Years"),
                            "description": _("Used to distinguish users."),
                            "description_template_name": "",
                            "domain": ".example.com",
                        },
                        {
                            "cookie_name": "_gid",
                            "duration": _("24 Hours"),
                            "description": _("Used to distinguish users."),
                            "description_template_name": "",
                            "domain": ".example.com",
                        },
                        {
                            "cookie_name": "_ga_*",
                            "duration": _("2 Years"),
                            "description": _("Used to persist session state."),
                            "description_template_name": "",
                            "domain": ".example.com",
                        },
                        {
                            "cookie_name": "_gat_gtag_UA_*",
                            "duration": _("1 Minute"),
                            "description": _("Stores unique user ID."),
                            "description_template_name": "",
                            "domain": ".example.com",
                        },
                    ]
                },
            ],
        },
        {
            "slug": "marketing",
            "title": _("Marketing Cookies"),
            "required": False,
            "summary": _(
                "These cookies are set by our advertising partners to track your activity and show you relevant ads on other sites as you browse the internet."),
            "description": _(
                "These cookies are set by our advertising partners to track your activity and show you relevant ads on other sites as you browse the internet."),
            "providers": [
                {
                    "title": _("Google Ads"),
                    "description": _("These cookies are related to Google Ads conversion tracking."),
                    "description_template_name": "",
                    "cookies": [
                        {
                            "cookie_name": "_gac_gb_*",
                            "duration": _("90 Days"),
                            "description": _(
                                "Contains campaign related information. If you have linked your Google Analytics and Google Ads accounts, Google Ads website conversion tags will read this cookie unless you opt-out."),
                            "description_template_name": "",
                            "domain": ".example.com",
                        },
                    ]
                },
            ],
        },
    ]
}

Include the widget to your base.html template

Load the CSS somewhere in the <head> section:

<link href="{% static 'gdpr-cookie-consent/css/gdpr-cookie-consent.css' %}" rel="stylesheet" />

Include the widget just before the closing </body> tag:

{% include "gdpr_cookie_consent/includes/cookie_consent.html" %}

Link to the cookie management view, for example, in the website's footer:

{% url "cookie_consent:cookies_management" as cookie_management_url %}
<a href="{{ cookie_management_url }}" rel="nofollow">
    {% trans "Manage Cookies" %}
</a>

Add GTM and Google Consent Mode snippets to your base.html template:

Add these two scripts in the <head> section:

<script nonce="{{ request.csp_nonce }}">
   window.dataLayer = window.dataLayer || [];
   function gtag(){dataLayer.push(arguments);}

   gtag('consent', 'default', {
      'analytics_storage': '{% if "analytics" in cookie_consent_controller.checked_sections %}granted{% else %}denied{% endif %}',
      'ad_storage': '{% if "marketing" in cookie_consent_controller.checked_sections %}granted{% else %}denied{% endif %}',
      'ad_user_data': '{% if "marketing" in cookie_consent_controller.checked_sections %}granted{% else %}denied{% endif %}',
      'ad_personalization': '{% if "marketing" in cookie_consent_controller.checked_sections %}granted{% else %}denied{% endif %}',
      'functionality_storage': 'denied',
      'personalization_storage': 'denied',
      'security_storage': 'granted'  // usually okay to grant by default
   });
</script>

<!-- Google Tag Manager (head) -->
<script nonce="{{ request.csp_nonce }}">(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');</script>

Add this iframe in the beginning of the <body> tag:

<!-- Google Tag Manager (body) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>

Replace GTM-XXXXXXX with your real GTM container ID.

Create JavaScript custom event handlers for granting and denying consent

Create a custom JavaScript file main.js and include it in the base.html template somewhere before the closing </body> tag:

<script src="{% static 'site/js/main.js' %}"></script>

In the JavaScript file, add these custom event handlers that will grant or deny cookie consent at Google–that's a new feature in Django GDPR Cookie Consent v4:

document.addEventListener('grantGDPRCookieConsent', (e) => {
    console.log(`${e.detail.section} cookies granted`);
    if (e.detail.section === 'analytics') {
        gtag('consent', 'update', {
            'analytics_storage': 'granted'
        });
        dataLayer.push({'event': 'analytics_consent_granted'});
    } else if (e.detail.section === 'marketing') {
        gtag('consent', 'update', {
            'ad_storage': 'granted',
            'ad_user_data': 'granted',
            'ad_personalization': 'granted'
        });
        dataLayer.push({'event': 'marketing_consent_granted'});
    }
});

document.addEventListener('denyGDPRCookieConsent', (e) => {
    console.log(`${e.detail.section} cookies denied`);
    if (e.detail.section === 'analytics') {
        gtag('consent', 'update', {
            'analytics_storage': 'denied'
        });
        dataLayer.push({'event': 'analytics_consent_denied'});
    } else if (e.detail.section === 'marketing') {
        gtag('consent', 'update', {
            'ad_storage': 'denied',
            'ad_user_data': 'denied',
            'ad_personalization': 'denied'
        });
        dataLayer.push({'event': 'marketing_consent_denied'});
    }
});

Check if your setup is correct

Check the correctness of your configuration with the following:

(venv)$ python manage.py check gdpr_cookie_consent

4. Test and debug with GTM preview mode

  1. Deploy your project to production.
  2. Install Tag Assistant Chrome extension.
  3. Enable Preview in Google Tag Manager dashboard.
  4. Visit your website via the preview mode.
  5. Accept cookies.
  6. You should see these events:
    • analytics_consent_granted – GA4 tag should fire.
    • marketing_consent_granted – Google Ads tag should fire.
  7. Check which tags fired — they should match the user’s choices.
  8. The Consent tab of each event should show the correct preferred consent choices.
  9. Google Analytics realtime report should track you only when the consent was given.

Django GDPR Cookie Consent in action GTM Tag Assistant

How it works

When a user visits your website, the default cookie consent mode is set based on their previously saved preferences. If it's their first visit, consent will default to “denied.”

When the user sets or updates their cookie preferences—via the modal dialog or the preferences form—your consent widget will fire custom JavaScript events grantGDPRCookieConsent or denyGDPRCookieConsent available since Django GDPR Cookie Consent v4.

Your JavaScript handler will listen for these events, update the Google Consent Mode accordingly, and send the updated values to Google Tag Manager.

Based on those values, Google Tag Manager will decide whether to activate tracking tags such as Google Analytics and Google Ads. These tags can then track usage statistics and, if allowed, ad-related cross-site behavior.

Final words

Now you should be all set. Google Analytics should respect user's privacy based on the choices in Cookie Consent widget, provided by Django GDPR Cookie Consent. Your website will be compliant with Google Consent Mode and will fire analytics and marketing tags only after consent.

Intermediate Django Javascript Django GDPR Cookie Consent Cookies