# Accepting Batch Commands

## Overview

As of Matter 1.3, Matter servers have been capable of receiving multiple (batch)
invoke requests in a single Invoke Interaction. This document explains how to
enable accepting batch commands.

## Background

The best place to look in the specification concerning batch commands is
sections that mention `MAX_PATHS_PER_INVOKE`. Another helpful area in the
specification for detailed information is the list of `InvokeRequests` within
the Invoke Request Action, and how specification outlines how requests are
processed and responded to.

One very important aspect of a batch of commands within a single Invoke Request
Message is that the paths associated with each of the commands SHALL be a
concrete (non-wildcard) path, where each path is unique.

## Enabling Batch Commands on Matter Server

Enabling should be as simple as setting `CHIP_CONFIG_MAX_PATHS_PER_INVOKE` to
the maximum number of commands you wish to be able to receive in a batch.

Note:

-   While `CHIP_CONFIG_MAX_PATHS_PER_INVOKE` can technically be set up to 65535,
    most Matter devices send traffic using UDP. This means that the real
    constraint will be the MTU of the UDP packet. In practice, a list of
    `CommandDataIB` containing only `CommandPath` and `CommandRef` with no
    `CommandFields` (For example: An On or Off command), you can fit roughly 58
    `CommandDataIB` into a single Invoke Request Message before exceeding the
    MTU of a UDP packet.

## Testing

Using `matter-repl`, you are able to send batch commands using
`SendBatchCommands` in `ChipDeviceController`.

Note: `chip-tool` does NOT support sending batch commands.

## Advanced: Leveraging Batch Commands for More Efficiently Handling Commands

As of July 2025, when this documentation was initially authored, the SDK simply
calls callbacks to process handling commands sequentially/serially. It is up to
the application/cluster code to identify commands that have been sent in a batch
and find more efficient ways of handling them. For example, a bridge might want
to collect all batched commands in a single invoke action and send them as a
group command to all bridged devices.

Theoretically this could be done by adding a Batcher instance and leveraging the
matter event loop. You would insert the instance of Batcher to handle relevant
command(s), it would append the command to a list, schedule a task on the event
loop to process the entire list. Pseudocode below:

```cpp
class Batcher : public CommandHandlerImpl::Callback{
public:
  class Command {
  public:
    // Command's constructor will need to capture all information needed
    // to process the command when DispatchCommandsAsList is scheduled
    // (or potentially in a subsequent scheduled task). It will also be
    // critical to instantiate an instance of CommandHandler::Handle
    // to allow for the command to be handled asynchronously. For more
    // information on how to properly use CommandHandler::Handle to
    // perform async command handling, see the documentation for
    // CommandHandler::Handle.
    Command(...) {...}
    [...]
  private:
    CommandHandler::Handle mHandle;
    [...]  // Other member variables important for processing the command.
            // This might, generally speaking, include a deep-copy of the command payload
            // (since that can point into network buffers that will go away before the async
            // code runs), information about the fabric and session involved (since both might
            // go away before the async code runs), and so on.
  };
  [...]
  static void DispatchCommandsAsList(intptr_t arg) {
    auto this_ = reinterpret_cast<Batcher*>(arg);
    std::vector<std::unique_ptr<Command>> commands;
    commands.swap(this_->mCommands);
    // Call thing that is capable of more efficiently processing
    // commands in parallel with arg of std::move(commands)
  }

  void DispatchCommand(...) override {
    bool has_previous_pending_request = !mCommands.empty();
    mCommands.push_back(std::make_unique<Command>(...));
    if (!has_previous_pending_request && !mCommands.empty()) {
      SystemLayer().ScheduleWork(DispatchCommandsAsList, this);
    }
  }
private:
  std::vector<std::unique_ptr<Command>> mCommands;
};
```
