> ## Documentation Index
> Fetch the complete documentation index at: https://iii.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Channels

> Stream large or binary payloads between iii workers.

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.

> [!NOTE]
>
> For the underlying model (how channels are addressed, multiplexed, and torn down), see [Channels
> architecture](/docs/0-24-0/understanding-iii/channels.md).

## Payload size

As of SDK version **0.24.5**, the Rust, Node, Python, and Go SDKs reject outbound JSON envelopes larger
than **16 MiB** locally with `payload_too_large`. The size is measured in bytes across the complete
serialized UTF-8 JSON envelope, not just the function payload value. The browser SDK in version
0.24.5 does not check outbound message size before sending. For browser payloads larger than
16 MiB, use a channel.

Messages at or below 16 MiB are sent as a single WebSocket text frame. Channels stream payload
bytes separately from the JSON function envelope, making them suitable for larger data and
continuous transfers.

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

**Node / TypeScript**

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
const channel = await worker.createChannel();

// channel.writer
// channel.reader
// channel.writerRef
// channel.readerRef
```

**Python**

```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
channel = iii_client.create_channel()

# channel.writer
# channel.reader
# channel.writer_ref
# channel.reader_ref
```

**Rust**

```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
let channel = worker.create_channel(None).await?;

// channel.writer
// channel.reader
// channel.writer_ref
// channel.reader_ref
```

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

**Node / TypeScript**

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
const channel = await worker.createChannel();

channel.writer.stream.end(Buffer.from("file contents"));
```

**Python**

```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
channel = await iii_client.create_channel_async()

await channel.writer.write(b"file contents")
await channel.writer.close_async()
```

**Rust**

```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
let channel = worker.create_channel(None).await?;

channel.writer.write(b"file contents").await?;
channel.writer.close().await?;
```

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

**Node / TypeScript**

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
const channel = await worker.createChannel();

let bytes = 0;
for await (const chunk of channel.reader.stream) {
  bytes += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk);
}
```

**Python**

```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
channel = await iii_client.create_channel_async()

bytes_total = 0
async for chunk in channel.reader:
    bytes_total += len(chunk)
```

**Rust**

```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
let channel = worker.create_channel(None).await?;

let mut bytes = 0;
while let Some(chunk) = channel.reader.next_binary().await? {
    bytes += chunk.len();
}
```

## 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 / TypeScript**

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
const result = await worker.trigger({
  function_id: "files::process",
  payload: {
    filename: "report.csv",
    reader: channel.readerRef,
  },
});
```

**Python**

```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
result = await iii_client.trigger_async({
    "function_id": "files::process",
    "payload": {
        "filename": "report.csv",
        "reader": channel.reader_ref.model_dump(),
    },
})
```

**Rust**

```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
use iii_sdk::TriggerRequest;
use serde_json::json;

let result = worker
    .trigger(TriggerRequest {
        function_id: "files::process".to_string(),
        payload: json!({
            "filename": "report.csv",
            "reader": channel.reader_ref,
        }),
        action: None,
        timeout_ms: None,
    })
    .await?;
```

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

> [!TIP]
>
> 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](/docs/0-24-0/understanding-iii/channels.md#channel-ownership).

### Read from a channel ref

**Node / TypeScript**

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
import type { ChannelReader } from "iii-sdk";

worker.registerFunction("files::process", async (input: { reader: ChannelReader }) => {
  let bytes = 0;
  for await (const chunk of input.reader.stream) {
    bytes += Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(chunk);
  }
  return { bytes };
});
```

**Python**

```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
async def process_file(input: dict) -> dict:
    reader = input["reader"]
    total = 0
    async for chunk in reader:
        total += len(chunk)
    return {"bytes": total}

worker.register_function("files::process", process_file)
```

**Rust**

```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
use iii_sdk::{ChannelDirection, ChannelReader, IIIError};
use serde_json::json;

let refs = iii_sdk::extract_channel_refs(&input);
let (_, reader_ref) = refs
    .iter()
    .find(|(k, r)| k == "reader" && matches!(r.direction, ChannelDirection::Read))
    .ok_or_else(|| IIIError::Handler("missing reader channel ref".into()))?;

let reader = ChannelReader::new(worker.address(), reader_ref);
let mut bytes = 0;
while let Some(chunk) = reader.next_binary().await? {
    bytes += chunk.len();
}
Ok(json!({ "bytes": bytes }))
```

### Write to a channel ref

**Node / TypeScript**

```typescript theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
import type { ChannelWriter } from "iii-sdk";

worker.registerFunction("files::generate", async (input: { writer: ChannelWriter }) => {
  input.writer.stream.write(Buffer.from("hello "));
  input.writer.stream.end(Buffer.from("world"));
  return { ok: true };
});
```

**Python**

```python theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
async def generate_file(input: dict) -> dict:
    writer = input["writer"]
    await writer.write(b"hello ")
    await writer.write(b"world")
    await writer.close_async()
    return {"ok": True}

worker.register_function("files::generate", generate_file)
```

**Rust**

```rust theme={"theme":{"light":"catppuccin-latte","dark":"dark-plus"}}
use iii_sdk::{ChannelDirection, ChannelWriter, IIIError};
use serde_json::json;

let refs = iii_sdk::extract_channel_refs(&input);
let (_, writer_ref) = refs
    .iter()
    .find(|(k, r)| k == "writer" && matches!(r.direction, ChannelDirection::Write))
    .ok_or_else(|| IIIError::Handler("missing writer channel ref".into()))?;

let writer = ChannelWriter::new(worker.address(), writer_ref);
writer.write(b"hello ").await?;
writer.write(b"world").await?;
writer.close().await?;
Ok(json!({ "ok": true }))
```

> [!NOTE]
>
> For the per-language channel API surface, see the SDK reference:
> [Node](/docs/0-24-0/reference/sdk-node.md), [Python](/docs/0-24-0/reference/sdk-python.md), and
> [Rust](/docs/0-24-0/reference/sdk-rust.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.