Using OpenUI
OpenUI is an open standard for generative UI. It pairs a compact streaming-first language (OpenUI Lang) with a React runtime and built-in component libraries, so model output can render as structured UI as it streams.
OpenUI connects to Mastra through the AG-UI protocol. The @ag-ui/mastra adapter wraps a Mastra Agent and emits AG-UI events, which OpenUI's agUIAdapter() parses on the client.
For a complete working example, see the mastra-chat example in the OpenUI repository.
Integration guideDirect link to Integration guide
Embed Mastra in your Next.js API route and connect an OpenUI <AgentInterface /> chat surface to it through the AG-UI protocol.
Scaffold a new OpenUI app:
- npm
- pnpm
- Yarn
- Bun
npx @openuidev/cli@latest create --name openui-mastra-chatpnpm dlx @openuidev/cli@latest create --name openui-mastra-chatyarn dlx @openuidev/cli@latest create --name openui-mastra-chatbun x @openuidev/cli@latest create --name openui-mastra-chatNavigate to your newly created project directory:
cd openui-mastra-chatThe scaffolded app is a Next.js project with the following structure:
openui-mastra-chat└── src├── app│ ├── api│ │ └── chat│ │ └── route.ts│ ├── globals.css│ ├── layout.tsx│ └── page.tsx├── generated│ └── system-prompt.txt└── library.tsThe chat route lives in
src/app/api/chat/route.ts, the chat surface insrc/app/page.tsx, and the component library insrc/library.ts. The OpenUI CLI writessrc/generated/system-prompt.txtfrom your library; regenerate it whenever the library changes.Add your OpenAI key to
.env.local:.env.localOPENAI_API_KEY=sk-...noteOpenUI requires a model provider key. Use any provider supported by Mastra and adjust the agent configuration in the next step.
Install the Mastra packages and the AG-UI adapter for Mastra:
- npm
- pnpm
- Yarn
- Bun
npm install @mastra/core @ag-ui/mastra @ag-ui/core zodpnpm add @mastra/core @ag-ui/mastra @ag-ui/core zodyarn add @mastra/core @ag-ui/mastra @ag-ui/core zodbun add @mastra/core @ag-ui/mastra @ag-ui/core zod@ag-ui/mastrawraps a MastraAgentin aMastraAgentthat emits AG-UI protocol events. OpenUI'sagUIAdapter()consumes those events on the client.Open
src/app/api/chat/route.ts. Define any tools your agent needs withcreateToolfrom@mastra/core/tools:src/app/api/chat/route.tsimport { createTool } from '@mastra/core/tools'import { z } from 'zod'const getWeather = createTool({id: 'get_weather',description: 'Get current weather for a city.',inputSchema: z.object({ location: z.string().describe('City name') }),execute: async ({ location }) => {return { location, temperature_celsius: 22, condition: 'Clear' }},})Wrap a Mastra
AgentinMastraAgent. Inject the generated system prompt so the agent knows how to use the OpenUI component library:src/app/api/chat/route.tsimport { MastraAgent } from '@ag-ui/mastra'import { Agent } from '@mastra/core/agent'import { readFileSync } from 'fs'import { join } from 'path'const systemPrompt = readFileSync(join(process.cwd(), 'src/generated/system-prompt.txt'), 'utf-8')const agent = new MastraAgent({agent: new Agent({id: 'openui-agent',name: 'OpenUI Agent',instructions: `You are a helpful assistant. Use tools when relevant.\n\n${systemPrompt}`,model: {id: 'openai/gpt-5.5',apiKey: process.env.OPENAI_API_KEY,},tools: { getWeather },}),resourceId: 'chat-user',})Export a
POSThandler that streams the agent's AG-UI events as Server-Sent Events (SSE):src/app/api/chat/route.tsimport type { Message } from '@ag-ui/core'import { NextRequest } from 'next/server'export async function POST(req: NextRequest) {const { messages, threadId }: { messages: Message[]; threadId: string } = await req.json()const encoder = new TextEncoder()const stream = new ReadableStream({start(controller) {const subscription = agent.run({ messages, threadId, runId: crypto.randomUUID(), tools: [], context: [] }).subscribe({next: event => {controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`))},complete: () => {controller.enqueue(encoder.encode('data: [DONE]\n\n'))controller.close()},error: error => {controller.enqueue(encoder.encode(`data: ${JSON.stringify({ error: error.message })}\n\n`),)controller.close()},})req.signal.addEventListener('abort', () => subscription.unsubscribe())},})return new Response(stream, {headers: {'Content-Type': 'text/event-stream','Cache-Control': 'no-cache, no-transform',Connection: 'keep-alive',},})}Wire the OpenUI
<AgentInterface />chat surface to the route. Build anllmadapter withfetchLLM()and setstreamAdaptertoagUIAdapter()so OpenUI knows to parse AG-UI events.src/app/page.tsx'use client'import '@openuidev/react-ui/components.css'import { AgentInterface, agUIAdapter, fetchLLM } from '@openuidev/react-ui'import { openuiChatLibrary } from '@openuidev/react-ui/genui-lib'const llm = fetchLLM({url: '/api/chat',streamAdapter: agUIAdapter(),})export default function Page() {return (<div className="relative h-screen w-screen overflow-hidden"><AgentInterface llm={llm} componentLibrary={openuiChatLibrary} /></div>)}The
componentLibraryprop controls which components the model can generate. ReplaceopenuiChatLibrarywith your own library to restrict or extend the output.Start the development server:
- npm
- pnpm
- Yarn
- Bun
npm run devpnpm run devyarn devbun run devOpen http://localhost:3000. You can now chat with your Mastra agent through the OpenUI chat surface, with structured UI rendered progressively as the model streams.
Streaming with AG-UIDirect link to Streaming with AG-UI
OpenUI consumes the AG-UI protocol, a transport-agnostic stream of typed events for AI agents. @ag-ui/mastra translates a Mastra Agent into this protocol:
- The server calls
agent.run({ messages, threadId, runId, ... })and serializes each emitted event as an SSE message. - The client passes
fetchLLM({ streamAdapter: agUIAdapter() })to<AgentInterface />, which parses the SSE stream into the internal events that drive OpenUI Lang rendering.
threadId ties a conversation together across requests, and runId identifies a single execution. Generate a fresh runId per request and persist threadId on the client.
Component librariesDirect link to Component libraries
OpenUI generates UI from a component library. The library defines which components are available, their props, and how the model is instructed to use them.
Built-in librariesDirect link to Built-in libraries
@openuidev/react-ui ships two libraries you can use as-is:
openuiChatLibrary: components for chat interfaces (cards, forms, tables, charts).openuiDashboardLibrary: components for dashboards and data-heavy surfaces.
Pass the library to <AgentInterface componentLibrary={...} /> to make its components available to the model.
Customizing the libraryDirect link to Customizing the library
To restrict or extend the output, define your own library in src/library.ts and export a subset of components. Pass that library to <AgentInterface /> and regenerate the system prompt whenever the library changes:
- npm
- pnpm
- Yarn
- Bun
npx @openuidev/cli generate src/library.ts --out src/generated/system-prompt.txt
pnpm dlx @openuidev/cli generate src/library.ts --out src/generated/system-prompt.txt
yarn dlx @openuidev/cli generate src/library.ts --out src/generated/system-prompt.txt
bun x @openuidev/cli generate src/library.ts --out src/generated/system-prompt.txt
The generated prompt is read by the API route and merged into the agent's instructions, so the agent knows exactly which components it can emit.