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
- 1Open the base template your pages extend, the one named in the extends tag at the top of the rest of your templates.
- 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.
- 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.
- 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.
- 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.
- 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.
| One tag, nothing else | There is no second file, no stylesheet and no package to install. The tag is the whole install. |
|---|---|
| Where it goes | Immediately 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 required | Without it the loader stops and writes a warning to the browser console rather than rendering anything. |
| It finds itself | The 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 once | A 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 tag | Everything 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 late | Only 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 collide | The 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
- Install on Next.jsapp/layout.tsx in the App Router, or pages/_document.tsx in the Pages Router. Not a client component that can unmount.
- Install on Reactindex.html at the project root for a Vite app, or public/index.html for Create React App. Immediately before the closing body tag.
- Install on Vueindex.html at the project root, after the div the app mounts into and immediately before the closing body tag.
- Every install guideThe same one line of HTML, and where it goes on each platform.
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.