diff --git a/docs/changes.rst b/docs/changes.rst
index 667251b4f..04e9c8bd3 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -19,6 +19,7 @@ Pending
* Updated logic that forces values to strings (``force_str``) to render
"Django Debug Toolbar was unable to parse value." when there's a decoding
error.
+* Added Django Channels chat app to the example project.
6.0.0 (2025-07-22)
------------------
diff --git a/example/asgi.py b/example/asgi.py
index 7c5c501f6..90df0854c 100644
--- a/example/asgi.py
+++ b/example/asgi.py
@@ -9,8 +9,23 @@
import os
+from channels.auth import AuthMiddlewareStack
+from channels.routing import ProtocolTypeRouter, URLRouter
+from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application
+from .async_.routing import websocket_urlpatterns
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.async_.settings")
+# Initialize Django ASGI application early to ensure the AppRegistry
+# is populated before importing code that may import ORM models.
+django_asgi_app = get_asgi_application()
-application = get_asgi_application()
+application = ProtocolTypeRouter(
+ {
+ "http": django_asgi_app,
+ "websocket": AllowedHostsOriginValidator(
+ AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
+ ),
+ }
+)
diff --git a/example/async_/consumers.py b/example/async_/consumers.py
new file mode 100644
index 000000000..04d0af6d3
--- /dev/null
+++ b/example/async_/consumers.py
@@ -0,0 +1,37 @@
+# This is the consumer logic for the Django Channels "Web Socket" chat app
+# https://channels.readthedocs.io/en/latest/tutorial/index.html
+import json
+
+from channels.generic.websocket import AsyncWebsocketConsumer
+
+
+class ChatConsumer(AsyncWebsocketConsumer):
+ async def connect(self):
+ self.room_name = self.scope["url_route"]["kwargs"]["room_name"]
+ self.room_group_name = f"chat_{self.room_name}"
+
+ # Join room group
+ await self.channel_layer.group_add(self.room_group_name, self.channel_name)
+
+ await self.accept()
+
+ async def disconnect(self, close_code):
+ # Leave room group
+ await self.channel_layer.group_discard(self.room_group_name, self.channel_name)
+
+ # Receive message from WebSocket
+ async def receive(self, text_data):
+ text_data_json = json.loads(text_data)
+ message = text_data_json["message"]
+
+ # Send message to room group
+ await self.channel_layer.group_send(
+ self.room_group_name, {"type": "chat.message", "message": message}
+ )
+
+ # Receive message from room group
+ async def chat_message(self, event):
+ message = event["message"]
+
+ # Send message to WebSocket
+ await self.send(text_data=json.dumps({"message": message}))
diff --git a/example/async_/routing.py b/example/async_/routing.py
new file mode 100644
index 000000000..ddb7724f6
--- /dev/null
+++ b/example/async_/routing.py
@@ -0,0 +1,9 @@
+# This is the routing logic for the Django Channels "Web Socket" chat app
+# https://channels.readthedocs.io/en/latest/tutorial/index.html
+from django.urls import re_path
+
+from . import consumers
+
+websocket_urlpatterns = [
+ re_path(r"ws/chat/(?P
+
+
+
+
+
+
diff --git a/example/templates/chat/room.html b/example/templates/chat/room.html
new file mode 100644
index 000000000..d2621cd0c
--- /dev/null
+++ b/example/templates/chat/room.html
@@ -0,0 +1,53 @@
+{% comment %}
+ This template is from the Django Channels "Web Socket" chat app
+ https://channels.readthedocs.io/en/latest/tutorial/index.html
+{% endcomment %}
+
+
+
+
+
+
+
+
+ {{ room_name|json_script:"room-name" }}
+
+
+
diff --git a/example/templates/index.html b/example/templates/index.html
index a10c2b5ac..cf1b233ec 100644
--- a/example/templates/index.html
+++ b/example/templates/index.html
@@ -26,6 +26,7 @@ Index of Tests