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, and IStreamPoll always 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:

  1. Register for a free account and copy the apiKey of your application from the Stream dashboard.

  2. In the dashboard, open your application and use the Explorer tab to create a user and copy its userId.

  3. Generate a userToken with 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:

undefined

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():

using StreamChat.Core;
using UnityEngine;

public class ChatManager : MonoBehaviour
{
    private IStreamChatClient _chatClient;

    private void Start()
    {
        _chatClient = 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 & Repliesmessage.GetThreadAsync(), message.LoadRepliesAsync(), Client.QueryThreadsAsync(), live thread.ReplyReceived events, per-thread read state.

  • Reactions — custom score, enforce-unique, message.ReactionCounts, OwnReactions.

  • Message SearchClient.SearchMessagesAsync() with channel and message filters, free-text queries, sort by relevance, cursor pagination.

  • PollsClient.Polls.CreatePollAsync(), poll.CastVoteAsync(), live VoteCasted / VoteChanged events.

  • File & Image Uploadschannel.UploadFileAsync(), channel.UploadImageAsync(), attach with StreamAttachmentRequest.

  • Pinned Messagesmessage.PinAsync() with optional expiry, channel.PinnedMessages.

  • Typing Indicatorschannel.SendTypingStartedEventAsync(), channel.TypingUsers.

  • Read State & Unread Countschannel.MarkChannelReadAsync(), localUserData.TotalUnreadCount, Client.GetLatestUnreadCountsAsync().

  • Channel Members & Inviteschannel.AddMembersAsync(), channel.InviteMembersAsync(), channel.AcceptInviteAsync().

  • User Presenceuser.PresenceChanged event, user.Online, user.LastActive, user.MarkInvisibleAsync().

  • Moderationchannel.BanUserAsync(), channel.ShadowBanUserAsync(), message.FlagAsync(), user.MuteAsync().

  • Push Notifications — register a device and let Stream deliver pushes via FCM / APNS / Huawei.

  • Permissionschannel.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.