Custom Reports
Use defineReportKind for the normal extension path. It lets you resolve payloads and return a small presentation tree instead of building a custom renderer for each report.
import { z } from "zod";import { createBuiltinRegistry, defineReportKind } from "mlform/engine";
const riskSummaryReport = defineReportKind({ kind: "risk-summary", schema: z.object({ id: z.string().optional(), kind: z.literal("risk-summary"), label: z.string().optional(), source: z.string().optional(), }), resolve: ({ report, result }) => result.reports[report.source], render: { summary: ({ payload }) => ({ title: payload.label ?? "Risk", value: payload.score, tone: payload.score > 0.8 ? "danger" : "neutral", }), content: ({ payload }) => [ { type: "metric", label: "Score", value: payload.score }, { type: "list", label: "Drivers", items: payload.drivers }, ], },});
const registry = createBuiltinRegistry().registerReport(riskSummaryReport);render.content can return text, metric, kv, list, table, badge, notice, or json nodes. The built-in declarative renderer handles the normal layout for you.
If resolve throws, MLForm marks only that report as error; the form submission can still complete for other reports.
Use defineReportDefinition plus a custom primitive renderer only when you need a fully custom visual contract.