# How to add a cookie consent banner to a Django site

> In Django, paste the snippet in the head of the base template every page extends (usually templates/base.html), above any block where child templates add scripts. Pass the site key from settings with a small context processor, mark tracking tags in the same template with type="text/plain" and data-cq-category, and list Django's sessionid and csrftoken cookies as strictly necessary.

Source: https://cookiequiet.com/platforms/django
Published: 2026-10-21
Publisher: Cookiequiet

## Steps

1. **Copy the snippet** In the Cookiequiet dashboard, add the website and copy the snippet with your site key, data-consent-mode="1" and data-auto-block="1".
2. **Add the key to settings** Add COOKIEQUIET_SITE to settings.py, read from an environment variable, and a context processor that passes it to every template.
3. **Paste the snippet in base.html** Open the base template that child templates name with extends and paste the snippet in the head, above any block where pages add their own scripts.
4. **Mark your tags** Paste analytics and advertising tags below the snippet with type="text/plain" and data-cq-category set to analytics or marketing.
5. **Add the CSP nonce if you use one** With Django's built-in Content Security Policy, allow https://cookiequiet.com in script-src and connect-src, and add nonce="{{ csp_nonce }}" to inline scripts, marked ones included.
6. **Add the Cookie preferences link** In the footer of base.html, add a plain anchor with href="#cq-preferences" and the data-cq-show-preferences attribute.
7. **Test with Reject all** Open the site in a private window, click Reject all and check that only cq_consent, sessionid and csrftoken can be set. Accept all, reload and check the consent log.

## Where does the snippet go in Django?

Django's template inheritance builds every page from a base "skeleton" template, which child templates name with `{% extends "base.html" %}` and fill through `{% block %}` tags. The snippet belongs in the head of that base template, above any block where pages add scripts:

```django
{# templates/base.html #}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script src="https://cookiequiet.com/cq.js" data-site="{{ COOKIEQUIET_SITE }}" data-consent-mode="1" data-auto-block="1" defer></script>
    <title>{% block title %}My site{% endblock %}</title>
    {% block extra_head %}{% endblock %}
</head>
<body>
    {% block content %}{% endblock %}
</body>
</html>
```

Every view that renders a template extending `base.html` now loads the banner, Wagtail pages included, since Wagtail projects use the same template inheritance.

**Pass the key from settings.** A context processor adds a variable to every template rendered with a request:

```python
# settings.py
COOKIEQUIET_SITE = os.environ.get("COOKIEQUIET_SITE", "")

# mysite/context_processors.py
from django.conf import settings

def cookiequiet(request):
    return {"COOKIEQUIET_SITE": settings.COOKIEQUIET_SITE}
```

Then add `"mysite.context_processors.cookiequiet"` to the `context_processors` list in `TEMPLATES`. Writing the key directly in the template works too: it is public, it appears in every page.

## How do I block my tags until consent?

Paste tracking tags under the snippet and change their opening tag:

```html
<script type="text/plain" data-cq-category="analytics" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXX"></script>
<script type="text/plain" data-cq-category="analytics">
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());
  gtag('config', 'G-XXXXXXX');
</script>
```

Cookiequiet runs marked tags when the category is granted, and at once on later visits where the stored choice includes it. Tags that a page adds in `{% block extra_head %}` work the same way when the block sits below the snippet. The [Google Analytics 4 guide](/platforms/google-analytics-4) explains the Consent Mode alternative, and the [guide on blocking scripts](/guides/block-scripts-until-consent) covers embeds and other tags. Scripts created at runtime by JavaScript are held by the auto-blocker when they come from one of the about 80 trackers it knows.

**Django admin.** The admin uses its own templates and does not extend your `base.html`, so it gets no banner. It is used by your staff, not by visitors, and sets only Django's own cookies.

## What about Django's own cookies?

Django sets `sessionid` when the session is used (for example after a login) and `csrftoken` for cross-site request forgery protection on pages with forms. Both serve a function the visitor asked for, so they are strictly necessary and need no consent in most readings of the ePrivacy rules. List them in the cookie table under necessary; the banner does not block them. The [cookie scanner](/tools/cookie-scanner) finds them along with any third-party cookies.

## How does it work with Django's Content Security Policy?

Django 6.0 added built-in Content Security Policy support: a middleware sends the header, the `SECURE_CSP` setting holds the policy, and the `django.template.context_processors.csp` context processor exposes a `csp_nonce` variable. Add `https://cookiequiet.com` to `script-src` and `connect-src` (the banner fetches its configuration, the geo lookup and the consent records from that origin), and give inline scripts `nonce="{{ csp_nonce }}"`. Marked inline scripts take it too (`<script type="text/plain" data-cq-category="analytics" nonce="{{ csp_nonce }}">`): Cookiequiet copies the nonce to the script it runs after consent, so the policy lets it through.

## How do I add the Cookie preferences link?

```html
<a href="#cq-preferences" data-cq-show-preferences>Cookie preferences</a>
```

## How do I test it?

1. Run the development server, or deploy, and view the page source: `cq.js` is the first script in the head.
2. Open the site in a private window and click **Reject all**. The cookies are `cq_consent` and, on pages with forms or after a login, `csrftoken` and `sessionid`.
3. Clear the cookies, click **Accept all**, reload: the marked tags run and `_ga` appears.
4. Check the consent log in the dashboard.

Setup is one tag in one template, with no Django package to install. Cookiequiet is free for one website with geo-targeting; Starter is €3 per website per month with no pageview limit.

## Questions

### Is there a Django package for Cookiequiet?

No. The banner is one script tag in base.html. A context processor of a few lines can pass the site key from settings, but nothing needs to be installed with pip.

### Do sessionid and csrftoken need consent?

They keep a logged-in session and protect forms against cross-site request forgery, which makes them strictly necessary in most readings of the ePrivacy rules. List them as necessary in the cookie table; the banner does not block them.

### Does the banner show in the Django admin?

Not unless you add it. The admin uses its own templates, which do not extend your base.html. It is a staff tool, so it usually does not need one.

### How do I use it with Django's built-in CSP?

Allow the cookiequiet.com origin in script-src and connect-src in SECURE_CSP, add the csp context processor, and put nonce="{{ csp_nonce }}" on inline scripts, marked ones included. Built-in CSP support arrived in Django 6.0.

## Sources

- [Django docs: The Django template language (template inheritance)](https://docs.djangoproject.com/en/6.0/ref/templates/language/)
- [Django docs: Writing your own context processors](https://docs.djangoproject.com/en/6.0/ref/templates/api/)
- [Django docs: How to use Django's Content Security Policy](https://docs.djangoproject.com/en/6.0/howto/csp/)
- [Django docs: Django 6.0 release notes](https://docs.djangoproject.com/en/6.0/releases/6.0/)
- [Django docs: Cross Site Request Forgery protection](https://docs.djangoproject.com/en/6.0/ref/csrf/)
