Skip to main content

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​

Clone 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
PathWhat it is
integrations/fake-crm/The Fake CRM code-native integration
frontend/routes/examples/headless-configuration.tsxThe app page that lists integrations and configures instances
frontend/components/headless/fake-crm-configuration-form.tsxThe field mapper form
Read the integration's configuration

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.ts
    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" }),
    });
  • A version, fake-crm-v1, which names this shape of the schema.

  • Three server functions the app calls while a customer configures an instance:

    • listRecordTypes returns the record types in the customer's CRM.
    • listFields returns the fields of one record type.
    • getSampleRecord returns 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.

Import and publish the integration

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.

Configure and start the example 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.

Set up Fake CRM
  1. 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 init function to get starting values.

  2. Choose Hooli Lead, the custom record type, and click Next: map fields. The record types came from the listRecordTypes server function.

  3. Map each Acme field to a Hooli_Leads__c field:

    • 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 listFields server function.

  4. Watch the Preview in Acme panel. Each time you change the mapping, the app sends the unsaved mapping to the getSampleRecord server function, and shows the Acme contact it makes from a sample Hooli lead.

  5. 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.

Run the flow

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"}
Publish an update

Add a field to the configuration, and see how an update reaches the instance.

  1. In integrations/fake-crm/src/configuration.ts, add an optional department field to fieldMapping:

    department: z.string().optional().meta({ title: "Department" }),

    Add "department" to the list of field mapping keys in the ui layout, so the app knows where to show it.

  2. In integrations/fake-crm/src/acme.ts, add the field to ACME_CONTACT_FIELDS, so the flow and the preview map it:

    { key: "department", label: "Department", required: false },
  3. Import, publish, and make the new version available, as before.

  4. 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.

  5. 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.

Next steps​