Skip to main content

Server Functions

A server function is a function in your code-native integration that your app calls while a customer configures an instance. Use server functions for anything your configuration form needs from a third-party app: a list of record types, the fields of a record type, a preview of mapped data.

Server functions are the headless counterpart of data sources in a config wizard. They run in Prismatic, alongside your integration's code and connections, so your app never handles third-party credentials.

Define a server function​

Define a server function with Spectral's serverFunction(). It takes an input schema, an output schema, and a perform function:

src/configuration.ts
import { serverFunction } from "@prismatic-io/spectral";
import { z } from "zod";
import { listFields } from "./fake-crm";

export const listFieldsFunction = serverFunction({
label: "List fields",
description: "The fields of a Fake CRM record type",
inputSchema: z.object({ recordType: z.string().min(1) }),
outputSchema: z.array(
z.object({ name: z.string(), label: z.string(), type: z.string() }),
),
perform: async (context, { recordType }) => listFields(recordType),
});
PropertyRequiredDescription
inputSchemaYesA zod or JSON Schema for the function's inputs. Prismatic validates inputs against it before running the function.
outputSchemaYesA zod or JSON Schema for the function's result. Prismatic publishes it for your app but doesn't enforce it.
performYesThe function. It receives a context and the validated inputs, and returns the result.
connectionsNoThe connections the function needs, such as ["instance.slack"]. See Connections.
labelNoA display name for the function.
descriptionNoA description of the function.

Then register it under a key in your configuration's serverFunctions. Your app calls the function by this key:

src/configuration.ts
export const integrationConfiguration = configuration({
instance: { schema: configurationSchema, version: "fake-crm-v1" },
serverFunctions: {
listRecordTypes: listRecordTypesFunction,
listFields: listFieldsFunction,
getSampleRecord: getSampleRecordFunction,
},
});

The server function context​

A server function's context includes:

PropertyDescription
loggerWrites to the instance's logs.
connectionsThe connections the function declared in connections, such as context.connections.instance.slack.
configurationThe instance's saved configuration. It may be empty or in an older shape while a customer is configuring.
userConfigurationThe current user's saved user-level configuration, if any.
componentsActions of the components in your integration's component registry.
customer, instance, userAttributes of the customer, instance, and user.

Pass unsaved form values as inputs​

While a customer fills in your form, their choices aren't saved yet, so context.configuration doesn't include them. Pass anything the function needs from the form as an input.

For example, the Fake CRM integration's getSampleRecord function takes the customer's unsaved field mapping and returns a sample record, mapped the same way the flow will map it. Your app shows the result as a live preview:

src/configuration.ts
export const getSampleRecordFunction = serverFunction({
label: "Get sample record",
description:
"A sample Fake CRM record and the Acme contact a mapping makes from it",
inputSchema: z.object({
recordType: z.string().min(1),
fieldMapping: z.record(z.string(), z.string()).optional(),
}),
outputSchema: z.object({
record: z
.record(z.string(), z.union([z.string(), z.number(), z.null()]))
.nullable(),
acmeContact: z.record(z.string(), z.string()),
}),
perform: async (_context, { recordType, fieldMapping }) => {
const [record] = listRecords(recordType);
if (!record) return { record: null, acmeContact: {} };
// The same function the flow uses, so the preview matches what's sent
return { record, acmeContact: toAcmeContact(record, fieldMapping ?? {}) };
},
});

Call a server function from your app​

Your app calls a server function through the configuration it belongs to. In React, use the useServerFunction hook:

const configuration = useConfiguration({ instanceId });
const listFields = useServerFunction<{ recordType: string }, Field[]>(
configuration,
"listFields",
);

const result = await listFields.execute({ inputs: { recordType: "Contact" } });
if (result.status === "success") {
setFields(result.data);
}

A server function runs against the integration version its configuration is for. When a customer reviews an update to a new version, your app calls the new version's server functions before the instance moves to it.

See Building configuration UIs with Solis React for details.

Test server functions​

A server function's perform is a plain async function, so you can unit test it directly:

src/configuration.test.ts
import { listFieldsFunction } from "./configuration";

test("lists the fields of a record type", async () => {
const fields = await listFieldsFunction.perform({} as never, {
recordType: "Contact",
});
expect(fields.map(({ name }) => name)).toContain("AccountName");
});

If your function reads from context, pass the properties it uses instead of an empty object.