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) in sync with the server over a realtime WebSocket connection.

Start here

This SDK is the client half of your integration. App settings, permissions, webhooks, push templates and data retention are configured from your backend with a server-side SDK or the REST API, and the token your app connects with has to be signed there too. See the server-side overview for what belongs there.

Build with Stream CLI and Agent Skills

The Stream CLI and Agent Skills give AI coding agents the tools and knowledge to work with Stream. There is no dedicated skill pack for Unity yet, but the default skills still help with live documentation and CLI-driven API work.

Install the CLI and the default skills:

curl -fsSL https://getstream.io/cli.sh | bash
getstream skills

Once installed, invoke /stream from your agent. It routes documentation lookups and API operations for you.

Stream Agent Skills can also be installed from skills.sh.

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.

Unity's Import Unity Package dialog listing the Stream Chat SDK contents, ready to import

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:

Unity console showing two errors: "Multiple precompiled assemblies with the same name Newtonsoft.Json.dll included or the current platform"

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 & Replies: message.GetThreadAsync(), message.LoadRepliesAsync(), Client.QueryThreadsAsync(), live thread.ReplyReceived events, 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(), live VoteCasted / VoteChanged events.

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

  • 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.PresenceChanged event, 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");
}

What's next

The Unity SDK ships the sample project rather than reusable UI components, so you build your own chat UI on top of the stateful models.

Found a bug or have a feature request? Open an issue on GitHub. We read every one.