# Defining a Schema with Zod

[Zod](https://zod.dev) is the recommended way to write a headless configuration's [schema](https://prismatic.io/docs/integrations/headless-configuration/schema.md). 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[​](#prerequisites "Direct link to Prerequisites")

* A [code-native integration](https://prismatic.io/docs/integrations/code-native.md) 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:

  ```bash
  npm install zod

  ```

## Define the schema[​](#define-the-schema "Direct link to 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

   ```typescript
   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

   ```typescript
   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

   ```typescript
   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

   ```typescript
   export default integration({
     name: "Fake CRM",
     flows,
     configuration: integrationConfiguration,
   });

   ```

## Tips for zod schemas[​](#tips-for-zod-schemas "Direct link to 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[​](#check-the-published-schema "Direct link to 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

```typescript
import { toJSONSchema } from "zod";
import { configurationSchema } from "./src/configuration";

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

```

## Next steps[​](#next-steps "Direct link to Next steps")

* [Write server functions](https://prismatic.io/docs/integrations/headless-configuration/server-functions.md) that supply your form's dynamic options
* [Change your schema safely](https://prismatic.io/docs/integrations/headless-configuration/configuration-versions.md) in later versions
