using StreamChat.Core;
using UnityEngine;
public class ChatManager : MonoBehaviour
{
private IStreamChatClient _chatClient;
private void Start()
{
_chatClient = StreamChatClient.CreateDefaultClient();
}
}Unity Introduction
The Stream Chat Unity SDK lets you ship production-grade chat and messaging into any Unity project — from mobile apps and PC/console games to WebGL and XR. The SDK is a C# library that talks to the Stream Chat service and keeps your local chat state — channels, messages, users, threads, polls — automatically in sync with the server over a realtime WebSocket connection.
If you'd like a guided walk-through first, try the Unity Introduction Tutorial.
Key features
-
Stateful, realtime client.
IStreamChatClient,IStreamChannel,IStreamMessage,IStreamUser,IStreamThread, andIStreamPollalways reflect the latest server state. The SDK maintains the connection and patches the local model as events arrive. -
Subscribe to live events with plain C# events. React to new messages, reactions, member changes, typing indicators, thread replies, presence changes, poll votes, channel invites, and more.
-
Full chat feature set. Direct messages, group channels, livestream chat, threads & replies, reactions, polls, message search, file/image uploads, pinned messages, mentions, channel invites, typing indicators, read state, unread counts, moderation, push notifications.
-
All Unity platforms. Android, iOS, WebGL, Windows, macOS, Linux, consoles, and the other targets Unity supports.
-
IL2CPP friendly. Works with IL2CPP and AOT-only platforms.
-
Open source. Full C# source on GitHub, actively developed.
Before you start
When your app connects to Stream Chat, it connects as a specific user identified by three values: apiKey, userId, and a userToken. This guide focuses on a regular authenticated user; guests and anonymous users are also possible. To obtain credentials:
-
Register for a free account and copy the
apiKeyof your application from the Stream dashboard. -
In the dashboard, open your application and use the Explorer tab to create a user and copy its
userId. -
Generate a
userTokenwith our online token generator for quick experiments. For production, your backend should issue tokens — see Connect a User below.
Getting started
Download the latest Stream Chat SDK from the Releases page and import the .unitypackage into your project.

Newtonsoft.Json conflict
The Unity SDK uses Unity's Newtonsoft Json package for serialization. If your project already includes a Newtonsoft.Json library you may see a duplicate-type compiler error:
Resolve it by deleting one of the duplicates — either the com.unity.nuget.newtonsoft-json@3.0.2 folder at StreamChat/Libs/Serialization (to keep your existing copy) or the Newtonsoft.Json package already in your project (to keep the one bundled with the SDK). The SDK-bundled copy is configured to work with Unity Engine and fully supports IL2CPP.
Initialize the client
The entry point to the API is IStreamChatClient. Create an instance with StreamChatClient.CreateDefaultClient():
The SDK runs its own update loop, so you don't need any per-frame work — just keep the client alive for the lifetime of your chat session.
Connect a User
Call ConnectUserAsync with your API key, user id, and user token. The returned IStreamLocalUserData exposes the connected user and live unread counters.
public async Task ConnectAsync()
{
// For prototyping you can generate an insecure dev token with
// StreamChatClient.CreateDeveloperAuthToken("user-id").
// In production, your backend must issue tokens.
var localUserData = await _chatClient.ConnectUserAsync("api-key", "user-id", "user-token");
Debug.Log($"User {localUserData.UserId} is connected!");
}For production use, pass an ITokenProvider so the SDK can fetch a fresh token from your backend whenever it needs one (initial connection, reconnect, after expiration). This keeps the WebSocket connected across token refreshes with no extra work on your side:
using StreamChat.Libs.Auth;
var tokenProvider = new YourTokenProvider();
await _chatClient.ConnectUserAsync("api-key", "user-id", tokenProvider);
// Your backend must authenticate the caller before issuing a Stream token —
// never expose an endpoint that returns a token for any userId.
public class YourTokenProvider : ITokenProvider
{
public Task<string> GetTokenAsync(string userId)
=> FetchTokenFromYourBackendAsync(userId);
}See Tokens & Authentication for the full auth guide.
Create or get a Channel
A channel is where messages live. There are two ways to obtain one:
Channel with an id
Use this for general-purpose channels — clans, clubs, public/private groups that users can join or leave:
var channel = await _chatClient.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");Channel for a group of users
Use this for direct messages and "shared history per unique group of users" — calling this with the same set of users always returns the same channel, regardless of order:
var filters = new IFieldFilterRule[]
{
UserFilter.Id.In("friend-user-id-1", "friend-user-id-2")
};
var friends = await _chatClient.QueryUsersAsync(filters);
var groupToChat = new List<IStreamUser>();
groupToChat.AddRange(friends); // Add friends
groupToChat.Add(_chatClient.LocalUserData.User); // Add local user
var channel = await _chatClient.GetOrCreateChannelWithMembersAsync(ChannelType.Messaging, groupToChat);Channel Type
The channel type controls the default permissions and settings. Use one of the predefined types (ChannelType.Messaging, ChannelType.Livestream, ChannelType.Team, ChannelType.Commerce, ChannelType.Gaming) or create a custom type in the dashboard. ChannelType.Messaging is a good default for classic chat applications.
Send a Message
Once you hold an IStreamChannel, sending a message is one line:
var message = await channel.SendNewMessageAsync("Hello");For richer messages — mentions, threaded replies, pinning, quoting, custom data — use the StreamSendMessageRequest overload:
var message = await channel.SendNewMessageAsync(new StreamSendMessageRequest
{
Text = "Hello @someone!",
MentionedUsers = new List<IStreamUser> { someUser }, // Mention a user
ParentId = parentMessage.Id, // Reply in a thread
QuotedMessage = otherMessage, // Quote another message
Pinned = true, // Pin the message
PinExpires = DateTimeOffset.Now.AddDays(7), // Pin for 7 days
ShowInChannel = true, // Show the thread reply in the channel too
CustomData = new StreamCustomDataRequest
{
{ "my_lucky_numbers", new List<int> { 7, 13, 81 } }
}
});Read Messages
Every channel exposes its loaded messages via IStreamChannel.Messages. The collection stays up to date automatically as messages are sent, edited, deleted, or reacted to.
var channel = await _chatClient.GetOrCreateChannelWithIdAsync(ChannelType.Messaging, "my-channel-id");
foreach (var message in channel.Messages)
{
Debug.Log(message.Text); // Message text
Debug.Log(message.User); // Message author
Debug.Log(message.ReactionCounts); // Per-type reaction counts
Debug.Log(message.Attachments); // Attached files / images / links
}
// Paginate older messages on demand (e.g. as the user scrolls up)
await channel.LoadOlderMessagesAsync();Listen to Realtime Events
This is what makes the Unity SDK stateful: as soon as you obtain a channel, you can subscribe to plain C# events on it and let the SDK push updates to your UI as they happen on the server — for the local user and every other participant.
// Client-level events fire for the local user across all channels
_chatClient.Connected += localUserData => Debug.Log($"Connected as {localUserData.UserId}");
_chatClient.Disconnected += () => Debug.Log("Disconnected");
_chatClient.ChannelDeleted += (cid, id, type) => Debug.Log($"Channel {cid} deleted");
_chatClient.AddedToChannelAsMember += (ch, member) => Debug.Log("Added to a channel");
_chatClient.ChannelInviteReceived += (ch, invitee) => Debug.Log("Invite received");
// Channel-level events fire on a channel you are watching
channel.MessageReceived += (ch, msg) => Debug.Log($"New message: {msg.Text}");
channel.MessageUpdated += (ch, msg) => Debug.Log($"Edited: {msg.Text}");
channel.MessageDeleted += (ch, msg, isHardDelete) => Debug.Log("Message deleted");
channel.ReactionAdded += (ch, msg, reaction) => Debug.Log($"{reaction.User.Name} reacted {reaction.Type}");
channel.MemberAdded += (ch, member) => Debug.Log($"{member.User.Name} joined");
channel.MemberRemoved += (ch, member) => Debug.Log($"{member.User.Name} left");
channel.UserStartedTyping += (ch, user) => Debug.Log($"{user.Name} is typing…");
channel.UserStoppedTyping += (ch, user) => Debug.Log($"{user.Name} stopped typing");
// User presence (online / offline) updates flow through the IStreamUser instance
foreach (var member in channel.Members)
{
member.User.PresenceChanged += (user, isOnline, lastActive) =>
Debug.Log($"{user.Name} online: {isOnline}");
}Channels returned by GetOrCreateChannel* and QueryChannelsAsync are automatically watched — they receive realtime updates. You can inspect channel.IsWatched and _chatClient.WatchedChannels at any time. See the Events guide for the full event catalog (channel, message, thread, user, poll).
Query Channels
Use QueryChannelsAsync with strongly typed filter builders to load multiple channels at once. A very common pattern is "show channels the local user belongs to":
public async Task QueryChannelsAsync()
{
var filters = new IFieldFilterRule[]
{
ChannelFilter.Members.In(_chatClient.LocalUserData.UserId)
};
// Optional: sort by latest activity, member count, custom fields, …
var sort = ChannelSort.OrderByDescending(ChannelSortFieldName.LastMessageAt);
var channels = await _chatClient.QueryChannelsAsync(filters, sort, limit: 30, offset: 0);
foreach (var channel in channels)
{
Debug.Log(channel.Id);
Debug.Log(channel.Name);
Debug.Log(channel.Messages.Count); // Messages
Debug.Log(channel.Members.Count); // Members
}
}Combine multiple ChannelFilter.* builders for complex queries (EqualsTo, In, Autocomplete, GreaterThan, custom fields, etc.). See Querying Channels for the full guide, including pagination, sorting, and filter composition.
Send a Reaction
IStreamMessage.SendReactionAsync lets the local user react to any message. Reactions count toward ReactionCounts and ReactionScores, and other participants receive a ReactionAdded event in realtime.
// Simple reaction with a score of 1
await message.SendReactionAsync("like");
// Reaction with a custom score value (e.g. for cumulative "claps")
await message.SendReactionAsync("clap", 10);
// Replace all previous reactions from this user with a new one
await message.SendReactionAsync("love", enforceUnique: true);More features
The Unity SDK supports the full Stream Chat feature set. Some of the most commonly used building blocks:
-
Threads & Replies —
message.GetThreadAsync(),message.LoadRepliesAsync(),Client.QueryThreadsAsync(), livethread.ReplyReceivedevents, per-thread read state. -
Reactions — custom score, enforce-unique,
message.ReactionCounts,OwnReactions. -
Message Search —
Client.SearchMessagesAsync()with channel and message filters, free-text queries, sort by relevance, cursor pagination. -
Polls —
Client.Polls.CreatePollAsync(),poll.CastVoteAsync(), liveVoteCasted/VoteChangedevents. -
File & Image Uploads —
channel.UploadFileAsync(),channel.UploadImageAsync(), attach withStreamAttachmentRequest. -
Pinned Messages —
message.PinAsync()with optional expiry,channel.PinnedMessages. -
Typing Indicators —
channel.SendTypingStartedEventAsync(),channel.TypingUsers. -
Read State & Unread Counts —
channel.MarkChannelReadAsync(),localUserData.TotalUnreadCount,Client.GetLatestUnreadCountsAsync(). -
Channel Members & Invites —
channel.AddMembersAsync(),channel.InviteMembersAsync(),channel.AcceptInviteAsync(). -
User Presence —
user.PresenceChangedevent,user.Online,user.LastActive,user.MarkInvisibleAsync(). -
Moderation —
channel.BanUserAsync(),channel.ShadowBanUserAsync(),message.FlagAsync(),user.MuteAsync(). -
Push Notifications — register a device and let Stream deliver pushes via FCM / APNS / Huawei.
-
Permissions —
channel.OwnCapabilities, role-based permission system.
Disconnect User
Call DisconnectUserAsync to close the WebSocket and clear local chat state (e.g. when signing the user out or quitting the app):
public async Task DisconnectAsync()
{
await _chatClient.DisconnectUserAsync();
Debug.Log("User disconnected");
}Next steps
-
Explore the sample project that ships with the SDK — it contains a full UGUI chat UI built on the APIs introduced above.
-
Read the Architecture & Benchmark guide to learn how the stateful client is structured internally.
-
Found a bug or have a feature request? Open an issue on GitHub — we read every one.