---
title: "Build an AI Commerce Insights Dashboard with Medusa and json-render"
description: "Learn how to integrate AI into Medusa Admin using json-render to generate dynamic dashboards and commerce reports from prompts, including abandoned cart, inventory, and sales insights."
url: https://medusajs.com/blog/medusa-json-render
last_updated: 2026-08-13T14:06:41Z
---

# Build an AI Commerce Insights Dashboard with Medusa and json-render

_Published 2026-01-20 by Shahed  Nasser in Product_

Medusa is a digital commerce framework that empowers AI ecommerce and agentic commerce development workflows. Medusa's flexible framework supports developers in building powerful commerce experiences that integrate with the latest technological tools and trends, including LLMs, AI agents, and commerce automation.

[json-render](https://json-render.dev/) is a library that generates UI from user prompts with AI. By integrating json-render with Medusa, you can create dashboards, forms, and reports on the fly that suit the user's needs, making Medusa an ideal AI-ready ecommerce framework for building dynamic admin experiences.

For example, you can allow admin users to query dynamic commerce insights like abandoned carts, product inventory, or sales analytics, then generate the report on the fly using an AI agent.

This guide will cover the general steps to build an AI-powered commerce insights page with json-render and Medusa.

## How it Works

To build an AI commerce reports dashboard with json-render and Medusa, you need:

1. An [API route](https://docs.medusajs.com/learn/fundamentals/api-routes) that returns a JSON stream of the UI to show in the dashboard. To do that, the API route will:
   1. Generate a context prompt that includes the commerce data necessary, like abandoned carts, and the catalog of UI elements.
   2. Pass the context and user prompts to the underlying LLM to generate the JSON.
   3. Return the JSON as a stream.
2. A [UI route](https://docs.medusajs.com/learn/fundamentals/admin/ui-routes) that adds a new page to the Medusa Admin dashboard. It will fetch the JSON from the API route and render the JSON as UI using json-render.

Admin users can then enter prompts from the dashboard, which will generate the JSON and render the UI, creating an AI analytics dashboard inside Medusa.

![](https://assets.medusajs.com/uploads/2026/01/json-render-summary-w28c4yi1.webp)

## Prerequisites: Commerce Framework with Medusa

To follow this guide, you need a Medusa application installed locally. If you don't have one, create a Medusa application with the following command:

```typescript
npx create-medusa-app@latest
```

Refer to the [Installation guide](https://docs.medusajs.com/learn/installation) for more details on prerequisites and troubleshooting.

## Install Dependencies

Next, you need to install the dependencies necessary for the implementation. Run the following command in your Medusa application:

```typescript
yarn install ai @ai-sdk/anthropic @json-render/core @json-render/react zod
```

Where:

- `ai` and `ai-sdk/anthropic`: AI toolkit and SDK provider to generate the UI as JSON. You can replace `ai-sdk/anthropic` with the [provider](https://ai-sdk.dev/providers/ai-sdk-providers) of the LLM you want to use.
- `@json-render/core` and `@json-render/react` : The json-render dependences.
- `zod`: Zod library to create validation schemas. json-render requires it to create the UI catalog, as you'll see later.

## Retrieve Data From Medusa

You'll first create the pieces needed for the AI insights API route.

The API route will retrieve data from Medusa relevant to the user's query. You can retrieve this data dynamically, or define specific functions to retrieve specific data.

For example, if the user's query mentions abandoned carts, you can retrieve the necessary data in Medusa:

```typescript
// src/lib/ai/data-context.ts

async function fetchAbandonedCarts(query: Query, days: number = 7) {
  const startDate = new Date()
  startDate.setDate(startDate.getDate() - days)

  try {
    const { data } = await query.graph({
      entity: "cart",
      fields: [
        "id",
        "email",
        "created_at",
        "updated_at",
        "completed_at",
        "items.id",
        "items.title",
        "items.quantity",
        "items.unit_price",
        "customer.id",
        "customer.first_name",
        "customer.last_name",
        "customer.email",
      ],
      filters: {
        completed_at: null, // Not completed = abandoned
        created_at: {
          $gte: startDate.toISOString(),
        },
      },
      pagination: {
        take: 50,
      },
    })

    return data || []
  } catch (error) {
    console.error("Error fetching carts:", error)
    return []
  }
}

export async function buildDataContext(
  query: Query,
  prompt: string
): Promise<Record<string, any>> {
  const context: Record<string, any> = {}

  if (hasKeywords(prompt, ["cart", "abandon"])) {
    const carts = await fetchAbandonedCarts(query, 7)
    context.abandonedCarts = carts.map((cart) => ({
      id: cart.id,
      email: cart.email || cart.customer?.email || "Guest",
      customerName:
        cart.customer?.first_name && cart.customer?.last_name
          ? `${cart.customer.first_name} ${cart.customer.last_name}`
          : "Guest",
      itemCount: cart.items?.length || 0,
      value: formatCurrency(
        cart.items?.reduce(
          (sum: number, item: any) =>
            sum + (item.unit_price || 0) * (item.quantity || 0),
          0
        ) || 0
      ),
      abandonedAt: new Date(cart.updated_at).toLocaleString(),
    }))
    context.cartAggregates = calculateCartValue(carts)
  }

  // handle other keywords...

  return context
}
```

## Define UI Catalog

You also need to define the [UI catalog](https://json-render.dev/docs/catalog) that json-render will translate into a context prompt. This context prompt will allow AI agents to generate a JSON describing the UI to render.

To ensure the design remains consistent with Medusa's dashboard, you can use the [Medusa UI library](https://docs.medusajs.com/ui). It provides components for containers, statuses, buttons, and more.

```typescript
// src/lib/ai/ai-catalog.ts

import { createCatalog } from "@json-render/core"
import { z } from "zod"

const ContainerSchema = z.object({
  className: z.string().optional().describe("CSS classes for styling"),
})

const StatusBadgeSchema = z.object({
  label: z.string().describe("Badge text"),
  variant: z.enum(["default", "success", "warning", "danger", "info"]).default("default"),
})

// other component schemas...

export const catalog = createCatalog({
  name: "Medusa",
  components: {
    Container: { props: ContainerSchema, hasChildren: true },
    StatusBadge: { props: StatusBadgeSchema, hasChildren: false },
    // other components...
  }
})
```

## AI Insights API Route

You can now create a custom API route that accepts a user's prompt and sends it to the LLM with the catalog and data as context. It streams the JSON of the generated UI in the response.

```typescript
// src/api/admin/ai-insights
import { streamText } from "ai"
import { createAnthropic } from "@ai-sdk/anthropic"
import { generateCatalogPrompt } from "@json-render/core"

export async function POST(
  req: MedusaRequest<AiInsightsSchemaType>,
  res: MedusaResponse
): Promise<void> {
  const { prompt, currentTree } = req.validatedBody

  // Resolve Query
  const query = req.scope.resolve("query")

  // Build data context based on prompt keywords
  const dataContext = await buildDataContext(query, prompt)

  // Generate catalog prompt
  const catalogPrompt = generateCatalogPrompt(catalog)

  const contextPrompt = `
# Available E-Commerce Data

${JSON.stringify(dataContext, null, 2)}

# Current UI State

${currentTree ? JSON.stringify(currentTree, null, 2) : "None - starting fresh"}

# Instructions

Create a visually appealing dashboard layout that answers the user's question using the available data and components.

CRITICAL OUTPUT FORMAT: You must output ONLY JSON patches in streaming format. Each line must be a separate, valid JSON object representing a patch operation. DO NOT format with newlines or indentation within each JSON object. DO NOT wrap in code blocks.

{"op":"set","path":"/root","value":"container1"}
{"op":"add","path":"/elements/container1","value":{"key":"container1","type":"Container","props":{},"children":["row1"]}}
{"op":"add","path":"/elements/row1","value":{"key":"row1","type":"Row","props":{"gap":"medium"},"children":["metric1","metric2","metric3"]}}
{"op":"add","path":"/elements/metric1","value":{"key":"metric1","type":"MetricCard","props":{"label":"Orders Today","value":"23","trend":"up","change":15.5,"icon":"cart"}}}
{"op":"add","path":"/elements/metric2","value":{"key":"metric2","type":"MetricCard","props":{"label":"Revenue","value":"$4,521","trend":"up","change":22.3,"icon":"dollar"}}}
{"op":"add","path":"/elements/metric3","value":{"key":"metric3","type":"MetricCard","props":{"label":"Avg Order Value","value":"$196","trend":"neutral","icon":"dollar"}}}
`

  // Initialize Anthropic client
  const anthropic = createAnthropic({
    apiKey: process.env.ANTHROPIC_API_KEY,
  })

  // Stream the AI response
  const result = streamText({
    model: anthropic("claude-sonnet-4-5-20250929"),
    system: catalogPrompt + "\n\n" + contextPrompt,
    prompt: prompt,
    temperature: 0.7,
    maxRetries: 2,
  })

  res.setHeader("Content-Type", "text/plain; charset=utf-8")
  res.setHeader("Cache-Control", "no-cache")
  res.setHeader("Transfer-Encoding", "chunked")

  // Iterate through stream chunks and write each one
  for await (const chunk of result.textStream) {
    res.write(chunk)
  }
  res.end()
}
```

In the API route, you:

1. Resolve [Query](https://docs.medusajs.com/learn/fundamentals/module-links/query) from the Medusa container. It allows you to retrieve data across modules.
2. Retrieve the data that will be used in the generated UI. For example, if the user asks for abandoned cart insights, the data will be the abandoned carts.
3. Generate the prompt explaining the UI catalog, allowing the LLM to understand what UI components it can include in the generated JSON.
4. Initialize the Anthropic client, passing your Anthropic API key.
5. Stream the response from the LLM. You pass it the catalog and context prompts with the user's prompt.

## Admin Components Registry

Before creating the page that displays the AI-generated UI, you need to define the React components that the generated JSON is translated into. These are the same components you defined Zod schemas for in the catalog earlier.

```typescript
// src/admin/lib/component-registry.tsx
import { ComponentRegistry } from "@json-render/react"
import { Container, StatusBadge } from "@medusajs/ui"

export const componentRegistry: ComponentRegistry = {
  Container,
  StatusBadge,
  // other components...
}
```

## Admin UI Route

Finally, you can define the UI route that will show the new AI insights page in the admin dashboard.

```typescript
// src/admin/routes/ai-insights/page.tsx
import { 
  useUIStream, 
  Renderer, 
  DataProvider, 
  ActionProvider, 
  VisibilityProvider
} from "@json-render/react"

const AIInsightsPage = () => {
  const [prompt, setPrompt] = useState("")

  // Use useUIStream hook for proper tree handling
  const { tree, isStreaming, error, send } = useUIStream({
    api: "/admin/ai-insights",
  })

  const handleSubmit = () => {
    if (prompt.trim() && !isStreaming) {
      send(prompt)
    }
  }

  return (
    <Container>
      {/* Header */}
      <div>
        <div className="flex items-center gap-2 mb-2">
          <Sparkles className="text-ui-fg-interactive" />
          <Heading level="h1">AI Analytics Dashboard</Heading>
        </div>
        <Text size="small" className="text-ui-fg-subtle">
          Ask questions about your store data and get instant insights powered by
          AI
        </Text>
      </div>

      {/* Input Section */}
      <div className="space-y-4 my-2">
        <div className="flex gap-2">
          <div className="flex-1">
            <Input
              placeholder="Ask a question about your store... (e.g., 'Show me abandoned carts')"
              value={prompt}
              onChange={(e) => setPrompt(e.target.value)}
              disabled={isStreaming}
            />
          </div>
          <Button
            onClick={handleSubmit}
            disabled={isStreaming || !prompt.trim()}
            isLoading={isStreaming}
          >
            {isStreaming ? "Generating..." : "Generate"}
          </Button>
        </div>
      {/* AI-sent UI (progressive rendering) */}
      {tree && (
        <div className="mt-6 pt-6">
          <DataProvider initialData={{}}>
            <ActionProvider handlers={{}}>
              <VisibilityProvider>
                <Renderer tree={tree} registry={componentRegistry} />
              </VisibilityProvider>
            </ActionProvider>
          </DataProvider>
        </div>
      )}
      
    </Container>
  )
}

export const config = defineRouteConfig({
  label: "AI Insights",
  icon: Sparkles,
})

export default AIInsightsPage
```

In the UI route, you:

1. Render an input that allows the user to enter the prompt. For example, "Show me abandoned cart insights".
2. When the user submits the prompt, send a request to the `/admin/ai-insights` route.
3. The route will return the generated response as a stream. json-render's `useUIStream` will handle parsing that stream into a UI tree that you render with their `Renderer` component.

## Test Implementation

To test out the implementation, start the Medusa application:

```typescript
yarn dev
```

Then:

1. Go to `localhost:9000/app` and log in.
2. Click on AI Insights in the sidebar.
3. In the input shown, enter a prompt like "Show me abandoned cart insights" and submit the form.
4. The UI will be gradually shown with abandoned cart insights. Based on the components you add to the catalog, the UI can show tables, charts, number insights, and more.

## Start Building with Medusa

If you're new to Medusa, check out the [documentation](https://docs.medusajs.com/learn), where you'll get a more in-depth understanding of what Medusa is, the commerce features it provides, and how to deploy Medusa to [Cloud](https://medusajs.com/pricing/).
