Before you start
- A Django project with a base template that your page templates
{% extends %}, oftentemplates/base.html. - Your website ID from Websites in the Pidge inbox. This is the plain snippet it comes in:
<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 %}
{% csp_nonce_attr %}adds Django's CSP nonce when you use one, and prints nothing when you don't. It's built in from Django 6.1, so there's nothing to{% load %}. On Django 6.0 it's a template error: writenonce="{{ csp_nonce }}"instead, or leave it out if you don't use a CSP.- The
{% if %}leaves the chat out whereverPIDGE_WEBSITE_IDisn't set. window.$pidge || []keeps any commands your own scripts pushed before this line ran.
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',
connect-srcneeds the chat's address twice: once as it is, and once withwss://for the live connection. Thereplacedoes that.- Without
CSP.UNSAFE_INLINEinstyle-src, the bubble loads with no styling and fills the page. - Without the
cspcontext processor,{% csp_nonce_attr %}prints nothing, so the first line of the snippet is blocked and there's no bubble. - The loader line doesn't need a nonce, because its address is in
script-src.
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
- Open your site in a private window, so you're seeing what a visitor sees.
- The chat bubble should be in the bottom corner. Send yourself a message and answer it from the inbox.
- 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
- WordPress
- Shopify
- Squarespace
- Wix
- Webflow
- Ghost
- Framer
- Next.js
- Astro
- React
- Vue and Nuxt
- Gatsby
- Hugo
- Jekyll
- Laravel
- Carrd
- Plain HTML
Stuck, or did a step look different on your screen? Tell us in the chat on this page.