Introduction

This guide quickly brings you up to speed on Stream’s Chat API. The API is flexible and allows you to build any type of chat or messaging. e sure to try the API tour and the React Chat tutorial before diving into these docs.

You're currently not logged in. If you Log in the examples below will use your API key and secret.

Setup

The Stream Chat API client is available as an NPM package.

After installing it, simply import it in your project and you're ready to go:

import { StreamChat } from 'stream-chat';
// or
const StreamChat = require('stream-chat').StreamChat;

Getting started

Chat Client

Let’s get started by installing the chat SDK, initializing the client and setting the current user.

// using npm
npm install stream-chat

// using Yarn
yarn add stream-chat
const client = new StreamChat('YOUR_API_KEY');
await client.setUser(
    {
        id: 'jlahey',
        name: 'Jim Lahey',
        image: 'https://i.imgur.com/fR9Jz14.png',
    },
    'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiamxhaGV5In0.OkDbpbujWJ-XIVHaf00Dnqt3v8Yp_nQ6CGzm-Z4QUVc',
);

The above snippet is for an in-browser or mobile integration. Server side API calls are a little different, but we will cover them in detail later in the docs.

Channels

Let’s continue by initializing your first channel. A Channel contains messages, a list of people that are watching the Channel and optionally a list of members (for private conversations). The example below shows how to set up a Channel to support chat for a group conversation:

const channel = client.channel('messaging', 'travel', {
    name: 'Awesome channel about traveling',
});
// fetch the channel state, subscribe to future updates
let state = await channel.watch();

The first two arguments are the Channel Type and the Channel ID (messaging and travel in this case). The Channel ID is optional, if you leave it out the ID will be automatically determined based on the list of members. The Channel type controls the settings we’re using for this Channel.

There are 5 default types of channels:

  • livestream
  • messaging
  • team
  • gaming
  • commerce
These 5 options give you the most sensible defaults for those use cases. You can also define your own channel types if the defaults don’t work well for you.

The third argument is an object containing the channel data. You can add as many custom fields as you want as long as the total size of the object is less than 5KB.

Messages

Now that we have the Channel setup, let's send our first chat message:

const text = 'I’m mowing the air Rand, I’m mowing the air.';
const response = await channel.sendMessage({
    text,
    customfield: '123',
});

Similar to users and Channels, the sendMessage method allows you to add custom fields. When you send a message to a Channel it will automatically broadcast to all the people that are watching this Channel and update in real time.

Events

This is how you can listen to events on the client side:

channel.on('message.new', event => {
    console.log('received a new message', event.message.text);
    console.log(`Now have ${channel.state.messages.length} stored in local state`);
});

Note how you receive the event and can access the full Channel state via channel.state.

Conclusion

Now that you understand the building blocks of a fully functional chat integration, let’s move on to the next sections of the documentation where we will go into more details on each API endpoint.

Initialization and Users

Initialization for browser/mobile

const chatClient = new StreamChat('YOUR_API_KEY', {
    timeout: 3000,
    httpAgent: new http.Agent({ keepAlive: 3000 }),
    httpsAgent: new http.Agent({ keepAlive: 3000 }),
});

The code above creates a Chat client instance for browser/mobile usage. The first parameter is the API Key and the second an object with additional options.

Extra params

Name Type Description Default Optional
timeout integer Timeout of API calls in milliseconds. 3000
httpAgent object Allows you to specify the keepAlive behavior when making HTTP requests from Node
httpsAgent string Allows you to specify the keepAlive behavior when making HTTPS requests from Node
Note: httpAgent and httpsAgent are ignored when using Stream on the client side.

Setting the user

Once initialized, you must specify the current user with setUser:

await chatClient.setUser(
    {
        id: 'john',
        name: 'John Doe',
        image: 'https://getstream.io/random_svg/?name=John',
    },
    'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiam9obiJ9.TxsU2JMbx7UXQ166lRabT6h7pAf415tLLKhJEZO_Kbg',
);

Set user params

Name Type Description
user object The user object. Must have id field. It can have as many custom fields as you want, as long as the total size of the object is less than 5KB.
userToken string The user authentication token. See below for details

User fields

The id field is the only required field for the user. There are a few other fields you should know about though:

User Fields

Name Type Description
id string The id field is required
name string The name of the user used by our component libraries. This is just a convention and not a required field.
image string The image for this user. This is used by our component libraries but other than that is not required.
invisible boolean Determines if the user should show as offline even when they are online (only visible on the event.me user info).
last_active string Reserved field indicating when the user was last active.
online boolean Reserved field indicating if the user is currently online.
role string Reserved field indicating the user's role. Can be either admin, moderator or user.
mutes array Reserved field containing a list of mutes by this user (only visible on the event.me user info).
created_at date Reserved field indicating when the user was created.
updated_at date Reserved field indicating when the user was last updated.

Tokens

Tokens are used to authenticate the user. Typically, you send this token from your backend to the client side when a user registers or logs in.

You can generate tokens server side with the following syntax:

const serverSideClient = new StreamChat('YOUR_API_KEY', 'API_KEY_SECRET');
const token = serverSideClient.createToken('john');

By default user tokens are valid indefinitely. You can set an expiration to tokens by passing it as second parameter. The expiration should contain the number of seconds since the epoch.

// create a token that expires in 1 hour using moment.js
const expirationTimestamp = moment().add('1h').format('X');
const token = serverSideClient.createToken('john', expirationTimestamp);

// the same can be done with plain JS
const token2 = serverSideClient.createToken('john', Math.floor(Date.now()/1000) + (60 * 60));

Development tokens

For development apps it is possible to disable token authentication and use client-side generated tokens. Disabling auth checks is not suitable for a production application and should only done for proof-of-concepts and applications in early development stage. To enable development tokens you need to change your application config.

await chatClient.setUser(
    {
        id: 'john',
        name: 'John Doe',
        image: 'https://getstream.io/random_svg/?name=John',
    },
   chatClient.devToken('john'),
);

The above code used the setUser call. The setUser call is the most convenient option when your app has authenticated users.
Alternatively you can use setGuestUser if you want to allow users to chat with a guest account or the setAnonymousUser if you want to allow anonymous users to watch the chat.

Guest Users

Guest sessions can be created client side and do not require any server-side authentication.

Guest users have a limited set of permissions. You can read more about how to configure permissions here. You can create a guest user session by using setGuestUser instead of setUser.

await client.setGuestUser({ id: 'tommaso' });
The user object schema is the same as the one described in the Setting the user portion of the docs.
    
    # the guest endpoint returns a user object and a token:
    curl -i -X POST -d "{\"user\":{\"id\":\"tommaso\"}}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: anonymous" \
    "https://chat-us-east-c1.stream-io-api.com/guest?api_key=YOUR_API_KEY"
    
    

Anonymous Users

If a user is not logged in you can call setAnonymous. While you’re anonymous you can’t do much, but for the livestream channel type you’re still allowed to read the chat conversation.

await client.setAnonymousUser();

Logging out

To disconnect a user (say that you’re for instance logging out and logging in as someone new) you can call the disconnect method and repeat the setUser call as someone else:

client.disconnect();
client.setUser(
    {
        id: 'jack',
        name: 'Jack Doe',
    },
    'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiamFjayJ9.eljgjcOSorcR01nrYxcQtRUrHxT3ZyJlpu05Iv-3rd4',
);

Querying Users

The Query Users endpoint allows you to search for users and see if they are online/offline. The example below shows how you can retrieve the details for 3 users in one API call:

const response = await client.queryUsers({ id: { $in: ['john', 'jack', 'jessie'] } });

The first argument is the filter object, the second the sorting and the third any additional options. Here's a more extensive example:

const response = await client.queryUsers(
    { id: { $in: ['jessica'] } },
    { last_active: -1},
    { presence: true },
);

The filter syntax uses Mongoose style queries. Note that we don't run Mongo on the background so you can only use a subset of queries.

You can filter and sort on the custom fields you've set for your user, the user id and when the user was last active

The options for the queryUser endpoint are "presence", "limit" and "offset". If presence is true this makes sure you receive the "user.presence.changed" event when a user goes online/offline.

Query Users Options

Name Type Description Default
presence boolean Get updates when the user goes offline/online false
limit integer Number of users to return 30
offset integer Offset for pagination 0

Note that you can subscribe to presence status of at most 30 users via this API call

Updating Users

To simplify the integration with Stream the setUser call automatically creates and updates the user. Note that the setUser call has some limitations:

  1. You can only update 1 user per API call
  2. The user's role isn't editable
  3. You can only add custom fields, you can't remove them

If you're looking to sync your userbase you'll want to use the updateUser(s) method instead. This endpoint allows you to update up to 100 users in 1 API call. It will also remove fields from the user if you don't send them in this update call. The updateUser endpoint is allowed to change a user's role and make them an admin. Have a look at the following example:

const response = await client.setUser(
    { id: userID, role: 'admin', favorite_color: 'green'},
    token
);
// user object is now {id: userID, role: 'user', favorite_color: 'green'}
// note how you are not allowed to make the user admin via this endpoint
const updateResponse = await serverClient.updateUsers([{ id: userID, role: 'admin', book: 'dune'}])
// user object is now {id: userID, role: 'admin', book: 'dune'}
// note how the user became admin and how the favorite_color field was removed

Note how the updateUser call can make the user an admin, but the setUser call is not allowed to do that. The reasons for this is that setUser is called client side, so for security reasons you can't edit a user's role client side. The second difference is that the updateUser call can remove fields. This is why the `favorite_color` field is removed after the update.

Send Message

Below is a detailed example of how to send a message using Stream Chat:

const response = await channel.sendMessage({
    text: '@Josh I told them I was pesca-pescatarian. Which is one who eats solely fish who eat other fish.',
    attachments: [
        {
            type: 'image',
            asset_url: 'https://bit.ly/2K74TaG',
            thumb_url: 'https://bit.ly/2Uumxti',
            myCustomField: 123
        }
    ],
    mentioned_users: [josh.id],
    anotherCustomField: 234
});

There are 4 built-in fields for the message:

Name Type Description Default Optional
text string The text of the chat message (Stream chat supports markdown and automatically enriches URLs).
attachments array A list of attachments (audio, videos, images, and text). Max is 10 attachments per message. Each attachment can have up to 5KB.
user object This value is automatically set in client-side mode. You only need to send this value when using the server-side APIs.
mentioned_users array A list of users mentioned in the message. You send this as a list of user IDs and receive back the full user data.
message custom data object Extra data for the message. Must not exceed 5KB in size.

Note that both the message and the attachments can contain custom fields. By default Stream’s frontend components support the following attachment types:

  • Audio
  • Video
  • Image
  • Text
You can specify different types as long as you implement the frontend rendering logic to handle them. Common use cases include:
  • Embedding products (photos, descriptions, outbound links, etc.)
  • Sharing of a users’ location
The React tutorial explains how to customize the Attachment component.

Message Format

When you post a message on a Channel, there are a few things that happen on the server:

  1. The text markdown format is parsed.
  2. The first URL found in message.text is enriched and additional information is added automatically. This gives you a preview of the images, videos, etc. from the open-graph data on the associated page.
  3. Any slash commands such as /giphy, /imgur, /ban, /flag etc. are handled.

Messages containing URLs will have a generated attachment with the following structure:

Name Type Description
type string The attachment type based on the URL resource. This can be: audio, image or video
author_name string The name of the author.
title string The attachment title.
title_link string The link to which the attachment message points to.
text string The attachment text. It will be displayed in the channel next to he original message.
image_url string The URL to the attached image. This is present for URL pointing to an image article (eg. Unsplash).
thumb_url string The URL to the attached file thumbnail. You can use this to represent the attached link.
asset_url string The URL to the audio, video or image related to the URL.
og_scrape_url string The original URL that was used to scrape this attachment.

Below is an example of URL enrichment as well as the resulting message structure:

const response = await channel.sendMessage({
    text: 'Check this bear out https://imgur.com/r/bears/4zmGbMN'
})

// response message object
{
    "id": "thierry-5e9619ec-1a0d-443b-ab26-c597ed7af3d0",
    "text": "Check this bear out https://imgur.com/r/bears/4zmGbMN",
    "html": "<p>Check this bear out <a href=\"https://imgur.com/r/bears/4zmGbMN\" rel=\"nofollow\">https://imgur.com/r/bears/4zmGbMN</a></p>\n",
    "type": "regular",
    "user": {
      "id": "thierry",
      "role": "user",
      "created_at": "2019-04-03T14:42:47.087869Z",
      "updated_at": "2019-04-16T09:20:03.982283Z",
      "last_active": "2019-04-16T11:23:51.168113408+02:00",
      "online": true
    },
    "attachments": [
      {
        "type": "image",
        "author_name": "Imgur",
        "title": "An update: Dushi made it safe to Bear Sanctuary Müritz",
        "title_link": "https://imgur.com/4zmGbMN",
        "text": "1678 views on Imgur",
        "image_url": "https://i.imgur.com/4zmGbMN.jpg?fb",
        "thumb_url": "https://i.imgur.com/4zmGbMN.jpg?fb",
        "og_scrape_url": "https://imgur.com/r/bears/4zmGbMN"
      }
    ],
    "latest_reactions": [],
    "own_reactions": [],
    "reaction_counts": null,
    "reply_count": 0,
    "created_at": "2019-04-16T09:40:04.665274Z",
    "updated_at": "2019-04-16T09:40:04.665274Z"
  }

Messages returned by the API follow this structure:

Name Type Description
id string The message ID. This is either created by Stream or set client side when the message is added.
html string The safe HTML generated from the raw text message. This field can only be set using server-side APIs or via the import
type string The message type. See below for more information.
user object The author user object. Schema is as described in the Setting the user portion of the docs.
attachments array The list of attachments, either provided by the user or generated from a command or as a result of URL scraping.
latest_reactions array The latest reactions to the message created by any user.
own_reactions array The reactions added to the message by the current user. e.g. ["haha", "angry"].
reaction_counts object The reaction count by type for this message. e.g. {"haha": 3, "angry": 2}.
reply_count integer Reserved field indicating the number of replies for this message.
parent_id string The ID of the parent message, if the message is a reply.
created_at date Reserved field indicating when the message was created.
updated_at date Reserved field indicating when the message was updated last time.
deleted_at date Reserved field indicating when the message was deleted.
mentioned_users array of users The list of users that are mentioned in this message.

Message Types

Chat supports different types of messages. The type of the message is set by the APIs or by chat bots and custom commands.

Name Description
regular A regular message created in the channel.
ephemeral A temporary message which is only delivered to one user. It is not stored in the channel history. Ephemeral messages are normally used by commands (e.g. /giphy) to prompt messages or request for actions.
error An error message generated as a result of a failed command. It is also ephemeral, as it is not stored in the channel history and is only delivered to one user.
reply A message in a reply thread. Messages created with parent_id are automatically of this type.
system A message generated by a system event, like updating the channel or muting a user.

Get a Message

You can get a single message by its ID using the getMessage endpoint:

await client.getMessage(messageID);

Update a Message

You can edit a message by calling the updateMessage endpoint and including a message with an ID – the ID field is required when editing a message:

const message = { id: 123, text: 'the edited version of my text' };
const updateResponse = await client.updateMessage(message);

Delete a Message

You can lete a message by calling the removeMessage endpoint and including a message with an ID:

await client.deleteMessage(messageID);

File Uploads

The channel.sendImage and channel.sendFile methods make it easy to upload files. Note that this functionality defaults to using the Stream CDN. If you would like, you can easily change the logic to upload to your own CDN of choice.

const promises = [
    channel.sendImage(
        fs.createReadStream('./helloworld.jpg'),
        'hello_world1.jpg',
    ),
    channel.sendImage(
        fs.createReadStream('./helloworld.jpg'),
        'hello_world2.jpg',
    ),
];
const results = await Promise.all(promises);
const attachments = results.map(response => {
    return {
        type: 'image',
        thumb_url: response.file,
        asset_url: response.file,
    };
});
const response = await channel.sendMessage({
    text: 'Check out what I have uploaded in parallel',
    attachments,
});
expect(response.message.attachments).to.equal(attachments);

In the example above, note how the message attachments are created after the files are uploaded. The React components support regular uploads, clipboard pasting, drag and drop, as well as URL enrichment via built-in open-graph scraping. As a bonus, the Stream CDN will automatically handle image resizing for you.

Send Reaction

Stream Chat has built-in support for user Reactions. Common examples are likes, comments, love, etc. Reactions can be customized so that you are able to use any type of Reaction your application requires.

Similar to other objects in Stream Chat, Reactions allow you to add custom data to the Reaction. This is helpful if you want to customize the Reaction logic. Custom data for reactions can be at most 1KB.

const reaction = await channel.sendReaction(messageID, {
    type: 'love',
    myCustomField: 123,
});

Removing a reaction

await channel.deleteReaction(messageID, 'love');

Pagianate rections

Messages returned by the APIs automatically include the 10 most recent reactions. You can also retrieve more reactions and paginate like this:

// get the first 10 reactions
const response = await channel.getReactions(messageID, { limit: 5 });

// get 3 reactions past the first 10
const response = await channel.getReactions(messageID, { limit: 3, offset: 10 });

Threads & Replies

Threads and replies provide your users with a way to go into more detail about a specific topic.

This can be very helpful to keep the conversation organized and reduce noise. To create a thread you simply send a message with a parent_id. Have a look at the example below:

const replyResponse = await channel.sendMessage({
    text: 'replying to a message',
    parent_id: parentID,
    show_in_channel: false,
});

If you specify show_in_channel, the message will be visible both in a thread of replies as well as the main Channel.

Search

Message search is built-in to the chat API. You can enable/disable the search indexing per chat type. The command shown below selects the channels in which John is a member. Next it searches the messages in those channels for the keyword “'supercalifragilisticexpialidocious'”:

const filters = { members: { $in: ['john'] } };
const response = await client.search(
   filters,
   'supercalifragilisticexpialidocious',
   { limit: 2, offset: 0 },
);

Pagination works via the standard limit and offset parameters. The first arguments, filters, uses a mongoose style query expression. Note that we don’t run Mongo on the backend, so only a subset of the standard filters are supported.

Event object

Events

The following events are used by Stream’s chat SDK:

Event Trigger Recipients
user.presence.changed when a user status changes (eg. online, offline, away, ...) clients subscribed to the user status
user.watching.start when a user starts watching a channel clients watching the channel
user.watching.stop when a user stops watching a channel clients watching the channel
user.updated when a user is updated clients subscribed to the user status
typing.start sent when a user starts typing clients watching the channel
typing.stop sent when a user stops typing clients watching the channel
message.new when a new message is added on a channel clients watching the channel
message.updated when a message is updated clients watching the channel
message.deleted when a message is deleted clients watching the channel
message.read when a channel is marked as read clients watching the channel
message.reaction when a message reaction is added or deleted clients watching the channel
member.added when a member is added to a channel clients watching the channel
member.removed when a member is removed from a channel clients watching the channel
channel.updated when a channel is updated clients watching the channel
health.check every 30 second to confirm that the client connection is still active all clients
connection.changed when the state of the connection changed local event
connection.recovered when the connection to chat servers is back online local event
notification.message_new when a message is added to a channel clients that are not currently watching the channel
notification.mark_read when the total count of unread messages (across all channels the user is a member) changes clients from the user affected by the change
notification.invited when the user is invited to join a channel clients from the user invited
notification.invite_accepted when the user accepts an invite clients from the user invited
notification.added_to_channel when the user rejects an invite clients from the user invited
notification.removed_from_channel when a user is removed from a channel clients from the user invited

Event object

Name Type Description Optional
cid string Channel ID
type string Event type
message object Message object
reaction object Reaction object
channel object Channel object
member object User object for the channel member that was added/remvoed
user object User object of the current user
me object User object of the health check user
total_unread_count int the number of unread messages for current user
watcher_count int Number of users watching this channel

Listening for events

As soon as you call watch on a Channel or queryChannels you’ll start to listen to these events. You can hook into specific events:

channel.on('message.deleted', event => {
    console.log('event', event);
    console.log('channel.state', channel.state);
});

You can also listen to all events at once:

channel.on(event => {
    console.log('event', event);
    console.log('channel.state', channel.state);
});

Client events

Not all events are specific to channels, events like: user status has changed, users' unread count has changed and other notifications are sent as client events. These events should be registered on the client directly:

// subscribe to all client events and log the unread_count field
client.on(event => {
	if (event.total_unread_count != null) {
		console.log(`unread messages count is now: ${event.total_unread_count}`);
	}
	if (event.unread_channels != null) {
		console.log(`unread channels count is now: ${event.unread_channels}`);
	}
});

// the initial count of unread messages is returned by client.setUser
const r = await client.setUser(user, userToken);
console.log(`you have ${r.me.total_unread_count} unread messages on ${r.me.unread_channels} channels`);

Connection events

The official SDKs make sure that a connection to Stream is kept alive at all times and that chat state is recovered when the user's internet connection comes back online. Your application can subscribe to changes to the connection using client events.

client.on('connection.changed', e => {
    if (e.online) {
        console.log('the connection is up!');
    } else {
        console.log('the connection is down!');
    };
});

Typing events

All official SDKs support typing events out of the box and are handled out of the box on all channels with the typing_events featured enabled.

There are two types of events related to user typing: "typing.start" and "typing.stop", both are sent client-side in response to user input. If you are building your own chat integration on top of an API client instead of using an SDK, we recommend following a few basic rules:

  1. Send the "typing.start" only the first time a user starts typing
  2. Once the user stops typing for longer than two seconds, send the "typing.stop" event
// sends an event typing.start to all channel participants
await channel.keystroke();

// sends an event typing.stop to all channel participants
await channel.stopTyping();

Notification Events

Notification events help you update your UI even if you're not watching a particular channel. There are 5 notification events:

  1. notification.mark_read
  2. notification.invited
  3. notification.invite_accepted
  4. notification.added_to_channel
  5. notification.removed_from_channel

Notification events are triggered at the client level. Here's an example of how you can render a new conversation when a user is added to a channel.

client.on('notification.added_to_channel', (event) => {
    console.log(`you were just added to channel ${event.channel}`)
});

Channel initialization

Here’s how you can initialize a single channel:

const conversation = authClient.channel('messaging', 'thierry-tommaso-1', {
    name: 'Founder Chat',
    image: 'http://bit.ly/2O35mws',
    members: ['thierry', 'tommaso'],
});

Channel init parameters

Name Type Description Optional
type string The channel type. Default types are livestream, messaging, team, gaming and commerce. You can also create your own types.
id string The channel id (optional). If you don't specify an ID the ID will be generated based on the list of members.
channel data object Extra data for the channel. Must not exceed 5KB in size.
The channel data can contain any number of custom fields. However, there are a few reserved fields that Stream uses:

Channel data reserved fields

Name Type Description
name string The Channel name. No special meaning, but by default the UI component will try to render this if the property is present.
image string The Channel image. Again there is no special meaning but by default, the UI components will try to render this property if it exists.
members array The members participating in this Channel. Note that you don’t need to specify members for a live stream or other public chat. You only need to specify the members if you want to limit access of this chat to these members and subscribe them to future updates

Watching a channel

Once you watch a channel, you will start receiving events for that channel. More info on that here.

To start watching a channel:

const conversationState = await conversation.watch();

Response schema

Name Type Description
config object The configuration for the channel type.
channel object The Channel object.
online integer Number of online members.
watchers object Users that are watching this channel. Represented as a mapping from the user id to the user object.
members object Channel members. Represented as a mapping from the user id to the user object.
read object Read messages grouped by user id. Represented as a mapping from the user id to the message object.
    
    curl -i -X POST -d "{\"state\":true,\"subscribe\":true}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiamxhaGV5In0.OkDbpbujWJ-XIVHaf00Dnqt3v8Yp_nQ6CGzm-Z4QUVc" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso/query?api_key=YOUR_API_KEY"
    
    
Make sure that you call client.setUser() before channel.watch()

Unwatching

To stop watching a channel:

await conversation.stopWatching();
      
      curl -i -X POST -d "{\"user_id\":\"john\"}"\
      -H "Content-Type: application/json" \
      -H "Stream-Auth-Type: jwt" \
      -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiam9obiJ9.TxsU2JMbx7UXQ166lRabT6h7pAf415tLLKhJEZO_Kbg" \
      "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso/stop-watching?api_key=YOUR_API_KEY"
      
    

Querying a list of channels

If you’re building a similar application to Facebook Messenger or Intercom, you’ll want to show a list of Channels. The Chat API supports MongoDB style queries to make this easy to implement. As an example, let's say that you want to query the last conversations I participated in sorted by last_message_at.

Note that we don’t run MongoDB on the backend so only a subset of the query options is available.

Here’s an example of how you can query the list of Channels:

const filter = { members: { $in: ['thierry'] } };
const sort = { last_message_at: -1 };

const channels = await authClient.queryChannels(filter, sort, {
    watch: true,
    state: true,
});
for (const c of channels) {
    console.log(c.custom.name, c.cid);
}

Query parameters

Name Type Description Optional
filters object The query filters to use. You can query on any of the custom fields you’ve defined on the Channel. You can also filter other built-n channel fields, see next section for reference.
sort object The sorting method to use for the channels. You can sort based on last_message_at, updated_at, created_at or member_count
options object Query options. See below

Channel queryable built-in fields

The following channel fields can be used to filter your query results

Field name Type Description Example
frozen boolean channel frozen status false
type string the type of channel messaging
id string the ID of the channel general
cid string the full channel ID messaging:general
members string or list of strings the list of user IDs of the channel members marcelo or [thierry, marcelo]

Query options

Name Type Description Default Optional
state boolean if true returns the Channel state True
watch boolean if true listen to changes to this Channel in real time
limit integer The number of channels to return (max is 30) 10
offset integer The offset. (max is 1000)

Response

The result of querying a channel is a list of ChannelStateobjects. The event system ensures that your local copy of the Channel state stays up to date. The state exposes the following information:

state.typing = Immutable({});
state.read = Immutable({});
state.messages = Immutable([]);
state.mutedUsers = Immutable([]);
state.watchers = Immutable({});
state.watcher_count = 0;
state.members = Immutable({});
Note that Channel state is immutable. It leverages seamless-immutable to prevent bugs caused by accidentally editing state.
    
    curl -i -g -X GET \
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiamxhaGV5In0.OkDbpbujWJ-XIVHaf00Dnqt3v8Yp_nQ6CGzm-Z4QUVc" \
    "https://chat-us-east-c1.stream-io-api.com/channels?api_key=YOUR_API_KEY&payload={\"filter_conditions\":{\"members\":{\"\$in\":[\"thierry\"]}},\"sort\":[{\"field\":\"last_message_at\",\"direction\":-1}],\"state\":true,\"subscribe\":true,\"watch\":true}"
    
    

Pagination

const result = await channel.query(
    { messages: { limit: 2, id_lte: 123 } },
    { members: { limit: 2, offset: 0 } },
    { watchers: { limit: 2, offset: 0 } },
);

As shown above, pagination for messages uses limit & id_lte parameters for pagination. The term id_lte stands for ID less than or equal. The ID-based pagination improves performance and prevents issues related to the list of messages changing while you’re paginating.

For members and watchers, we use limit and offset parameters.

    
    curl -i -X POST -d "{\"messages\":{\"limit\":2,\"id_lte\":123},\"members\":{\"limit\":2,\"offset\":0},\"watchers\":{\"limit\":2,\"offset\":0}}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.3YMs1NzsGNdP6xm5i8FX4w9Sci48oDJe8VfFyo2kksM" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso/query?api_key=YOUR_API_KEY"
    
    

Updating a channel

You can edit a Channel using the edit method:

const response = await channel.edit(
    {
        name: 'myspecialchannel',
        color: 'green',
    },
    { text: 'Thierry changed the channel color to green' },
);

Request params

Name Type Description
channel data object Object with the new channel information. One special field is frozen. If you set that to true new messages will be blocked.
message object Message object allowing you to show a system message in the Channel that something changed.
    
    curl -i -X POST -d "{\"message\":{\"text\":\"Thierry changed the channel color to green\"},\"data\":{\"name\":\"myspecialchannel\",\"color\":\"green\"}}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.3YMs1NzsGNdP6xm5i8FX4w9Sci48oDJe8VfFyo2kksM" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"
    
    

Changing channel members

Adding/removing channel members

await channel.addMembers(['thierry', 'josh']);
await channel.removeMembers(['tommaso']);
    
    #add thiery
    curl -i -X POST -d "{\"add_members\":[\"thierry\"]}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.3YMs1NzsGNdP6xm5i8FX4w9Sci48oDJe8VfFyo2kksM" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"

    #remove tommaso
    curl -i -X POST -d "{\"remove_members\":[\"tommaso\"]}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.3YMs1NzsGNdP6xm5i8FX4w9Sci48oDJe8VfFyo2kksM" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"
    
    
Note: You can only add/remove up to 100 members at once.

Adding/removing moderators to a channel

These operations can only be performed server-side.

Adds users as moderators(or updates their role to moderator if already members) or removes the moderator status

await channel.addModerators(['thierry', 'josh']);
await channel.demoteModerators(['tommaso']);
    
    #add thiery
    curl -i -X POST -d "{\"add_moderators\":[\"thierry\"]}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXJ2ZXJzaWRlIjp0cnVlfQ.OYCKMIZtQ4xS9Ial_PfHcdmZgVoz0_UAjTryXpBEw44" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"

    #remove tommaso
    curl -i -X POST -d "{\"demote_moderators\":[\"tommaso\"]}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXJ2ZXJzaWRlIjp0cnVlfQ.OYCKMIZtQ4xS9Ial_PfHcdmZgVoz0_UAjTryXpBEw44" \
    "https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"
    
    
Note: You can only add/remove up to 100 moderators at once.

Channel Invites

Inviting users

Users can be invited to a channel. When that happen, their client will receive a notification event.

const conversation = authClient.channel('messaging', 'awesome-chat', {
    name: 'Founder Chat',
    members: ['thierry', 'tommaso'],
    invites: ['nick'],
});

await conversation.create();

Accepting an invite

// nickClient is Nick's client 
const nickChannel = nickClient.channel('messaging', 'awesome-chat');

await nickChannel.acceptInvite({
    message: { text: 'Nick accepted the chat invite.' },
});

// start watching the channel as usual
await nickChannel.watch();

Request params

Name Type Description
message object Message object allowing you to show a system message in the Channel that something changed.

Rejecting an invite

await nickChannel.rejectInvite();

Delete a channel

You can delete a Channel using the delete method. This marks the channel as deleted and hides all the content.

const response = await myChannel.delete();
      
curl -i -X DELETE
-H "Content-Type: application/json" \
-H "Stream-Auth-Type: jwt" \
-H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.3YMs1NzsGNdP6xm5i8FX4w9Sci48oDJe8VfFyo2kksM" \
"https://chat-us-east-c1.stream-io-api.com/channels/messaging/thiery-tommaso?api_key=YOUR_API_KEY"
      
    

Note that if you recreate this channel it will show up empty. Recovering the old messages is currently not supported via the API.

Features

There are 5 built-in channel types:

  • livestream: Sensible defaults in case you want to build chat like YouTube or Twitch.
  • messaging: Configured for apps such as Whatsapp or Messenger.
  • gaming: Configured for in-game chat.
  • commerce: Good defaults for building something like your own version of Intercom or Drift.
  • team: If you want to build your own version of Slack or something similar start here.

As you can see in the examples below you can define your own Channel types and configure them to fit your needs. The Channel type allows you to configure these features:

  • typing_events: Controls if typing indicators are shown. Enabled by default.
  • read_events: Controls whether the chat shows how far you’ve read. Enabled by default.
  • connect_events: Determines if events are fired for connecting and disconnecting to a chat. Enabled by default.
  • search: Controls if messages should be searchable (this is a premium feature). Disabled by default.
  • reactions: If users are allowed to add reactions to messages. Enabled by default.
  • replies: Enables message threads and replies. Enabled by default.
  • mutes: Determines if users are able to mute other users. Enabled by default.

It allows you to specify these settings:

Name Type Description Default
automod string Disabled, simple or AI are valid options for the Automod (AI based moderation is a premium feature). simple
message_retention string A number of days or infinite infinite
max_message_length int The max message length 5000
commands list An array of strings. all, fun_set, moderation_set or the names of individual commands. I.E. giphy, imgur, ban, flag and mute. ['all']

Note: you need to use server side authentication to create, edit or delete a channel type.

Creating a Channel Type

await client.createChannelType({
    name: 'public', commands: ['fun_set'], permissions:[
        new Permission(
            name: 'Allow reads for all',
            priority: 999,
            resources: ['ReadChannel', 'CreateMessage'],
            AnyRole,
        ),
        DenyAll
    ],
    mutes: false,
    reactions: false
});

If not provided, the permission policies will default to the ones from the built-in team type.

List Channel Types

You can retrieve the list of all channel types defined for your application.

await client.listChannelTypes();

Get a Channel Type

You can retrieve a channel type definition with this endpoint. Note: features and commands are also returned by other channel endpoints.

client.getChannelType('public');

Edit a Channel Type

Channel type features, commands and permissions can be changed. Only the fields that must change need to be provided, fields that are not provided to this API will remain unchanged.

client.updateChannelType('public', {
    permissions: [AllowAll, DenyAll],
    replies: false,
    commands: ["all"]
});
    
    curl -i -X PUT -d "{\"permissions\": [\"AllowAll\", \"DenyAll\"], \"replies\": false}"\
    -H "Content-Type: application/json" \
    -H "Stream-Auth-Type: jwt" \
    -H "Authorization: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXJ2ZXJzaWRlIjp0cnVlfQ.OYCKMIZtQ4xS9Ial_PfHcdmZgVoz0_UAjTryXpBEw44" \
    "https://chat-us-east-c1.stream-io-api.com/channeltypes/public?api_key=YOUR_API_KEY"
    
  

Features of a channel can be updated by passing the boolean flags:

client.updateChannelType("public", {
  typing_events: false,
  read_events: true,
  connect_events: true,
  search: false,
  reactions: true,
  replies: false,
  mutes: true
});

Settings can also be updated by passing in the desired new values:

client.updateChannelType("public", {
  automod: "disabled",
  message_retention: "7",
  max_message_length: 140,
  commands: ["imgur", "giphy", "ban"]
});

Permissions for a channel type can be updated either by using predefined values or creating new ones:

client.updateChannelType("public", {
    permissions: [
        new Permission(
            name: 'Allow reads for all',
            priority: 999,
            resources: ['ReadChannel', 'CreateMessage'],
            AnyRole
        ),
        DenyAll
    ]
});

Remove a Channel Type

client.deleteChannelType('public');

Note: you cannot delete a channel type if there are any active channels of that type.

Permissions

The channel type also allows you to specify the permissions your chat can use. If not provided, the permissions will default to team’s channel type permissions.

The Channel object allows you to define a list of moderators and admins. The user object allows you to mark a user as a moderator or admin for your entire application.

Not that you can only modify these fields via the backend API:

// editing a channel
const data = {
    image: image,
    created_by: elon,
    roles: { elon: 'admin', gwynne: 'moderator' },
};
const spacexChannel = serverClient.channel('organization', 'spacex').edit(data);

You can edit a user like this:

await serverClient.editUser({
    id: 'tommaso',
    name: 'Tommaso Barbugli',
    role: 'admin',
});

Server-side, you’re allowed to do everything. For client-side integrations, the permission system kicks in. This system basically specifies a list of permissions the given user is allowed to do.

Note: changing user roles is only allowed server-side.

Permission checking is performed taking into account parameters:

  • API Request: the action the user is performing (e.g. send a message, edit a message)

Channel Type

Most API requests are specific to a channel (eg. add a message to channel “livestream:rockets”). Channel types have different permissions and can be configured via APIs. The 5 default channel types come with good default permission policies. You can find more information on how to manage permissions in the Channel Types section.

User Role & User Channel Role

  1. admin: This role allows users to perform more advanced actions. This role should be granted only to staff users.
  2. moderator: Allows users to perform moderation (e.g. ban users, add/remove users, …).
  3. user: This is the default role assigned to any user.
  4. channel member: A user that is also listed as member on a channel.
  5. guest: Users added using setGuestUser get this role automatically.
  6. anonymous: Users added using setAnonymousUser get this role automatically.

Note: a user can have the moderator role for some specific channels and the user role for all other channels.

Object Ownership

If applicable, ownership of the entity is taken into account. This parameter allows you to grant users the ability to edit their own message while denying editing others’ messages. Permission policies are organized as list ordered by priority. A permission policy has the following fields:

Channel Type

The channel the policy applies to.

Name

The unique name for the policy (eg. "Channel member permissions").

Resources

The list of API resources the policy applies to. Policies can match one more resources by their name (see full list below) or any resource by using the wildcard resource value “*”.

Roles

The list of roles the policy applies to. This value can be empty in that case the user role is not going to be taken into account.

Owner

Whether the policy should be applied to requested that alter an object owned by the requesting user. This field is either true or false.

Action

The action to apply to the API request once resources, roles and action fields are matching. The two allowed values are: Allow and Deny.

Priority

The priority of the policy. Policies are evaluated ordered by their priority, this field allows you to create a stable order.

Default Permission Policies

The five built-in channel types ship with default permission setting.

Messaging

Admin Moderator User Guest Anonymous Channel Member Owner
Create Channel Yes Yes Yes No No Yes -
Read Channel Yes Yes No No No Yes -
Update Channel Members Yes Yes No No No No Yes
Update Channel Roles No No No No No No -
Update Channel Yes Yes No No No No Yes
Create Message Yes Yes No No No Yes -
Update Message Yes Yes No No No No Yes
Delete Message Yes Yes No No No No Yes
Ban User Yes Yes No No No No -
Edit User Role No No No No No No -
Edit User No No No No No No Yes
Upload Attachment Yes Yes No No No Yes -
Delete Attachment Yes Yes No No No No Yes
Use Commands Yes Yes No No No Yes -
Add Links Yes Yes No No No Yes -

Livestream

Admin Moderator User Guest Anonymous Channel Member Owner
Create Channel Yes Yes Yes No No Yes -
Read Channel Yes Yes Yes Yes Yes Yes -
Update Channel Members Yes No No No No No -
Update Channel Roles Yes No No No No No -
Update Channel Yes No No No No No -
Create Message Yes Yes Yes No No Yes -
Update Message Yes Yes No No No No -
Delete Message Yes Yes No No No No Yes
Ban User Yes Yes No No No No -
Edit User Role No No No No No No -
Edit User No No No No No No Yes
Upload Attachment Yes Yes Yes No No Yes -
Delete Attachment Yes Yes No No No No Yes
Use Commands Yes Yes Yes No No Yes -
Add Links Yes Yes Yes No No Yes -

Gaming

Admin Moderator User Guest Anonymous Channel Member Owner
Create Channel Yes No No No No No -
Read Channel Yes Yes No No No Yes Yes
Update Channel Members Yes No No No No No -
Update Channel Roles Yes No No No No No -
Update Channel Yes No No No No No -
Create Message Yes Yes No No No Yes -
Update Message Yes Yes No No No No Yes
Delete Message Yes Yes No No No No Yes
Ban User Yes Yes No No No No -
Edit User Role Yes No No No No No -
Edit User Yes No No No No No Yes
Upload Attachment Yes Yes No No No Yes -
Delete Attachment Yes Yes No No No No Yes
Use Commands Yes Yes No No No Yes -
Add Links Yes Yes No No No Yes -

Commerce

Admin Moderator User Guest Anonymous Channel Member Owner
Create Channel Yes Yes No Yes No Yes -
Read Channel Yes Yes No No No Yes Yes
Update Channel Members Yes Yes No No No No Yes
Update Channel Roles No No No No No No -
Update Channel Yes Yes No No No No -
Create Message Yes Yes No No No Yes -
Update Message Yes Yes No No No No Yes
Delete Message Yes Yes No No No No Yes
Ban User Yes Yes No No No No -
Edit User Role No No No No No No -
Edit User Yes Yes No No No No Yes
Upload Attachment Yes Yes No Yes No Yes -
Delete Attachment Yes Yes No No No No Yes
Use Commands Yes Yes No No No No -
Add Links Yes Yes No Yes No Yes -

Team

Admin Moderator User Guest Anonymous Channel Member Owner
Create Channel Yes Yes Yes No No Yes -
Read Channel Yes Yes No No No Yes Yes
Update Channel Members Yes Yes No No No No Yes
Update Channel Roles No No No No No No -
Update Channel Yes Yes No No No No Yes
Create Message Yes Yes No No No Yes -
Update Message Yes Yes No No No No Yes
Delete Message Yes Yes No No No No Yes
Ban User Yes Yes No No No No -
Edit User Role No No No No No No -
Edit User No No No No No No Yes
Upload Attachment Yes Yes No No No Yes -
Delete Attachment Yes Yes No No No No Yes
Use Commands Yes Yes No No No Yes -
Add Links Yes Yes No No No Yes -

Commands

By default the following commands are supported:

  • /giphy query
  • /imgur query
  • /ban @userid reason
  • /unban @userid
  • /mute @userid
  • /unmute @userid
  • /flag @userid

Additionally, it’s possible to add your own commands. Commands are implemented using actions and attachments. Let’s have a look what data is returned when you run /giphy rock

{
    "args": "rock",
    "attachments": [
        {
            "type": "image",
            "thumb_url": "https://media0.giphy.com/media/3o85xx69HG11jP4QI/giphy.gif",
        }
        "actions": [
            {
                "name": "image_action",
                "text": "Send",
                "style": "primary",
                "type": "button",
                "value": "send"
            },
            {
                "name": "image_action",
                "text": "Shuffle",
                "style": "default",
                "type": "button",
                "value": "shuffle"
            },
            {
                "name": "image_action",
                "text": "Cancel",
                "style": "default",
                "type": "button",
                "value": "cancel"
            }
        ]
    ],
    "command": "giphy",
    "created_at": "2018-11-30T21:25:04.147725Z",
    "html": "<p>/gihpy rock</p>\n",
    "id": "33",
    "reactions": [],
    "reply_count": 0,
    "status": "received",
    "text": "/giphy rock",
    "type": "ephemeral",
    "updated_at": "2018-11-30T21:25:04.147725Z",
    "user": {
        "id": "thierry",
        "image": "myimageurl",
        "name": "Thierry",
        "role": "admin",
        "status": "busy"
    }
}

Actions

Actions have a name, text, style, type and value attributes. These simple fields allow you to build complex interactions with your bots & slash commands. All of the available Stream UI Components know how to render these actions. So for /giphy ocean, the result will be this:

Unread Counts

The most common use case for client level events are unread counts. Here's an example of a complete unread count integration for your chat app. As a first step we get the unread count when the user connects:

const response = await client.setUser(
     { id: 'myid' },
     token,
   );

// response.me.total_unread_count returns the unread count
// response.me.unread_channels returns the count of channels with unread messages

By default the React components will mark the messages as read automatically. You can also make the API call manually like this:

// mark all messages on a channel as read
await channel.markRead()

While you're using the app the unread count can change. A user can be added to a channel, a new message can be created or the user can mark the messages as seen on another tab/device. To support updating the unread count in realtime you can listen to these events.

  1. notification.added_to_channel
  2. notification.removed_from_channel
  3. notification.message_new
  4. notification.mark_read

All 4 of these events include the fields: total_unread_count and unread_channels. You can listen to them all at once like this:

client.on((event) => {

     if (event.total_unread_count !== undefined) {
         console.log(event.total_unread_count);
     }

     if (event.unread_channels !== undefined) {
         console.log(event.unread_channels);
     }

});

Channels

When you retrieve a channel from the API (eg. using Query Channels), the read state for all members is included in the response. This allows you to display which messages are read by each user. For each member we include the last time he or she marked the channel as read.

const channel = client.channel('messaging', 'test');
await chan.watch();

console.log(chan.state.read);

//{ '2fe6019c-872f-482a-989e-ecf4f786501b':
//  { user: 
//    { 
//      id: '2fe6019c-872f-482a-989e-ecf4f786501b',
//      role: 'user',
//      created_at: '2019-04-24T13:09:19.664378Z',
//      updated_at: '2019-04-24T13:09:23.784642Z',
//      last_active: '2019-04-24T13:09:23.781641Z',
//      online: true
//    },
//    last_read: 2019-04-24T13:09:21.623Z
//  }
//}

Unread messages per channel

You can retrieve the count of unread messages for the current user on a channel like this:

channel.countUnread();

Unread mentions per channel

You can retrieve the count of unread messages mentioning the current user on a channel like this:

channel.countUnreadMentions();

Mark all as read

You can mark all channels as read for a user like this:

// client-side
await client.markAllRead();

// mark all as read for one user server-side 
await serverSideClient.markAllRead({ user:  { id: 'myid' } });

Presence

User presence allows you to show when a user was last active and if they are online right now. Whenever you read a user the data will look like this:

Data Format

{
    id: 'myuserid',
    online: true,
    status: 'eating a burger',
    last_active: '2019-01-07T13:17:42.375Z'
}

The online field indicates if the user is online. The status field just stores a text indicating the current user status.

Invisible

To mark a user invisible simply set the invisible property to true. You can also set a custom status message at the same time:

// mark a user as invisible
authClient.setUser({
    id: 'myuserid',
    invisible: true
});

When invisible is set to true the current user will appear as offline to other users.

Listening to User Presence Changes

Of course, you want to listen to the user presence changes. This allows you to show a user as offline when they leave and update their status in real time. These 3 endpoints allow you to watch user presence:

// 3 ways of watching user presence

// If you pass presence: true to channel.watch it will watch the list of user presence changes.
// Note that you can listen to at most 10 users using this API call
const channel = authClient.channel('messaging', 'my-conversation-123', {
    members: ['john', 'jack'],
    color: 'green'
})
const state = await channel.watch({presence: true})

// queryChannels allows you to listen to the members of the channels that are returned
// so this does the same thing as above and listens to online status changes for john and jack
const channels = await authClient.queryChannels(
    {color: 'green'},
    {last_message_at: -1},
    {presence: true},
)

// queryUsers allows you to listen to user presence changes for john and jack
const users = await authClient.queryUsers({id: {$in: ['john', 'jack']}}, {id: -1}, {presence: true})

Moderation

Flag

Any user is allowed to flag a message or a user.

const data = await authClient.flagMessage(messageResponse.message.id);

Mute

Any user is allowed to mute another user.

const data = await authClient.muteUser('eviluser');

Ban

Users can be banned from an app entirely or from a channel. When a user is banned, it will be not allowed to post messages until the ban is removed or expired. In most cases only admins or moderators are allowed to ban other users from a channel.

Name Type Description Default Optional
timeout number The timeout in minutes until the ban is automatically expired. no limit
reason string The reason the ban was created.

Note: Banning a user from all channels can only be done using server-side auth.

// ban a user for 60 minutes from all channel
let data = await authClient.banUser('eviluser', {
    timeout: 60,
    reason: 'Banned for one hour',
});

// ban a user from the livestream:fortnite channel
data = await channel.banUser('eviluser', {
    reason: 'Profanity is not allowed here',
});

// remove ban from channel
data = await channel.unbanUser('eviluser');

// remove global ban
data = await authClient.unbanUser('eviluser');

Automod

For livestream and gaming channel types Automod is enabled by default. There are three options for moderation:

  • disabled
  • simple (blocks messages that contain a list of profane words)
  • ai (uses an AI-based classification system to detect various types of bad content)

You can configure the thresholds for the AI-based system by specifying a value between 0 and 1 for the following types of bad content. As an example, if you set the value for Spam to 0.7 it will block any message that seems very likely to be spam. On the other hand, if you set a value of 0.3 it will be very sensitive and block anything that remotely looks like spam.

  • Spam:
    • Unsolicited messages: typically used as clickbait for financial gain from bot messages.
  • Toxic:
    • Not a constructive comment, or in general not nice.
    • Ex: "This is dumb" would be classified highly as toxic and only slightly obscene.
  • SevereToxic:
    • Worst of the worst. Typically insulting, obscene and degrading.
    • Ex: "What a motherfucking piece of crap those fuckheads for blocking us!"
  • Obscene:
    • Generally reserved for inappropriate words. Typically offensive to accepted standards of morality.
  • Threat:
    • A direct threat to another user.
    • Ex: "I will spit on you" would classified highly as toxic and a threat but only slightly obscene.
  • Insult:
    • Insulting another user.
    • Ex: "You are dumb" would be classified as toxic and insulting, yet only moderately obscene.
  • IdentityHate:
    • Degradation of a protected class.

AI-based moderation is a premium feature. Please contact sales to discuss your options.

Server-side API Clients

Official Clients

Official clients are currently available for a number of languages:

Server-side API Usage

Here’s an example of sending a chat message from a server-side integration:

const options = {};
const serverClient = new StreamChat('key', 'secret', options);
const spacexChannel = serverClient.channel('organization', 'spacex', {
    image: image,
    created_by: elon,
});
const createResponse = await spacexChannel.create();
const text = 'I was gonna get to mars but then I got high';
const message = {
    text,
    user: elon,
}
const response = await spacexChannel.sendMessage(message);

Note the 3 differences compared to the client side integration:

  1. Initialize the client with the server side secret.
  2. Create the Channel instead of initializing it (we don't want to retrieve the state or subscribe to changes in this case).
  3. Pass the user parameter if the endpoint requires it (eg. creating a channel or a message)
const tommaso = { id: "tommaso" };

// create a channel and assign user Tommaso as owner
const channel = serverClient.channel("team", "stream", { created_by: tommaso })
await channel.create();

// send a message for tommaso
const message = {
   "hi there!",
   user: tommaso,
};
await channel.sendMessage(message);

Stream CLI

tream's Command Line Interface (CLI) makes it easy to create and manage your Stream apps directly from the terminal. Currently, only Chat is supported. You can find the source code on GitHub

Installation

yarn global add getstream-cli

# OR

npm install -g getstream-cli

First time setup

In order to initialize the CLI, you need to configure it with your Stream credentials, such as your API Key and Secret. These can be found on the Dashboard

$ stream config:set
✔ What is your full name? · Bobby Tables
✔ What is your email address associated with Stream? · bob@tables.com
✔ What is your Stream API key? · foo
✔ What is your Stream API secret? · ***

Documentation

Later in this docs we will also refer to how you can use the CLI tool to make changes to your chat application.
You can find the complete documentation for Stream CLI here.

Role management

Change a user role

await serverClient.updateUser({
    id: 'tommaso',
    name: 'Tommy Doe',
    role: 'admin',
});

Add moderators to a channel

let channel = serverClient.channel("livestream", "fortnite");
await channel.addModerators(["thierry", "tommaso"]));

Remove moderators from a channel

await channel.demoteModerators(["thierry"]));

Authentication

Enabling development tokens

Token validation can be disabled for development apps. This allows you to use development tokens and work on user token provisioning later on.

// disable auth checks, allows dev token usage
await client.updateAppSettings({
    disable_auth_checks: true,
});

// re-enable auth checks
await client.updateAppSettings({
    disable_auth_checks: false,
});

Disable permission checking

By default all apps ship with role based permission checks. During development you can decide to turn off permission checks, this way all users will act as admin users.

// disable permission checks
await client.updateAppSettings({
	disable_permissions_checks: true,
});

// re-enable permission checks
await client.updateAppSettings({
	disable_permissions_checks: false,
});

Webhooks

By using webhooks, you can receive all events within your application. When configured, every event happening on Stream Chat will propagate to your webhook endpoint via a HTTP POST request.

Webhook can help you migrating from a different chat provider to Stream without disruption or to support complex notification mechanisms (eg. send an SMS to an offline user when a direct message is sent).

Webhook requirements

In order to use webhooks, the endpoint responding to the URL must:

  • Be reachable from public internet, tunneling services like ngrok are supported
  • Respond with a 200 HTTP code in less than 3 seconds
  • Handle HTTP requests with POST body
  • Able to parse JSON payloads
  • Support HTTP/1.1

While not required, we recommend to follow these best-practices for production environments:

  • Use SSL over HTTP using a certificate from a trusted authority (eg. Letsencrypt)
  • Verify the x-signature header
  • Support Keep-Alive
  • Be highly available
  • Offload the processing of the message (read, store and forget)

Configuration

stream chat:push:webhook \
    --url 'https://acme.com/my/awesome/webhook/handler/'

Verify events via X-Signature

All HTTP requests can be verified as coming from Stream (and not tampered by a 3rd party) by analyzing the signature attached to the request. Every request includes an HTTP header called "x-signature" containing a cryptographic signature of the message. Your webhook endpoint can validate that payload and signature match.

// body is the request data as a string
client.verifyWebhook(body, req.headers['x-signature']);
import stream_chat

client = stream_chat.connect('API_KEY', 'API_SECRET')

# Django request
client.verify_webhook(request.body, request.META['HTTP_X_SIGNATURE'])

# Flask request
client.verify_webhook(request.data, request.headers['X-SIGNATURE'])
# TODO: add check for x-signature check
// TODO: add check for x-signature check
// TODO: add check for x-signature check
// TODO: add check for x-signature check
// TODO: add check for x-signature check

Webhook events

Below you can find the complete list of events that are sent via web-hooks together with the description of the data payload.

For message and channel events the webhook request body will also include the list of channel members and attach additional information about their read status. For performance reasons, such list is only included for channels with up to 50 members.

When applicable, the following attributes are included to the event user and to the event members:

total_unread_count the total count of messages across all channels.
unread_channels the count of channels with at least one unread message.
channel_last_read_at the last time this channel was marked as read.
channel_unread_count the count of unread messages on this channel.

Webhook event types

Event Triggered
message.new when a new message is added
message.read when a user calls mark as read
message.updated when a message is updated
message.deleted when a message is deleted
message.reminder when a user has an unread message on a channel for longer than ten minutes. the ten minutes tracking is restarted once the user marks the channel as read.
reaction.new when a message reaction is added
reaction.deleted when a message reaction deleted
member.added when a member is added to a channel
member.updated when a member is updated
member.removed when a member is removed from a channel
channel.updated when a channel is updated
channel.deleted when a channel is deleted
user.updated when a user is updated

message.new

{
  "cid": "messaging:fun",
  "type": "message.new",
  "message": {
    "id": "fff0d7c0-60bd-4835-833b-3843007817bf",
    "text": "8b780762-4830-4e2a-aa43-18aabaf1732d",
    "html": "<p>8b780762-4830-4e2a-aa43-18aabaf1732d</p>\n",
    "type": "regular",
    "user": {
      "id": "97b49906-0b98-463b-aa47-0aa945677eb2",
      "role": "user",
      "created_at": "2019-04-24T08:48:38.440123Z",
      "updated_at": "2019-04-24T08:48:38.440708Z",
      "online": false
    },
    "attachments": [],
    "latest_reactions": [],
    "own_reactions": [],
    "reaction_counts": null,
    "reply_count": 0,
    "created_at": "2019-04-24T08:48:39.918761Z",
    "updated_at": "2019-04-24T08:48:39.918761Z",
    "mentioned_users": []
  },
  "user": {
    "id": "97b49906-0b98-463b-aa47-0aa945677eb2",
    "role": "user",
    "created_at": "2019-04-24T08:48:38.440123Z",
    "updated_at": "2019-04-24T08:48:38.440708Z",
    "online": false,
    "channel_unread_count": 1,
    "channel_last_read_at": "2019-04-24T08:48:39.900585Z",
    "total_unread_count": 1,
    "unread_channels": 1,
    "unread_count": 1
  },
  "created_at": "2019-04-24T08:48:38.949986Z",
  "members": [
    {
      "user_id": "97b49906-0b98-463b-aa47-0aa945677eb2",
      "user": {
        "id": "97b49906-0b98-463b-aa47-0aa945677eb2",
        "role": "user",
        "created_at": "2019-04-24T08:48:38.440123Z",
        "updated_at": "2019-04-24T08:48:38.440708Z",
        "online": false,
        "channel_unread_count": 1,
        "channel_last_read_at": "2019-04-24T08:48:39.900585Z",
        "total_unread_count": 1,
        "unread_channels": 1,
        "unread_count": 1
      },
      "created_at": "2019-04-24T08:48:39.652296Z",
      "updated_at": "2019-04-24T08:48:39.652296Z"
    }
  ]
}

message.read

{
  "cid": "messaging:fun",
  "type": "message.read",
  "user": {
    "id": "a6e21b36-798b-408a-9cd1-0cf6c372fc7f",
    "role": "user",
    "created_at": "2019-04-24T08:49:58.170034Z",
    "updated_at": "2019-04-24T08:49:59.345304Z",
    "last_active": "2019-04-24T08:49:59.344201Z",
    "online": true,
    "total_unread_count": 0,
    "unread_channels": 0,
    "unread_count": 0,
    "channel_unread_count": 0,
    "channel_last_read_at": "2019-04-24T08:49:59.365498Z"
  },
  "created_at": "2019-04-24T08:49:59.365489Z"
}

message.updated

{
  "cid": "messaging:fun",
  "type": "message.updated",
  "message": {
    "id": "93163f53-4174-4be8-90cd-e59bef78da00",
    "text": "new stuff",
    "html": "<p>new stuff</p>\n",
    "type": "regular",
    "user": {
      "id": "75af03a7-fe83-4a2a-a447-9ed4fac2ea36",
      "role": "user",
      "created_at": "2019-04-24T08:51:26.846395Z",
      "updated_at": "2019-04-24T08:51:27.973941Z",
      "last_active": "2019-04-24T08:51:27.972713Z",
      "online": false
    },
    "attachments": [],
    "latest_reactions": [],
    "own_reactions": [],
    "reaction_counts": null,
    "reply_count": 0,
    "created_at": "2019-04-24T08:51:28.005691Z",
    "updated_at": "2019-04-24T08:51:28.138422Z",
    "mentioned_users": []
  },
  "user": {
    "id": "75af03a7-fe83-4a2a-a447-9ed4fac2ea36",
    "role": "user",
    "created_at": "2019-04-24T08:51:26.846395Z",
    "updated_at": "2019-04-24T08:51:27.973941Z",
    "last_active": "2019-04-24T08:51:27.972713Z",
    "online": true,
    "channel_unread_count": 1,
    "channel_last_read_at": "2019-04-24T08:51:27.994245Z",
    "total_unread_count": 2,
    "unread_channels": 2,
    "unread_count": 2
  },
  "created_at": "2019-04-24T10:51:28.142291+02:00"
}

message.deleted

{
  "cid": "messaging:fun",
  "type": "message.deleted",
  "message": {
    "id": "268d121f-82e0-4de1-8c8b-ef1201efd7a3",
    "text": "new stuff",
    "html": "<p>new stuff</p>\n",
    "type": "regular",
    "user": {
      "id": "76cd8430-2f91-4059-90e5-02dffb910297",
      "role": "user",
      "created_at": "2019-04-24T09:44:21.390868Z",
      "updated_at": "2019-04-24T09:44:22.537305Z",
      "last_active": "2019-04-24T09:44:22.535872Z",
      "online": false
    },
    "attachments": [],
    "latest_reactions": [],
    "own_reactions": [],
    "reaction_counts": {},
    "reply_count": 0,
    "created_at": "2019-04-24T09:44:22.57073Z",
    "updated_at": "2019-04-24T09:44:22.717078Z",
    "deleted_at": "2019-04-24T09:44:22.730524Z",
    "mentioned_users": []
  },
  "created_at": "2019-04-24T09:44:22.733305Z"
}

reaction.new

{
  "cid": "messaging:fun",
  "type": "reaction.new",
  "message": {
    "id": "4b3c7b6c-a39d-4069-9450-2a3716cf4ca6",
    "text": "new stuff",
    "html": "<p>new stuff</p>\n",
    "type": "regular",
    "user": {
      "id": "57fabaed-446a-40b4-a6ec-e0ac8cad57e3",
      "role": "user",
      "created_at": "2019-04-24T09:49:47.158005Z",
      "updated_at": "2019-04-24T09:49:48.301933Z",
      "last_active": "2019-04-24T09:49:48.300566Z",
      "online": false
    },
    "attachments": [],
    "latest_reactions": [
      {
        "message_id": "4b3c7b6c-a39d-4069-9450-2a3716cf4ca6",
        "user": {
          "id": "57fabaed-446a-40b4-a6ec-e0ac8cad57e3",
          "role": "user",
          "created_at": "2019-04-24T09:49:47.158005Z",
          "updated_at": "2019-04-24T09:49:48.301933Z",
          "last_active": "2019-04-24T09:49:48.300566Z",
          "online": true
        },
        "type": "lol",
        "created_at": "2019-04-24T09:49:48.481994Z"
      }
    ],
    "own_reactions": [],
    "reaction_counts": {
      "lol": 1
    },
    "reply_count": 0,
    "created_at": "2019-04-24T09:49:48.334808Z",
    "updated_at": "2019-04-24T09:49:48.483028Z",
    "mentioned_users": []
  },
  "reaction": {
    "message_id": "4b3c7b6c-a39d-4069-9450-2a3716cf4ca6",
    "user": {
      "id": "57fabaed-446a-40b4-a6ec-e0ac8cad57e3",
      "role": "user",
      "created_at": "2019-04-24T09:49:47.158005Z",
      "updated_at": "2019-04-24T09:49:48.301933Z",
      "last_active": "2019-04-24T09:49:48.300566Z",
      "online": true
    },
    "type": "lol",
    "created_at": "2019-04-24T09:49:48.481994Z"
  },
  "user": {
    "id": "57fabaed-446a-40b4-a6ec-e0ac8cad57e3",
    "role": "user",
    "created_at": "2019-04-24T09:49:47.158005Z",
    "updated_at": "2019-04-24T09:49:48.301933Z",
    "last_active": "2019-04-24T09:49:48.300566Z",
    "online": true,
    "unread_channels": 2,
    "unread_count": 2,
    "channel_unread_count": 1,
    "channel_last_read_at": "2019-04-24T09:49:48.321138Z",
    "total_unread_count": 2
  },
  "created_at": "2019-04-24T09:49:48.488497Z"
}

reaction.deleted

{
  "cid": "messaging:fun",
  "type": "reaction.deleted",
  "message": {
    "id": "4b3c7b6c-a39d-4069-9450-2a3716cf4ca6",
    "text": "new stuff",
    "html": "<p>new stuff</p>\n",
    "type": "regular",
    "user": {
      "id": "57fabaed-446a-40b4-a6ec-e0ac8cad57e3",
      "role": "user",
      "created_at": "2019-04-24T09:49:47.158005Z",
      "updated_at": "2019-04-24T09:49:48.301933Z",
      "last_active": "2019-04-24T09:49:48.300566Z",
      "online": false
    },
    "attachments": [],
    "latest_reactions": [],
    "own_reactions": [],
    "reaction_counts": {},
    "reply_count": 0,
    "created_at": "2019-04-24T09:49:48.334808Z",
    "updated_at": "2019-04-24T09:49:48.511631Z",
    "mentioned_users": []
  },
  "reaction": {
    "message_id": "4b3c7b6c-a39d-4069-9450-2a3716cf4ca6",
    "user": {
      "id": "57fabaed-446a-40b4-a6ec-e0ac8cad57e3",
      "role": "user",
      "created_at": "2019-04-24T09:49:47.158005Z",
      "updated_at": "2019-04-24T09:49:48.301933Z",
      "last_active": "2019-04-24T11:49:48.497656+02:00",
      "online": true
    },
    "type": "lol",
    "created_at": "2019-04-24T09:49:48.481994Z"
  },
  "user": {
    "id": "57fabaed-446a-40b4-a6ec-e0ac8cad57e3",
    "role": "user",
    "created_at": "2019-04-24T09:49:47.158005Z",
    "updated_at": "2019-04-24T09:49:48.301933Z",
    "last_active": "2019-04-24T11:49:48.497656+02:00",
    "online": true,
    "total_unread_count": 2,
    "unread_channels": 2,
    "unread_count": 2,
    "channel_unread_count": 1,
    "channel_last_read_at": "2019-04-24T09:49:48.321138Z"
  },
  "created_at": "2019-04-24T09:49:48.511082Z"
}

member.added

{
  "cid": "messaging:fun",
  "type": "member.added",
  "member": {
    "user_id": "d4d7b21a-78d4-4148-9830-eb2d3b99c1ec",
    "user": {
      "id": "d4d7b21a-78d4-4148-9830-eb2d3b99c1ec",
      "role": "user",
      "created_at": "2019-04-24T09:49:47.149933Z",
      "updated_at": "2019-04-24T09:49:47.151159Z",
      "online": false
    },
    "created_at": "2019-04-24T09:49:48.534412Z",
    "updated_at": "2019-04-24T09:49:48.534412Z"
  },
  "user": {
    "id": "d4d7b21a-78d4-4148-9830-eb2d3b99c1ec",
    "role": "user",
    "created_at": "2019-04-24T09:49:47.149933Z",
    "updated_at": "2019-04-24T09:49:47.151159Z",
    "online": false,
    "channel_last_read_at": "2019-04-24T09:49:48.537084Z",
    "total_unread_count": 0,
    "unread_channels": 0,
    "unread_count": 0,
    "channel_unread_count": 0
  },
  "created_at": "2019-04-24T09:49:48.537082Z"
}

member.updated

{
  "cid": "messaging:fun",
  "type": "member.updated",
  "member": {
    "user_id": "d4d7b21a-78d4-4148-9830-eb2d3b99c1ec",
    "user": {
      "id": "d4d7b21a-78d4-4148-9830-eb2d3b99c1ec",
      "role": "user",
      "created_at": "2019-04-24T09:49:47.149933Z",
      "updated_at": "2019-04-24T09:49:47.151159Z",
      "online": false
    },
    "is_moderator": true,
    "created_at": "2019-04-24T09:49:48.534412Z",
    "updated_at": "2019-04-24T09:49:48.547034Z"
  },
  "user": {
    "id": "d4d7b21a-78d4-4148-9830-eb2d3b99c1ec",
    "role": "user",
    "created_at": "2019-04-24T09:49:47.149933Z",
    "updated_at": "2019-04-24T09:49:47.151159Z",
    "online": false,
    "total_unread_count": 0,
    "unread_channels": 0,
    "unread_count": 0,
    "channel_unread_count": 0,
    "channel_last_read_at": "2019-04-24T09:49:48.549211Z"
  },
  "created_at": "2019-04-24T09:49:48.54921Z"
}

member.removed

{
    "cid": "messaging:fun",
    "type": "member.removed",
    "user": {
        "id": "6585dbbb-3d46-4943-9b14-a645aca11df4",
        "role": "user",
        "created_at": "2019-03-22T14:22:04.581208Z",
        "online": false
    },
    "created_at": "2019-03-22T14:22:07.040496Z"
}

channel.updated

{
    "cid": "messaging:fun",
    "type": "channel.updated",
    "channel": {
        "cid": "messaging:fun",
        "id": "fun",
        "type": "messaging",
        "last_message_at": "2019-04-24T09:49:48.576202Z",
        "created_by": {
            "id": "57fabaed-446a-40b4-a6ec-e0ac8cad57e3",
            "role": "user",
            "created_at": "2019-04-24T09:49:47.158005Z",
            "updated_at": "2019-04-24T09:49:48.301933Z",
            "last_active": "2019-04-24T09:49:48.497656Z",
            "online": true
        },
        "created_at": "2019-04-24T09:49:48.180908Z",
        "updated_at": "2019-04-24T09:49:48.180908Z",
        "frozen": false,
        "config": {
            "created_at": "2016-08-18T16:42:30.586808Z",
            "updated_at": "2016-08-18T16:42:30.586808Z",
            "name": "messaging",
            "typing_events": true,
            "read_events": true,
            "connect_events": true,
            "search": true,
            "reactions": true,
            "replies": true,
            "mutes": true,
            "message_retention": "infinite",
            "max_message_length": 5000,
            "automod": "disabled",
            "commands": [
                "giphy",
                "flag",
                "ban",
                "unban",
                "mute",
                "unmute"
            ]
        },
        "awesome": "yes"
    },
    "created_at": "2019-04-24T09:49:48.594316Z"
}

channel.deleted

{
  "cid": "messaging:fun",
  "type": "channel.deleted",
  "channel": {
    "cid": "messaging:fun",
    "id": "fun",
    "type": "messaging",
    "created_at": "2019-04-24T09:49:48.180908Z",
    "updated_at": "2019-04-24T09:49:48.180908Z",
    "deleted_at": "2019-04-24T09:49:48.626704Z",
    "frozen": false,
    "config": {
      "created_at": "2016-08-18T18:42:30.586808+02:00",
      "updated_at": "2016-08-18T18:42:30.586808+02:00",
      "name": "messaging",
      "typing_events": true,
      "read_events": true,
      "connect_events": true,
      "search": true,
      "reactions": true,
      "replies": true,
      "mutes": true,
      "message_retention": "infinite",
      "max_message_length": 5000,
      "automod": "disabled",
      "commands": [
        "giphy",
        "flag",
        "ban",
        "unban",
        "mute",
        "unmute"
      ]
    }
  },
  "created_at": "2019-04-24T09:49:48.630913Z"
}

user.updated

{
  "type": "user.updated",
  "user": {
    "id": "thierry-7b690297-98fa-42dd-b999-a75dd4c7c993",
    "role": "user",
    "online": false,
    "awesome": true
  },
  "created_at": "2019-04-24T12:54:58.956621Z",
  "members": []
}

Push notification

Push Notifications for iOS

Using the APNs, your users' apps can receive push notifications directly on their client app for new messages when offline. Stream supports both Certificate-based provider connection trust(.p12 certificate), as well as Token-based provider connection trust(JWT).

Setup APN push using token authentication

Token based authentication is the preferred way to setup push. Token based is very easy to setup and guarantees strong security.

Step 1. Retrieve your TeamID

Sign in to your Apple Developer Account and then navigate to Membership. Copy your Team ID and store it somewhere safe.

Download

Step 2. Retrieve your Bundle ID

  1. From App Store Connect, navigate to My Apps

  2. Select the app you are using Stream Chat with

  3. Make sure the App Store tab is selected and navigate to App Information on the left bar

    App store connect
  4. In the Bundle ID(1)dropdown, make sure the proper bundle id is selected. Copy the bundle id next to Your Bundle ID(2)

    App info

Step 3. Generate a token

  1. From your Apple Developer Account overview, navigate to Certificates, Identifiers & Providers

    Certificate Dialog
  2. Make sure iOS, tvOS, watchOS is selected on the navigation pane on the left, and go to Keys > All

    Select Key Dialog
  3. Click on the + button to Add a new key

    Add Key Dialog
  4. In the Name field input a name for your key. In the Key Services section, select Apple Push Notifications service (APNs) and then click on Continue

    Key Type Dialog
  5. Review the information from the previous step and click on Confirm

  6. Copy your Key ID and store it somewhere safe.

  7. Save the key on your hard drive. Note:You can only dowload your key now. If you lose the key, you will have to repeat step 2

    Save Key

Step 4. Upload the Key credentials to Stream chat

Upload the TeamID, KeyID, Key and BundleID from the previous steps.

await client.updateAppSettings({
    apn_config: {
        auth_key: fs.readFileSync(
            './auth-key.p8',
            'utf-8',
        ),
        auth_type: 'token',
        key_id: 'key_id',
        bundle_id: 'com.apple.test',
        team_id: 'team_id',
        notification_template: `{"aps" :{"alert":{"title":"{{ sender.name }}","subtitle":"New direct message from {{ sender.name }}","body":"{{ message.text }}"},"badge":"{{ unread_count }}","category":"NEW_MESSAGE"}}`
    },
})
stream chat:push:apn \
    --auth_key './auth-key.p8' \
    --team_id 'team_id' \
    --key_id 'key_id' \
    --bundle_id 'com.apple.test' \
    -n '{"aps":{"alert":{"title":"{{ sender.name }}","subtitle":"New direct message from {{ sender.name }}","body":"{{ message.text }}"},"badge":{{ unread_count }},"category":"NEW_MESSAGE"}}'

Note:If your wish to use the APNs development endpoint instead of the production one, you must specify this when uploading the Key Credentials via the --development parameter:

await client.updateAppSettings({
    apn_config: {
        auth_key: fs.readFileSync(
            './auth-key.p8',
            'utf-8',
        ),
        key_id: 'key_id',
        auth_type: 'token',
        development: true,
        bundle_id: 'com.apple.test',
        team_id: 'team_id',
        notification_template: `{"aps" :{"alert":{"title":"{{ sender.name }}","subtitle":"New direct message from {{ sender.name }}","body":"{{ message.text }}"},"badge":{{ unread_count }},"category":"NEW_MESSAGE"}}`
    },
})
stream chat:push:apn \
    --auth_key './auth-key.p8' \
    --team_id 'team_id' \
    --key_id 'key_id' \
    --bundle_id 'com.apple.test' \
    --development \
    -n '{"aps":{"alert":{"title":"{{ sender.name }}","subtitle":"New direct message from {{ sender.name }}","body":"{{ message.text }}"},"badge":{{ unread_count }},"category":"NEW_MESSAGE"}}'

Setup APN push using certificate authentication

If token based authentication is not an option, you can setup APN with certificate authentication. You will need to generate a valid p12 certificate for your application and upload it to Stream.

Step 1. Create a Certificate Signing Request(CSR)

  1. On your Mac, open Keychain Access
  2. Go to Keychain Access > Certificate Assistant > Request a Certificate from a Certificate Authority.

    Request Certificate
  3. Fill out the information in the Certificate Information window as specified below and click "Continue."

    • In the User Email Address field, enter the email address to identify with this certificate
    • In the Common Name field, enter your name
    • In the Request group, click the "Saved to disk" option
    Certificate Dialog
  4. Save the file on your hard drive.(E.g.: /Users/john/Downloads/CertificateSigningRequest.certSigningRequest)

Step 2. Create a Push Notification SSL certificate

  1. Sign in to your Apple Developer Account and then navigate to Certificates, Identifiers & Providers

    Certificate Dialog
  2. Make sure iOS, tvOS, watchOS is selected on the navigation pane on the left, and go to Certificates > All

    Select Certificate Dialog
  3. Click on the + button to Add a new certificate

    Add Certificate Dialog
  4. In the Development section, select Apple Push Notification service SSL (Sandbox) and then click on Continue

    Certificate Type
  5. Select your app in the dropdown list and then click on Continue

  6. You will see instructions on how to generate a .certSigningRequest file. This was already covered in the previous section. Click on Continue

  7. Click on Choose File and then navigate to where you have saved the .certSigningRequest file from the previous section (E.g.: /Users/john/Downloads/CertificateSigningRequest.certSigningRequest). Click on Continue

  8. Click on Download to save your certificate to your hard drive.(E.g.: /Users/john/Downloads/aps_development.cer)

    Download

Step 3. Export the certificate in .p12 format

  1. On your mac, navigate to where you have saved the .cer file from the previous section (E.g.: /Users/john/Downloads/aps_development.cer) and double click on the file. This will add it to your Keychain

  2. Go to Keychain Access

  3. At the top left, select Keychains > login.

    Then, at the bottom left, select Category > Certificates

    Download
  4. Select the certificate you've created in the previous step. It should look like Apple Development IOS Push Services: YOUR_APP_NAME and expand it to see the private key(it should be named after the Name you provided when creating the Certificate Signing Request: in the case of this example: John Smith)

    Download
  5. Right-click the private key and click on Export. In the File format section select Personal Information Exchange (.p12) and save the file on your hard drive

    Download

Step 4. Upload the certificate to Stream chat

await client.updateAppSettings({
    apn_config: {
        p12_cert: fs.readFileSync(
            './certificate.p12',
        ),
        auth_type: 'certificate',
        notification_template: `{"aps":{"alert":{"title":"{{ sender.name }}","subtitle":"New direct message from {{ sender.name }}","body":"{{ message.text }}"},"badge":{{ unread_count }},"category":"NEW_MESSAGE"}}`
    },
})
stream chat:push:apn \
    --p12_cert './certificate.p12' \
    -n '{"aps":{"alert":{"title":"{{ sender.name }}","subtitle":"New direct message from {{ sender.name }}","body":"{{ message.text }}"},"badge":{{ unread_count }},"category":"NEW_MESSAGE"}}'

Note:If your wish to use the APNs development endpoint instead of the production one, this information will be automatically taken from your certificate.

Message payload

Stream offers basic templating (via {{ VARIABLE_NAME }}) for the apn payload. The templates are renderered using handlebars syntax.

Supported variables

Name Type Description
channel object Channel object. You can access the channel name and any other custom field you have defined for ths channel
sender object Sender object. You can access the user name or any other custom field you have defined for the user
message object Message object. You can acess the text of the message (or a preview of it if the message is too large) or any other custom field you have defined for the message
unread_count integer Number of unread messages

Note:If you do not provide a payload template for APNs, the default one will be used:

{
    "aps" : {
        "alert" : {
            "title" : "{{ sender.name }} @ {{ channel.name }}",
            "body" : "{{ message.text }}"
        },
        "badge": {{ unread_count }},
        "category" : "NEW_MESSAGE"
    }
}

Here is another example of using variable interpolation:

{
    "aps" : {
        "alert" : {
            "title" : "{{ channel.name }}",
            "subtitle" : "{{ unread_count }} new message(s) from {{ sender.name }}",
            "body" : "{{ message.text }}"
        },
        "category" : "NEW_MESSAGE"
    }
}

The payload that will be sent to APNs will look like this:

{
    "aps" : {
        "alert" : {
            "title" : "Chat with Mike",
            "subtitle" : "3 new message(s) from Mike",
            "body" : "Hey why aren't you answering"
        },
        "category" : "NEW_MESSAGE"
    }
}

Push Notifications for Android and Web

Using Firebase, your users' apps can receive push notifications directly on their client app for new messages when offline.

In order to push notifications to Android devices, you need to have an application on Firebase and configure your Stream account using the Firebase server key.

Retrieving the Server Key from Firebase

  1. From the Firebase Console, select the project your app belongs to.

  2. Click on the gear icon next to Project Overview and navigate to Project settings

    Project settings
  3. Navigate to the Cloud Messaging tab

  4. Under Project Credentials, locate the Server key and copy it

    Project settings
  5. Upload the Server Key

    await client.updateAppSettings({
        firebase_config: {
            server_key: 'server_key',
            notification_template: `{"message":{"notification":{"title":"New messages","body":"You have {{ unread_count }} new message(s) from {{ sender.name }}"},"android":{"ttl":"86400s","notification":{"click_action":"OPEN_ACTIVITY_1"}}}}`
        },
    })
    stream chat:push:firebase \
        -k 'server_key' \
        -n '{"message":{"notification":{"title":"New messages","body":"You have {{ unread_count }} new message(s) from {{ sender.name }}"},"android":{"ttl":"86400s","notification":{"click_action":"OPEN_ACTIVITY_1"}}}}'

Message payload

Stream offers basic templating (via {{ VARIABLE_NAME }}) for the firebase payload. The templates are renderered using handlebars syntax.

Supported variables

Name Type Description
channel object Channel object. You can access the channel name and any other custom field you have defined for ths channel
sender object Sender object. You can access the user name or any other custom field you have defined for the user
message object Message object. You can acess the text of the message (or a preview of it if the message is too large) or any other custom field you have defined for the message
unread_count integer Number of unread messages

Here is an example on how to use variable interpolation:

{
    "message": {
        "notification": {
            "title": "New messages",
            "body": "You have {{ unread_count }} new message(s) from {{ sender.name }}"
        },
        "android": {
            "ttl": "86400s",
            "notification": {
                "click_action": "OPEN_ACTIVITY_1"
            }
        }
    }
}

The message that will be sent to firebase will look like this:

{
    "message": {
        "notification": {
            "title": "New messages",
            "body": "You have 3 new message(s) from Tom"
        },
        "android": {
            "ttl": "86400s",
            "notification": {
                "click_action": "OPEN_ACTIVITY_1"
            }
        }
    }
}

Push Devices

Once your app has push enabled, you can use the APIs to register user devices such as iPhones and Android phones.

Device parameters

Name Type Description
user_id string The user ID for this device.
id string The device ID.
provider string The push provider for this device. Either apn or firebase.

Register a device

stream chat:push:device:add \
    --device_id '2ffca4ad6599adc9b5202d15a5286d33c19547d472cd09de44219cda5ac30207'
    --user_id '42'
    --provider 'apn'

Deregister a device

stream chat:push:device:delete \
    --device_id '2ffca4ad6599adc9b5202d15a5286d33c19547d472cd09de44219cda5ac30207'
    --user_id '42'

Push Notification Testing

Once you're all setup with push notifications, you can use the CLI to test how the these notifications will look for your devices.

In preparation of this make sure that:

  • Your app has push notifications configured for at least one provider(APNs or Firebase)
  • You have a user that has at least one device associated

The base command for testing push notifications is:

stream chat:push:test --user_id 'user_123'

This will do several things for you:

  1. Pick a random message from a channel that this user is part of
  2. Use the notification templates configured for your push providers to render the payload using this message
  3. Send this payload to all of the user's devices

This particular use case is ideal for testing a newly configured app, to make sure the push notifications are exactly as wanted.

In some other cases, your app is already configured with push notifications and is running smoothly, but you want to try out a new notification template. For example, let's say you want to test a new APN notification template:

stream chat:push:test --user_id 'user_123' --apn_notification_template '{"aps":{"alert":{"title":"{{ sender.name }} @ {{ channel.name }}","body":"testing out new stuff:{{ message.text }}"},"category":"NEW_MESSAGE_2"}}'

As you can see, this one time only, the new template will be used for the push notification instead of the configured one:

CLI Push Test

Here's a full list of parameters that you can use with the test command:

Push test parameters

Name Type Description Optional
user_id string The user ID.
message_id string ID of the message that should be used instead of a random one. If the message doesn't exist, an error will occur
apn_notification_template string Notification template to be used instead of the configured APN one. This is one time only.
firebase_notification_template string Notification template to be used instead of the configured Firebase one. This is one time only.

React Native

To try out push notifications on an actual device, you can use React Native to quickly build an app that just receives notifications.

Note: This tutorial is not supported in Expo applications. We need access to the native code to get the device tokens required to send push notifications for Stream Chat. If you are hoping to integrate this into an existing Expo application, you will need to eject.

Before getting down to business with React Native, a few things need to be setup first:

  1. Make sure you’ve created a development app within the dashboard.

    New App
  2. To make things easier, we are going to disable auth so that we can easily test push messages. Please note that this only suitable for test/dev environments.

    From your app's dashboard, navigate to the Chat tab and then scroll down to Chat Events. Make sure the Disable Auth Checks toggle is On and click on Save.

    Disable Auth
  3. Install XCode and Command Line Tools. More info on that here

  4. Install dependencies and initialize your test project(for this tutorial we'll use the name chat-push-example)

    yarn global add react-native-cli
    react-native init chat-push-example
    cd chat-push-example
    yarn add stream-chat react-native-push-notifications
    react-native link react-native-push-notifications
    Note: If you are adding this to an existing React Native project created with Expo or CRNA, you will need to eject. Please note that this ejecting is irreversible; however, it is required to access native iOS and Android capabilities.
    Shortcut: If you don't want to jump through all the hoops described in the following sections, we have a mostly preconfigured repository with everything described in this doc on GitHub. Just follow the instructions in the README to get started.

iOS React Native Setup

For iOS, we will be using Apple’s APN service to power the push functionality, rather than Expo or Firebase Cloud Messaging.

You will need to link the PushNotificationIOS library, which is exported from React Native, to your project:

  1. Open Xcode and in the sidebar on the left, make sure you’re in the Project Navigator tab (the folder icon). Right click on the Libraries directory within your project name and select Add files to YOUR_PROJECT_NAME.

    Add Library
  2. A file dialog will pop-up. Navigate to the root of your project, then to node_modules/react-native/Libraries/PushNotificationsIOS/RCTPushNotification.xcodeproj. Click Add.

    File Dialog
  3. Click the root of your application's Xcode project in the navigator sidebar and click Build Phases in the tabs at the top of the middle pane. On the next screen, find the dropdown labeled Link Binaries with Libraries, expand it, and click the plus icon in the bottom left.

    Link library
  4. Type RCTPushNotification in the search box. You should see a file named libRCTPushNotification.a in the list. Select it and click Add.

    Select library
  5. Navigate to AppDelegate.m in your project.

    App Delegate
  6. Add the following import just below #import "AppDelegate.h".

    #import "AppDelegate.h"
    
    #import <React/RCTPushNotificationManager.h> // <-- THIS
  7. Add the following snippet to the bottom of the file, just before @end.

    // Required to register for notifications
    - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
     {
      [RCTPushNotificationManager didRegisterUserNotificationSettings:notificationSettings];
     }
     // Required for the register event.
    - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
     {
      [RCTPushNotificationManager didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
     }
     // Required for the notification event. You must call the completion handler after handling the remote notification.
    - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
                                                            fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
     {
       [RCTPushNotificationManager didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
     }
     // Required for the registrationError event.
    - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
     {
      [RCTPushNotificationManager didFailToRegisterForRemoteNotificationsWithError:error];
     }
     // Required for the localNotification event.
    - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
     {
      [RCTPushNotificationManager didReceiveLocalNotification:notification];
     }

Running on iOS devices

Note: Unfortunately, push notifications do not work in the iOS simulator, so you’re going to need a physical iOS device to be able to see the fruits of your labor.

If you want to run your React Native application on a device, you’re going to have to edit the XCode project to include code signing and push capabilities:

  1. In XCode, from the navigation bar on the left make sure your project is selected. Navigate to the General tab.

    Select project
  2. Expand Signing and make sure you are logged in with your Apple Developer Account. Once logged in, select your development team and be sure to check Automatically Manage Signing.

    Dev Team
  3. Expand Identity and make sure your Bundle Identifier matches the one you used to configure push notifications with Stream. If you haven’t done this yet, please refer to the docs here and then return to this tutorial.

    Bundle
  4. Navigate to the Capabilities tab and make sure Push Notifications are enabled

    Capabilities
  5. You’re all set! Plug in your iOS device, select it from the run menu in XCode and press Run.

    Run

Android React Native Setup

For Android, we’ll be using Firebase Cloud Messaging to power push notifications:

  1. Go to the Firebase Console, create a new application OR select an existing project.
  2. Go to Project Settings and under the General tab.
  3. Click on Your Apps, add an Android application and download your google-services.json file – you need to put this in the root of your projects android directory.

    Google Services
  4. Put google-services.json in the root of your project's android directory.(e.g. ./android)

  5. Make sure google-services.json file is included in your Android project’s dependencies by navigating to your project level build.gradle file (./android/build.gradle) and adding the following line:

    buildscript {
    	// ...
    	dependencies {
    		// ...
    		classpath 'com.google.gms:google-services:+'
    		// ...	
    	}
    	// ...
    }
  6. In the same directory, find the settings.gradle file and copy the following content into the file if it isn’t already there.

    include ':react-native-push-notification'
    project(':react-native-push-notification').projectDir = file('../node_modules/react-native-push-notification/android')
    Note: When you previously ran react-native link, it should have added the necessary files; however, it’s best to always double check. Linking can be temperamental at times.
  7. Navigate to ./android/app/src and check you have the res folder. If not, create it and inside create another folder values. Then create a new file named colours.xml whose content is the following:

    
    <resources>
    <color name="white">#FFF</color>
    </resources>
            
  8. Linking may have also taken care of this step, but once again, navigate to MainApplication.java in android/app/src/main and check it has these two parts, as highlighted with comments:

    import com.dieam.reactnativepushnotification.ReactNativePushNotificationPackage;  // <--- THIS
    
    public class MainApplication extends Application implements ReactApplication {
    
      private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
          @Override
          protected boolean getUseDeveloperSupport() {
            return BuildConfig.DEBUG;
          }
  9. Go to android/app/src/main/AndroidManifest.xml and copy the following (the comments below will help guide you for your particular setup)

    
    <!-- < Only if you're using GCM or localNotificationSchedule() > -->
      <uses-permission android:name="android.permission.WAKE_LOCK" />
      <permission
        android:name="${applicationId}.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />
       <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />
    
       <!-- < Only if you're using GCM or localNotificationSchedule() > -->
       <uses-permission android:name="android.permission.VIBRATE" />
       <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    
    <!-- Then, within the <application> block, place the following -->
    
      <receiver
        android:name="com.google.android.gms.gcm.GcmReceiver"
        android:exported="true"
        android:permission="com.google.android.c2dm.permission.SEND" >
        <intent-filter>
            <action android:name="com.google.android.c2dm.intent.RECEIVE" />
            <category android:name="${applicationId}" />
        </intent-filter>
      </receiver>
    
      <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
      <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
        <intent-filter>
          <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
      </receiver>
      <service android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationRegistrationService"/>
    
      <service
          android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerServiceGcm"
          android:exported="false" >
        <intent-filter>
          <action android:name="com.google.android.c2dm.intent.RECEIVE" />
        </intent-filter>
      </service>
    
      <service
            android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationListenerService"
            android:exported="false" >
        <intent-filter>
            <action android:name="com.google.firebase.MESSAGING_EVENT" />
        </intent-filter>
    </service>
            

Unlike iOS, the Android emulator does support push notifications - just make sure you use an emulator with Google Play Services installed (shown by the Play Store Icon in the AVD Manager) Google play

Integrating with Stream

Thanks to react-native-push-notifications, we are provided a platform agnostic experience while still using native push on each platform.

All that's left is integrating the test app with the Stream Chat client. Open up App.js in the root of your project and check that componentDidMount exists. If the file does not exist add an async componentDidMount method to the class:

//...
import { PushNotificationIOS } from 'react-native';
import PushNotification from 'react-native-push-notification';
import { StreamChat } from 'stream-chat';
import { API_KEY, USER_TOKEN, USER_ID } from 'react-native-dotenv';

export default class App extends Component {

    async componentDidMount() {
  
      const client = new StreamChat(API_KEY, null);
  
      await client.setUser({ id: USER_ID }, USER_TOKEN);
  
      PushNotification.configure({
        onRegister(token) {
          client
            .addDevice(token.token, token.os === 'ios' ? 'apn' : 'firebase')
            .then(() => {
              console.log(`registered device with token ${token}`);
            })
            .catch((e) => {
              console.error(`registering device failed: ${e}`);
            });
        },
        onNotification(notification) {
          notification.finish(PushNotificationIOS.FetchResult.NoData);
        },
        senderID: "1069091084846",// (Android Only) Grab this from your Firebase Dashboard where you got google-services.json
        requestPermissions: true
      });
    }
    //...
}

For the sake of this tutorial, we created a .env file to store these values and grab them using react-native-dotenv as shown above – a good idea for your API_KEY in any case, but your USER_ID and USER_TOKEN will likely come from elsewhere in a real-world setting, depending on your use-case.

All that's left to do is to use the stream-cli to send out a test push notification. You can find out how that works in this section of the docs. Just make sure you use the same user_id in both the cli and this test app.

Chat Bots

To build your own chat bot you can use the following 4 features of the chat API:

  1. Webhooks
  2. Attachments, Fields and Actions
  3. Custom Slash Commands
  4. Custom Attachments

Step 1 - Webhook setup

As an example let's say that you want to build a chatbot that handles customer care for a bank. You'll typically want to gather some data automatically before routing the request to a human. To achieve that you would start by setting up a webhook (webhook docs). The webhook will be called whenever there is a new message on the channel.

Step 2 - AI

When handling the webhook you'll want to run some AI to determine what the user is talking about. A good starting point is recognizing the most common requests. For instance your AI could recoganize a message like this: "I noticed a weird transaction on my account." Next the AI can reply by asking the user for more details leveraging the attachment, field and action system.

Step 3 - Attachment, fields and actions

Let's have the AI reply with a message asking for more information.

const message = {
    text: 'Which account was affected by this issue?',
    attachments: [
    {
        type: 'form',
        title: 'Select your account',
        actions: [
                {
                        name: 'account',
                        text: 'Checking',
                        style: 'primary',
                        type: 'button',
                        value: 'checking',
                },
                {
                        name: 'account',
                        text: 'Saving',
                        style: 'default',
                        type: 'button',
                        value: 'saving',
                },
                {
                        name: 'account',
                        text: 'Cancel',
                        style: 'default',
                        type: 'button',
                        value: 'cancel',
                },
        ],
        },
        ],
};
const response = await channel.sendMessage(message);

When the user submits their choice the webhook endpoint will be called again. Now that you have some more information gathered it's time to connect the user to a real support agent. You can do this by adding a member to the conversation and your support agent will be notified in real time. To improve their productivity you'll want to leverage slash commands. Slash commands make it easy to automate common tasks. Lets show how to create your own slash command for managing tickets

Step 4 - Slash Commands

As a first step you'll want to setup a custom slash command for the "ticket" keyword. At the moment this isn't fully documented yet, so contact support if you need help. With the slash command setup your account managers will be able to automate common tasks like this: /ticket suspicious transaction with id 1234

const message = {
    text: '/ticket suspicious transaction with id 1234'
};
const response = await channel.sendMessage(message);
Step 5 - Custom Attachments

Custom attachments can also be helpful when building chat bots. You could for instance create a custom attachment for allowing users to select a date. The React Chat tutorial shows an example of how to create a custom attachment.

Query Syntax

The Chat API allows you to specify filters and ordering for several endpoints. You can query channels, users and messages. The query syntax is similar to Mongoose. Note that we don't run Mongo in the background and only support a subset of operations. Have a look below at the complete list of supported operations:

Supported Operators

Name Description Example
$eq Matches values that are equal to a specified value. { "key": { "$eq": "value" } }

or the simplest form

{ "key": "value" }
$gt Matches values that are greater than a specified value. { "key": { "$gt": 4 } }
$gte Matches values that are greater than or equal to a specified value. { "key": { "$gte": 4 } }
$lt Matches values that are less than a specified value. { "key": { "$lt": 4 } }
$lte Matches values that are less than or equal to a specified value. { "key": { "$lte": 4 } }
$ne Matches all values that are not equal to a specified value. { "key": { "$ne": "value" } }
$in Matches any of the values specified in an array. { "key": { "$in": [ 1, 2, 4 ] } }
$nin Matches none of the values specified in an array. { "key": { "$in": [ 3, 5, 7 ] } }
$and Matches all the values specified in an array. { "$and": [ { "key": { "$in": [ 1, 2, 4 ] } }, { "some_other_key": 10 } ] }
$or Matches at least one of the values specified in an array. { "$or": [ { "key": { "$in": [ 1, 2, 4 ] } }, { "key2": 10 } ] }
$nor Matches none of the values specified in an array. { "$nor": [ { "key": { "$in": [ 1, 2, 4 ] } }, { "key2": 10 } ] }

Importing data

If you've just started using Stream, you might want to import data from your infrastructure or provider. Instead of using the APIs and creating your own import scripts, you can make use of our import feature.

The process

The steps for importing data into your app are as follows:

  1. Generate the import file for your data (full file reference below)
  2. Reach out to send us the import file
  3. The file will be validated according to the rules described in this doc
  4. If validation passes, a member of our team will approve and run the import
  5. Once the import is completed (usually a few minutes), you will get a confirmation email

Import file

The import file must be structured as a JSON array of objects. Each object is an item to add to your application (eg. a user, a message, ...).

Import entries format

Name Type Description
type string The type of data for this entry. Allowed values are: user, channel, message, reaction, member
item object The data for his entry, see below for the format of each type

Item types

An import file can contain entries with any of these types.

user type
Name Type Description Default Optional
id string the unique id for the user
role string the role of the user user
created_at string the creation time of the user in RFC3339
* string/list/object/point Add as many custom fields as needed
{
  "type": "user",
  "item": {
    "id": "aad24491-c286-4169-bbfc-9280de419cb6",
    "name": "Jesse",
    "profile_image": "http://getstream.com",
    "created_at": "2017-01-01T01:00:00Z",
    "description": "Taj Mahal guitar player at some point"
  }
}
channel type
Name Type Description Default Optional
id string the unique id for the channel
type string the type of channel, eg. livestream
created_by string the id of the user that created the message
frozen boolean channel frozen state false
* string/list/object/point Add as many custom fields as needed
{
  "type": "channel",
  "item": {
    "id": "6e693c74-262d-4d3d-8846-686364c571c8",
    "type": "livestream",
    "created_by": "aad24491-c286-4169-bbfc-9280de419cb6",
    "name": "Rock'n Roll Circus"
  }
}
message type
Name Type Description Default Optional
id string The id of the message A random UUIDv4
channel_type string the type of channel for this message
channel_id string the id of the channel for this message
type string the type of message: regular, deleted or system regular
user string the id of the user that posted the message
attachments array of objects list of message attachments, see attachment type section below []
parent_id string the id of the parent message (if the message is a reply) null
created_at string message creation time in RFC3339 format
updated_at string last time the message was updated created_at
deleted_at string message deletion time in RFC3339 format null
show_in_channel bool reply messages are only shown inside the message thread unless show_in_channel is set to true, in that case the message is shown in the channel as well false
* string/list/object/point Add as many custom fields as needed
message attachments

Messages can contain a list of attachments such as videos, images and other custom types.

Name Type Description Optional
type string the type of attachment. Eg. text, audio, video, image
fallback string
color string
pretext string
author_name string
author_link string
author_icon string
title string
title_link string
text string
image_url string
thumb_url string
footer string
footer_icon string
asset_url string
* string/list/object/point Add as many custom fields as needed
{
  "type": "message",
  "item": {
    "id": "977e691a-c091-4e3b-8f70-ba8944a3e500",
    "channel_type": "livestream",
    "channel_id": "6e693c74-262d-4d3d-8846-686364c571c8",
    "user":"aad24491-c286-4169-bbfc-9280de419cb6",
    "text": "Such a great song, check out my solo at 2:25",
    "type": "regular",
    "created_at": "2017-02-01T02:00:00Z",
    "attachments": [
      {
        "type": "video",
        "url": "https://www.youtube.com/watch?v=flgUbBtjl9c"
      }
    ]
  }
}
reaction type
Name Type Description Optional
message_id string the ID of the message
type string the type of reaction
user_id string the ID of the user
created_at string the creation time of the reaction in RFC3339
* string/list/object/point Add as many custom fields as needed
{
  "type": "reaction",
  "item": {
    "message_id": "977e691a-c091-4e3b-8f70-ba8944a3e500",
    "type": "love",
    "user_id": "aad24491-c286-4169-bbfc-9280de419cb6",
    "created_at": "2019-03-02T15:00:00Z"
  }
}
member type
Name Type Description Default Optional
channel_type string the type of channel for this message
channel_id string the id of the channel
user_id string the user ID
is_moderator boolean true if the user is a channel moderator false
created_at string the membership creation time in RFC3339 current time
{
  "type": "member",
  "item": {
    "channel_type": "livestream",
    "channel_id": "6e693c74-262d-4d3d-8846-686364c571c8",
    "user_id": "aad24491-c286-4169-bbfc-9280de419cb6",
    "is_moderator": true,
    "created_at": "2017-02-01T02:00:00Z"
  }
}

Migrating from Layer

Migrating from Layer is easy. This guide will help you complete the migration quickly:

  1. Export

    The first step is downloading an export of your Layer data. The Layer docs specify how to create an export. Next email support@getstream.io your data export to have it imported to Stream. This process typically takes 1 business day.

  2. Frontend components

    Layer doesn’t provide frontend components. You will want to decide if you want to customize one of Stream’s frontend components or work with the chat API from your own frontend. Implementing a fully featured Chat in React can be very time-consuming.

    Have a look at these 5 examples and see if you can customize them. Swapping the front end components is the fastest way to integrate with Stream.

  3. Differences

    • Stream provides 5 built-in chat types for the most common use cases. The commerce chat type is the most similar to Layer. So you’ll want to use that one as a starting point.
    • Layer has the concept of Distinct Conversations and Non Distinct conversations. With Stream you initialize a channel with a channel type and channel id. So you simply need to make sure the ID is unique when you want to create a new conversation.
    • Stream doesn’t have MessageParts we have Message Attachments. The docs for sending messages are here
    • Conversations are called Channels and instead of Participants Stream has Members.
    • Layer allows you to specify a Metadata field. Stream allows you to add custom data directly to users, channels, messages, attachments and events.
  4. Go live

    After testing your awesome new chat solution discuss a go-live date with Stream’s support team. We’ll re-import your data backup.

  5. Help

    If you have more tips for migrating from Layer to Stream be sure to contact us. We’re refining this guide continuously.

Migrating from Sendbird

Channel types

Sendbird has 2 channel types. The open channel or the group channel. Stream has 5 built-in channel types: livestream, messaging, commerce, gaming, and team. You can also create your own channel types. This allows you to configure the features and permissions to exactly fit your use case. Usually, you’ll want to use the “livestream” chat type if you’re using a Sendbird open channel.

Channels

Instead of the getChannel and channel.enter, Stream uses 1 API call to both get and enter a channel.

The concept of UserMessage and FileMessage in Sendbird is replaced using a Message with a list of attachments.

Thumbnails

Instead of specifying thumbnail sizes upfront you can request different image sizes at read time.

Private vs. Public groups

This difference is handled by Stream’s permission system. You can allow or not allow non-members to edit the list of members for a channel.

Limitations

  • Stream currently doesn’t directly support translating messages. You can write a custom bot to support this though.
  • You can query users