Skip to main content
Channels move large or binary payloads between iii workers without putting the data in a JSON function payload. Use one for files, images, datasets, payloads above the JSON message limit, audio or video streams, or incremental progress updates during long-running work. For small JSON, use a regular worker.trigger(...) call.
For the underlying model (how channels are addressed, multiplexed, and torn down), see Channels architecture.

Payload size

The limit applies to the complete serialized UTF-8 JSON envelope, measured in bytes. The Rust, Node, Python, and Go SDKs support messages up to 64 MiB. The browser SDK has a 16 MiB outbound limit and does not fragment messages because browser WebSocket APIs do not expose frame control. Browser workers also have a 16 MiB receive limit. For Rust, Node, Python, and Go, messages up to 16 MiB are sent as one text frame. Larger messages, up to and including 64 MiB, are sent as one fragmented WebSocket text message using multiple frames. The engine sends every message larger than 1 MiB to a worker as one fragmented text message of 1 MiB frames, so no frame it sends exceeds a worker’s frame size limit. Applications send and receive these messages through the usual function APIs. The receiving worker must run SDK version 0.24.5 or later to accept messages larger than 16 MiB. An updated engine checks the serialized envelope against the target worker’s receive limit and returns payload_too_large to the caller when the target cannot accept it. The engine sets that limit when the worker registers:
  • A browser worker gets 16 MiB.
  • A worker that declares the large-messages.v1 protocol feature gets 64 MiB, whatever version it reports. The Rust, Node, Python, and Go SDKs declare it from 0.25.0; the browser SDK does not.
  • Any other worker gets 64 MiB when the version it reports is an SDK version of 0.24.5 or later, written as semver (0.24.6, 0.25.0-rc.5) or PEP 440 (0.25.0rc5, 0.24.5.post1). It gets 16 MiB when the version is missing, cannot be parsed, or is older, including pre-releases of 0.24.5. Other features, such as observe.v1, do not count.
SDK 0.24.5 through 0.25.0-rc.5 do not declare large-messages.v1, so the engine relies on the version they report. A Rust worker on one of them that sets WorkerMetadata.version to its own version instead of the SDK version therefore stays at 16 MiB. Upgrade it to SDK 0.25.0 or later. Above 64 MiB, the Rust, Node, Python, and Go SDKs reject the message locally with payload_too_large; the error message links to this Payload size section. Engines older than the updated size check forward messages as a single frame without checking the target’s limit. An older worker may then disconnect if the message is too large for it. Use an engine and SDKs from the same release, or newer. The engine reads up to 64 MiB per message from a worker, whether the message arrives as one frame or as fragments, so it also accepts the single large frames of SDKs older than 0.24.5. When a worker sends a larger message, the engine drops it and the connection stays open. A call gets payload_too_large as its result, and when a function returns a result that is too large, its caller gets payload_too_large instead. Other calls on the connection are not affected. A message the engine cannot answer, such as a call that expects no result, is dropped and logged. Observer activations follow the same limits, and the engine keeps at most 64 MiB of one observed event (every field its observers capture, input and output counted separately). An event over either limit is not delivered: the observer counts a TOO_LARGE drop and the engine logs a warning. See Observers / Event size. Use a channel for payloads above 64 MiB. Channels stream payload bytes separately from the JSON function envelope. See Using channels.

Using channels

A channel is created by one worker and has two local stream ends: writer and reader; plus two serializable refs (writerRef / readerRef) that can be handed to another function. The subsections below cover the local-end API: creating a channel, writing bytes into its writer, and reading bytes from its reader.

Create a channel

worker.createChannel() returns a channel with two local stream objects and two serializable refs: writer and reader are the local stream ends, and writerRef / readerRef are the tokens you pass to another function so it can read or write the other end.

Write to a channel

Write the payload to the local writer and close it when finished. The bytes flow through the engine to whichever worker holds the matching reader.

Read from a channel

Read the local reader until the other end closes. The bytes arrive in the order they were written by whichever worker holds the matching writer.

Using channels across functions

A channel only becomes useful once both ends are owned by different code paths. Typically one function that holds the local writer / reader and another that receives the matching ref in its payload. The two subsections below cover that handoff: first how to send a ref alongside a normal trigger call, then how the receiving function turns it back into a live stream to read or write.

Pass a channel ref to another function

Pass the readerRef (or writerRef) as part of a normal function invocation. The receiving function uses the ref to read from (or write to) the channel.
Node and Python deserialize incoming channel refs into live ChannelReader / ChannelWriter objects before the handler runs, so the ref arrives ready to iterate or write to. Rust receives the ref in JSON and reconstructs the reader or writer explicitly with ChannelReader::new(...) or ChannelWriter::new(...).
The channel belongs to the worker that created it. Keep that worker connected until the receiving function has attached to the ref. If the creator disconnects first, unattached ends are dropped. See Channel ownership.

Read from a channel ref

Write to a channel ref

For the per-language channel API surface, see the SDK reference: Node, Python, and Rust.