const newUser: UserRequest = {
id: 'user-id',
role: 'user',
custom: {
color: 'red',
},
name: 'This is a test user',
image: 'link/to/profile/image',
};
await client.upsertUsers([newUser]);User management
The operations performed on users, such as updating and deleting, have an effect on all products (chat, feeds and video).
Creating and connecting users
When creating users, there are a few important things to keep in mind:
- The
idfield is mandatory, in most cases you want this to be the same ID you use on your database. - The
rolefield is optional, by default it is set touserbut you can specify any existing role. - Custom data can be added to users in the
customfield. nameandimageare optional and handled by all SDKs automatically to render users.- The
languagefield (ISO 639-1) is optional but recommended when using activity and comment translation. It helps the API determine the source language when translating content authored by that user.
Users can be created on the fly when using client-side SDKs. Users created with this method will have the user role:
import { FeedsClient } from "@stream-io/feeds-client";
const client = new FeedsClient("<API key>");
await client.connectUser(
{
id: "john",
// Optional data
name: "John",
image: "url/to/profile/picture",
},
"<user token or provider>",
);
// When logging out: await client.disconnectUser();Built-in fields for users
UserResponse
| Name | Type | Description | Constraints |
|---|---|---|---|
avg_response_time | integer | - | - |
ban_expires | number | Date when ban expires | - |
banned | boolean | Whether a user is banned or not | Required |
blocked_user_ids | string[] | - | Required |
created_at | number | Date/time of creation | Required |
custom | object | Custom data for this object | Required |
deactivated_at | number | Date of deactivation | - |
deleted_at | number | Date/time of deletion | - |
devices | DeviceResponse[] | List of devices user is using | - |
id | string | Unique user identifier | Required |
image | string | - | - |
invisible | boolean | - | Required |
language | string | Preferred language of a user | Required |
last_active | number | Date of last activity | - |
name | string | Optional name of user | - |
online | boolean | Whether a user online or not | Required |
privacy_settings | PrivacySettingsResponse | User privacy settings | - |
push_notifications | PushNotificationSettingsResponse | User push notification settings | - |
revoke_tokens_issued_before | number | Revocation date for tokens | - |
role | string | Determines the set of user permissions | Required |
shadow_banned | boolean | Whether a user is shadow banned | Required |
teams | string[] | List of teams user is a part of | Required |
teams_role | object | - | - |
updated_at | number | Date/time of the last update | Required |
Querying users
You can query and filter users using the queryUsers endpoint. This allows you to search for users based on various criteria such as custom fields, roles, and other user properties.
const response = await client.queryUsers({
payload: {
filter_conditions: {
role: "admin",
},
sort: [{ field: "created_at", direction: -1 }],
limit: 10,
offset: 0,
},
});
console.log(response.users);All filters use a Mongoose-style syntax; however, we do not run MongoDB on the backend, so only a subset of Mongoose queries are supported. The supported filters are described below.
Supported Filters
| Name | Type | Description | Allowed Operators |
|---|---|---|---|
| id | string | ID of the user | $eq, $ne, $in, $nin, $gt, $gte, $lt, $lte, $autocomplete |
| role | string | Role of the user | $eq, $gt, $gte, $lt, $lte, $in |
| banned | boolean | Whether the user is banned | $eq, $ne |
| shadow_banned | boolean | Whether the user is shadow banned | $eq, $ne |
| created_at | string, must be formatted as an RFC3339 timestamp | Time when the user was created | $eq, $gt, $gte, $lt, $lte, $in |
| updated_at | string, must be formatted as an RFC3339 timestamp | Time when the user was updated | $eq, $gt, $gte, $lt, $lte, $in |
| last_active | string, must be formatted as an RFC3339 timestamp | Time when the user was last active | $eq, $gt, $gte, $lt, $lte, $in, $exists |
| teams | string | Teams the user belongs to | $eq, $contains, $in |
| name | string | Name of the user | $eq, $in, $autocomplete |
| username | string | Username of the user | $eq, $autocomplete |
| custom properties | string, boolean, int | Custom user properties | $eq |
Query Users Performance
Query Users runs against an indexed user database. To keep response times predictable for apps of every size, filters that cannot use an index are rejected with a 400 error.
Custom fields only support exact matches. Custom properties are stored in a JSON column, and the index on that column only supports exact-match lookups. Range operators such as $gt, $gte, $lt, and $lte (as well as $in, $exists, and $contains) on a custom field cannot use the index and would require scanning every user in your app, so they are not allowed. If you need range queries on a field, consider using one of the built-in indexed fields (such as created_at, updated_at, or last_active), or query your own database instead.
Not-equal operators are only allowed on id and boolean fields. The $ne, $nin, and $nor operators are only supported on the id field (and $ne on boolean fields such as banned). On other fields these negative filters prevent the database query planner from using an index: instead of seeking directly to matching rows, it has to walk through everything that does not match, which can result in slow queries and timeouts.
You can usually rewrite a negative filter as a positive one. For example, instead of role: { $ne: "admin" }, query for the roles you do want: role: { $eq: "user" } or role: { $in: ["user", "moderator"] }. Positive filters are index-backed and fast.
Supported Sort
You can sort results by specifying a field and direction (1 for ascending, -1 for descending).
| Name | Description |
|---|---|
| id | User ID |
| created_at | Time when the user was created |
| updated_at | Time when the user was updated |
| last_active | Time when the user was last active |
| role | Role of the user |
Supported Options
The options for the queryUsers method are primarily used for pagination.
| Name | Type | Description | Default | Optional |
|---|---|---|---|---|
| limit | integer | Number of users to return | 30 | ✓ |
| offset | integer | Offset for pagination | 0 | ✓ |
| id_gt | string | ID-based pagination. Return IDs greater than this ID. If this is not empty, the default sort order will be [{id: -1}]. | - | ✓ |
| id_gte | string | ID-based pagination. Return IDs greater than or equal to this ID. If this is not empty, the default sort order will be [{id: -1}]. | - | ✓ |
| id_lt | string | ID-based pagination. Return IDs less than this ID. If this is not empty, the default sort order will be [{id: -1}]. | - | ✓ |
| id_lte | string | ID-based pagination. Return IDs less than or equal to this ID. If this is not empty, the default sort order will be [{id: -1}]. | - | ✓ |
| include_deactivated_users | boolean | Include deactivated users in the response | - | ✓ |
The maximum offset value is 1000.
Filter examples
You can use various filter operators to query users:
// Query users by custom field
const response = await client.queryUsers({
payload: {
filter_conditions: {
"custom.color": "red",
},
},
});
// Query users with multiple conditions
const response2 = await client.queryUsers({
payload: {
filter_conditions: {
role: { $in: ["admin", "moderator"] },
"custom.plan": "premium",
},
},
});
// Query users by name (text search)
const response3 = await client.queryUsers({
payload: {
filter_conditions: {
name: { $autocomplete: "john" },
},
},
});Updating users
You can update users in two ways:
- Replace updates: replace the entire user object with the one provided to the API call
- Partial update: choose which fields you want to change
const user: UserRequest = {
id: 'userid',
role: 'user',
custom: {
color: 'blue',
},
name: 'This is a test user',
image: 'link/to/profile/image',
};
client.upsertUsers([user]);
// or
client.updateUsersPartial({
users: [
{
id: user.id,
set: {
'new-field': 'value',
},
unset: ['name'],
},
],
});Deactivating and deleting users
Depending on your use case, you can choose to delete users or deactivate them. There are some differences between these two approaches.
Deactivating users:
- the user will not be allowed to perform API requests / connect
- user data is retained on Stream's side and returned from API
- deactivated users can be re-activated
Deleting users:
- the user will no longer be able to perform API requests / connect
- the user is deleted and, by default, not returned from API
- all data from the user is marked as deleted
- by default, the data is retained and "soft" deleted; you can optionally request hard deletion
- deletion is not reversible
Note: Both deletion and deactivation are performed asynchronously by Stream API. A task ID is returned and you can use that to check the status of its processing.
Deactivating users
client.deactivateUser({
user_id: '<id>',
});
// reactivate
client.reactivateUsers({
user_ids: ['<id>'],
});
// deactivating users in bulk is performed asynchronously
const deactivateResponse = client.deactivateUsers({
user_ids: ['<id1>', '<id2>'...],
});Deactivating users in bulk can take some time. This is how you can check the progress:
// Example of monitoring the status of an async task
// The logic is same for all async tasks
const response = _; // Result of a Stream async API request
// You need to poll this endpoint
const taskResponse = await client.getTask({ id: response.task_id });
console.log(taskResponse.status === "completed");Deleting users
client.deleteUsers({ user_ids: ["<id>"] });
//restore
client.restoreUsers({ user_ids: ["<id>"] });By default users are soft deleted. If you need control over deleting chat/video data please refer to the Chat documentation.
Authless users
Stream lets you give unauthenticated users access to a limited subset of Stream's capabilities. This is done by using either Guest or Anonymous users.
These two user types are ideal for use cases where users either need to be able to see feed activity prior to authenticating or in scenarios where the additional friction of creating a user account might be unnecessary for a user.
Configuration for guest and anonymous roles can be managed in the dashboard.
Guest Users
Guest sessions can be created client-side and do not require any server-side authentication. Use cases like support or public or visible feeds often benefit from guest users, because you may want a visitor to be able to view or interact with feeds on your application without (or before) having a regular user account. Guest users are temporary users.
Guest users are not available to applications using multi-tenancy (teams).
Guest users are counted towards your MAU usage.
You can generate a guest user in a front end client by using the following code:
import { FeedsClient } from "@stream-io/feeds-client";
const client = new FeedsClient("<API key>");
await client.connectGuest({ id: "tommaso" });To create a guest user from your server and obtain a token for the client:
const response = await client.createGuest({
user: { id: "tommaso", name: "Tommaso" },
});
// Return response.user and response.access_token to your clientThe user object schema is the same as the one described in the Built-in fields for users section above.
Creation of guest users can be disabled for your application in the dashboard.
Anonymous Users
While anonymous, users have limited capabilities by default, but they can still read feed content where allowed.
import { FeedsClient } from "@stream-io/feeds-client";
const client = new FeedsClient("<API key>");
const connectResponse = await client.connectAnonymous();
console.log(connectResponse.me);Anonymous users are not counted toward your MAU and can't establish WebSocket connection.
Anonymous users are not allowed to perform any write operations.