Get Started with Headless Config
In this tutorial, you run a complete headless configuration example from start to finish. You publish a code-native integration called Fake CRM, configure it from your own app with a field mapper and a live preview, run its flow, and then publish an update.
The integration syncs people from Fake CRM, a stand-in for a Salesforce-like CRM, to contacts in Acme, your app.
Each customer stores people differently: some use the standard Lead or Contact record types, and one customer, Hooli, uses a custom Hooli_Leads__c record type with its own field names.
So each customer chooses a record type and maps its fields to Acme's contact fields.
Fake CRM's data is static, so the integration needs no connections or credentials.
Before you begin
You need:
- A Prismatic organization with the headless configuration feature enabled. Contact your Prismatic representative to enable it.
- An admin account in that organization
- The Prism CLI 10.6.0 or later, logged in with
prism login - Bun and Node.js 22.19.0 or later, which Prism 11 requires
- A customer in your organization to sign in as. The example app signs in as an embedded user of this customer.
Run the example
The example lives in Prismatic's embedded repository on GitHub. It has two parts: the example embedded app, and the integration it configures.
git clone https://github.com/prismatic-io/embedded.git
cd embedded/examples/example-embedded-app
| Path | What it is |
|---|---|
integrations/fake-crm/ | The Fake CRM code-native integration |
frontend/routes/examples/headless-configuration.tsx | The app page that lists integrations and configures instances |
frontend/components/headless/fake-crm-configuration-form.tsx | The field mapper form |
Open integrations/fake-crm/src/configuration.ts.
Instead of config pages, the integration defines a configuration with these parts:
-
A schema, written with zod, that describes what each instance saves: a record type, and the Fake CRM field each Acme contact field is read from.
integrations/fake-crm/src/configuration.tsexport 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" }),}); -
A version,
fake-crm-v1, which names this shape of the schema. -
Three server functions the app calls while a customer configures an instance:
listRecordTypesreturns the record types in the customer's CRM.listFieldsreturns the fields of one record type.getSampleRecordreturns a sample record and the Acme contact the customer's unsaved mapping makes from it.
-
An init function that suggests starting values: the saved values when there are some, and a standard Lead mapping when there aren't.
Then open integrations/fake-crm/src/flows.ts.
The Sync Contacts to Acme flow reads the saved values from context.configuration, maps each Fake CRM record to an Acme contact, and logs what it would send to Acme.
Build the integration and import it into Prismatic:
cd integrations/fake-crm
bun install
bun run import
The command prints the new integration's ID. Publish the integration, which prints the ID of the published version, and make that version available in your marketplace:
prism integrations:publish <integration-id> --comment "First version"
prism integrations:marketplace <version-id> --available --deployable
You can also publish and manage marketplace availability in Prismatic's web app.
Return to the example-embedded-app directory and follow the app's setup instructions.
You create a .env.local file with your organization and customer, and an RSA key pair so the app can sign embedded JWTs.
The README lists every value the app requires.
Set PRISMATIC_ROLE to admin, so the embedded user can activate integrations.
Then start the app:
cd ../..
bun install
bun run dev
Open http://localhost:3000 and click Headless Configuration in the sidebar.
The page lists your marketplace with the app's own card components. Each card shows the integration's configuration experience: headless for Fake CRM, and hosted for integrations that use a config wizard.
-
On the Fake CRM card, click Set up, then click Activate Fake CRM. The app creates an instance that isn't deployed yet, and runs the integration's
initfunction to get starting values. -
Choose Hooli Lead, the custom record type, and click Next: map fields. The record types came from the
listRecordTypesserver function. -
Map each Acme field to a
Hooli_Leads__cfield:- Email to Primary Email
- First name to Given Name
- Last name to Family Name
- Company to Organization
- Job title to Job Title
Leave Phone set to Don't sync. The dropdown options come from the
listFieldsserver function. -
Watch the Preview in Acme panel. Each time you change the mapping, the app sends the unsaved mapping to the
getSampleRecordserver function, and shows the Acme contact it makes from a sample Hooli lead. -
Click Save and activate. The app saves the values, which Prismatic validates against the schema, and then deploys the instance.
The instance's status changes to Active, and the page shows the saved configuration.
The page shows a curl command with the flow's webhook URL.
Run it:
curl -X POST 'https://hooks.prismatic.io/trigger/...'
In Prismatic, open your customer's Fake CRM instance and view its logs. The flow logged each Hooli lead as an Acme contact, using your mapping:
Mapping 2 Hooli_Leads__c records to Acme contacts
Sending contact to Acme: {"email":"gavin.belson@hooli.example","firstName":"Gavin","lastName":"Belson","company":"Hooli","jobTitle":"Chief Executive Officer"}
Add a field to the configuration, and see how an update reaches the instance.
-
In
integrations/fake-crm/src/configuration.ts, add an optionaldepartmentfield tofieldMapping:department: z.string().optional().meta({ title: "Department" }),Add
"department"to the list of field mapping keys in theuilayout, so the app knows where to show it. -
In
integrations/fake-crm/src/acme.ts, add the field toACME_CONTACT_FIELDS, so the flow and the preview map it:{ key: "department", label: "Department", required: false }, -
Import, publish, and make the new version available, as before.
-
Back in the app, click Manage on the Fake CRM card. The page offers Update to version 2.
The field is optional, so the instance's saved values still satisfy the schema. You kept the configuration version,
fake-crm-v1, so the update doesn't require reconfiguration, and the app moves the instance to it with one click. -
Click Edit configuration, then click Next: map fields. The new Department field appears in the mapper, although you didn't change the app. The form reads its fields from the configuration's schema.
If you made a change that saved values can't satisfy, such as renaming a field, you would also change the configuration version.
The update would then require reconfiguration, and your init function would migrate the saved values for the customer to review.
See Configuration versions and init.
What you learned
- A headless integration defines a schema, a version, server functions, and an init function in place of config pages.
- Your app renders the configuration form with its own components, using Solis, Prismatic's custom UI SDK.
- Server functions supply dynamic data, including previews of values the customer hasn't saved.
- Prismatic validates saved values, and flows read them with their real types from
context.configuration. - Keeping the configuration version lets instances update without reconfiguring.