# Managing Users

The Stream user object is central to the chat system 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 for Chat such as a username and image URL.

## Client-side User Creation

The `connectUser` method automatically creates _and_ updates the user. If you are looking to onboard your userbase lazily, this is typically a perfectly viable option.

However, it is also common to add your users to Stream before going Live and keep properties of your user base in sync with Stream. For this you'll want to use the `upsertUsers` function server-side and send users in bulk.

## Creating and Updating Users Server-Side

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.

Depending on the permission configuration of your application, you may also allow users to update their own user objects client-side.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

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

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import UserRequest

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

</codetabs-item>

<codetabs-item value="go" label="Go">

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

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// 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" }
        }
    }
});
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
await chatClient.upsertUser(user);
```

</codetabs-item>

<codetabs-item value="nodejs" label="Node">

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

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```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' }) }
  )
)
```

</codetabs-item>

<codetabs-item value="java" label="Java">

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

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
Client->UpsertUsers({User});
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// Only Id field is required, the rest is optional
var createOrUpdateUser = new StreamUserUpsertRequest
{
  Id = "my-user-id",
  // BanExpires = DateTimeOffset.Now.AddDays(7),
  // Banned = true,
  // Invisible = true,
  // Role = "user",
  // Name = "David",
  // Image = "image-url", // You can upload image to Stream CDN or your own
  // CustomData = new StreamCustomDataRequest
  //{
  //  { "Age", 24 },
  //  { "Passions", new string[] { "Tennis", "Football", "Basketball" } }
  //}
};

// Upsert means: update user with a given ID or create a new one if it doesn't exist
var users = await Client.UpsertUsers(new[] { createOrUpdateUser });
```

</codetabs-item>

</codetabs>

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

<codetabs>

<codetabs-item value="nodejs" 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
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```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"}),
)
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```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"}},
	},
})
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// 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" }
    }
});
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```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' }),
    }
  )
)
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```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();
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
var usersToCreateOrUpdate = new[]
{
  new StreamUserUpsertRequest
  {
    Id = "my-user-id",
    Role = "user",
  },
  new StreamUserUpsertRequest
  {
    Id = "my-user-id-2",
    // BanExpires = DateTimeOffset.Now.AddDays(7),
    // Banned = true,
    // Invisible = true,
    // Role = "user",
    // Name = "David",
    // Image = "image-url", // You can upload image to Stream CDN or your own
    // CustomData = new StreamCustomDataRequest
    //{
    //  { "Age", 24 },
    //  { "Passions", new string[] { "Tennis", "Football", "Basketball" } }
    //}
  },
};

// Upsert means: update user with a given ID or create a new one if it doesn't exist
var users = await Client.UpsertUsers(usersToCreateOrUpdate);
```

</codetabs-item>

</codetabs>

<admonition type="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.

</admonition>

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

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```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);
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```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"]),
])
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```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']),
    ]
  )
)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```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"],
    )]
));
```

</codetabs-item>

<codetabs-item value="java" label="Java">

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

</codetabs-item>

<codetabs-item value="go" label="Go">

```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",
				},
			},
		},
	},
})
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
// Partial update is not supported in Swift SDK
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// 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" }
        }
    }
});
```

</codetabs-item>

</codetabs>

<admonition type="info">

Partial updates support batch requests, similar to the upsertUser endpoint.

</admonition>

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

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```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",
});
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```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')
)
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```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")
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```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();
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// 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"
});
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```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"),
})
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```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",
));
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// This can be set in https://dashboard.getstream.io/ -> Open your application -> Overview -> Authentication
```

</codetabs-item>

</codetabs>

<admonition type="info">

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

</admonition>

## Deactivate a User

To deactivate a user, Stream Chat exposes a server-side `deactivateUser` method. A deactivated user cannot connect to Stream Chat but will be present in user queries and channel history.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
const deactivate = await client.deactivateUser(userID);

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

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
response = client.deactivate_user(user_id)

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

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
Models = GetStream::Generated::Models

response = client.common.deactivate_user(user_id, Models::DeactivateUserRequest.new)

response = client.common.deactivate_user(user_id,
  Models::DeactivateUserRequest.new(
    mark_messages_deleted: true,
    created_by_id: 'joe'
  )
)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\GeneratedModels as Models;

$response = $client->deactivateUser($userId, new Models\DeactivateUserRequest());

$response = $client->deactivateUser($userId, new Models\DeactivateUserRequest(
    markMessagesDeleted: true,
    createdByID: "joe",
));
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
_, err := client.DeactivateUser(ctx, userID, &getstream.DeactivateUserRequest{})

_, err = client.DeactivateUser(ctx, userID, &getstream.DeactivateUserRequest{
	MarkMessagesDeleted: getstream.PtrTo(true),
	CreatedByID: getstream.PtrTo("joe"),
})
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// dotnet add package getstream-net
using GetStream;
using GetStream.Models;

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

await client.DeactivateUserAsync(userId, new DeactivateUserRequest());

// With options:
await client.DeactivateUserAsync(userId, new DeactivateUserRequest
{
    MarkMessagesDeleted = true,
    CreatedByID = "joe"
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
client.deactivateUser(userId, DeactivateUserRequest.builder()
    .createdByID(userId2).build()).execute();
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// This is a server-side only feature, choose any of our server-side SDKs to use it
```

</codetabs-item>

</codetabs>

## Deactivate Many Users

Many users (up to 100) can be deactivated and reactivated with a single call. The operation runs asynchronously, and the response contains a task_id which can be polled using the [getTask endpoint](/chat/docs/<framework>#tasks-gettask) to check the status of the operation.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
let resp = await serverClient.deactivateUsers([userIDs, ...], {
   created_by_id: userID, // optional: define who deactivated the users
   mark_messages_deleted: true // optional: the messages will be marked as deleted
})

```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// This is a server-side only feature, choose any of our server-side SDKs to use it
```

</codetabs-item>

</codetabs>

| Name                  | Type    | Description                                       | Default | Optional |
| --------------------- | ------- | ------------------------------------------------- | ------- | -------- |
| mark_messages_deleted | boolean | Soft deletes all of the messages sent by the user | false   | ✓        |

## Reactivate a User

To reinstate the user as active, use the `reactivateUser` method by passing the users ID as a parameter:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
const reactivate = await client.reactivateUser(userID);

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

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
response = client.reactivate_user(user_id)

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

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
Models = GetStream::Generated::Models

response = client.common.reactivate_user(user_id, Models::ReactivateUserRequest.new)

response = client.common.reactivate_user(user_id,
  Models::ReactivateUserRequest.new(
    restore_messages: true,
    name: 'I am back',
    created_by_id: 'joe'
  )
)
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\GeneratedModels as Models;

$response = $client->reactivateUser($userId, new Models\ReactivateUserRequest());

$response = $client->reactivateUser($userId, new Models\ReactivateUserRequest(
    restoreMessages: true,
    name: "I am back",
    createdByID: "joe",
));
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
_, err := client.ReactivateUser(ctx, userID, &getstream.ReactivateUserRequest{})

_, err = client.ReactivateUser(ctx, userID, &getstream.ReactivateUserRequest{
	RestoreMessages: getstream.PtrTo(true),
	Name: getstream.PtrTo("I am back"),
	CreatedByID: getstream.PtrTo("joe"),
})
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// dotnet add package getstream-net
using GetStream;
using GetStream.Models;

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

await client.ReactivateUserAsync(userId, new ReactivateUserRequest());

// With options:
await client.ReactivateUserAsync(userId, new ReactivateUserRequest
{
    RestoreMessages = true,
    Name = "I am back",
    CreatedByID = "joe"
});
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
client.reactivateUser(userId, ReactivateUserRequest.builder()
    .createdByID(userId2).build()).execute();
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// This is a server-side only feature, choose any of our server-side SDKs to use it
```

</codetabs-item>

</codetabs>

## Deleting Many Users

You can delete up to 100 users and optionally all of their channels and messages using this method. First the users are marked deleted synchronously so the user will not be directly visible in the API. Then the process deletes the user and related objects asynchronously.

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

```js
let response = await serverClient.deleteUsers(['userID1', 'userID2'], {
  user: 'soft',
  messages: 'hard',
});

response = await serverClient.getTask(response['task_id']);

if(response['status] === 'completed') {
  // success!
}
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
response = client.delete_users(
    user_ids=["userID1", "userID2"],
    user="soft",
    messages="hard",
)

response = client.get_task(response.data.task_id)

if response.data.status == "completed":
    # success!
    pass
```

</codetabs-item>

<codetabs-item value="go" label="Go">

```go
resp, err := client.DeleteUsers(ctx, &getstream.DeleteUsersRequest{
	UserIds: []string{"userID1", "userID2"},
	User:     getstream.PtrTo("soft"),
	Messages: getstream.PtrTo("hard"),
})
if err != nil {
	return err
}

taskResp, err := client.GetTask(ctx, resp.Data.TaskID, &getstream.GetTaskRequest{})
if err != nil {
	return err
}

if taskResp.Data.Status == "completed" {
	// success!
}
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\GeneratedModels as Models;

$response = $client->deleteUsers(new Models\DeleteUsersRequest(
    userIds: ["userID1", "userID2"],
    user: "soft",
    messages: "hard",
));

$taskResponse = $client->getTask($response->getData()->taskId);

if ($taskResponse->getData()->status === "completed") {
    // success!
}
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
Models = GetStream::Generated::Models

response = client.common.delete_users(
  Models::DeleteUsersRequest.new(
    user_ids: ['userID1', 'userID2'],
    user: 'soft',
    messages: 'hard'
  )
)

task_response = client.common.get_task(response['task_id'])

if task_response['status'] == 'completed'
  # success!
end
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
var resp = client.deleteUsers(DeleteUsersRequest.builder()
    .userIds(List.of(userID1, userID2))
    .user("soft")
    .messages("hard")
    .build()).execute();
String taskId = resp.getData().getTaskId();

var taskStatusResponse = client.getTask(taskId).execute();
// "completed".equals(taskStatusResponse.getData().getStatus());
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// dotnet add package getstream-net
using GetStream;
using GetStream.Models;

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

var resp = await client.DeleteUsersAsync(new DeleteUsersRequest
{
    UserIds = new List<string> { "userID1", "userID2" },
    User = "soft",
    Messages = "hard"
});

var taskResp = await client.GetTaskAsync(resp.Data.TaskID);

var finished = taskResp.Data.Status == "completed";
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// This is a server-side only feature, choose any of our server-side SDKs to use it
```

</codetabs-item>

</codetabs>

The `deleteUsers` method is an asynchronous API where the response contains a task_id which can be polled using the [getTask endpoint](/chat/docs/<framework>#tasks-gettask) to check the status of the deletions.

These are the request parameters which determine what user data is deleted:

| name                 | type                       | description                                                                                                                                                                                                                                                                               | default | optional |
| -------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | -------- |
| user_ids             | array                      | List of users who will be deleted                                                                                                                                                                                                                                                         | -       |          |
| 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 - this requires hard option for **messages** and **conversation** as well.                                                            | -       | ✓        |
| conversations        | enum (soft, hard)          | Soft: marks all conversation channels as deleted (same effect as Delete Channels with 'hard' option disabled). Hard: deletes channel and all its data completely including messages (same effect as Delete Channels with 'hard' option enabled).                                          |         | ✓        |
| messages             | enum (soft, pruning, hard) | Soft: marks all user messages as deleted without removing any related message data. Pruning: marks all user messages as deleted, nullifies message information and removes some message data such as reactions and flags. Hard: deletes messages completely with all related information. | -       | ✓        |
| new_channel_owner_id | string                     | Channels owned by hard-deleted users will be transferred to this userID.                                                                                                                                                                                                                  | -       | ✓        |

<admonition type="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`

</admonition>

## 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; memberships, messages, reactions, etc. are not restored.

You can restore up to 100 users per call:

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

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

</codetabs-item>

<codetabs-item value="python" label="Python">

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

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
// This is a server-side only feature, choose any of our server-side SDKs to use it
```

</codetabs-item>

</codetabs>

## Querying Users

The Query Users method lets you search for users, though in many cases it's more practical to query your own user database instead. Like other Stream query APIs, it accepts filter, sort, and options parameters.

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val request = QueryUsersRequest(
  filter = Filters.`in`("id", listOf("john", "jack", "jessie")),
  querySort = QuerySortByField.descByName("last_active"),
  offset = 0,
  limit = 10,
)

client.queryUsers(request).enqueue { result ->
  if (result is Result.Success) {
    val users: List<User> = result.value
  } else {
    // Handle Result.Failure
  }
}
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

```js
const response = await client.queryUsers(
  { id: { $in: ["john", "jack", "jessie"] } },
  { last_active: -1 },
  { limit: 10, offset: 0 },
);
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
FilterObject filter = Filters.in("id", Arrays.asList("john", "jack", "jessie"));
QuerySorter<User> sort = QuerySortByField.descByName("last_active");
int offset = 0;
int limit = 10;
QueryUsersRequest request = new QueryUsersRequest(filter, offset, limit, sort);

client.queryUsers(request).enqueue(result -> {
  if (result.isSuccess()) {
    List<User> users = result.data();
  } else {
    // Handle result.error()
  }
});
```

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let controller = chatClient.userListController(
  query: .init(
    filter: .in(.id, values: ["john", "jack", "jessie"]),
    sort: [.init(key: .lastActivityAt, isAscending: false)],
    pageSize: 10
  )
)

controller.synchronize { error in
  if let error = error {
    // handle error
    print(error)
  } else {
    // access users
    print(controller.users)
  }
}
```

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\GeneratedModels as Models;

$response = $client->queryUsers(new Models\QueryUsersPayload(
    filterConditions: (object)["id" => (object)['$in' => ["john", "jack", "jessie"]]],
    sort: [new Models\SortParamRequest(field: "last_active", direction: -1)],
    limit: 10,
    offset: 0,
));
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
final result = client.queryUsers(
  filter: Filter.in_('id', ['john', 'jack', 'jessie']),
  sort: [SortOption('last_active', direction: SortOption.DESC)],
  pagination: PaginationParams(limit: 10, offset: 0),
);
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
Client->QueryUsers(
  FFilter::In(TEXT("id"), {TEXT("john"), TEXT("jack"), TEXT("jessie")}),
  {FUserSortOption{EUserSortField::LastActive}},
  false,      // Presence
  {{10, 0}},  // Pagination: limit, offset
  [](const TArray<FUserRef>& Users)
  {
    // Do something with Users
  });
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// dotnet add package getstream-net
using GetStream;
using GetStream.Models;

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

var resp = await client.QueryUsersAsync(new QueryUsersPayload
{
    FilterConditions = new Dictionary<string, object>
    {
        ["id"] = new Dictionary<string, object> { ["$in"] = new[] { "john", "jack", "jessie" } }
    },
    Sort = new List<SortParamRequest>
    {
        new SortParamRequest { Field = "last_active", Direction = -1 }
    },
    Limit = 10,
    Offset = 0
});
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import QueryUsersPayload, SortParamRequest

client.query_users(
    QueryUsersPayload(
        filter_conditions={"id": {"$in": ["john", "jack", "jessie"]}},
        sort=[SortParamRequest(field="last_active", direction=-1)],
        limit=10,
        offset=0,
    )
)
```

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
var filters = new IFieldFilterRule[]
{
  UserFilter.Id.In("john", "jack", "jessie")
};

var sort = UsersSort.OrderByDescending(UserSortField.LastActive);
var limit = 10;
var offset = 0;

// Returns collection of IStreamUser
var users = await Client.QueryUsersAsync(filters, sort, offset, limit);
```

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
Models = GetStream::Generated::Models

client.common.query_users(
  Models::QueryUsersPayload.new(
    filter_conditions: { 'id' => { '$in' => ['john', 'jack', 'jessie'] } },
    sort: [Models::SortParamRequest.new(field: 'last_active', direction: -1)],
    limit: 10,
    offset: 0
  )
)
```

</codetabs-item>

</codetabs>

<partial id="shared/user-management/_query-users-filters-sort-options"></partial>

### Querying with Autocomplete

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

<codetabs>

<codetabs-item value="kotlin" label="Kotlin">

```kotlin
val request = QueryUsersRequest(
  filter = Filters.autocomplete("name", "ro"),
  offset = 0,
  limit = 10,
)

client.queryUsers(request).enqueue { /* ... */ }
```

</codetabs-item>

<codetabs-item value="javascript" label="JavaScript">

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

</codetabs-item>

<codetabs-item value="php" label="PHP">

```php
use GetStream\GeneratedModels as Models;

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

</codetabs-item>

<codetabs-item value="swift" label="Swift">

```swift
let controller = chatClient.userListController(
  query: .init(filter: .autocomplete(.name, text: "ro"))
)

controller.synchronize { error in
  if let error = error {
    // handle error
    print(error)
  } else {
    // access users
    print(controller.users)
  }
}
```

</codetabs-item>

<codetabs-item value="java" label="Java">

```java
FilterObject filter = Filters.autocomplete("name", "ro");
int offset = 0;
int limit = 10;
QueryUsersRequest request = new QueryUsersRequest(filter, offset, limit);

client.queryUsers(request).enqueue(result -> { /* ... */ });
```

</codetabs-item>

<codetabs-item value="dart" label="Dart">

```dart
final _result = client.queryUsers(
 filter: Filter.autoComplete('name', 'ro'),
);
```

</codetabs-item>

<codetabs-item value="unreal" label="Unreal">

```cpp
Client->QueryUsers(
  FFilter::Autocomplete(TEXT("name"), TEXT("ro")),
  {},   // Sort
  true,  // Presence
  {},   // Pagination options
  [](const TArray<FUserRef>& Users)
  {
    // Do something with Users
  });
```

</codetabs-item>

<codetabs-item value="csharp" label="C#">

```csharp
// dotnet add package getstream-net
using GetStream;
using GetStream.Models;

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

var resp = await client.QueryUsersAsync(new QueryUsersPayload
{
    FilterConditions = new Dictionary<string, object>
    {
        ["name"] = new Dictionary<string, object> { ["$autocomplete"] = "ro" }
    }
});
```

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import QueryUsersPayload

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

</codetabs-item>

<codetabs-item value="ruby" label="Ruby">

```ruby
Models = GetStream::Generated::Models

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

</codetabs-item>

<codetabs-item value="unity" label="Unity">

```csharp
var filters = new IFieldFilterRule[]
{
  UserFilter.Name.Autocomplete("Ro")
};
// Returns collection of IStreamUser
var users = await Client.QueryUsersAsync(filters);
```

</codetabs-item>

</codetabs>

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

<codetabs>

<codetabs-item value="javascript" label="JavaScript">

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

</codetabs-item>

<codetabs-item value="python" label="Python">

```python
from getstream.models import QueryUsersPayload

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

</codetabs-item>

</codetabs>


---

This page was last updated at 2026-04-10T16:29:34.730Z.

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