Skip to main content

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.configuration in 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 headless with Prism 10.6.0 or later.

  • @prismatic-io/spectral 10.35.0 or later

  • zod 4:

    npm install zod

Define the schema​

  1. Create src/configuration.ts and describe the values each instance saves. Use .meta({ title }) to give each field a label your app can display:

    src/configuration.ts
    import { 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.

  2. Pass the schema to configuration(), with a version name:

    src/configuration.ts
    import { configuration } from "@prismatic-io/spectral";

    export const CONFIGURATION_VERSION = "fake-crm-v1";

    export const integrationConfiguration = configuration({
    instance: {
    schema: configurationSchema,
    version: CONFIGURATION_VERSION,
    },
    });
  3. Register the configuration's type, so flows can read typed values from context.configuration:

    src/configuration.ts
    declare module "@prismatic-io/spectral" {
    interface IntegrationDefinitionConfiguration extends TIntegrationConfiguration {}
    }

    type TIntegrationConfiguration = typeof integrationConfiguration;
  4. Add the configuration to your integration in src/index.ts:

    src/index.ts
    export default integration({
    name: "Fake CRM",
    flows,
    configuration: integrationConfiguration,
    });

Tips for zod schemas​

  • Use real types. Use z.number(), z.boolean(), and z.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. title and description carry 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's default, so your app can show it, and init can 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(), and z.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:

print-schema.ts
import { toJSONSchema } from "zod";
import { configurationSchema } from "./src/configuration";

console.log(
JSON.stringify(
toJSONSchema(configurationSchema, { io: "input", reused: "inline" }),
null,
2,
),
);

Next steps​