Add live chat to Django

In Django, the snippet goes in base.html, the template your other templates extend. A small context processor passes the website ID from your settings, and Django's own CSP needs one extra attribute.

Tested with Django 6.1 on Python 3.13 on 2 November 2026, with and without Django's Content Security Policy.

Before you start

HTML
<script>window.$pidge = []; window.PIDGE_WEBSITE_ID = "YOUR-WEBSITE-ID";</script>
<script src="https://app.pidge.chat/client/l.js" async></script>

Your own copy is in the Pidge inbox under Websites, in Code for your website, with your website ID already filled in. If you've set up a chat domain, it loads from there instead.

You can paste those two lines into base.html as they are. The steps below keep the ID in your settings instead, so staging and production can use different websites.

1. Add the website ID to your settings

In settings.py, read it from the environment:

import os

PIDGE_WEBSITE_ID = os.environ.get("PIDGE_WEBSITE_ID", "")

If you load settings another way, like django-environ, use that. The template only needs settings.PIDGE_WEBSITE_ID.

2. Pass it to every template

Create context_processors.py in one of your apps (here the app is called pages):

from django.conf import settings


def pidge(request):
    return {"pidge_website_id": settings.PIDGE_WEBSITE_ID}

Then add it to TEMPLATES in settings.py:

TEMPLATES = [
    {
        ...
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'pages.context_processors.pidge',
            ],
        },
    },
]

Context processors run for templates rendered with a request, which is what render() and the generic views do. A template rendered without one won't get the ID.

3. Add the snippet to base.html

Paste this just before </body>:

{% if pidge_website_id %}
  <script {% csp_nonce_attr %}>window.$pidge = window.$pidge || []; window.PIDGE_WEBSITE_ID = "{{ pidge_website_id }}";</script>
  <script src="https://app.pidge.chat/client/l.js" async></script>
{% endif %}

Open the chat from a button

Without a Content Security Policy, a plain onclick works:

<button type="button" onclick="window.$pidge.push(['do', 'chat:open'])">Chat with us</button>

With Django's CSP on, the browser blocks inline onclick. Add the listener in a script with the nonce instead:

<button type="button" class="open-chat">Chat with us</button>
<script {% csp_nonce_attr %}>
  document.querySelectorAll(".open-chat").forEach((button) => {
    button.addEventListener("click", () => window.$pidge.push(["do", "chat:open"]));
  });
</script>

The widget commands list everything else you can push, like ["set", "user:nickname", [name]] for a logged-in user. For emails your team can trust, sign them on the server: see signed visitor emails.

If you use Django's Content Security Policy

Django 6.0 and later have a built-in CSP. It's off in a new project: you turn it on with ContentSecurityPolicyMiddleware and the SECURE_CSP setting. With it on, the chat needs its script, its connection and its inline styles allowed. This is the policy we tested:

from django.utils.csp import CSP

PIDGE_ORIGIN = "https://app.pidge.chat"

SECURE_CSP = {
    "default-src": [CSP.SELF],
    "script-src": [CSP.SELF, CSP.NONCE, PIDGE_ORIGIN],
    "connect-src": [CSP.SELF, PIDGE_ORIGIN, PIDGE_ORIGIN.replace("http", "ws", 1)],
    "style-src": [CSP.SELF, CSP.UNSAFE_INLINE],
}

Add the middleware and the csp context processor, which is what gives {% csp_nonce_attr %} the nonce:

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.middleware.csp.ContentSecurityPolicyMiddleware',
    ...
]

# in TEMPLATES → OPTIONS → context_processors:
    'django.template.context_processors.csp',

If you already have a SECURE_CSP, add these sources to it rather than replacing it. We haven't tested image attachments under a CSP. If they don't show, the browser console will name the directive to change.

List your domains in Pidge

The chat only connects on domains you've listed for the website. In the inbox, open Websites and put each domain on its own line in Domains. Subdomains are included, so example.com also covers www.example.com.

If people visit your site at a second address your host gave it, list that full address too.

To try the chat while you develop, add localhost to the list too. Pidge matches the host name, so any port works. 127.0.0.1 isn't accepted, and that's the address manage.py runserver prints, so open http://localhost:8000 instead.

Check that it works

  1. Open your site in a private window, so you're seeing what a visitor sees.
  2. The chat bubble should be in the bottom corner. Send yourself a message and answer it from the inbox.
  3. In the inbox, the website's Not installed yet label goes away after the first visit from a listed domain.

If the bubble shows but the chat won't connect, the domain you're on isn't in the website's list in Pidge. If there's no bubble at all, view the page source and look for PIDGE_WEBSITE_ID. If it's missing, the setting is empty or the context processor isn't in TEMPLATES. If it's there, check the browser console for a Content Security Policy error.

Django's own guides: docs.djangoproject.com/en/6.1/ref/templates/api/ and docs.djangoproject.com/en/6.1/ref/csp/

Other platforms

Stuck, or did a step look different on your screen? Tell us in the chat on this page.

Get your snippet

Start free

The free plan covers one website and two people replying. No card needed.