# Users

The Stream user object is central to every product and appears in many API responses, effectively following the user throughout the platform. Only an `id` is required to create a user but you can store additional custom data. We recommend only storing what is necessary, such as a username and image URL.

>
> **Info:** The operations performed on users, such as updating and deleting, have an effect on all products (chat, video and feeds).
>

## Creating users

When creating users, there are a few important things to keep in mind:

- The `id` field is mandatory, in most cases you want this to be the same ID you use on your database.
- The `role` field is optional, by default it is set to `user` but you can specify any existing role.
- Custom data can be added to users in the `custom` field.
- `name` and `image` are optional and handled by all SDKs automatically to render users.
- The `language` field (ISO 639-1) is optional but recommended when using [activity and comment translation](https://getstream.io/activity-feeds/docs/node/translation/) in Feeds. It helps the API determine the source language when translating content authored by that user.

Users are also created on the fly when they connect from a client-side SDK, with the `user` role. However, it is also common to add your users to Stream before going live and keep properties of your user base in sync. For this you'll want to use the `upsertUsers` function server-side and send users in bulk.

The `upsertUser` method creates or updates a user, replacing its existing data with the new payload (see below for partial updates). To create or update users in batches of up to 100, use the `upsertUsers` or `partialUpdateUsers` APIs, which accept an array of user objects.

```js label="Node.js"
const updateResponse = await serverClient.upsertUser({
  id: userID,
  role: "admin",
  book: "dune",
});
```

```python label="Python"
from getstream.models import UserRequest

client.upsert_users(UserRequest(id=user_id, role="admin", custom={"book": "dune"}))
```

```ruby label="Ruby"
Models = GetStream::Generated::Models

client.common.update_users(
  Models::UpdateUsersRequest.new(
    users: { user_id => Models::UserRequest.new(id: user_id, role: 'admin', custom: { 'book' => 'dune' }) }
  )
)
```

```go label="Go"
client.UpdateUsers(ctx, &getstream.UpdateUsersRequest{
	Users: map[string]getstream.UserRequest{
		userID: {ID: userID, Role: getstream.PtrTo("admin"), Custom: map[string]any{"book": "dune"}},
	},
})
```

```csharp label="C#"
// dotnet add package getstream-net
using GetStream;
using GetStream.Models;

var client = new StreamClient("{{ api_key }}", "{{ api_secret }}");

await client.UpdateUsersAsync(new UpdateUsersRequest
{
    Users = new Dictionary<string, UserRequest>
    {
        ["bob-1"] = new UserRequest
        {
            ID = "bob-1",
            Role = "admin",
            Custom = new Dictionary<string, object> { ["book"] = "dune" }
        }
    }
});
```

```java label="Java"
client.updateUsers(UpdateUsersRequest.builder()
    .users(Map.of(userId1, UserRequest.builder().id(userId1).role("admin").build()))
    .build()).execute();
```

And for a batch of users, simply add additional entries (up to 100) into the array you pass to `upsertUsers`:

```js label="Node.js"
const updateResponse = await serverClient.upsertUsers([
  { id: userID1, role: "admin", book: "dune" },
  { id: userID2, role: "user", book: "1984" },
  { id: userID3, role: "admin", book: "Fahrenheit 451" },
]);
// each user object is updated accordingly
```

```python label="Python"
from getstream.models import UserRequest

client.upsert_users(
    UserRequest(id=user_id1, role="admin", custom={"book": "dune"}),
    UserRequest(id=user_id2, role="user", custom={"book": "1984"}),
    UserRequest(id=user_id3, role="admin", custom={"book": "Fahrenheit 451"}),
)
```

```ruby label="Ruby"
Models = GetStream::Generated::Models

client.common.update_users(
  Models::UpdateUsersRequest.new(
    users: {
      user_id1 => Models::UserRequest.new(id: user_id1, role: 'admin', custom: { 'book' => 'dune' }),
      user_id2 => Models::UserRequest.new(id: user_id2, role: 'user', custom: { 'book' => '1984' }),
      user_id3 => Models::UserRequest.new(id: user_id3, role: 'admin', custom: { 'book' => 'Fahrenheit 451' }),
    }
  )
)
```

```go label="Go"
client.UpdateUsers(ctx, &getstream.UpdateUsersRequest{
	Users: map[string]getstream.UserRequest{
		userID1: {ID: userID1, Role: getstream.PtrTo("admin"), Custom: map[string]any{"book": "dune"}},
		userID2: {ID: userID2, Role: getstream.PtrTo("user"), Custom: map[string]any{"book": "1984"}},
		userID3: {ID: userID3, Role: getstream.PtrTo("admin"), Custom: map[string]any{"book": "Fahrenheit 451"}},
	},
})
```

```csharp label="C#"
// dotnet add package getstream-net
using GetStream;
using GetStream.Models;

var client = new StreamClient("{{ api_key }}", "{{ api_secret }}");

await client.UpdateUsersAsync(new UpdateUsersRequest
{
    Users = new Dictionary<string, UserRequest>
    {
        [userId1] = new UserRequest { ID = userId1, Role = "admin" },
        [userId2] = new UserRequest { ID = userId2, Role = "user" },
        [userId3] = new UserRequest { ID = userId3, Role = "admin" }
    }
});
```

```java label="Java"
client.updateUsers(UpdateUsersRequest.builder()
    .users(Map.of(
        userId1, UserRequest.builder().id(userId1).role("admin").build(),
        userId2, UserRequest.builder().id(userId2).role("admin").build(),
        userId3, UserRequest.builder().id(userId3).role("admin").build()))
    .build()).execute();
```

>
> **Info:** If any user in a batch of users contains an error, the entire batch will fail, and the first error encountered will be returned.
>

## Partial updates

If you need to update a subset of properties for a user(s), you can use a partial update method. Both set and unset parameters can be provided to add, modify, or remove attributes to or from the target user(s). The set and unset parameters can be used separately or combined.

```js label="Node.js"
// partial update for a single user setting and unsetting multiple fields
const update = {
  id: "userID",
  set: {
    role: "admin",
    field: {
      text: "value",
    },
    "field2.subfield": "test",
  },
  unset: ["field.unset"],
};

const response = await client.partialUpdateUser(update);

// partial update for multiple users
const updates = [
  {
    id: "userID",
    set: {
      field: "value",
    },
  },
  {
    id: "userID2",
    unset: ["field.value"],
  },
];

const response = await client.partialUpdateUsers(updates);
```

```python label="Python"
from getstream.models import UpdateUserPartialRequest

# partial update for a single user
client.update_users_partial(users=[
    UpdateUserPartialRequest(
        id="userID",
        set={
            "role": "admin",
            "field": {"text": "value"},
            "field2.subfield": "test",
        },
        unset=["field.unset"],
    )
])

# partial update for multiple users
client.update_users_partial(users=[
    UpdateUserPartialRequest(id="userID", set={"field": "value"}),
    UpdateUserPartialRequest(id="userID2", unset=["field.value"]),
])
```

```ruby label="Ruby"
Models = GetStream::Generated::Models

# partial update for a single user
client.common.update_users_partial(
  Models::UpdateUsersPartialRequest.new(
    users: [Models::UpdateUserPartialRequest.new(
      id: 'userID',
      set: {
        'role' => 'admin',
        'field' => { 'text' => 'value' },
        'field2.subfield' => 'test',
      },
      unset: ['field.unset']
    )]
  )
)

# partial update for multiple users
client.common.update_users_partial(
  Models::UpdateUsersPartialRequest.new(
    users: [
      Models::UpdateUserPartialRequest.new(id: 'userID', set: { 'field' => 'value' }),
      Models::UpdateUserPartialRequest.new(id: 'userID2', unset: ['field.value']),
    ]
  )
)
```

```php label="PHP"
use GetStream\GeneratedModels as Models;

$client->updateUsersPartial(new Models\UpdateUsersPartialRequest(
    users: [new Models\UpdateUserPartialRequest(
        id: $userId,
        set: (object)["role" => "admin", "field2.subfield" => "test"],
        unset: ["field.unset"],
    )]
));
```

```go label="Go"
resp, err := client.UpdateUsersPartial(ctx, &getstream.UpdateUsersPartialRequest{
	Users: []getstream.UpdateUserPartialRequest{
		{
			ID: user.ID,
			Set: map[string]any{
				"role": "admin",
				"field": map[string]any{
					"text": "value",
				},
			},
		},
	},
})
```

```csharp label="C#"
// dotnet add package getstream-net
using GetStream;
using GetStream.Models;

var client = new StreamClient("{{ api_key }}", "{{ api_secret }}");

await client.UpdateUsersPartialAsync(new UpdateUsersPartialRequest
{
    Users = new List<UpdateUserPartialRequest>
    {
        new UpdateUserPartialRequest
        {
            ID = userId,
            Set = new Dictionary<string, object>
            {
                ["role"] = "admin",
                ["field2.subfield"] = "test"
            },
            Unset = new List<string> { "field.unset" }
        }
    }
});
```

```java label="Java"
client.updateUsersPartial(UpdateUsersPartialRequest.builder()
    .users(List.of(UpdateUserPartialRequest.builder()
        .id(user.getId())
        .set(Map.of("role", "admin", "field2.subfield", "test"))
        .build()))
    .build()).execute();
```

>
> **Info:** Partial updates support batch requests, similar to the upsertUser endpoint.
>

## Unique usernames

Clients can set a username, by setting the `name` custom field. The field is optional and by default has no uniqueness constraints applied to it, however this is configurable by setting the `enforce_unique_username` to either _app_ or _team_.

When checking for uniqueness, the name is _normalized_, by removing any white-space or other special characters, and finally transforming it to lowercase. So "John Doe" is considered a duplicate of "john doe", "john.doe", etc.

With the setting at **app**, creating or updating a user fails if the username already exists anywhere in the app. With **team**, it only fails if the username exists within the same team.

```js label="Node.js"
// Enable uniqueness constraints on App level
await client.updateAppSettings({
  enforce_unique_usernames: "app",
});

// Enable uniqueness constraints on Team level
await client.updateAppSettings({
  enforce_unique_usernames: "team",
});
```

```python label="Python"
# Enable uniqueness constraints on App level
client.update_app(enforce_unique_usernames="app")

# Enable uniqueness constraints on Team level
client.update_app(enforce_unique_usernames="team")
```

```ruby label="Ruby"
Models = GetStream::Generated::Models

# Enable uniqueness constraints on App level
client.common.update_app(
  Models::UpdateAppRequest.new(enforce_unique_usernames: 'app')
)

# Enable uniqueness constraints on Team level
client.common.update_app(
  Models::UpdateAppRequest.new(enforce_unique_usernames: 'team')
)
```

```php label="PHP"
use GetStream\GeneratedModels as Models;

// Enable uniqueness constraints on App level
$client->updateApp(new Models\UpdateAppRequest(
    enforceUniqueUsernames: "app",
));

// Enable uniqueness constraints on Team level
$client->updateApp(new Models\UpdateAppRequest(
    enforceUniqueUsernames: "team",
));
```

```go label="Go"
// Enable uniqueness constraints on App level
resp, err := client.UpdateApp(ctx, &getstream.UpdateAppRequest{
	EnforceUniqueUsernames: getstream.PtrTo("app"),
})

// Enable uniqueness constraints on Team level
resp, err = client.UpdateApp(ctx, &getstream.UpdateAppRequest{
	EnforceUniqueUsernames: getstream.PtrTo("team"),
})
```

```csharp label="C#"
// dotnet add package getstream-net
using GetStream;
using GetStream.Models;

var client = new StreamClient("{{ api_key }}", "{{ api_secret }}");

// Enable uniqueness constraints on App level
await client.UpdateAppAsync(new UpdateAppRequest
{
    EnforceUniqueUsernames = "app"
});

// Enable uniqueness constraints on Team level
await client.UpdateAppAsync(new UpdateAppRequest
{
    EnforceUniqueUsernames = "team"
});
```

```java label="Java"
// Enable uniqueness constraints on App level
client.updateApp(UpdateAppRequest.builder()
    .enforceUniqueUsernames("app")
    .build()).execute();

// Enable uniqueness constraints on Team level
client.updateApp(UpdateAppRequest.builder()
    .enforceUniqueUsernames("team")
    .build()).execute();
```

>
> **Info:** Enabling this setting will only enforce the constraint going forward and will not try to validate existing usernames.
>

## 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 |
| `bypass_moderation` | `boolean` | - | - |
| `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 |

## Guest and anonymous users

Stream lets you give unauthenticated users access to a limited subset of features. Guest and anonymous users are ideal when users need to see content before authenticating, such as watching a livestream or browsing public feeds, or where the friction of creating an account is unnecessary.

>
> **Info:** Configuration for the `guest` and `anonymous` roles can be managed in the [dashboard](https://getstream.io/signin/).
>

### Guest users

Guest users are temporary user accounts with a name and image. Guest sessions can be created client-side and do not require server-side authentication. Guest users are not available to applications using multi-tenancy (teams).

>
> **Info:** Guest users are counted towards your MAU usage. Creation of guest users can be disabled for your application in the [dashboard](https://getstream.io/signin/).
>

To create a guest user from your server and obtain a token for the client:

```js label="Node.js"
const response = await client.createGuest({
  user: { id: "tommaso", name: "Tommaso" },
});
// Return response.user and response.access_token to your client
```

```python label="Python"
response = self.client.create_guest(
    user=UserRequest(id="tommaso", name="Tommaso")
)
# Return response.user and response.access_token to your client
```

```ruby label="Ruby"
response = client.common.create_guest(
  GetStream::Generated::Models::CreateGuestRequest.new(
    user: { 'id' => 'tommaso', 'name' => 'Tommaso' }
  )
)
# Return response.user and response.access_token to your client
```

```php label="PHP"
$response = $client->createGuest(new GeneratedModels\CreateGuestRequest(
    user: [
        'id' => 'tommaso',
        'name' => 'Tommaso',
    ]
));
// Return $response->user and $response->access_token to your client
```

```go label="Go"
response, err := client.CreateGuest(context.Background(), &getstream.CreateGuestRequest{
    User: getstream.UserRequest{
        ID:   "tommaso",
        Name: getstream.PtrTo("Tommaso"),
    },
})
if err != nil {
    log.Fatal("Error creating guest:", err)
}
// Return response.Data.User and response.Data.AccessToken to your client
```

```csharp label="C#"
var request = new CreateGuestRequest
{
    User = new UserRequest { ID = "tommaso", Name = "Tommaso" }
};
var response = await _client.CreateGuestAsync(request);
// Return response.User and response.AccessToken to your client
```

```java label="Java"
CreateGuestRequest request = CreateGuestRequest.builder()
    .user(UserRequest.builder().id("tommaso").name("Tommaso").build())
    .build();
CreateGuestResponse response = client.createGuest(request).execute().getData();
// Return response.getUser() and response.getAccessToken() to your client
```

>
> **Info:** The user object schema is the same as the one described in the [Built-in fields for users](#built-in-fields-for-users) section above.
>

Feeds client SDKs can also create guest sessions directly, without a server call:

```js label="React"
import { useCreateFeedsClient } from "@stream-io/feeds-react-sdk";

const client = useCreateFeedsClient({
  apiKey,
  userData: { id: "tommaso" },
  tokenOrProvider: "guest",
});
```

```js label="React Native"
import { useCreateFeedsClient } from "@stream-io/feeds-react-native-sdk";

const client = useCreateFeedsClient({
  apiKey,
  userData: { id: "tommaso" },
  tokenOrProvider: "guest",
});
```

```js label="JavaScript"
import { FeedsClient } from "@stream-io/feeds-client";

const client = new FeedsClient("<API key>");
await client.connectGuest({ id: "tommaso" });
```

### Anonymous users

Anonymous users are not authenticated and have limited capabilities by default: they can read content where permissions allow, such as a public livestream or feed, but they are not allowed to perform any write operations. Anonymous users can be connected using client-side SDKs.

>
> **Info:** Anonymous users are not counted toward your MAU and can't establish a WebSocket connection.
>

Feeds client SDKs connect anonymous users like this:

```js label="React"
import { useCreateFeedsClient } from "@stream-io/feeds-react-sdk";

const client = useCreateFeedsClient({
  apiKey,
  tokenOrProvider: "anonymous",
});
```

```js label="React Native"
import { useCreateFeedsClient } from "@stream-io/feeds-react-native-sdk";

const client = useCreateFeedsClient({
  apiKey,
  tokenOrProvider: "anonymous",
});
```

```js label="JavaScript"
import { FeedsClient } from "@stream-io/feeds-client";

const client = new FeedsClient("<API key>");
const connectResponse = await client.connectAnonymous();
console.log(connectResponse.me);
```

## 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.

```js label="Node.js"
const response = await client.queryUsers({
  payload: {
    filter_conditions: {
      role: "admin",
    },
    sort: [{ field: "created_at", direction: -1 }],
    limit: 10,
    offset: 0,
  },
});

console.log(response.users);
```

```python label="Python"
from getstream.models import QueryUsersPayload, SortParamRequest

response = client.query_users(
    payload=QueryUsersPayload(
        filter_conditions={
            "role": "admin",
        },
        sort=[
            SortParamRequest(field="created_at", direction=-1)
        ],
        limit=10,
        offset=0,
    )
)
```

```ruby label="Ruby"
payload = GetStream::Generated::Models::QueryUsersPayload.new(
  filter_conditions: {
    'role' => 'admin',
  },
  sort: [
    {
      'field' => 'created_at',
      'direction' => -1,
    },
  ],
  limit: 10,
  offset: 0,
)

response = client.common.query_users(payload)
```

```php label="PHP"
$response = $client->queryUsers(
    new GeneratedModels\QueryUsersPayload(
        filterConditions: (object)['role' => 'admin'],
        sort: [
            ['field' => 'created_at', 'direction' => -1]
        ],
        limit: 10,
        offset: 0
    )
);
```

```go label="Go"
response, err := client.QueryUsers(context.Background(), &getstream.QueryUsersRequest{
    Payload: &getstream.QueryUsersPayload{
        FilterConditions: map[string]any{
            "role": "admin",
        },
        Sort: []getstream.SortParamRequest{
            {
                Field:     getstream.PtrTo("created_at"),
                Direction: getstream.PtrTo(-1),
            },
        },
        Limit:  getstream.PtrTo(10),
        Offset: getstream.PtrTo(0),
    },
})
```

```csharp label="C#"
var response = await client.QueryUsersAsync(
    new QueryUsersRequest
    {
        Payload = new QueryUsersPayload
        {
            FilterConditions = new Dictionary<string, object>
            {
                ["role"] = "admin"
            },
            Sort = new[]
            {
                new SortParamRequest
                {
                    Field = "created_at",
                    Direction = -1
                }
            },
            Limit = 10,
            Offset = 0
        }
    }
);
```

```java label="Java"
Map<String, Object> filterConditions = new HashMap<>();
filterConditions.put("role", "admin");

QueryUsersRequest request = QueryUsersRequest.builder()
    .payload(QueryUsersPayload.builder()
        .filterConditions(filterConditions)
        .sort(List.of(
            SortParamRequest.builder()
                .field("created_at")
                .direction(-1)
                .build()
        ))
        .limit(10)
        .offset(0)
        .build())
    .build();

QueryUsersResponse response = client.queryUsers(request).execute().getData();
```

>
> **Warning:** 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.
>

### Filter examples

You can use various filter operators to query users:

```js label="Node.js"
// 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",
    },
  },
});
```

```python label="Python"
from getstream.models import QueryUsersPayload

# Query users by custom field
response = client.query_users(
    payload=QueryUsersPayload(
        filter_conditions={
            "custom.color": "red",
        }
    )
)

# Query users with multiple conditions
response2 = client.query_users(
    payload=QueryUsersPayload(
        filter_conditions={
            "role": {"$in": ["admin", "moderator"]},
            "custom.plan": "premium",
        }
    )
)
```

```ruby label="Ruby"
# Query users by custom field
payload = GetStream::Generated::Models::QueryUsersPayload.new(
  filter_conditions: {
    'custom.color' => 'red',
  },
)
response = client.common.query_users(payload)

# Query users with multiple conditions
payload2 = GetStream::Generated::Models::QueryUsersPayload.new(
  filter_conditions: {
    'role' => { '$in' => ['admin', 'moderator'] },
    'custom.plan' => 'premium',
  },
)
response2 = client.common.query_users(payload2)
```

### 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 on 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.

>
> **Info:** 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                                                                                            | -       | ✓        |

>
> **Info:** The maximum offset value is 1000.
>

### Querying with Autocomplete

You can use the `$autocomplete` operator to search for users by name or ID with partial matching.

```js label="Node.js"
const response = await client.queryUsers({
  payload: {
    filter_conditions: {
      name: { $autocomplete: "ro" },
    },
  },
});
```

```python label="Python"
from getstream.models import QueryUsersPayload

client.query_users(
    QueryUsersPayload(filter_conditions={"name": {"$autocomplete": "ro"}})
)
```

```ruby label="Ruby"
Models = GetStream::Generated::Models

client.common.query_users(
  Models::QueryUsersPayload.new(
    filter_conditions: { 'name' => { '$autocomplete' => 'ro' } }
  )
)
```

```php label="PHP"
use GetStream\GeneratedModels as Models;

$response = $client->queryUsers(new Models\QueryUsersPayload(
    filterConditions: (object)["name" => (object)['$autocomplete' => "ro"]],
));
```

### Querying Inactive Users

You can use the `last_active` field with the `$exists` operator to find users who have never connected. Use `$exists: false` for users who have never been active, or `$exists: true` for users who have connected at least once.

```js label="Node.js"
const response = await client.queryUsers({
  payload: {
    filter_conditions: {
      id: { $in: [activeUser, neverActiveUser] },
      last_active: { $exists: false },
    },
  },
});
```

```python label="Python"
from getstream.models import QueryUsersPayload

users = client.query_users(
    QueryUsersPayload(filter_conditions={"last_active": {"$exists": False}})
)
```

## 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

Both bulk deletion and bulk deactivation are performed asynchronously by the Stream API. A task ID is returned and you can use that to check the status of its processing.

### Deactivating users

```js label="Node.js"
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>'...],
});
```

```python label="Python"
# Deactivating a single user
client.deactivate_user(user_id=user_id)

# Deactivating users in bulk is performed asynchronously
response = client.deactivate_users(
    user_ids=[user_id, user_id_2]
)

# Reactivate users
client.reactivate_users(
    user_ids=[user_id, user_id_2]
)
```

```php label="PHP"
// Deactivating a single user
$client->deactivateUser(
    'user1',
    new GeneratedModels\DeactivateUserRequest()
);

// Deactivating users in bulk is performed asynchronously
$response = $client->deactivateUsers(
    new GeneratedModels\DeactivateUsersRequest(
        userIds: ['user1', 'user2']
    )
);

// Reactivate users
$client->reactivateUsers(
    new GeneratedModels\ReactivateUsersRequest(
        userIds: ['user1', 'user2']
    )
);
```

```go label="Go"
// Deactivating a single user
_, err = client.DeactivateUser(context.Background(), "user1", &getstream.DeactivateUserRequest{})

// Deactivating users in bulk is performed asynchronously
_, err = client.DeactivateUsers(context.Background(), &getstream.DeactivateUsersRequest{
    UserIds: []string{"user2"},
})

// Reactivate users
_, err = client.ReactivateUsers(context.Background(), &getstream.ReactivateUsersRequest{
    UserIds: []string{"user1", "user2"},
})
```

```csharp label="C#"
// Deactivating a single user
await client.DeactivateUserAsync(userId, new DeactivateUserRequest());

// Deactivating users in bulk is performed asynchronously
var deactivateRequest = new DeactivateUsersRequest
{
    UserIds = new[] { userId, userId2 }
};
await client.DeactivateUsersAsync(deactivateRequest);

// Reactivate users
var reactivateRequest = new ReactivateUsersRequest
{
    UserIds = new[] { userId, userId2 }
};
await client.ReactivateUsersAsync(reactivateRequest);
```

```java label="Java"
// Deactivating a single user
client.deactivateUser(userId, DeactivateUserRequest.builder().build()).execute();

// Deactivating users in bulk is performed asynchronously
DeactivateUsersRequest deactivateRequest = DeactivateUsersRequest.builder()
    .userIds(List.of(userId, userId2))
    .build();
client.deactivateUsers(deactivateRequest).execute();

// Reactivate users
ReactivateUsersRequest reactivateRequest = ReactivateUsersRequest.builder()
    .userIds(List.of(userId, userId2))
    .build();
client.reactivateUsers(reactivateRequest).execute();
```

Deactivating users in bulk can take some time. Monitor the returned task ID as described in [Async operations](https://getstream.io/docs/platform/async-operations/).

In Chat, deactivation and reactivation accept additional parameters: `mark_messages_deleted` on deactivate soft-deletes all messages the user sent, `restore_messages` on reactivate restores them, and `created_by_id` records which user performed the operation.

```js label="Node.js"
const deactivate = await client.deactivateUser(userID, {
  mark_messages_deleted: true,
  created_by_id: "joe",
});

const reactivate = await client.reactivateUser(userID, {
  restore_messages: true,
  name: "I am back",
  created_by_id: "joe",
});
```

```python label="Python"
response = client.deactivate_user(user_id,
                 mark_messages_deleted=True,
                 created_by_id="joe")

response = client.reactivate_user(user_id,
                 restore_messages=True,
                 name="I am back",
                 created_by_id="joe")
```

### Deleting users

```js label="Node.js"
client.deleteUsers({ user_ids: ["<id>"] });

//restore
client.restoreUsers({ user_ids: ["<id>"] });
```

```python label="Python"
# Delete users
client.delete_users(user_ids=["<id>"])

# Restore users
client.restore_users(user_ids=["<id>"])
```

```ruby label="Ruby"
# Delete users
client.delete_users(
  GetStream::Generated::Models::DeleteUsersRequest.new(
    user_ids: ['<id>']
  )
)

# Restore users
client.restore_users(
  GetStream::Generated::Models::RestoreUsersRequest.new(
    user_ids: ['<id>']
  )
)
```

```php label="PHP"
// Delete users (soft delete by default)
$client->deleteUsers(
    new GeneratedModels\DeleteUsersRequest(
        userIds: ['<id>']
    )
);

// Restore users
$client->restoreUsers(
    new GeneratedModels\RestoreUsersRequest(
        userIds: ['<id>']
    )
);
```

```go label="Go"
// Delete users
deleteRequest := &getstream.DeleteUsersRequest{
    UserIds: []string{"<id>"},
}
_, err = client.DeleteUsers(context.Background(), deleteRequest)

// Restore users
restoreRequest := &getstream.RestoreUsersRequest{
    UserIds: []string{"<id>"},
}
_, err = client.RestoreUsers(context.Background(), restoreRequest)
```

```csharp label="C#"
// Delete users
await client.DeleteUsersAsync(new DeleteUsersRequest
{
    UserIds = new List<string> { "<id>" }
});

// Restore users
await client.RestoreUsersAsync(new RestoreUsersRequest
{
    UserIds = new List<string> { "<id>" }
});
```

```java label="Java"
// Delete users
client.deleteUsers(
    DeleteUsersRequest.builder()
        .userIds(Arrays.asList("<id>"))
        .build()
).execute();

// Restore users
client.restoreUsers(
    RestoreUsersRequest.builder()
        .userIds(Arrays.asList("<id>"))
        .build()
).execute();
```

You can delete up to 100 users per call. The `user` parameter controls what happens to the user object itself:

| Name | Type                       | Description                                                                                                                                          | Default | Optional |
| ---- | -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| user | enum (soft, pruning, hard) | Soft: marks user as deleted and retains all user data. Pruning: marks user as deleted and nullifies user information. Hard: deletes user completely. | soft    | ✓        |

Each product adds parameters that control what happens to the user's product data.

#### Chat parameters

| Name                 | Type                       | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | Default | Optional |
| -------------------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| conversations        | enum (soft, hard)          | - Soft: marks all conversation channels as deleted (same effect as Delete Channels with 'hard' option disabled). <br /> - Hard: deletes channel and all its data completely including messages (same effect as Delete Channels with 'hard' option enabled). <br /><br /> A conversation channel is any channel with two or fewer members where one member is the deleted user. <br /><br /> When `user` is `hard`, omitting this field automatically promotes it to `hard`. Setting `conversations` to `soft` with `user` set to `hard` returns an error. | -       | ✓        |
| messages             | enum (soft, pruning, hard) | - Soft: marks all user messages as deleted without removing any related message data. <br /> - Pruning: marks all user messages as deleted, nullifies message information and removes some message data such as reactions and flags. <br /> - Hard: deletes messages completely with all related information. <br /><br /> When `user` is `hard`, omitting this field automatically promotes it to `hard`. Setting `messages` to `soft` or `pruning` with `user` set to `hard` returns an error.                                                          | -       | ✓        |
| new_channel_owner_id | string                     | Channels owned by hard-deleted users will be transferred to this userID.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | -       | ✓        |

>
> **Caution:** When `user` is set to `hard`, the server requires that both `messages` and `conversations` are also `hard`. If either field is omitted, it is automatically promoted to `hard`. If either is explicitly set to a non-hard value (`soft` or `pruning`), the request returns a validation error.
>

>
> **Info:** When deleting a user, if you wish to transfer ownership of their channels to another user, provide that user's ID in the `new_channel_owner_id` field. Otherwise, the channel owner will be updated to a system generated ID like `delete-user-8219f6578a7395g`.
>

#### Video parameters

| Name  | Type              | Description                                                                                                                                      | Default | Optional |
| ----- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | -------- |
| calls | enum (soft, hard) | Soft: marks calls and related data as deleted. Hard: deletes calls and related data completely. This applies only to 1:1 calls, not group calls. | -       | ✓        |

Exporting or deleting user data to meet compliance requests is covered on [GDPR and privacy](https://getstream.io/docs/platform/gdpr/).

### Restoring deleted users

If users are _soft_ deleted, they can be restored using the server-side client. However, only the user's metadata is restored; product data such as memberships, messages and reactions is not restored.

You can restore up to 100 users per call:

```js label="Node.js"
await client.restoreUsers(["userID1", "userID2"]);
```

```python label="Python"
client.restore_users(user_ids=["userID1", "userID2"])
```

---

For the most recent version of this documentation, visit [https://getstream.io/docs/platform/users/](https://getstream.io/docs/platform/users/).