Skip to main content

Configuration Schema

A headless configuration's schema defines the values each instance saves. Prismatic publishes it as JSON Schema so your app can build a form from it, validates saved values against it, and passes the values to your flows already parsed.

You can write the schema with zod (recommended) or as a JSON Schema object. See Defining a schema with zod for a step-by-step guide.

Where the schema lives​

The schema belongs to the instance scope of your integration's configuration. Alongside it, you set a version and, optionally, a ui layout:

src/configuration.ts
import { configuration } from "@prismatic-io/spectral";
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" }),
lastName: z.string().min(1).meta({ title: "Last name" }),
phone: z.string().optional().meta({ title: "Phone" }),
}),
});

export const integrationConfiguration = configuration({
instance: {
schema: configurationSchema,
version: "fake-crm-v1",
ui: {
type: "uiSchema",
value: {
type: "VerticalLayout",
elements: [{ type: "Control", scope: "#/properties/recordType" }],
},
},
},
});
PropertyRequiredDescription
schemaYesA zod schema or a JSON Schema object that describes the saved values.
versionYesA name for this shape of the schema. Change it when saved values can no longer satisfy the schema. See Configuration versions.
uiNoA layout for your app to use: { type: "uiSchema", value } with a JSON Forms layout, or { type: "custom", value } with any value your app understands.
versionSchemasNoSchemas of earlier versions, so init can read values saved in an older shape.
connectionsNoConnections the instance needs. See Connections.

Then pass the configuration to your integration:

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

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

An integration with a configuration doesn't use configPages.

What your app receives​

When you publish your integration, Spectral converts a zod schema to JSON Schema. Your app reads it from the configuration resource in Solis. Solis passes the ui layout to your app unchanged, as configuration.data.ui, which is absent or null when the integration doesn't define one. Prismatic doesn't interpret or validate a custom layout's value. The schema above publishes as:

Published JSON Schema
{
"type": "object",
"properties": {
"recordType": { "type": "string", "minLength": 1, "title": "Record type" },
"fieldMapping": {
"type": "object",
"properties": {
"email": { "type": "string", "minLength": 1, "title": "Email" },
"lastName": { "type": "string", "minLength": 1, "title": "Last name" },
"phone": { "type": "string", "title": "Phone" }
},
"required": ["email", "lastName"]
}
},
"required": ["recordType", "fieldMapping"]
}

Your app can render a form from the schema generically, or render a form built for one integration and use the schema only for labels and required fields. The example app's form does the latter: it reads each Acme field's title and whether it's required from the schema.

JSON Schema properties have no order

Don't rely on the order of a schema's properties. If your app needs fields in a particular order, list them in the ui layout, which is ordered, and read the order from configuration.data.ui.

Validation​

When your app saves values, Prismatic validates them against the schema. If any value is invalid, nothing is saved, and the save returns an error with the code PRISMATIC_CONFIGURATION_INVALID and a list of fields, each with a JSON path and a message. A field's path is null when the error applies to the configuration as a whole. Your app can show each message next to the field it belongs to.

Your app can also validate as the customer types, but Prismatic's validation on save is the one that counts.

Using configuration in flows​

Every flow's context has a configuration property. To type it from your schema, register your configuration's type in the file that defines your configuration:

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

type TIntegrationConfiguration = typeof integrationConfiguration;

context.configuration is then typed from your schema. It's undefined when the instance has no saved configuration:

src/flows.ts
onExecution: async (context) => {
const { configuration } = context;
if (!configuration) {
throw new Error("This instance has no saved configuration.");
}
// configuration.fieldMapping.email is a string
return { data: configuration.fieldMapping };
},

Values arrive with their real types. Unlike config variables, which store every value as a string, a number in the schema is a number in the flow, and an array is an array.

User-level configuration​

An integration can also ask each user of an instance for their own settings. Add a userLevel scope with its own schema and version, and flows read those values from context.userConfiguration. Your app edits them with useUserConfiguration. See User-level configuration for when to use user-level configuration.