import Link from "next/link"; import { Code } from "@/components/code"; export const metadata = { title: "Custom Schema & Renderer | json-render", }; export default function CustomSchemaPage() { return (

Custom Schema & Renderer

Build your own schema and renderer with{" "} @json-render/core.

Overview

@json-render/core is schema-agnostic. While{" "} @json-render/react provides a ready-to-use schema and renderer, you can create your own to match any JSON structure - whether it's a domain-specific format, an existing protocol, or something entirely custom.

1. Define Your Schema

Start by defining the JSON structure your system will use. Here's an example of a simple dashboard schema:

{`{ "layout": "grid", "columns": 2, "widgets": [ { "type": "metric", "title": "Revenue", "value": "$12,345", "trend": "up" }, { "type": "chart", "title": "Sales", "chartType": "line", "dataKey": "salesData" }, { "type": "table", "title": "Recent Orders", "columns": ["id", "customer", "amount"], "dataKey": "orders" } ] }`}

2. Create the Catalog

Define a catalog that describes your components and validates props:

{`import { createCatalog } from '@json-render/core'; import { z } from 'zod'; export const dashboardCatalog = createCatalog({ components: { metric: { description: 'Displays a single metric value', props: z.object({ title: z.string(), value: z.string(), trend: z.enum(['up', 'down', 'flat']).optional(), change: z.string().optional(), }), }, chart: { description: 'Renders a chart visualization', props: z.object({ title: z.string(), chartType: z.enum(['line', 'bar', 'pie', 'area']), dataKey: z.string(), height: z.number().optional(), }), }, table: { description: 'Displays tabular data', props: z.object({ title: z.string(), columns: z.array(z.string()), dataKey: z.string(), pageSize: z.number().optional(), }), }, text: { description: 'Displays text content', props: z.object({ content: z.string(), variant: z.enum(['heading', 'body', 'caption']).optional(), }), }, }, });`}

3. Define the Root Schema

Create a schema for the overall document structure:

{`import { z } from 'zod'; const WidgetSchema = z.object({ type: z.string(), title: z.string().optional(), // Additional props validated by catalog }).passthrough(); export const DashboardSchema = z.object({ layout: z.enum(['grid', 'stack', 'tabs']), columns: z.number().optional(), widgets: z.array(WidgetSchema), }); export type Dashboard = z.infer; export type Widget = z.infer;`}

4. Build the Renderer

Create a renderer that maps your schema to React components:

{`import React from 'react'; import { dashboardCatalog } from './catalog'; import type { Dashboard, Widget } from './schema'; // Widget component registry const widgetComponents: Record> = { metric: ({ title, value, trend, change }) => (

{title}

{value}

{trend && (

{trend === 'up' ? '+' : '-'}{change}

)}
), chart: ({ title, chartType, data }) => (

{title}

{/* Your chart library here */} {chartType} chart
), table: ({ title, columns, data }) => (

{title}

{columns.map((col: string) => ( ))} {data?.map((row: any, i: number) => ( {columns.map((col: string) => ( ))} ))}
{col}
{row[col]}
), text: ({ content, variant = 'body' }) => { const className = { heading: 'text-xl font-bold', body: 'text-base', caption: 'text-sm text-muted-foreground', }[variant]; return

{content}

; }, }; // Main renderer export function DashboardRenderer({ spec, data = {}, }: { spec: Dashboard; data?: Record; }) { const layoutClass = { grid: \`grid gap-4 \${spec.columns ? \`grid-cols-\${spec.columns}\` : 'grid-cols-2'}\`, stack: 'flex flex-col gap-4', tabs: 'space-y-4', }[spec.layout]; return (
{spec.widgets.map((widget, index) => { const Component = widgetComponents[widget.type]; if (!Component) { console.warn(\`Unknown widget type: \${widget.type}\`); return null; } // Resolve data references const widgetData = widget.dataKey ? data[widget.dataKey] : undefined; return ( ); })}
); }`}

5. Generate LLM Prompts

Use the catalog to generate system prompts for AI:

{`const systemPrompt = dashboardCatalog.prompt({ customRules: [ 'Use metric widgets for single KPI values', 'Use chart widgets for time-series data', 'Use table widgets for lists of records', 'Limit dashboards to 6 widgets maximum', ], }); // Use with any LLM const response = await generateText({ model: 'gpt-4', system: systemPrompt, prompt: 'Create a sales dashboard with revenue, orders, and a chart', });`}

6. Validate Specs

Validate incoming specs against your schema:

{`import { validate } from '@json-render/core'; function validateDashboard(spec: unknown) { // Validate root structure const rootResult = DashboardSchema.safeParse(spec); if (!rootResult.success) { return { valid: false, errors: rootResult.error.errors }; } // Validate each widget against catalog const errors: string[] = []; for (const widget of rootResult.data.widgets) { const result = validate( { type: widget.type, props: widget }, dashboardCatalog ); if (!result.valid) { errors.push(...result.errors.map(e => \`\${widget.type}: \${e}\`)); } } return { valid: errors.length === 0, errors }; }`}

Usage Example

{`'use client'; import { useState } from 'react'; import { DashboardRenderer } from './renderer'; import type { Dashboard } from './schema'; const initialSpec: Dashboard = { layout: 'grid', columns: 2, widgets: [ { type: 'metric', title: 'Revenue', value: '$12,345', trend: 'up' }, { type: 'metric', title: 'Orders', value: '156', trend: 'up' }, { type: 'chart', title: 'Sales Trend', chartType: 'line', dataKey: 'sales' }, { type: 'table', title: 'Recent Orders', columns: ['id', 'customer', 'amount'], dataKey: 'orders' }, ], }; const data = { sales: [/* chart data */], orders: [ { id: '001', customer: 'Acme Inc', amount: '$500' }, { id: '002', customer: 'Globex', amount: '$750' }, ], }; export function MyDashboard() { const [spec, setSpec] = useState(initialSpec); return ; }`}

Next

See how to integrate with{" "} A2UI {" "} or{" "} Adaptive Cards {" "} protocols.

); }