Install guide

Installing a chat widget on a Django site

Django sites hang off one base template that everything else extends, which makes this a single paste. The interesting parts are the block structure, which decides whether a child template can turn the widget off, and the handful of templates that never extend the base at all.

The tag

<script src="https://cdn.askably.xyz/w.js" data-key="pk_live_YOUR_KEY" defer></script>

Your own key replaces the placeholder. You will find it on the install tab of the assistant.

The origin the page is served from has to be on the allowlist for that assistant, or nothing renders and the browser console says which origin was refused. An apex domain and its www are two different origins to a browser, so list both, along with any staging or preview host you want it to work on.

Where it goes on Django

The base template your pages extend, usually templates/base.html, immediately before the closing body tag.

Any Django project that renders HTML through the template engine. A project that only serves an API and leaves rendering to a separate front end needs the tag in that front end instead.

Step by step

  1. 1Open the base template your pages extend, the one named in the extends tag at the top of the rest of your templates.
  2. 2Paste the tag on its own line immediately before the closing body tag. Django template syntax acts only on its own delimiters, so a plain script tag passes through untouched and needs no escaping helper.
  3. 3If the base defines a scripts block at the end of the body, put the tag inside it. A child template can then override that block to suppress the widget on one page.
  4. 4Leave a comment there noting that any child overriding the block replaces its contents outright, so those templates need the block super call if they want to keep the tag.
  5. 5Check your 404 and 500 templates. Django renders them through the same engine, but they only carry the launcher if you wrote them to extend the base.
  6. 6Restart the process if you run the cached template loader, then load a page and confirm the launcher appears.

What goes wrong on Django

127.0.0.1 and localhost are different origins

The development server prints one of these and most people habitually type the other. A browser treats them as two separate origins, so allowlisting the address you were told about and then browsing the address you typed leaves you with a page that has the tag in its source and no launcher on it.

The console names the origin that was refused, verbatim. Read it before changing anything else, because this one costs people an afternoon.

The static files machinery has nothing to do with this

The reflex on a Django project is to drop a JavaScript file into a static directory and reference it with the static template tag. Do not. The loader is served from its own address, so there is no file for collectstatic to gather, no entry needed in STATICFILES_DIRS, and no hashed name for a manifest storage backend to rewrite.

Routing it through the static pipeline produces a reference to a file that does not exist and a 404 in your logs, which then looks like a broken widget rather than an absent one.

Overriding the scripts block silently drops it

A child template that declares a block of the same name replaces the parent's contents instead of adding to them. A single page that defines its own scripts block to pull in one chart library loses the launcher, and nothing anywhere reports that it happened.

Either put the block super call at the top of every child block, or keep the tag outside the block entirely and accept that no page can opt out.

Origins to allowlist

These are the origins a Django site is typically served from. List every one you want the assistant to answer on, including the ones only you visit.

  • http://127.0.0.1:8000
  • http://localhost:8000
  • https://yourdomain.com

How the tag behaves, wherever you put it

The rest of this is the same on every platform, so it is worth reading once rather than on each guide.

Loader behaviour
One tag, nothing elseThere is no second file, no stylesheet and no package to install. The tag is the whole install.
Where it goesImmediately before the closing body tag. It works in the head too, because it defers, but the body keeps it out of the way of anything that measures render order.
data-key is requiredWithout it the loader stops and writes a warning to the browser console rather than rendering anything.
It finds itselfThe loader reads the tag it was loaded from, and falls back to the first tag on the page carrying a data-key. Platforms that move or re-inject the tag do not break it.
It installs onceA second copy of the tag on the same page is ignored, so a template that renders twice does not produce two launchers.
Assets follow the tagEverything else it needs is fetched from the same origin the tag was served from, so a proxy or a private domain needs no extra configuration.
The panel loads lateOnly the small launcher is on the page at first. The conversation panel is fetched the first time a visitor opens it, in its own frame.
Styles cannot collideThe launcher renders inside a closed shadow root and the panel inside a frame, so nothing on the host page can restyle either, and neither can restyle the host page.

Set up on Django for a particular trade

The tag is the same everywhere. What is not the same is which of your pages the assistant can read once it is running, and on Django that depends on what kind of site this is. These go one level further than this guide.

Taking it off again

Delete the line from the base template and redeploy. If you run the cached template loader in production, restart the process so it reloads the template from disk.

Before you remove it

  • Removing the tag stops the widget on that site. It does not delete the assistant, its material, or the enquiries it has already collected.
  • If you are moving the widget to a different host rather than retiring it, add the new origin to the allowlist before you remove the old tag, or there is a window where neither works.

Questions

Should this go through a context processor or a custom template tag?
No. It is static markup carrying one publishable key, so it belongs in the template exactly as written. A context processor would add work to every request and buy nothing.
Do I need to change ALLOWED_HOSTS?
That setting decides which host headers Django itself will answer, and it is unrelated. The list that matters here is the origin allowlist on the assistant, checked against what the browser is showing rather than against your server config.
Why does it work on the site but not on the error page?
Your 404 or 500 template almost certainly does not extend the base. Django looks those up at the root of your template directories and renders them standalone unless you wrote them to inherit.
Can it answer questions about a signed in user's account?
No. It answers from indexed material, and a page behind a login is not something an anonymous crawl can read. Account specific questions should be handed to a person, and the assistant should be told to do exactly that.

Keep reading

Try it on your own material

Upload a document or point it at your site, paste one line of HTML, then ask it something only your business could answer.