Headless Config Overview
When customers activate an instance of your integration, they need to supply the information that makes the integration work for them: which records to sync, how fields map, where to send notifications. Prismatic offers two ways to collect that information:
- Hosted configuration: Your app embeds Prismatic's config wizard in an iframe. You define config pages and config variables, and Prismatic renders the wizard.
- Headless configuration: Your app renders the configuration experience with its own UI components. You define the shape of the configuration in your code-native integration, and Prismatic handles validation, storage, and instance lifecycle.
Both approaches are fully supported. Hosted configuration is the quickest path to a working config wizard. Choose headless configuration when you want the configuration experience to look and feel exactly like the rest of your app.
How headless configuration works
A headless integration replaces config pages with a configuration definition.
You write it in TypeScript with Spectral's configuration() function, and it has four parts:
| Part | What it does |
|---|---|
| Schema | Defines the shape of the values an instance saves. You write it with zod or JSON Schema. See Schema. |
| Version | Names the current shape of the schema, such as acme-crm-v1. See Configuration versions. |
| Init | Suggests starting values for your instance's configuration. It helps when converting a hosted configuration to headless, or when upgrading versions. |
| Server functions | Functions your app calls while a customer configures an instance, such as listing the record types in a customer's CRM. See Server functions. |
The configuration can also declare the connections the integration needs.
import { configuration } from "@prismatic-io/spectral";
import { z } from "zod";
export const integrationConfiguration = configuration({
instance: {
schema: z.object({
recordType: z.string().min(1),
}),
version: "fake-crm-v1",
},
});
Your app then uses Solis, Prismatic's custom UI SDK for building rich configuration experiences, to drive the configuration:
- Your app reads the configuration's schema and the list of server functions the integration defines.
- Your app calls init to get suggested values, and shows them in its own form.
- As the customer fills in the form, your app calls server functions for dynamic data, such as dropdown options or a preview.
- Your app saves the customer's values. Prismatic validates them against the schema and returns any invalid fields.
- Your app deploys the instance, which starts running with the saved values.
When a flow runs, it reads the saved values, already parsed and typed, from context.configuration:
export const syncContacts = flow({
name: "Sync Contacts to Acme",
stableKey: "sync-contacts-to-acme",
onExecution: async (context) => {
// Typed from the schema, and validated when it was saved.
// To type it, register your configuration as shown in Configuration schema.
const { configuration } = context;
if (!configuration) {
throw new Error("This instance has no saved configuration.");
}
context.logger.info(`Syncing ${configuration.recordType} records`);
return { data: null };
},
});
Scheduled flows
A headless integration has no config variables, so a flow's schedule can't come from one. Instead, a scheduled flow uses either a fixed schedule or a schedule that whoever deploys the instance supplies:
export const nightlySync = flow({
name: "Nightly Sync",
stableKey: "nightly-sync",
// A fixed schedule: { value: "0 2 * * *", timezone: "America/Chicago" }
// Or let the deploying user choose:
schedule: { fromDeployer: true },
onExecution: async (context) => {
// ...
return { data: null };
},
});
For a fromDeployer schedule, your app collects the schedule in its configuration UI and passes it to Solis when it saves the configuration.
See Flow schedules.
What Prismatic handles for you
With headless configuration, your app owns the UI and Prismatic owns everything behind it:
- Validation: Prismatic validates saved values against your schema and reports errors for each field.
- Storage: Prismatic stores values as JSON with their real types, not as strings. A number stays a number, and a list stays a list.
- Dynamic data: Server functions run in Prismatic, next to your integration's code and connections, so your app never handles third-party credentials.
- Versioning: When a new version of your integration changes the configuration's shape, Prismatic tells your app that the instance needs to be reconfigured, and your
initfunction migrates its values. - Instance lifecycle: Creating, deploying, updating, pausing, and removing instances work the same way as they do with hosted configuration.
View and edit configuration in Prismatic
Your team can review and change a headless instance's configuration from the Prismatic app, for example to help a customer troubleshoot.
Open the instance and select its Configuration tab.
Prismatic shows the instance's saved values as a read-only form built from your schema.
If your configuration's ui is a JSON Forms layout ({ type: "uiSchema", value }), the form uses that layout.
Otherwise, including when ui is { type: "custom", value }, Prismatic arranges the form from the schema alone.

To change the configuration:
- Click Reconfigure.
Prismatic fills the form with your
initfunction's suggested values. If the integration has noinit, the form starts from the instance's saved values, with your schema's defaults for any values that aren't saved. - Edit the values.
- Click Save and deploy. Prismatic validates the values against your schema and highlights any invalid fields. Valid values are saved, and the instance is deployed with them.
To edit the configuration, you need permission to both update and deploy the instance.
Prismatic runs init with the connections it declares, so you can't start editing until each one can be used: it must be connected for the customer, and it can't be a user-level connection.
If a customer has several connections of the same kind and the instance isn't linked to one of them, Prismatic can't choose one for you.
The Configuration tab lists the connections that are blocking you.
Fill in values with server functions
The Server functions panel lets you run your integration's server functions for the instance, such as looking up a record type or fetching a sample record. Running a function doesn't change the configuration. To open the panel, click the { } button at the top of the Configuration tab. Then choose a function, enter its inputs, and click Run. You can explore the response as a Tree, or switch to Values to search a flat list of every value and its path. The same connection requirements that apply to editing apply to running a function.
You can copy a value from a response into the configuration while you're editing:
- Click the { } button next to a form field, run a function, and click Use beside the value you want.
- Or click Use… beside a value in a response, and then choose the field it should fill.
You can use a single value or a whole object, such as a sample record. Prismatic offers only the fields whose schema accepts the value. Inserted values become part of your unsaved changes, so click Save and deploy to keep them.
Choosing between hosted and headless configuration
You might choose hosted configuration when:
- You want a working config wizard with the least effort
- Your configuration is a set of connections and simple inputs
- You build low-code integrations
You might choose headless configuration when:
- The configuration experience needs to match your app's design system exactly
- You want complex interactions, such as a field mapper with a live preview, that are easier to build in your own components
- You want saved values with real types that your flows don't need to parse
You can mix both approaches.
Each integration version reports its configuration experience, hosted or headless, so your app can render its own form for headless integrations and open Prismatic's wizard for the rest.
Try the example
The example embedded app includes a Headless Configuration page and the Fake CRM integration it configures. A customer chooses a record type in a mock CRM, maps its fields to Acme contact fields, and previews the result before saving. To build and run it step by step, follow Get started with headless configuration.