Defining a Schema with Zod
Zod is the recommended way to write a headless configuration's schema. One zod schema gives you three things:
- The JSON Schema Prismatic publishes for your app
- The validation Prismatic applies when your app saves values
- The TypeScript type of
context.configurationin your flows
This guide builds the schema of the example Fake CRM integration, which syncs people from a CRM to Acme contacts.
Prerequisites
-
A code-native integration project. To start a new headless integration, run
prism integrations:init my-integration --configuration-type headlesswith Prism 10.6.0 or later. -
@prismatic-io/spectral10.35.0 or later -
zod 4:
npm install zod
Define the schema
-
Create
src/configuration.tsand describe the values each instance saves. Use.meta({ title })to give each field a label your app can display:src/configuration.tsimport { z } from "zod";export const configurationSchema = z.object({recordType: z.string().min(1).meta({ title: "Record type" }),fieldMapping: z.object({email: z.string().min(1).meta({ title: "Email" }),firstName: z.string().optional().meta({ title: "First name" }),lastName: z.string().min(1).meta({ title: "Last name" }),phone: z.string().optional().meta({ title: "Phone" }),company: z.string().optional().meta({ title: "Company" }),jobTitle: z.string().optional().meta({ title: "Job title" }),}).meta({ title: "Field mapping" }),});export type Configuration = z.infer<typeof configurationSchema>;Required fields are required in the published JSON Schema. Fields marked
.optional()aren't. -
Pass the schema to
configuration(), with a version name:src/configuration.tsimport { configuration } from "@prismatic-io/spectral";export const CONFIGURATION_VERSION = "fake-crm-v1";export const integrationConfiguration = configuration({instance: {schema: configurationSchema,version: CONFIGURATION_VERSION,},}); -
Register the configuration's type, so flows can read typed values from
context.configuration:src/configuration.tsdeclare module "@prismatic-io/spectral" {interface IntegrationDefinitionConfiguration extends TIntegrationConfiguration {}}type TIntegrationConfiguration = typeof integrationConfiguration; -
Add the configuration to your integration in
src/index.ts:src/index.tsexport default integration({name: "Fake CRM",flows,configuration: integrationConfiguration,});
Tips for zod schemas
- Use real types. Use
z.number(),z.boolean(), andz.array()where they fit. Values reach your flows with those types, so flows don't parse strings. - Keep display data next to IDs. If a customer picks something from a list, consider saving
{ id, name }rather than only the ID, so your app can show the saved choice without calling a server function. - Use
.meta()for labels and descriptions.titleanddescriptioncarry over to the JSON Schema. - Defaults make fields optional for your app. Spectral publishes the schema's input shape, so a field with
.default()is optional in the JSON Schema your app reads. The JSON Schema includes the field'sdefault, so your app can show it, andinitcan suggest it too. - Prefer constraints JSON Schema can express. Checks written with
.refine()don't appear in the JSON Schema your app reads, so your app can't check them as the customer types. Constraints such as.min(),.max(), andz.enum()carry over.
Check the published schema
To see the JSON Schema your app will receive, convert the schema with zod the way Spectral does.
Spectral also removes the $schema key from the result:
import { toJSONSchema } from "zod";
import { configurationSchema } from "./src/configuration";
console.log(
JSON.stringify(
toJSONSchema(configurationSchema, { io: "input", reused: "inline" }),
null,
2,
),
);
Next steps
- Write server functions that supply your form's dynamic options
- Change your schema safely in later versions