Batch Updates

You can perform batch updates on multiple channels at once. This is useful for making changes to a large number of channels without having to update each one individually, which could result in potentially thousands of API calls.

Batch updates run asynchronously by default: the request returns a task ID, and the updates are processed in the background. For bounded channel data updates, you can instead request synchronous execution.

When an asynchronous update is requested, a task will be created and the task ID will be returned in the response. You can use this task ID to check the status of the update operation.

Synchronous channel data updates

Set synchronous: true on a server-side PUT /channels/batch request to wait for a bounded updateData database write before continuing. This mode supports channel data updates only; member operations remain asynchronous.

Provide a root filter.cids using $eq or $in with 1 to 100 CIDs. A types filter can further restrict that selection. Types-only filters, root $or or $and, and more than 100 CIDs return HTTP 400. Split larger selections into separate requests. Requests with no fields to update or no matching channels also return HTTP 400.

For example, this request freezes two channels:

{
  "operation": "updateData",
  "synchronous": true,
  "filter": {
    "cids": { "$in": ["messaging:channel-a", "messaging:channel-b"] }
  },
  "data": { "frozen": true }
}

A synchronous success includes a positive success_channels_count and no task_id:

{
  "success_channels_count": 2
}

The count is the number of matching channels loaded for the completed update, excluding missing and duplicate CIDs. It is not an affected-row count: a concurrent hard deletion can reduce the number of rows written.

Always inspect the response shape. If task_id is present, the operation was queued and you must wait for the asynchronous task before continuing, even if you sent synchronous: true. An older API node can ignore the new field during a rollout or rollback. Use an SDK release that exposes both synchronous and success_channels_count.

Database writes for one synchronous request commit together. Event delivery remains best effort and is not guaranteed by a successful response. Synchronous updates emit per-channel update events but do not emit channel_batch_update.started or channel_batch_update.completed task events. Disabling a channel can still schedule background thread read-state work.

A timeout does not prove the write failed: the database may already have committed. Read the channel state before deciding to retry. Repeating the same idempotent update may emit duplicate events. Concurrent channel updates are not serialized with this operation.

How to target channels

In order to perform batch updates, you need to target which channels you want to update.

You can do this by providing a filter that matches the channels you want to update.

The filters that are supported for batch updates, using the standard query operator syntax, are:

Name Description Available Operators
cids Filters channels by Channel ID (CID) in the format type:id (e.g., "messaging:channel1"). Must use explicit operators. Direct arrays are not supported. $eq: Single CID value (e.g., {"cids": {"$eq": "messaging:channel1"}}). $in: Multiple CID values (e.g., {"cids": {"$in": ["messaging:channel1", "livestream:channel2"]}})
types Filters channels by channel type (e.g., "messaging", "livestream", "team"). Must use explicit operators. Direct arrays are not supported. $eq: Single channel type (e.g., {"types": {"$eq": "messaging"}}). $in: Multiple channel types (e.g., {"types": {"$in": ["messaging", "livestream"]}})

Filter examples

// 1) Filter by type
const filters = {
  types: { $in: ["messaging"] },
};

// 2) Filter by specific CIDs
const filtersByCIDS = {
  channel_cids: {
    $in: [
      "messaging:3b11838a-7734-4ece-8547-4b8524257671",
      "messaging:a266bee6-dc3c-4188-a37d-e554d4bfac34",
      "messaging:40fef12a-0b7c-4bcf-bd97-3ddf604efed5",
      "messaging:2a58963e-d769-4ce3-9309-bff93c14db57",
    ],
  },
};

Supported operations

You can perform different operations on the channels but only once at a time. The supported operations are:

Operation Name Description Parameters
addMembers Add members to the channels. members
addMembersHideHistory Add members without exposing older messages. members, hide_history_before
removeMembers Remove members from the channels. members
addModerators Add moderators to the channels. members
demoteModerators Remove moderator status from members in the channels. members
hide Hide the channels for members. members
show Show the channels for members. members
archive Archive the channels for members. members
unarchive Unarchive the channels for members. members
updateData Update the channel data for the channels. channelData
assignRoles Assign roles to members in the channels. members
inviteMembers Send invites to users to join the channels. members

Hiding history from added members

The addMembersHideHistory operation requires a hide_history_before timestamp. New memberships receive this cutoff in every channel matched by the filter, so those members cannot see messages created before that time. It is the batch equivalent of the hide_history_before option when adding members.

Property Type Description
hide_history_before string (RFC 3339) Required. Hides messages created before this time from the members being added. Must be in the past.
{
  "operation": "addMembersHideHistory",
  "filter": {
    "cids": { "$in": ["messaging:channel-1", "messaging:channel-2"] }
  },
  "members": [{ "user_id": "jane" }],
  "hide_history_before": "2024-01-01T10:00:00Z"
}

Things worth knowing:

  • The operation and cutoff are required together. Use addMembers to add members without a history cutoff.
  • It applies only to memberships this operation creates or restores. A user who is already a member of a matched channel is unaffected, even if their ID is in members.
  • When a soft-deleted membership is restored, the requested cutoff is applied only if that membership does not already have one. If it already has cutoff A, it keeps A instead of using the requested cutoff B.
  • The timestamp must be in the past; a future value is rejected with hide_history_before must be in the past.
  • Sending hide_history_before with any other operation, including addMembers, is rejected rather than silently ignored.
  • This operation does not replace or clear a cutoff that a membership already carries.

Channel data update properties

When using the updateData operation, you can update the following channel properties. All properties are optional - only the provided values will be updated.

Property Type Description
frozen boolean Freeze the channel to prevent new messages
disabled boolean Disable the channel
custom object Custom data fields for the channel
team string Team ID to assign the channel to
config_overrides object Override channel type configuration settings
auto_translation_enabled boolean Enable automatic message translation
auto_translation_language string Language code for auto translation

Partial custom updates

custom replaces the whole custom object: every key you do not send is deleted from every matched channel. When you only want to change some keys, use custom_set and custom_unset instead. They merge the named keys into each channel's existing custom data.

Both live at the root of the request, next to operation and filter, not inside data.

Property Type Description
custom_set object Keys to merge into each matched channel's existing custom object
custom_unset array of strings Keys to delete from each matched channel's existing custom object

Batch custom merges are not safe against concurrent custom-data writes. The batch reads each channel before writing the merged object, so another request that updates custom data after that read can have its changes overwritten. Avoid concurrent custom-data writes while the batch is running. When you need a concurrency-safe merge, use the single-channel partial-update endpoint for each channel instead.

Rules:

  • updateData only, and mutually exclusive with data.custom. A request carrying both is rejected with a 400, because "replace everything" and "change these keys" have no combined meaning.
  • Keys are dot-paths: a.b addresses key b inside object a. The parent object must already exist on the channel or be written by the same custom_set. Keys are applied in alphabetical order, so a parent such as expiration is written before expiration.version. This operation never creates intermediate objects on its own. A custom_set key whose parent object is missing on a channel is an error, and so is any key whose parent exists but is not an object (a is a string and you address a.b), for custom_unset too. A merge that pushes the channel's custom data past the 5 KB limit is an error as well.
  • A failing channel fails the chunk it was batched into, not just itself. The batch is split into chunks of up to 25 channels, and each chunk is applied as a unit. If one channel in a chunk fails any of the checks above, every CID in that chunk is reported under failed_channels with the same reason and none of them is modified, including the channels that would have merged cleanly. Other chunks in the same batch still apply. Plan for this when patching a large set: one channel missing a parent object can cost you 25 channels' worth of the update, so make sure the parent exists everywhere you are targeting, set the parent in the same custom_set, or narrow the filter.
  • Deleting a key that does not exist is a no-op, not an error.
  • The same key cannot appear in both custom_set and custom_unset (400). Neither can a parent path in one and its child in the other, such as custom_unset: ["expiration"] with custom_set: {"expiration.version": 2} (400).
  • Keys always go into custom data, even when they match a built-in channel property. custom_set: {"team": "x"} stores a custom key named team. It does not change the channel's team. This differs from the single-channel partial update, where set on a built-in property changes that property or is rejected. Use data to change built-in properties such as team or frozen.
{
  "operation": "updateData",
  "filter": { "types": { "$in": ["messaging"] } },
  "custom_set": { "campaign": "summer-2026", "expiration.version": 2 },
  "custom_unset": ["legacy_owner"]
}

Given a channel whose custom is {"campaign": "spring-2026", "expiration": {"version": 1}, "legacy_owner": "u1", "color": "blue"}, the request above leaves {"campaign": "summer-2026", "expiration": {"version": 2}, "color": "blue"}; color is untouched, which a custom replace would have removed.

Config overrides

The config_overrides object allows you to override the default channel type configuration for specific channels:

Property Type Description
typing_events boolean Enable/disable typing indicators
reactions boolean Enable/disable message reactions
replies boolean Enable/disable message replies (threads)
quotes boolean Enable/disable message quotes
uploads boolean Enable/disable file uploads
url_enrichment boolean Enable/disable URL preview enrichment
max_message_length integer Maximum message length (1-20000)
blocklist string Name of the blocklist to apply
blocklist_behavior string Blocklist behavior: flag or block
grants object Permission grants modifiers
commands array List of enabled command names

Most of the operations require additional parameters to be specified, such as the members to add or remove, or the channelData to update.

We've prepared convenience methods for all operations, some examples are shown below:

// Add members
const updater = client.channelBatchUpdater();
const filter = {
  types: {
    $in: ["messaging"],
  },
};

const members = ["user-123"];

const resp = await updater.addMembers(filter, members);

// Update channel data
const updater = client.channelBatchUpdater();
const filter = {
  types: {
    $in: ["messaging", "team"],
  },
};

const data = {
  frozen: true,
  custom: {
    color: "blue",
  },
};

const resp = await updater.updateData(filter, data);

Webhooks

When an asynchronous batch update is started, a webhook event channel_batch_update.started will be triggered.

Additionally, for each channel that is updated, the corresponding webhook events will be triggered as well. For example, if members are added to channels, the member.added event will be triggered for each channel that is updated.

When the asynchronous batch update operation is completed, a webhook event channel_batch_update.completed will be triggered. This event will contain the task ID and the status of the operation (success or partial failure). If some channels failed to update, they are listed in failed_channels, grouped by reason - see the partial failure example.

For the format of the status please see next section.

Status

You can check the status of a batch update operation by using the task ID returned when the operation was started.

To get the status of the task, you use the Get Task endpoint with the task ID.

const taskResponse = await client.getTask({ id: response.task_id });

The response will contain information about the task, including its status, result, and any errors that occurred during the operation.

{
  "task_id": "23685bb3-d1c7-492b-a02a-3dbaa12855e1",
  "status": "completed",
  "created_at": "2025-12-13T10:25:15.856428Z",
  "updated_at": "2025-12-13T10:35:00.512601Z",
  "result": {
    "operation": "show",
    "status": "completed",
    "success_channels_count": 1080,
    "task_id": "23685bb3-d1c7-492b-a02a-3dbaa12855e1",
    "batch_created_at": "2025-12-13T10:25:16Z",
    "failed_channels": [
      {
        "reason": "cannot invite members to the distinct channel",
        "cids": ["messaging:550e8400-e29b-41d4-a716-446655440000"]
      },
      {
        "reason": "user not found: user_id 'user_12345' does not exist",
        "cids": ["team:7c9e6679-7425-40de-944b-e07fc1f90ae7"]
      }
    ],
    "finished_at": "2025-12-13T10:35:00.512579053Z"
  },
  "duration": "36.23ms"
}

Poll the inner result.status, not the top-level status. The top-level field describes the task that fans the work out, and it flips to completed as soon as that fan-out returns while the channels are still being updated. The batch itself is finished only once result.status is completed and result.finished_at is set; until then success_channels_count and failed_channels are not final. Reading a channel back on the top-level field alone will often show you its old data.

You can try to re apply the operation on the failed channels again by creating a new task with the same operation and just the CIDs of the failed channels.