mastra-ai/mastra · error

Messages must be an array of UIMessage objects

Error message

Messages must be an array of UIMessage objects

What it means

The chat route expects the request body's messages field to be an array of AI SDK UIMessage objects (the useChat wire format). If messages is missing or not an array, the handler cannot build the stream and throws before invoking the agent. This is an input-shape validation guard.

Source

Thrown at client-sdks/ai-sdk/src/chat-route.ts:327

  // same or it would execute a stale/empty code-defined agent (issue #18574). An
  // explicit agentVersion (from query params or route options) wins; otherwise we
  // default to the published version, matching the built-in agent handlers.
  let agentObj = baseAgent;
  const editorAgent = mastra.getEditor?.()?.agent;
  if (editorAgent) {
    agentObj = await editorAgent.applyStoredOverrides(
      baseAgent,
      agentVersion ?? { status: 'published' },
      requestContext as RequestContext | undefined,
    );
  } else if (agentVersion) {
    // No editor configured: preserve the prior behavior of surfacing the
    // "editor required for versioned agent lookup" error for explicit versions.
    agentObj = await mastra.getAgentById(agentId, agentVersion);
  }

  if (!Array.isArray(messages)) {
    throw new Error('Messages must be an array of UIMessage objects');
  }

  // Capture the last assistant message ID for the stream response.
  // This helps the frontend identify which message the response corresponds to.
  let lastMessageId: string | undefined;
  let messagesToSend = messages;

  if (messages.length > 0) {
    const lastMessage = messages[messages.length - 1]!;
    if (lastMessage?.role === 'assistant') {
      lastMessageId = lastMessage.id;

      // For regeneration, remove the last assistant message so the LLM generates fresh text
      if (trigger === 'regenerate-message') {
        messagesToSend = messages.slice(0, -1);
      }
    }
  }

View on GitHub (pinned to 75dd419e61)

Solutions

  1. Send messages as an array of UIMessage objects: [{ id, role: 'user', parts: [{ type: 'text', text: '...' }] }]
  2. Use the AI SDK client (useChat) which serializes messages correctly, instead of hand-built fetch bodies
  3. Validate/normalize the body in a middleware before it reaches the handler
  4. Log the incoming body to confirm the messages field survives any proxies/gateways

Example fix

// before
fetch('/api/chat/my-agent', { method: 'POST', body: JSON.stringify({ prompt: 'hi' }) })
// after
fetch('/api/chat/my-agent', { method: 'POST', body: JSON.stringify({
  messages: [{ id: 'm1', role: 'user', parts: [{ type: 'text', text: 'hi' }] }]
}) })
Defensive patterns

Strategy: type-guard

Validate before calling

function isUIMessageArray(v: unknown): v is Array<{ id: string; role: string; parts: unknown[] }> {
  return Array.isArray(v) && v.every(m => m && typeof m === 'object' && 'role' in m && 'parts' in m);
}
if (!isUIMessageArray(body.messages)) throw new Error('messages must be a UIMessage[]');

Type guard

function isUIMessage(m: unknown): m is { id: string; role: 'user' | 'assistant' | 'system'; parts: unknown[] } {
  return (
    typeof m === 'object' && m !== null &&
    typeof (m as any).id === 'string' &&
    typeof (m as any).role === 'string' &&
    Array.isArray((m as any).parts)
  );
}

Try / catch

try {
  const stream = await handleChatStream({ ...params });
} catch (err) {
  if (err instanceof Error && err.message.includes('must be an array of UIMessage')) {
    return new Response(JSON.stringify({ error: 'INVALID_MESSAGES' }), { status: 400 });
  }
  throw err;
}

Prevention

When it happens

Trigger: POSTing a body where messages is a single object, a string, or absent; custom clients (curl, scripts) that send prompt-style payloads instead of the UIMessage array format.

Common situations: Calling the endpoint manually with { prompt: '...' }; migrating from v4-style { messages: [...] } with different message shapes; proxy layers that unwrap the array; frontend bug sending undefined messages before chat init.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30). Data as JSON: /api/errors/84d86f818125736c. Report an issue: GitHub.