For AI agents: Find a documentation index at https://talkjs.com/llms.txt (full content at https://talkjs.com/llms-full.txt). Get a markdown version of any page by appending .md to its URL path.
Skip to content

Django chat

Django chat without Channels, Daphne, or Redis

Most Django chat tutorials start by installing Channels, running Daphne next to your app, and setting up Redis. With TalkJS, you skip all three. The browser connects to TalkJS, which stores and delivers the messages, and your project adds one view that signs a token for request.user.

# chat/views.py
def talkjs_token(request):
    if not request.user.is_authenticated:
        return JsonResponse({"error": "login required"}, status=401)
    token = jwt.encode({
        "tokenType": "user",
        "iss": settings.TALKJS_APP_ID,
        "sub": str(request.user.pk),
        "exp": int(time.time()) + 24 * 3600,
    }, settings.TALKJS_SECRET_KEY, algorithm="HS256")
    return JsonResponse({"token": token})
Frank
Hi! Can I book the studio for Thursday at 10?
8m ago
You
Thursday at 10 works. I’ve sent the booking link here.
6m ago
Frank
Booked, thank you!
5m ago
ok-hand
thinking
You
You’re welcome. I’ll have the studio ready for you.
2m ago
Write a reply…
You
Hi Priya! Do you have a piano lesson free on Thursday?
6m ago
Priya
Yes, 4pm works. Is it for you or your daughter?
4m ago
thumbs-up
You
My daughter. She’s a beginner.
2m ago
Priya
Perfect. I’ll send the booking link here.
just now
Thanks, see you Thursday!

What does adding chat change in a Django project?

With Channels, you run the realtime part yourself, and your project changes before it stores a single message.

What each approach adds to a Django project

Part of the projectWith TalkJSWith Channels
ServersWith TalkJSThe WSGI server you run nowWith ChannelsDaphne or another ASGI server, alone or beside your WSGI server with a proxy in front
Other servicesWith TalkJSNoneWith ChannelsRedis, for channels_redis, the only official channel layer for production
SettingsWith TalkJSYour TalkJS app ID and secret keyWith Channelsdaphne and channels in INSTALLED_APPS, ASGI_APPLICATION, CHANNEL_LAYERS
The logged-in userWith TalkJSrequest.user in a normal viewWith ChannelsAuthMiddlewareStack, then scope["user"] in each consumer
Database accessWith TalkJSYour views as they areWith Channelsdatabase_sync_to_async around ORM calls in async consumers
History, offline notifications, moderationWith TalkJSIncludedWith ChannelsYours to build
The Channels column comes from the Channels docs, November 2026.

The WebSocket is the small part of the job. We keep every message with no retention limit. Email, SMS, and mobile push reach people who are offline (SMS is charged separately), and word filters and contact masking come on every plan. Development mode is free, and after launch you pay per monthly active user.

The view, the URL, and the template

Who talks to whom once the chat is in

Your Django appServes the page and signs a token at /talkjs/token/
Requests both ways between the browser and your Django appHTTP
The browserShows the chat and sends the token with the session
Messages both ways between the browser and TalkJSSecure WebSockets
TalkJSStores and delivers the messages, and sends notifications to people who are offline
No chat traffic goes through your Django app. If you want messages in your own database, TalkJS can also send webhooks to a view you choose.

Install PyJWT next to Django with pip install pyjwt, and add TALKJS_APP_ID and TALKJS_SECRET_KEY to your settings, read from the environment. The view signs a token for whoever is logged in:

# chat/views.py
import time

import jwt
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse
from django.shortcuts import render


def talkjs_token(request):
    if not request.user.is_authenticated:
        return JsonResponse({"error": "login required"}, status=401)
    token = jwt.encode(
        {
            "tokenType": "user",
            "iss": settings.TALKJS_APP_ID,
            "sub": str(request.user.pk),
            "exp": int(time.time()) + 24 * 3600,
        },
        settings.TALKJS_SECRET_KEY,
        algorithm="HS256",
    )
    return JsonResponse({"token": token})


@login_required
def chat(request, other_user_id):
    ids = sorted([str(request.user.pk), str(other_user_id)])
    return render(request, "chat/chat.html", {
        "app_id": settings.TALKJS_APP_ID,
        "other_user_id": other_user_id,
        "conversation_id": "dm_" + "_".join(ids),
    })

Add both to urls.py:

path("chat/<int:other_user_id>/", views.chat),
path("talkjs/token/", views.talkjs_token),

The template, chat/templates/chat/chat.html, loads the chat from jsDelivr, passes the token to the session, and opens a conversation between the two users. Use your own display name field in place of get_username:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@talkjs/web-components@0.3.13/default.css">
<script type="importmap" nonce="{{ csp_nonce }}">
{"imports": {
  "@talkjs/web-components": "https://cdn.jsdelivr.net/npm/@talkjs/web-components@0.3.13",
  "@talkjs/core": "https://cdn.jsdelivr.net/npm/@talkjs/core@1.12.1"
}}
</script>
<script type="module" nonce="{{ csp_nonce }}">
  import "@talkjs/web-components";
  import { getTalkSession } from "@talkjs/core";

  const session = getTalkSession({ appId: "{{ app_id }}", userId: "{{ request.user.pk }}" });
  const res = await fetch("/talkjs/token/");
  session.setToken((await res.json()).token);

  session.currentUser.createIfNotExists({ name: "{{ request.user.get_username|escapejs }}" });
  const conversation = session.conversation("{{ conversation_id }}");
  conversation.createIfNotExists();
  conversation.participant("{{ other_user_id }}").createIfNotExists();
</script>

<t-chatbox
  style="width: 400px; height: 600px;"
  app-id="{{ app_id }}"
  user-id="{{ request.user.pk }}"
  conversation-id="{{ conversation_id }}"
></t-chatbox>

The other user needs a TalkJS user before they can join: create one when they sign up, from their own session or with the REST API. For a longer walkthrough with a full project, see our Django chat tutorial.

Once the chat loads with a token, turn on authentication in your TalkJS dashboard, so a browser without a valid token can’t connect. Tokens from this view last 24 hours; the advanced authentication guide covers longer sessions.

If you’ve turned on Django 6’s Content Security Policy

Django 6.0 added a built-in Content Security Policy. It’s off until you add ContentSecurityPolicyMiddleware and a SECURE_CSP setting. Once it’s on, a chat loaded from jsDelivr needs a policy that names jsDelivr as well as TalkJS.

Two policies, the same template

Policy: The sources on the TalkJS CSP page, all on talkjs.comResult: No chat. The components and their stylesheet come from cdn.jsdelivr.net
Policy: The SECURE_CSP belowResult: A live chat, with no 'unsafe-inline'
Django 6.1.3 with @talkjs/web-components 0.3.13, November 2026.

This is the setting that rendered the chat:

# settings.py
from django.utils.csp import CSP

MIDDLEWARE = [
    # ...
    "django.middleware.csp.ContentSecurityPolicyMiddleware",
]

# Add to TEMPLATES[0]["OPTIONS"]["context_processors"]:
#     "django.template.context_processors.csp",

SECURE_CSP = {
    "default-src": [CSP.SELF],
    "script-src": [CSP.SELF, CSP.NONCE, "https://cdn.jsdelivr.net"],
    "style-src": [CSP.SELF, "https://cdn.jsdelivr.net"],
    "connect-src": [CSP.SELF, "https://*.talkjs.com", "wss://*.talkjs.com"],
    "img-src": [CSP.SELF, "https://*.talkjs.com", "data:"],
    "frame-src": [CSP.NONE],
}

The context processor puts csp_nonce in your templates for the import map and the module script. The components don’t use a frame, so frame-src stays 'none'. File sharing and notification sounds need a few more sources, which the TalkJS CSP page lists. If you serve the TalkJS packages from your own static files instead of jsDelivr, CSP.SELF covers the scripts and the stylesheet.

Styleseat logo
Because implementing TalkJS was as fast and easy as it was, we were able to greenlight and move this project along very quickly. Had we not chosen this path, the feature would have either been delayed, or shelved due to the resources needed to implement it.
Sean Elliot
Engineering Manager, Styleseat

Ready-made chat UI you can customize

You style the pre-built chat UIs with properties, CSS, or a custom theme. Need a chat popup? Open a chatbox from a button of your own.

Inbox

The TalkJS Inbox shows the chat history on the left, selected conversation on the right. It’s designed to act as the messaging center of your app, usually living on its own page.

Chatbox

The TalkJS Chatbox is the right half of the Inbox UI mode. It takes up less space and is designed to be embedded in your app near an order confirmation, a booking, or user profile.

Conversation list

The user’s conversations in one list. Put a Chatbox next to it to open the one they pick, or use the Inbox, which combines the two.

FAQs

Yes. The token carries your user’s primary key as the TalkJS user ID, and only your view signs it, so the person Django says is logged in is the person who chats.

Yes. The view and the template use nothing new in Django 6. Only the Content Security Policy settings need Django 6.0 or later, so on an older version, leave out the CSP middleware, the setting, and the nonce attributes.

Through webhooks. TalkJS can send a message.sent event to a URL you choose, so a Django view can save each message or start your own follow-up. Mark that view @csrf_exempt and verify the webhook signature instead.