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
- Send messages as an array of UIMessage objects: [{ id, role: 'user', parts: [{ type: 'text', text: '...' }] }]
- Use the AI SDK client (useChat) which serializes messages correctly, instead of hand-built fetch bodies
- Validate/normalize the body in a middleware before it reaches the handler
- 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
- Use useChat from the AI SDK for the client so the body format is always correct
- When testing with curl/scripts, copy the exact UIMessage shape ({ id, role, parts })
- Validate the request body with a schema (zod) in middleware before the handler
- Check for proxies/gateways that might rewrite or unwrap the messages field
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
- runId is required when resumeData is provided
- Query parameters "versionId" and "status" are mutually exclu
- UI Messages require a data property when using data- prefixe
- Messages are required
- messageIds is required
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/84d86f818125736c.
Report an issue: GitHub.