CopilotKit/CopilotKit · error · Error
WhatsApp control value too large to round-trip: encoded id i
Error message
WhatsApp control value too large to round-trip: encoded id is ${encoded.length} chars (max ${WA_LIMITS.controlId}). Use a smaller value or a short key the handler maps. What it means
WhatsApp interactive buttons/rows carry the action id in a payload with a hard character limit (WA_LIMITS.controlId). CopilotKit encodes `actionId::JSON.stringify(value)` into that field; when the encoded string exceeds the limit the renderer refuses to emit a message that could never round-trip back through decodeInteraction.
Source
Thrown at packages/channels-whatsapp/src/render/message.ts:269
if (handler && typeof handler === "object" && "id" in handler) {
const id = (handler as { id?: unknown }).id;
if (typeof id === "string") return id;
}
return undefined;
}
/**
* Build a reply-control id. A bare minted id is short and safe to clamp. When a
* value must round-trip (WhatsApp replies carry only an id, no value field) we
* encode `${id}::${JSON.stringify(value)}`; if that exceeds WhatsApp's 256-char
* id limit it CANNOT round-trip, so we fail loudly rather than truncate (which
* would make decodeInteraction silently parse garbage).
*/
function buildControlId(actionId: string, value: unknown): string {
if (value === undefined) return truncateText(actionId, WA_LIMITS.controlId);
const encoded = `${actionId}::${JSON.stringify(value)}`;
if (encoded.length > WA_LIMITS.controlId) {
throw new Error(
`WhatsApp control value too large to round-trip: encoded id is ${encoded.length} chars ` +
`(max ${WA_LIMITS.controlId}). Use a smaller value or a short key the handler maps.`,
);
}
return encoded;
}
/**
* Flatten a `BotChildren` tree to a single text string. Handles both plain
* string children and `renderToIR`-lowered `{ type: "text", props: { value } }`
* leaf nodes.
*/
function textOf(children: unknown): string {
if (children == null || children === false || children === true) return "";
if (typeof children === "string") return children;
if (typeof children === "number") return String(children);
if (Array.isArray(children)) return children.map(textOf).join("");
const node = children as ChannelNode;View on GitHub (pinned to 68fbe97d87)
Solutions
- Pass a short key (id/index) as value and map it back to the full data inside your handler
- Pre-serialize/truncate large values and store them server-side, sending only an id
- If value is undefined, only actionId is used — prefer omitting value entirely
Example fix
// before
{ type: "list", value: bigProductObject }
// after
{ type: "list", value: bigProductObject.sku } // handler looks up by sku Defensive patterns
Strategy: validation
Validate before calling
const encoded = `${actionId}::${JSON.stringify(value)}`;
if (encoded.length > 256 /* conservative stand-in for WA_LIMITS.controlId */) value = shortKeyFor(value); Type guard
function isControlValueSafe(actionId: string, value: unknown): boolean {
return value === undefined || `${actionId}::${JSON.stringify(value)}`.length <= 200;
} Try / catch
null
Prevention
- Pass short ids/keys as values, look up full data in the handler
- Never put base64 or long strings in control values
- Add a unit test asserting encoded lengths stay under the limit
When it happens
Trigger: Rendering a control (list item, button, etc.) with a large `value` prop — e.g. a whole object, long string, or base64 blob — so actionId+JSON exceeds the limit.
Common situations: Passing full row data (records, URLs, base64 images) as the value instead of a key; generating dynamic values that grow unbounded.
Related errors
- WhatsApp uploadMedia failed: ${res.status} ${await safeText(
- WhatsApp uploadMedia returned no id
- WhatsApp media meta failed: ${metaRes.status}
- WhatsApp media meta returned no url
- WhatsApp media download failed: ${blobRes.status}
AI-assisted analysis of CopilotKit/CopilotKit@68fbe97d87 (2026-08-27).
Data as JSON: /api/errors/ff3b11dee8e3050c.
Report an issue: GitHub.