Applies to: Chat | Server-side only
Availability: Beta (feature-flag gated)
Endpoint: PUT /channels/batch
Quick Answer
- Batch Channel Update lets you perform the same operation on multiple channels at once, identified by a filter. It is a server-side only endpoint.
- The endpoint is asynchronous — it returns a
task_idimmediately. The actual work happens in the background. - Supported operations:
addMembers,removeMembers,inviteMembers,assignRoles,addModerators,demoteModerators,hide,show,archive,unarchive, andupdateData. - You can update up to 100 members per request. Channels are processed in chunks of 25 internally.
- Track progress by polling the
GetTaskendpoint with the returnedtask_id, or listen for webhook events (channel_batch_update.startedandchannel_batch_update.completed).
Overview
The Batch Channel Update endpoint allows you to apply a single operation to all channels matching a filter query, without making individual API calls per channel. This is useful for:
- Organizational changes — Add or remove a user from hundreds of channels at once (e.g., when an employee joins or leaves a team).
- Bulk moderation — Freeze, archive, or hide channels matching certain criteria.
- Data migrations — Update custom data or configuration overrides across many channels.
- Role management — Assign or change member roles across channels in bulk.
Because the operation is asynchronous, it does not block your API calls or consume rate limit budget for individual channel update endpoints. The server processes channels in parallel chunks for optimal throughput.
Prerequisites
- Server-side authentication. This endpoint requires an API key and secret (server-side token). Client-side tokens will be rejected.
- Feature flag enabled. Batch channel update is currently in beta. The feature must be enabled for your application. If the endpoint returns an empty response, contact Stream support to enable the flag for your app.
Request Format
Endpoint
PUT /channels/batch
Request body
| Field | Type | Required | Description |
|---|---|---|---|
operation |
string | Yes | The operation to perform. See Supported Operations. |
filter |
object | Yes | A channel filter query to select which channels to update. Only accepts cids and types. |
members |
array | Conditional | Required for member operations (addMembers, removeMembers, inviteMembers, assignRoles, addModerators, demoteModerators). Max 100 items. |
data |
object | Conditional | Required for updateData operation. Contains the channel fields to update. |
Member object
| Field | Type | Required | Description |
|---|---|---|---|
user_id |
string | Yes | The ID of the user. |
channel_role |
string | Conditional | Required for assignRoles. Optional for addMembers and inviteMembers. |
Data object (for updateData)
| Field | Type | Description |
|---|---|---|
frozen |
boolean | Freeze or unfreeze the channel. |
disabled |
boolean | Disable or enable the channel. |
custom |
object | Custom data fields to set on the channel. |
team |
string | Move the channel to a different team. |
config_overrides |
object | Per-channel configuration overrides. |
auto_translation_enabled |
boolean | Enable or disable auto-translation. |
auto_translation_language |
string | Set the auto-translation target language. |
All data fields are optional. Only include the fields you want to change — omitted fields are left unchanged.
Response
{
"task_id": "bf1e18a7-c0b2-4cda-b68e-c971dfd7f4d3",
"duration": "12.34ms"
}The task_id is used to track the progress of the batch operation via the GetTask endpoint.
Supported Operations
| Operation | Description | Requires members
|
Requires data
|
|---|---|---|---|
addMembers |
Add users as members to all matching channels. | Yes | No |
removeMembers |
Remove users from all matching channels. | Yes | No |
inviteMembers |
Send membership invitations to users for all matching channels. | Yes | No |
assignRoles |
Assign channel-level roles to members. channel_role is required on each member. |
Yes | No |
addModerators |
Promote users to moderator in all matching channels. | Yes | No |
demoteModerators |
Demote moderators back to regular members in all matching channels. | Yes | No |
hide |
Hide all matching channels (for the specified members). | Yes | No |
show |
Show (unhide) all matching channels (for the specified members). | Yes | No |
archive |
Archive all matching channels. | No | No |
unarchive |
Unarchive all matching channels. | No | No |
updateData |
Update channel data fields (frozen, disabled, custom, team, config_overrides, auto-translation settings). | No | Yes |
Operation Examples
Add members to all channels of a type
Add two users to every messaging channel in your application:
cURL
curl -X PUT "https://chat.stream-io-api.com/channels/batch" \
-H "Content-Type: application/json" \
-H "api_key: YOUR_API_KEY" \
-H "Authorization: YOUR_SERVER_TOKEN" \
-H "stream-auth-type: jwt" \
-d '{
"operation": "addMembers",
"filter": {
"types": "messaging"
},
"members": [
{ "user_id": "alice" },
{ "user_id": "bob", "channel_role": "channel_moderator" }
]
}'Response
{
"task_id": "bf1e18a7-c0b2-4cda-b68e-c971dfd7f4d3",
"duration": "8.21ms"
}Remove members from channels matching a filter
Remove a user from all channels belonging to a specific team:
curl -X PUT "https://chat.stream-io-api.com/channels/batch" \
-H "Content-Type: application/json" \
-H "api_key: YOUR_API_KEY" \
-H "Authorization: YOUR_SERVER_TOKEN" \
-H "stream-auth-type: jwt" \
-d '{
"operation": "removeMembers",
"filter": {
"types": "team",
},
"members": [
{ "user_id": "former-employee-123" }
]
}'Assign roles across channels
Promote a user to moderator role in all channels they are a member of:
curl -X PUT "https://chat.stream-io-api.com/channels/batch" \
-H "Content-Type: application/json" \
-H "api_key: YOUR_API_KEY" \
-H "Authorization: YOUR_SERVER_TOKEN" \
-H "stream-auth-type: jwt" \
-d '{
"operation": "assignRoles",
"filter": {
"types": "messaging",
"members": { "$in": ["trusted-user-456"] }
},
"members": [
{ "user_id": "trusted-user-456", "channel_role": "channel_moderator" }
]
}'Freeze channels with updateData
Freeze all channels that have a specific custom field value:
curl -X PUT "https://chat.stream-io-api.com/channels/batch" \
-H "Content-Type: application/json" \
-H "api_key: YOUR_API_KEY" \
-H "Authorization: YOUR_SERVER_TOKEN" \
-H "stream-auth-type: jwt" \
-d '{
"operation": "updateData",
"filter": {
"types": "messaging",
"custom.status": "inactive"
},
"data": {
"frozen": true
}
}'Update custom data in bulk
Set custom metadata on all channels of a certain type:
curl -X PUT "https://chat.stream-io-api.com/channels/batch" \
-H "Content-Type: application/json" \
-H "api_key: YOUR_API_KEY" \
-H "Authorization: YOUR_SERVER_TOKEN" \
-H "stream-auth-type: jwt" \
-d '{
"operation": "updateData",
"filter": {
"types": "team"
},
"data": {
"custom": {
"migration_version": "2",
"updated_by": "admin-script"
}
}
}'Archive channels
curl -X PUT "https://chat.stream-io-api.com/channels/batch" \
-H "Content-Type: application/json" \
-H "api_key: YOUR_API_KEY" \
-H "Authorization: YOUR_SERVER_TOKEN" \
-H "stream-auth-type: jwt" \
-d '{
"operation": "archive",
"filter": {
"types": "messaging",
"last_message_at": { "$lt": "2025-01-01T00:00:00Z" }
}
}'Tracking Progress
The batch operation runs asynchronously. Use the returned task_id to check its status.
Poll the GetTask endpoint
curl "https://chat.stream-io-api.com/tasks/bf1e18a7-c0b2-4cda-b68e-c971dfd7f4d3" \ -H "api_key: YOUR_API_KEY" \ -H "Authorization: YOUR_SERVER_TOKEN" \ -H "stream-auth-type: jwt"
JavaScript / Node.js
const { StreamChat } = require('stream-chat');
const serverClient = StreamChat.getInstance('YOUR_API_KEY', 'YOUR_API_SECRET');
// Start the batch operation
const response = await serverClient.put('/channels/batch', {
operation: 'addMembers',
filter: { types: 'messaging' },
members: [{ user_id: 'alice' }],
});
console.log('Task started:', response.task_id);
// Poll for completion
let task;
do {
await new Promise(resolve = setTimeout(resolve, 5000)); // wait 5 seconds
task = await serverClient.getTask(response.task_id);
console.log('Status:', task.status);
} while (task.status !== 'completed' && task.status !== 'failed');Tip: For a non-polling approach, use webhook events to receive a notification when the batch completes.
Webhook Events
Two webhook events are emitted during a batch channel update:
channel_batch_update.started
Fired immediately when the batch task begins processing.
{
"type": "channel_batch_update.started",
"task_id": "bf1e18a7-c0b2-4cda-b68e-c971dfd7f4d3",
"operation": "addMembers",
"status": "started",
"batch_created_at": "2026-02-26T10:00:00.000Z"
}channel_batch_update.completed
Fired when all channels have been processed (whether all succeeded or some failed).
{
"type": "channel_batch_update.completed",
"task_id": "bf1e18a7-c0b2-4cda-b68e-c971dfd7f4d3",
"operation": "addMembers",
"status": "completed",
"success_channels_count": 142,
"failed_channels": [],
"batch_created_at": "2026-02-26T10:00:00.000Z",
"finished_at": "2026-02-26T10:00:12.345Z"
}Handling partial failures
If some channels fail, the failed_channels array contains details about each failure:
{
"type": "channel_batch_update.completed",
"task_id": "bf1e18a7-c0b2-4cda-b68e-c971dfd7f4d3",
"operation": "addMembers",
"status": "completed",
"success_channels_count": 140,
"failed_channels": [
{
"channel_cid": "messaging:channel-abc",
"error": "member limit exceeded"
},
{
"channel_cid": "messaging:channel-xyz",
"error": "channel is disabled"
}
],
"batch_created_at": "2026-02-26T10:00:00.000Z",
"finished_at": "2026-02-26T10:00:12.345Z"
}The batch does not stop on individual channel failures. It continues processing all channels and reports failures at the end.
Limits and Constraints
| Constraint | Value |
|---|---|
| Authentication | Server-side only |
| Members per request | 100 maximum |
| Internal chunk size | 25 channels per subtask |
| Rate limit tier | Low frequency (300 requests/minute) |
| Task data TTL | 24 hours (progress tracking data expires after 24 hours) |
| Concurrency | Up to 10 concurrent subtasks globally, 5 per app |
Common Errors
Empty response (no error, no task_id)
| Detail | Value |
|---|---|
| HTTP Status | 200 |
| Response Body |
{} or null
|
Cause: The batch channel update feature is not enabled for your application.
Fix: Contact Stream support to enable the feature flag for your app.
Validation error: invalid operation
{
"code": 4,
"message": "operation must be one of: addMembers removeMembers inviteMembers invites assignRoles addModerators demoteModerators hide show archive unarchive updateData",
"StatusCode": 400
}Cause: The operation value is not one of the supported operations.
Fix: Use one of the valid operation strings listed in the Supported Operations table.
Validation error: missing filter
{
"code": 4,
"message": "filter is required",
"StatusCode": 400
}Cause: The filter field is missing from the request body.
Fix: Always include a filter object to specify which channels to update.
Validation error: missing members
{
"code": 4,
"message": "members is required when operation is addMembers",
"StatusCode": 400
}Cause: A member-based operation was requested but the members array is missing.
Fix: Include a members array with at least one member object containing a valid user_id.
Validation error: too many members
{
"code": 4,
"message": "members must contain at most 100 items",
"StatusCode": 400
}Cause: The members array contains more than 100 entries.
Fix: Split the members into batches of 100 or fewer and issue multiple batch requests.
Validation error: missing channel_role for assignRoles
{
"code": 4,
"message": "members[0].channel_role cannot be empty for assignRoles operation",
"StatusCode": 400
}Cause: The assignRoles operation requires every member entry to include a channel_role.
Fix: Add a valid channel_role to each member object (e.g., "channel_member", "channel_moderator").
Validation error: invalid channel role
{
"code": 4,
"message": "role invalid_role cannot be used as channel member role",
"StatusCode": 400
}Cause: The channel_role value does not match any valid role in your permission system.
Fix: Use a valid channel member role. Built-in roles include channel_member and channel_moderator. Check your custom roles via the Dashboard or the ListRoles endpoint.
Best Practices
-
Use specific filters. Always scope your filter as narrowly as possible. A broad filter like
{}or{ "types": "messaging" }may match thousands of channels, leading to long-running tasks. Prefer filters with specific conditions such as team, custom fields, or member presence. - Test with a narrow filter first. Before running a batch operation on all channels, test it against a small subset using a specific filter (e.g., a single channel by CID). Verify the result before expanding the scope.
-
Listen for webhook events. Instead of polling the
GetTaskendpoint, configure a webhook to receivechannel_batch_update.completedevents. This is more efficient and provides immediate notification. -
Handle partial failures. The batch operation does not abort on individual channel failures. Always check the
failed_channelsarray in the completed webhook event to identify and resolve failures. -
Split large member lists. If you need to add more than 100 members, split them into multiple batch requests of 100 or fewer. Each request returns its own
task_id. - Avoid overlapping batch operations. Do not run multiple batch operations with overlapping channel filters simultaneously. While the system handles concurrency internally, overlapping operations on the same channels can lead to unpredictable results (e.g., adding and removing the same member concurrently).
-
Use
updateDatainstead of individual updates. If you need to change a channel property (frozen, disabled, custom data) on many channels, use a single batchupdateDatacall instead of callingUpdateChannelorUpdateChannelPartialper channel. This is faster and avoids rate limiting.
Diagnostics Checklist
If you need to escalate a batch channel update issue, copy this template, fill it out, and include it in your support request:
Batch Channel Update Issue Diagnostics ======================================= 1. App ID (from Dashboard): 2. Task ID (from the response): 3. Operation used: 4. Filter object (paste the full JSON): 5. Members array (paste if applicable): 6. Data object (paste if applicable): 7. Full error response (paste the JSON): 8. Expected behavior: 9. Actual behavior: 10. Webhook events received? [ ] started [ ] completed [ ] neither 11. Failed channels in completed event (paste if applicable): 12. How many channels match the filter (approx)? 13. Is the feature flag enabled? [ ] Yes [ ] No [ ] Unsure 14. SDK + version (if using an SDK): 15. Environment: [ ] Development [ ] Production
Escalation
| Level | When | Action |
|---|---|---|
| Self-Service | Validation errors (missing fields, invalid operation, too many members) | Follow the fix steps in the Common Errors section above. |
| Tier 1 Support | Empty response (feature flag), task stuck in progress, or unexpected failures | Collect the diagnostics checklist. Verify the feature flag is enabled. Check the task status via GetTask. |
| Tier 2 Support | Partial failures with unclear error messages, or batch completes but channels are not updated | Investigate failed channel CIDs. Check for permission issues, channel state (disabled, frozen), or member limits on specific channels. |
| Engineering | Task disappears or never completes, webhook events not firing, or data inconsistency after completion | File an internal ticket with the task ID, app ID, filter, and timeline of events. |
Related Links
Related Endpoints
- QueryChannels — Uses the same filter syntax for selecting channels
- UpdateChannel / UpdateChannelPartial — Update a single channel
- Channel Members — Add, remove, and manage members on individual channels
- Moderation — Freeze, ban, and moderate channels
Comments
0 comments
Please sign in to leave a comment.