# Prismatic Docs > Prismatic is the integration platform for B2B SaaS companies, enabling them to build and manage integrations with ease. These docs provide comprehensive guides, tutorials, and API references to help developers create low-code and code-native integrations using Prismatic's platform. ## Documentation ### What is Prismatic? If you're a software company that sells to businesses (you're a B2B SaaS), chances are, you're not the only software vendor your customers use. Your customers buy software from you, and a bunch of other vendors. And, they naturally expect that the data in your system syncs with data they store in the other apps and services they use. Meaning you've gotta build integrations that sync data between your app and the other apps your customers use. Doing it all yourself is time-consuming. There are a TON of things you need to account for beyond writing some code that gets data from point A to point B. With Prismatic, you provide the integration logic (either through our low-code designer or code-native SDK), and we provide the infrastructure that integrations need to run - things like a configuration experience, webhooks, OAuth 2.0, compute, CI/CD deployment, logging, monitoring, alerting... the list goes on. ![responsibilities](/docs/assets/images/responsibilites-cf90d28cabaa514276f0a39f423b2209.png) #### How do I build integrations?[​](#how-do-i-build-integrations "Direct link to How do I build integrations?") You have three options for building integrations: 1. Your developers can build workflows and design a configuration experience using Prismatic's TypeScript SDK. Dev tools like Prismatic's [VSCode extension](https://prismatic.io/docs/dev-tools/vscode-extension.md) and [MCP Dev Server](https://prismatic.io/docs/dev-tools/prism-mcp.md) expedite the development process. 2. Prismatic's **low-code designer** lets both developers and technical non-developers design workflows and a configuration wizard experience, and package them together into a productized integration. 3. Your customers can build workflows for themselves within your app when you embed the [Workflow Builder](https://prismatic.io/docs/embed/workflow-builder.md), and your customers can leverage [Prismatic's AI co-pilot](https://prismatic.io/blog/introducing-ai-copilot-for-embedded-workflow-builder/) to build quickly. #### What apps can I integrate with?[​](#what-apps-can-i-integrate-with "Direct link to What apps can I integrate with?") Prismatic offers a library of hundreds of [built-in connectors](https://prismatic.io/docs/components.md) for common third-party apps. But, you're not limited to our library. You can build your own [custom connector](https://prismatic.io/docs/custom-connectors.md) for your own app and niche, industry-specific apps. If the app has a web-based API, or if there's a Node.js library that supports it (e.g. database, SFTP, LDAP, etc), you can integrate with it. --- ### Prismatic Changelog AUGUST 04, 2026 ##### Improved step outputs and references in EWB and low-code[​](#improved-step-outputs-and-references-in-ewb-and-low-code "Direct link to Improved step outputs and references in EWB and low-code") When you reference a previous step in the Embedded Workflow Builder or low-code designer, you can now see the real data that step would return, without running a test of your whole workflow. The reference panel has also been redesigned so the payload data you reference most is front and center, with less common properties tucked out of the way. Update your custom connectors to take advantage of new step output options - read more at [Step Outputs](https://prismatic.io/docs/custom-connectors/step-outputs.md). JULY 22, 2026 ##### Authentication profiles for the prism CLI[​](#authentication-profiles-for-the-prism-cli "Direct link to Authentication profiles for the prism CLI") The `prism` CLI now supports authentication profiles, so you can stay logged in to multiple Prismatic stacks or tenants at once. Each profile stores its own endpoint, tenant, and credentials, which makes switching between regions much simpler than juggling `PRISMATIC_URL` environment variables. Create a named profile with `prism login --profile `, then select it per command with the `--profile` flag, for a shell session with `PRISM_PROFILE`, or as your default with `prism profiles:use `. Run `prism profiles:list` to view saved profiles and `prism profiles:delete ` to remove one. See [authenticating with the CLI tool](https://prismatic.io/docs/cli.md#authentication-profiles) for details. JULY 06, 2026 ##### Upgraded charts for at-a-glance integration health[​](#upgraded-charts-for-at-a-glance-integration-health "Direct link to Upgraded charts for at-a-glance integration health") We've rebuilt the charts across Prismatic. New interactive charts now appear on the Home page, integration landing pages, and your Customer, Instance, and Org Utilization views, so you can spot trends and jump straight to the executions behind any spike. Toggle any chart between bar and line, and click into a day on the Executions Success/Failure chart to drill through to the underlying execution. Where available, you'll see up to 14 days of history. JULY 01, 2026 ##### New default toolchain for components and code-native integrations[​](#new-default-toolchain-for-components-and-code-native-integrations "Direct link to New default toolchain for components and code-native integrations") `prism components:init` and `prism integrations:init` now scaffold new projects with a new, modern toolchain for building, testing, linting, and formatting your code. If you run into issues with the modern toolchain, you can scaffold with the previous toolchain by passing `--toolchain legacy` - and please [contact support](mailto:support@prismatic.io) so we can address it. This change ships in a new major version of the `prism` CLI. Want to update an existing code-native integration or custom connector to use our new recommended toolchain? See the [Spectral 10.22 Upgrade Guide](https://prismatic.io/docs/spectral/spectral-10-22-upgrade-guide.md) for instructions. JUNE 24, 2026 ##### Large data sync with batchFlowTrigger[​](#large-data-sync-with-batchflowtrigger "Direct link to Large data sync with batchFlowTrigger") Code-native integrations can now use `batchFlowTrigger` to backfill large datasets on deploy and process real-time events through the same execution path - no manual recursion required. Define an `onDeploy` function to page through your source API on instance deployment, returning a `paginationState` cursor so each invocation picks up where the last one left off. Define an `onTrigger` function to handle incoming webhooks after the initial sync completes. Both funnel records into a single `onExecution` function, and `batchConfig` controls how many records arrive per call and how many batches run concurrently. ![Large data sync execution results](/docs/assets/images/large-data-sync-execution-results-31977ac3a25800a959b219f73aaa90db.png) See [Large Data Sync](https://prismatic.io/docs/integrations/common-patterns/large-data-sync.md) for usage and examples and [here](https://prismatic.io/docs/get-started/build-integrations/large-data-syncs.md) for a video walkthrough. JUNE 22, 2026 ##### Structured and dynamic object inputs for custom connectors[​](#structured-and-dynamic-object-inputs-for-custom-connectors "Direct link to Structured and dynamic object inputs for custom connectors") Custom connector actions now support two new input types that let you represent complex data directly on the canvas - no code step required. **Structured object inputs** group related sub-inputs into a single named object. For example, a "Mailing Address" input can expose individual `street`, `city`, `state`, and `zip` fields rather than forcing builders to construct the object manually. In your action's `perform` function, the value is a plain object accessible by field name (e.g. `inputs.address.city`). **Dynamic object inputs** show a different set of sub-inputs depending on which record type or configuration the builder selects. For example, a "Create Record" action can present Account, Lead, or Contact input fields based on the builder's choice, with `inputs.record.configuration` identifying the selection and `inputs.record.values` holding the corresponding field values. See [structured object inputs](https://prismatic.io/docs/custom-connectors/inputs.md#structured-object-inputs) and [dynamic object inputs](https://prismatic.io/docs/custom-connectors/inputs.md#dynamic-object-inputs) for usage and code examples. JUNE 04, 2026 ##### Open Source Public Components[​](#open-source-public-components "Direct link to Open Source Public Components") The source for Prismatic-built components is now on GitHub. When one of our components is close but not quite what your customers need, you no longer have to rebuild from scratch. Fork it, add the behavior you want, and publish it as your own private component. You can also use our components as a reference for building net-new ones, or just read the source to see exactly how something works. Check it out in [GitHub](https://github.com/prismatic-io/components). MAY 11, 2026 ##### Connection management has been streamlined[​](#connection-management-has-been-streamlined "Direct link to Connection management has been streamlined") We've refreshed how connections are set up and managed to make reusable connections a first-class part of building an integration. ![New connections experience in the Components section](/docs/assets/images/new-reusable-connection-ui-c64f82f204306b7822837995a34a834c.png) A few things to know about the new experience: * Reusable connections are now created and managed from the **Components** section. Search for a component, open its **Connections** tab, and click **+ Add Connection**. * You can also create reusable connections directly from the canvas - when picking a trigger, adding a step, or from a step's connection input field. * A pencil icon next to a step's connection lets you edit it without leaving the canvas. Existing connections, integrations, and instances continue to work without changes. APRIL 16, 2026 ##### Workflow Contexts for Embedded Workflow Builder[​](#workflow-contexts-for-embedded-workflow-builder "Direct link to Workflow Contexts for Embedded Workflow Builder") You can now define **workflow contexts** to give your customers a guided starting point when they create workflows in the embedded workflow builder. A context pre-configures the trigger and its inputs and accepts runtime data from your application. For example, a "Create automation" button on a ticket page can create a workflow that is already wired to your "Ticket updated" trigger and scoped to that ticket's project - no blank-canvas decisions required. Use the new `prismatic.createWorkflow` SDK method to create a workflow from a context, and `prismatic.queryWorkflows` to list workflows by context or external ID. Additionally, you can dictate which components and actions are available to your customers when building workflows from a context, giving you more control over the experience and ensuring customers only use components that are relevant to the workflow they're building. See [Workflow contexts](https://prismatic.io/docs/embed/workflow-builder/workflow-contexts.md) for setup instructions, SDK usage, and TypeScript typing support. APRIL 16, 2026 ##### AI Copilot for Embedded Workflow Builder General Availability[​](#ai-copilot-for-embedded-workflow-builder-general-availability "Direct link to AI Copilot for Embedded Workflow Builder General Availability") The AI copilot for the embedded workflow builder is now generally available to all customers who have embedded workflow builder enabled. Read more on our [announcement blog post](https://prismatic.io/blog/ai-copilot-for-embedded-workflow-builder-early-access/) and in [docs](https://prismatic.io/docs/embed/workflow-builder/ai-copilot.md). ![Screenshot of AI copilot in embedded workflow builder](/docs/assets/images/ai-copilot-2bcbb1335a5604d53a83b8014d4ec01d.png) APRIL 15, 2026 ##### Prismatic Skills for Claude Code[​](#prismatic-skills-for-claude-code "Direct link to Prismatic Skills for Claude Code") [Prismatic Skills](https://github.com/prismatic-io/prismatic-skills/) is an open-source Claude Code plugin that provides your AI coding assistant with the context and tools necessary to be an expert on Prismatic. Key skills include a code-native integration builder, custom component builder, embed advisor for embedding the marketplace and workflow builder, migration analyzer for migrating integrations from other iPaaS platforms, and Orby - a platform expert for querying your Prismatic environment and troubleshooting. Read more on our [announcement blog post](https://prismatic.io/blog/introducing-prismatic-skills-for-claude-code/) and in [docs](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md). APRIL 01, 2026 ##### New Components - April 2026[​](#new-components---april-2026 "Direct link to New Components - April 2026") We are pleased to announce the following components have been recently added to the Prismatic library: * [Redshift](https://prismatic.io/docs/components/aws-redshift.md) - Amazon Redshift is a cloud data warehouse service. Use the component to manage clusters, execute queries, load data, and monitor warehouse performance. * [SAP ECC](https://prismatic.io/docs/components/sap-ecc.md) - SAP ECC (ERP Central Component) is an enterprise resource planning platform for managing core business processes. Use the component to manage business objects, transactions, and data across modules like finance, logistics, and human resources. * [System Surveyor](https://prismatic.io/docs/components/ssv.md) - System Surveyor is a visual documentation platform for physical security and IT systems. Use the component to manage surveys, sites, devices, and floor plans. MARCH 06, 2026 ##### New Components - March 2026[​](#new-components---march-2026 "Direct link to New Components - March 2026") We are pleased to announce the following components have been recently added to the Prismatic library: * [Datadog](https://prismatic.io/docs/components/datadog.md) - Datadog is a monitoring and analytics platform for cloud-scale applications. Use the component to manage monitors, events, metrics, dashboards, and incidents. * [Salesforce Marketing Cloud](https://prismatic.io/docs/components/salesforce-marketing-cloud.md) - Salesforce Marketing Cloud is a digital marketing platform for managing customer journeys across email, mobile, social, and advertising channels. Use the component to manage contacts, campaigns, data extensions, and automations. * [SurveyMonkey](https://prismatic.io/docs/components/surveymonkey.md) - SurveyMonkey is a leading online survey platform that enables organizations to create surveys, collect responses, and analyze feedback at scale. Use the component to manage surveys, collectors, responses, contacts, and webhooks. * [UKG Pro](https://prismatic.io/docs/components/ukg-pro.md) - UKG Pro is a human capital management (HCM) platform. Use the component to manage employees, organizational structures, payroll data, and talent onboarding processes. MARCH 03, 2026 ##### AI Copilot for Embedded Workflow Builder[​](#ai-copilot-for-embedded-workflow-builder "Direct link to AI Copilot for Embedded Workflow Builder") You can now enable an AI copilot in the embedded workflow builder to assist your customers as they build their own workflows. The copilot can configure connections, triggers, actions, branches, loops and more based on natural language instructions from your customers. See our announcement [blog post](https://prismatic.io/blog/ai-copilot-for-embedded-workflow-builder-early-access/). The copilot feature is in "Early Access" and will be generally available soon. If you'd like early access to the copilot, please reach out to your Prismatic account manager. FEBRUARY 19, 2026 ##### Per-customer execution concurrency controls[​](#per-customer-execution-concurrency-controls "Direct link to Per-customer execution concurrency controls") You can now set per-customer execution concurrency limits to prevent a single customer's high-volume integrations from consuming a disproportionate share of your organization's execution capacity. When a customer reaches their limit, additional execution attempts return a 429 "too many requests" response. You can configure limits from the Prismatic web app, via the GraphQL API, or via embedded JWT claims. You can also set up a **Customer Concurrency Threshold Warning** alert monitor to be notified when any customer approaches their limit. This feature is available on Enterprise plans. See [per-customer execution concurrency](https://prismatic.io/docs/customers/managing-customers.md#per-customer-execution-concurrency) for more information. FEBRUARY 04, 2026 ##### Extended Flow Concurrency Controls[​](#extended-flow-concurrency-controls "Direct link to Extended Flow Concurrency Controls") You can now control how many concurrent executions are allowed for individual flows within an integration. This allows you to prevent overwhelming third-party APIs with too many simultaneous requests, or to limit resource usage for particularly intensive flows. Previously, you could configure a flow to allow only one execution at a time by enabling FIFO (first in, first out) processing. Now, you can set a specific maximum number of concurrent executions for each flow. ![Flow concurrency settings in the integration designer](/docs/assets/images/flow-concurrency-config-9133179679fd7f0409cb7a07874a6dba.png) See [Flow Concurrency](https://prismatic.io/docs/integrations/triggers/fifo-queue.md) for more information. JANUARY 26, 2026 ##### Instance Profiles[​](#instance-profiles "Direct link to Instance Profiles") You can now create and assign [instance profiles](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md#instance-profiles) to customize resource allocations and execution constraints for specific integrations and instances. This allows you to increase memory limits for memory-intensive integrations, control step result and log retention, and enable quickstart to reduce cold start times for synchronous invocations. ##### New Integration Landing Page[​](#new-integration-landing-page "Direct link to New Integration Landing Page") We've redesigned the integration landing page to provide a one-stop shop for all information related to a specific integration. When you as an organization team member opens an integration, you'll see information about deployed instances, recent executions, marketplace configuration, and more. ![Screenshot of new integration landing page](/docs/assets/images/new-integration-landing-page-b049ce9750ef14b4ab5a827b092db8cb.png) DECEMBER 12, 2025 ##### Listening Mode with Code-Native Integrations[​](#listening-mode-with-code-native-integrations "Direct link to Listening Mode with Code-Native Integrations") You can now enter [listening mode](https://prismatic.io/docs/integrations/triggers/listening-mode.md) when testing webhook-based triggers in code-native integrations. This allows you to quickly set up temporary webhooks in third-party applications and capture incoming webhook requests from the third-party from your IDE. Read more in [Testing Code-Native Integrations](https://prismatic.io/docs/integrations/code-native/testing.md#listening-for-webhook-invocations-from-the-cli). DECEMBER 05, 2025 ##### New Public Region: South Africa[​](#new-public-region-south-africa "Direct link to New Public Region: South Africa") Prismatic is now available in a new public region in South Africa (Cape Town). This new region provides low-latency access for customers in Africa and surrounding areas. See the [deployment regions documentation](https://prismatic.io/docs/configure-prismatic/deployment-regions.md) for more information about accessing this and other regions. DECEMBER 04, 2025 ##### Multi-Tenant Switching[​](#multi-tenant-switching "Direct link to Multi-Tenant Switching") You can now switch between Prismatic tenants without logging out. If you have access to multiple tenants (Dev, Prod, Staging, etc.), you'll see a new **Tenant Picker** in the left-hand sidebar. It shows which environment you're working in and lets you switch between environments instantly. No more juggling browser sessions or credentials. ![Screenshot of multi-tenant switching in Prismatic](/docs/assets/images/multi-tenant-switching-d46a0cc43b6311c22cbe3153b3130335.png) Also in this release: * **Single identity across tenants** - Use the same email and password for all your Prismatic environments. No more email aliases. * **SSO works seamlessly** - If you're using SSO, it now works seamlessly across multiple tenants in the same region without workarounds. NOVEMBER 20, 2025 ##### Webhook Lifecycle Handlers and Listening Mode[​](#webhook-lifecycle-handlers-and-listening-mode "Direct link to Webhook Lifecycle Handlers and Listening Mode") Two new instance [lifecycle events](https://prismatic.io/docs/integrations/lifecycle.md) are now available for custom connector triggers: `webhookLifecycleHandlers.create` and `webhookLifecycleHandlers.delete`. These functions run when an instance is deployed or disabled, and also when you enter or exit [listening mode](https://prismatic.io/docs/integrations/triggers/listening-mode.md). **Listening mode** is a new feature in the integration designer and embedded workflow builder that allows you to quickly test webhook triggers without deploying an instance. When you or your customers enter listening mode, the `webhookLifecycleHandlers.create` function is executed to create a temporary webhook in the third-party application that points to your integration designer's test instance. When you exit listening mode, the `webhookLifecycleHandlers.delete` function is executed to remove the temporary webhook. While in listening mode, you can see incoming webhook requests in real-time and save webhook payloads to use as test data for your trigger. NOVEMBER 10, 2025 ##### In-app Low-Code to Code-Native Converter[​](#in-app-low-code-to-code-native-converter "Direct link to In-app Low-Code to Code-Native Converter") You can now convert [low-code](https://prismatic.io/docs/integrations/low-code-integration-designer.md) integrations to [code-native](https://prismatic.io/docs/integrations/code-native.md) directly within the Prismatic UI. This allows you to prototype a new integration quickly using the low-code designer, and then switch to code-native when you need more flexibility or control. To get started, open any low-code integration and click **Convert to Code**. You'll get a preview of the generated code, and can choose to download a zip file containing the code-native TypeScript project. ![Screenshot of low-code to code-native converter in app](/docs/assets/images/in-app-low-code-convert-e2c52c2e11ecc426c065e978bb3afdda.png) OCTOBER 24, 2025 ##### Embedded Connections Management Screen[​](#embedded-connections-management-screen "Direct link to Embedded Connections Management Screen") You can now embed a dedicated connections management screen in your application using the new `prismatic.showConnections()` function. This screen provides your customers with a centralized location to view and manage all of their reusable [customer connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md) for both marketplace integrations and custom workflows. **What's new:** * **Unified connections view**: Display all reusable connections in one place with the `prismatic.showConnections()` function * **Connection details**: Customers can click on any connection to view which instances and workflows use it * **Full management capabilities**: Edit or delete connections directly from the embedded screen * **Seamless experience**: Consistent connection management whether customers are working with marketplace integrations or custom workflows **Benefits:** * Simplified connection management for customers who use the same credentials across multiple integrations * Better visibility into where connections are being used * Reduced need for custom connection management UI in your application This feature requires `@prismatic-io/embedded` version `4.2.0` or later. See the [embedding the connections screen](https://prismatic.io/docs/embed/additional-screens/show-connections.md) documentation for implementation details. OCTOBER 17, 2025 ##### New Component - October 2025[​](#new-component---october-2025 "Direct link to New Component - October 2025") We are pleased to announce the following component has been recently added to the Prismatic library: * [Okta](https://prismatic.io/docs/components/okta-management-api.md) - Okta is an identity and access management platform that provides secure authentication and authorization services. Use the component to manage users, groups, applications, and access policies. OCTOBER 15, 2025 ##### Reusable Customer Connections[​](#reusable-customer-connections "Direct link to Reusable Customer Connections") Customer connections are now easier to use. Your customers can now save their credentials once and reuse them across multiple marketplace integrations and custom workflows, eliminating the need to re-enter credentials they've already provided. **What's new:** * **Save and reuse credentials**: When customers configure a marketplace integration or build a workflow, they can save their connection credentials and select from existing saved connections in future configurations * **Cross-environment reusability**: Connections created in the embedded workflow builder can be reused in marketplace integrations, and vice versa * **Unified experience**: Customers see a consistent connection management experience whether they're configuring an integration or building a custom workflow * **Test credential support**: Organizations can configure test credentials to preview the end-customer experience during development **Benefits:** * Faster integration activation for customers who use the same third-party services across multiple integrations * Reduced friction in the configuration experience * Better alignment between marketplace and embedded workflow builder experiences To enable this feature for your integrations, assign a [customer connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md) to your marketplace integration and ensure you're using `@prismatic-io/embedded` version `4.2.0` or later. OCTOBER 14, 2025 ##### New MCP UI in Integration Designer[​](#new-mcp-ui-in-integration-designer "Direct link to New MCP UI in Integration Designer") We've added a new MCP tab to the integration designer that makes it easier to configure and manage Model Context Protocol (MCP) connections for your agentic flows. **Highlights:** * View and copy the custom MCP endpoint URL for connecting AI agents to a specific integration's agentic flows * Enable or disable individual flows as MCP tools with simple toggle controls * See at a glance which flows are available as tools for AI agents like Claude, Cursor, or custom applications * Streamlined workflow for configuring which agentic flows should be exposed through the MCP flow server Check out the [MCP flow server documentation](https://prismatic.io/docs/ai/model-context-protocol.md) and [connecting AI agents guide](https://prismatic.io/docs/ai/connect-ai-agent.md) for more information. OCTOBER 06, 2025 ##### Prismatic Event Webhooks[​](#prismatic-event-webhooks "Direct link to Prismatic Event Webhooks") Prismatic event webhooks provide a powerful way to receive real-time notifications about events happening in your Prismatic account. With webhooks, you can be informed when customers create workflows using the embedded workflow builder, simplify your integration billing, and stay informed about important changes to customers, integrations, instances and more. For example, you can say "When an `instance.deployed` or `instance.deleted` events occurs, let me know at `https://`." Key features of Prismatic event webhooks include: * **Real-time Notifications**: Receive immediate updates when events occur, without polling the Prismatic API. * **Multiple Event Types**: You can create granular webhooks that subscribe to over 30 unique events types. * **Security**: Use HMAC signatures to verify the authenticity of incoming webhook requests. To get started with Prismatic event webhooks, check out the [webhooks documentation](https://prismatic.io/docs/webhooks.md). SEPTEMBER 30, 2025 ##### Code-Native Development Updates[​](#code-native-development-updates "Direct link to Code-Native Development Updates") Today we're releasing updates that significantly improve the code-native development experience on Prismatic. Build, test, and deploy integrations entirely in code with AI assistance, streamlined component management, and enhanced TypeScript support. **Prism MCP Server** * Enable AI coding assistants (Claude, Cursor) to understand Prismatic's SDK and generate integration flows, components, and test configurations using natural language **VS Code Extension** * Run test executions with detailed logs, pull existing integrations from your Prismatic instance, configure instances through embedded config wizard, and manage authentication - all without leaving your IDE **Improved Component Manifest Generation** * Generate component manifests with a single command `npx cni-component-manifest ` and use components without installing npm packages **Enhanced Component Referencing** * Full TypeScript support with real-time validation for defining connections, data sources, and triggers in configuration wizards Check out the [code-native development updates blog post](https://prismatic.io/blog/announcing-code-native-development-updates) for additional info and quick start demo videos. SEPTEMBER 30, 2025 ##### Singleton Executions for Scheduled Flows[​](#singleton-executions-for-scheduled-flows "Direct link to Singleton Executions for Scheduled Flows") Prismatic now supports singleton executions for flows that run on a schedule. This means that if a scheduled flow is still running when the next execution is triggered, the new execution will be skipped. **Highlights:** * Enable singleton executions in the trigger's configuration * Prevent overlapping executions for [scheduled flows](https://prismatic.io/docs/integrations/triggers/schedule.md) and flows that use a [polling trigger](https://prismatic.io/docs/integrations/triggers/app-events.md#app-event-triggers-with-polling) * Avoid duplicate processing, race conditions and troubleshooting headaches Learn about [ensuring singleton executions for scheduled flows](https://prismatic.io/docs/integrations/triggers/schedule.md#ensuring-singleton-executions-for-scheduled-flows). SEPTEMBER 09, 2025 ##### New Feature - Support visibility for Workflows[​](#new-feature---support-visibility-for-workflows "Direct link to New Feature - Support visibility for Workflows") Organizations can now see valuable insights into customer-built workflows through the Prismatic admin **Highlights:** * See the number of customer-built workflows separately from organization-built integrations * View and filter Instances pages, logs, and execution history by their instance type (integration or workflow) * Gain insights on workflow creation and enablement separately from integration instances on customer utilization pages SEPTEMBER 09, 2025 ##### New Feature - Workflow Templates for Embedded Workflow Builder[​](#new-feature---workflow-templates-for-embedded-workflow-builder "Direct link to New Feature - Workflow Templates for Embedded Workflow Builder") You can now create Workflow Templates for Embedded Workflow Builder and publish them to your organization's workflow template library. **Highlights:** * New Workflow Templates option available under the Build section of the Prismatic sidebar * Create and edit templates directly in the Workflow Builder low-code canvas * Save drafts separately from published templates SEPTEMBER 08, 2025 ##### New Feature - FIFO Queues[​](#new-feature---fifo-queues "Direct link to New Feature - FIFO Queues") Prismatic now supports first-in, first-out (FIFO) execution for webhook-triggered flows. Events are processed strictly in order, without needing any external queues or workarounds. **Highlights:** * Flow-level toggle to enable FIFO * One execution at a time while subsequent events queue in order * Built-in event deduplication using an optional ID field * Error handling ensures failed executions don't block the queue Learn about [FIFO queue triggers](https://prismatic.io/docs/integrations/triggers/fifo-queue.md). SEPTEMBER 05, 2025 ##### New Component - September 2025[​](#new-component---september-2025 "Direct link to New Component - September 2025") We are pleased to announce the following component has been recently added to the Prismatic library: * [Guru](https://prismatic.io/docs/components/guru.md) - Guru is a knowledge management platform that brings company information to your team where they work. Use the component to manage cards, collections, boards, and knowledge sharing workflows. AUGUST 15, 2025 ##### New Component - August 2025[​](#new-component---august-2025 "Direct link to New Component - August 2025") We are pleased to announce the following component has been recently added to the Prismatic library: * [Azure Cosmos DB](https://prismatic.io/docs/components/azure-cosmos-db.md) - Azure Cosmos DB is a Microsoft database service designed for handling various applications. Use the component to manage databases, collections, and documents. AUGUST 01, 2025 ##### Theming Update: Improved Coverage and Customization[​](#theming-update-improved-coverage-and-customization "Direct link to Theming Update: Improved Coverage and Customization") We've expanded theme support across both the embedded and org-facing UI to deliver more consistent and brand-aligned experiences. What's new: You can now define a Neutral value in your theme settings, which dynamically generates the full neutral palette. Many UI elements (including inputs, dropdowns, hover states, filter menus, and more) now reference theme tokens for styling. Previously unthemable components have been updated to use a theme value that you can control for better alignment with your brand. Where theme improvements apply: * Embedded Workflow Builder * Embedded Designer * Configuration Wizard * Marketplace * Dashboard * Integration Listing Pages These improvements enhance visual consistency, boost polish and professionalism, and help your product feel even more cohesive to end users. JULY 17, 2025 ##### Embedded Workflow Builder[​](#embedded-workflow-builder "Direct link to Embedded Workflow Builder") We're excited to announce the next evolution of our embedded workflow builder (formerly embedded designer)! Based on extensive customer feedback, we've reimagined the experience to make customer self-service integrations simpler and more powerful. Key improvements: * **Streamlined workflow building:** Configure connections and data sources as part of setting up step actions, eliminating the need for separate configuration wizards. * **One-click publishing:** Enable workflows instantly without additional marketplace configuration or instance setup steps. * **Error Management:** All errors consolidated within a single Status menu for faster troubleshooting. * **Safe exploration:** New read-only view allows examining workflows without risking accidental edits. * **Consolidated inputs:** Simplified input system consolidates Text, Config Variable, Reference, and Template inputs into a single, flexible type. * **Native integration experience:** Seamless embedding with customizable branding, terminology, and styling. Existing customers using the previous embedded designer can continue using that functionality while having the option to upgrade to the new embedded workflow builder at no additional charge. Read more about the [embedded workflow builder in our blog post](https://prismatic.io/blog/embedded-workflow-builder-is-now-generally-available/). JUNE 05, 2025 ##### MCP Flow Server and Flow Invocation Schema[​](#mcp-flow-server-and-flow-invocation-schema "Direct link to MCP Flow Server and Flow Invocation Schema") We recently announced [several](https://prismatic.io/blog/prismatic-ai-next-evolution/) AI-related initiatives at Prismatic. Today, we're excited to release [Flow Invocation Schema](https://prismatic.io/docs/ai/flow-invocation-schema.md) which gives your AI agents the context necessary to invoke your Prismatic workflows. Along with Flow Invocation Schema, we're also releasing a [Model Context Protocol (MCP)](https://prismatic.io/docs/ai/model-context-protocol.md) flow server which exposes your agentic flows as tools for your AI agents to use. JUNE 04, 2025 ##### New Components - June 2025[​](#new-components---june-2025 "Direct link to New Components - June 2025") We are pleased to announce the following AI components have been recently added to the Prismatic library: * [Anthropic](https://prismatic.io/docs/components/anthropic.md) - Anthropic is an artificial intelligence research company that provides various AI systems and large language models (LLM) * [Google Gemini](https://prismatic.io/docs/components/google-gemini.md) - Google Gemini is a family of advanced multimodal AI models developed by Google DeepMind. * [DeepSeek](https://prismatic.io/docs/components/deepseek.md) - DeepSeek is an AI developer of large language models (LLM) focused on providing high performance models. MAY 19, 2025 ##### New Component - May 2025[​](#new-component---may-2025 "Direct link to New Component - May 2025") We are pleased to announce the following component has been recently added to the Prismatic library: * [Hibob](https://prismatic.io/docs/components/hibob.md) - Hibob is a cloud-based HR platform that provides tools for managing employee data, payroll, benefits, and performance. MAY 13, 2025 ##### Low-code / code-native converter and testing code-native from the CLI[​](#low-code--code-native-converter-and-testing-code-native-from-the-cli "Direct link to Low-code / code-native converter and testing code-native from the CLI") Two significant improvements to code-native integrations are now out of beta: 1. You can now convert a low-code integration to code-native. This is ideal if you've built a proof-of-concept in the low-code designer, but would like the flexibility to build the rest of the integration in pure TypeScript. Learn how to [convert low-code integrations to code-native](https://prismatic.io/docs/integrations/code-native/get-started/convert-low-code-code-native.md). 2. As a developer, you probably want to remain in your IDE as you build and test your integration. With the `prism integrations:flows:test` command, you can now invoke a test of your code-native integration's flow from your terminal. Learn about [testing code-native integrations from the CLI](https://prismatic.io/docs/integrations/code-native/testing.md#testing-a-code-native-integration-from-the-cli). APRIL 17, 2025 ##### Templated connection inputs[​](#templated-connection-inputs "Direct link to Templated connection inputs") If your users need to enter the same information in several connection input fields - for example, if they need to enter their third-party custom domain in an OAuth authorization URL input, and token input, and API base URL input - [templated connection inputs](https://prismatic.io/docs/custom-connectors/connections.md#templating-connection-inputs) can help. Our custom connector SDK has been updated so you can now prompt your customer to enter a single value, and other inputs for that connection can be generated automatically using that value. ![Screenshot of templating connection inputs](/docs/assets/images/templating-connection-inputs-80d173992b5c6fbdf862b383526cd84a.png) Additionally, `comments` you provide for inputs on your connections now support markdown, so you can bold or otherwise highlight important instructions for your customers. The built-in [Shopify](https://prismatic.io/docs/components/shopify.md) connector has been updated to use this new templated connection input logic, as Shopify issues unique OAuth 2.0 endpoints for each Shopify store. APRIL 17, 2025 ##### New Components - April 2025[​](#new-components---april-2025 "Direct link to New Components - April 2025") We are pleased to announce the following components have been recently added to the Prismatic library: * [Goto Webinar](https://prismatic.io/docs/components/gotowebinar.md) - Goto Webinar is a platform for hosting, managing, and attending live or pre-recorded webinars. This component allows you to schedule, manage, and subscribe to webinars, registrants, attendees, and more. * [Oracle Database](https://prismatic.io/docs/components/oracledb.md) - Oracle Database is a popular relational database system. This component allows you to query an Oracle database. MARCH 31, 2025 ##### Resetting JSON Forms on Input Change[​](#resetting-json-forms-on-input-change "Direct link to Resetting JSON Forms on Input Change") You can now configure your JSON Forms config variables to reset data to defaults if inputs to the config variable change. This is useful if your JSON Form generates default data that is derived from other config variables on previous pages, and the defaults should change if the user selects different values on previous config pages. Learn about [resetting JSON Forms data when inputs change](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md#handling-form-data-updates). MARCH 24, 2025 ##### Recursive Flow Trigger[​](#recursive-flow-trigger "Direct link to Recursive Flow Trigger") Flows can run for up to 15 minutes. But, sometimes you have more than 15 minutes of work to do. Maybe you have 100,000 records to import when an instance is deployed, and you know that it'll take you 4 hours to process them. The [Recursive Flow](https://prismatic.io/docs/components/recursive-flow.md) component helps you chain a series of executions together, so you can process a set of data for more than 15 minutes. See the [Processing Data with Recursive Flows](https://prismatic.io/docs/integrations/common-patterns/processing-data-recursive-flows.md) article for examples of how you can process large sets of data across several executions. MARCH 21, 2025 ##### New Component - March 2025[​](#new-component---march-2025 "Direct link to New Component - March 2025") We are pleased to announce the following component has been recently added to the Prismatic library: * [WhatsApp](https://prismatic.io/docs/components/whatsapp.md) - WhatsApp is a messaging app that allows users to send texts, make voice and video calls, and share media. MARCH 11, 2025 ##### Debug mode[​](#debug-mode "Direct link to Debug mode") You can now enable [debug mode](https://prismatic.io/docs/integrations/troubleshooting.md#debug-mode) in the integration designer or for a specific instance, which gives you more insight into time and memory metrics of your running flow. When debug is enabled, a log line is emitted after each step that details how long the step took and how much memory was consumed. Additionally, a `debug` object has been added to the [`context`](https://prismatic.io/docs/custom-connectors/actions.md#the-context-parameter) parameter so that you can optionally emit debug lines, measure how long certain tasks take within a code block or custom action, or measure how much memory is consumed by portions of your custom code when debug mode is enabled. Learn more about [debug mode](https://prismatic.io/docs/integrations/troubleshooting.md#debug-mode). Prismatic's public components will start transitioning to using debug mode rather than action-specific debug toggles. MARCH 06, 2025 ##### Introducing Additional Connection Types[​](#introducing-additional-connection-types "Direct link to Introducing Additional Connection Types") We've enhanced our connection capabilities to give you better control over authentication across your integrations. What's new: * **Updated**: [Organization (customer) connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md) capabilities have been expanded to include support for connections that use OAuth 2.0 Client Credentials. This improves your organization's ability to establish a connection on behalf of your customers. * **New** [Organization (global) connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-global.md) allows your organization to configure and establish a central live connection that's invisible to customers at deploy time and works across multiple instances. * **New** [Customer connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md) creates a centralized system for managing connections that customers enable themselves. * **Updated** General UI/UX improvements. Head to the **Connections** tab in your organization's settings to set up a new integration-agnostic connection, and manage your connections and test credentials from there. These enhancements enable you to deliver a frictionless, customer-friendly authentication experience tailored to your integration needs, while also making connections easier to manage and reuse across multiple integrations. JANUARY 29, 2025 ##### Hiding the initial config wizard page[​](#hiding-the-initial-config-wizard-page "Direct link to Hiding the initial config wizard page") You can now *opt-in* to remove the first page of the config wizard, creating a smoother activation process for your customers. Opt-in by upgrading the [embedded SDK](https://www.npmjs.com/package/@prismatic-io/embedded) to version 2.12.0 and setting the `configurationWizard.mode` to `"streamlined"`. Read [docs](https://prismatic.io/docs/embed/marketplace.md#configuration-wizard-customization) for more details. This is completely optional and won't affect your existing implementations. DECEMBER 17, 2024 ##### Product update: Native cross-flow calling[​](#product-update-native-cross-flow-calling "Direct link to Product update: Native cross-flow calling") We've simplified how flows work together in Prismatic. Now you can directly reference and call other flows within the same integration with a simple cross-flow component. This makes it more intuitive to build integrations that need to coordinate between multiple flows. ![Add a cross-flow trigger to an integration](/docs/assets/images/cross-flow-triggers-db59f94eb52d2a5471ec2af9aef4f50f.png) Key improvements: > 🖥️ Directly call other flows through a simple interface > > 🔃 Clear visibility of flow relationships in both testing and execution > > ⏱️ Real-time monitoring of called flow status Perfect for breaking down complex processes into manageable pieces or creating reusable logic across your integrations. *Want to get started? Check out our [docs](https://prismatic.io/docs/integrations/triggers/cross-flow.md).* DECEMBER 13, 2024 ##### New Components - December 2024[​](#new-components---december-2024 "Direct link to New Components - December 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [Active Directory](https://prismatic.io/docs/components/ldap.md) - Active Directory for LDAP (Lightweight Directory Access Protocol) is a protocol for accessing and managing directory information. This component provides tools for operations such as authentication, querying, and managing directory entries. * [TeamViewer](https://prismatic.io/docs/components/teamviewer.md) - TeamViewer is support software that allows users to connect and control devices remotely for troubleshooting, collaboration, and management purposes. NOVEMBER 21, 2024 ##### Polling Triggers[​](#polling-triggers "Direct link to Polling Triggers") Many apps offer [webhooks](https://prismatic.io/docs/integrations/triggers/webhook.md) which notify you when something changes in their system. Not all apps support webhooks, though. When an app doesn't offer webhooks, you need to poll their API periodically for new data. Today, we're releasing **Polling Triggers** - triggers that poll for new records and start an execution if new data is available to process. We've added polling triggers to our [Dropbox](https://prismatic.io/docs/components/dropbox.md) and [Google Drive](https://prismatic.io/docs/components/google-drive.md) components, with more on the way! You can read more about polling triggers [here](https://prismatic.io/docs/integrations/triggers/app-events.md#app-event-triggers-with-polling), or [write your own](https://prismatic.io/docs/custom-connectors/triggers.md#app-event-polling-triggers). NOVEMBER 15, 2024 ##### New Components - November 2024[​](#new-components---november-2024 "Direct link to New Components - November 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [Toast](https://prismatic.io/docs/components/toast.md) - Toast is a cloud-based point-of-sale system designed specifically for the restaurant industry, offering tools for order management, payments, and business insights. * [Tenable Vulnerability Management](https://prismatic.io/docs/components/tenable-vulnerability-management.md) - Tenable Vulnerability Management is a leading security solution that identifies, evaluates, and prioritizes vulnerabilities to reduce risk and enhance cybersecurity. * [PDQ](https://prismatic.io/docs/components/pdq.md) - PDQ provides a suite of management tools to automate software deployment, manage patches, and track inventory across a company's networks. * [Freshservice](https://prismatic.io/docs/components/freshservice.md) - Freshservice is a cloud based IT service management software that streamlines IT operations, automates workflows, and improves service delivery for organizations. * [Azure Event Grid](https://prismatic.io/docs/components/azure-event-grid.md) - Microsoft Event Grid is used to build data pipelines, integrate applications, and create event-driven serverless solutions with a fully managed publish-subscribe messaging service. NOVEMBER 05, 2024 ##### Requiring Components in Embedded Builder[​](#requiring-components-in-embedded-builder "Direct link to Requiring Components in Embedded Builder") If your customers to build integrations for themselves within your app using the [embedded builder](https://prismatic.io/docs/embed/workflow-builder.md) you may want to require that your customers' integrations include particular components (like your custom components). You can now require that your customers include certain components in their integration before they are able to publish. Read more in [docs](https://prismatic.io/docs/embed/workflow-builder/designer#requiring-components). OCTOBER 22, 2024 ##### Control Names of Branded Elements[​](#control-names-of-branded-elements "Direct link to Control Names of Branded Elements") We call the place where your customers go to enable integrations the "Marketplace", and we call a set of flows with a config wizard an "Integration". But, your company may use other terms like "Solution" or "Workflow" to describe these concepts. You can now control the names of branded elements (like "Marketplace" or "Integration") and rename them to the names that you use internally. ![Renamed marketplace in the embedded app](/docs/assets/images/renamed-marketplace-e33bb695e45b89cc5d9a6dc43d5ee29a.png) Changes you make will be reflected in the embedded marketplace and the embedded workflow builder. Read more about controlling the names of branded elements in [docs](https://prismatic.io/docs/embed/theming.md#renaming-integration-and-marketplace). OCTOBER 10, 2024 ##### New Components - October 2024[​](#new-components---october-2024 "Direct link to New Components - October 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [Frontify](https://prismatic.io/docs/components/frontify.md) - Frontify is a comprehensive brand management platform that enables organizations to create, manage, and distribute brand assets, guidelines, and digital content across teams and channels, streamlining brand consistency and collaboration. * [PagerDuty](https://prismatic.io/docs/components/pagerduty.md) - PagerDuty is an industry leading incident management tool. Use this component to create and manage Incidents and events. SEPTEMBER 25, 2024 ##### Organization (Customer) Connections[​](#organization-customer-connections "Direct link to Organization (Customer) Connections") If you rely on connections that are customer-specific, and the same connection is used in multiple integrations, [organization (customer) connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md) can help. Organization (customer) connections let you define a connection once for a customer, and then use that connection in multiple instances that are deployed for that customer. This is especially handy when interacting with your own app in integrations. Each of your customers likely has a unique API key for your API. You can set an API key for each of your customers once, and when an instance is deployed to a customer, that instance will use that customer's API key that you set. SEPTEMBER 13, 2024 ##### New Components - September 2024[​](#new-components---september-2024 "Direct link to New Components - September 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [Ramp](https://prismatic.io/docs/components/ramp.md) - Ramp is a spend management platform focused on automating accounts payable and procurement processes. * [Microsoft Entra ID (Formerly Azure Active Directory)](https://prismatic.io/docs/components/ms-entra-id.md) - Microsoft Entra ID (Formerly Azure Active Directory) is a cloud-based identity and access management service from Microsoft that helps employees sign in and access resources. * [Gorgias](https://prismatic.io/docs/components/gorgias.md) - Gorgias is a customer support platform designed to help e-commerce businesses manage customer inquiries and support tickets efficiently. * [SAP Business One](https://prismatic.io/docs/components/sap-business-one.md) - SAP Business One is an integrated enterprise resource planning (ERP) solution designed for organizations to manage their entire operations. AUGUST 05, 2024 ##### New Components - August 2024[​](#new-components---august-2024 "Direct link to New Components - August 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [Klaviyo](https://prismatic.io/docs/components/klaviyo.md) - Klaviyo is a cloud based email marketing solution that enables e-commerce businesses to create, send, and analyze email and SMS campaigns. * [Bill](https://prismatic.io/docs/components/bill.md) - Bill is a leading provider of cloud-based software that simplifies and automates back-office financial operations for small and midsize businesses. JULY 31, 2024 ##### Write-Only Connection Inputs[​](#write-only-connection-inputs "Direct link to Write-Only Connection Inputs") In some situations, it can be helpful to make a connection input **write-only** (e.g. a user can enter an API key, password, etc, but cannot retrieve it later). You can now mark any connection input as **write-only**. Read more about write-only connection inputs in our [docs](https://prismatic.io/docs/integrations/config-wizard/config-variables.md#write-only-connection-inputs). JULY 17, 2024 ##### Reference Existing Actions in Code-Native[​](#reference-existing-actions-in-code-native "Direct link to Reference Existing Actions in Code-Native") You can now reference existing components' actions in a code-native integration's flow. This allows you to leverage Prismatic's existing [component library](https://prismatic.io/docs/components.md) while working in your favorite IDE with TypeScript and other tools you love. Read more about invoking actions in code-native in our [docs](https://prismatic.io/docs/integrations/code-native/existing-components.md). JULY 08, 2024 ##### New Components - July 2024[​](#new-components---july-2024 "Direct link to New Components - July 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [Karbon](https://prismatic.io/docs/components/karbon.md) - Karbon is a collaborative practice management platform for accounting firms. * [Workday (Beta)](https://prismatic.io/docs/components/workday.md) - Workday HCM is a single, cloud-based solution for workforce planning, talent management, and payroll processes. * [Duro PLM](https://prismatic.io/docs/components/duro-plm.md) - Duro PLM is a platform designed to intuitively centralize part data, manage change orders, and connect to the rest of your tech stack. JULY 08, 2024 ##### Start a New Integration From a Template[​](#start-a-new-integration-from-a-template "Direct link to Start a New Integration From a Template") When you create a new integration, you can now start from a variety of Prismatic-built integration templates. The integration templates demonstrate common integration patterns across our most popular connectors. ![Configure a new integration](/docs/assets/images/integration-templates-1619ffda54d0f9c8cba599a66f413548.png) JUNE 20, 2024 ##### GitHub Actions for Components and Integrations[​](#github-actions-for-components-and-integrations "Direct link to GitHub Actions for Components and Integrations") You can now use Prismatic-provided GitHub Actions to automate the deployment of your components and integrations to Prismatic. This is handy if you have multiple Prismatic tenants and you've designated one for development and the other(s) for production. Read more about how to use the [Prismatic GitHub Actions](https://prismatic.io/docs/api/github-actions.md) in our docs. JUNE 14, 2024 ##### New Components - June 2024[​](#new-components---june-2024 "Direct link to New Components - June 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [Sage 200](https://prismatic.io/docs/components/sage-200.md) - Sage 200 is an online business management solution designed to help businesses manage their finances, customers, and business insight in one solution. * [Bynder](https://prismatic.io/docs/components/bynder.md) - Bynder is a leading digital asset management software that allows users to easily create, find, and use content, such as documents, graphics, and videos. JUNE 11, 2024 ##### In-app Docs[​](#in-app-docs "Direct link to In-app Docs") You can now search Prismatic's docs from within the web app. This makes it easier to find the information you need while building integrations. To open up the docs search, click "Help" on the top right of the Prismatic web app and search for the topic you're interested in. ![In-app docs](/docs/assets/images/in-app-docs-be1787e72a35f7646eba875fd7fd9d9c.png) MAY 21, 2024 ##### On-Prem Agent[​](#on-prem-agent "Direct link to On-Prem Agent") The on-prem agent allows you to connect your integrations to resources that are on private networks or behind firewalls. This is handy if you want to integrate with a database, file system, or other resource that is not accessible from the public internet. Read more about how the on-prem agent works in [docs](https://prismatic.io/docs/integrations/connections/on-prem-agent.md). MAY 14, 2024 ##### New Components - May 2024[​](#new-components---may-2024 "Direct link to New Components - May 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [Microsoft Intune](https://prismatic.io/docs/components/ms-intune.md) - Microsoft Intune is a cloud-based service that focuses on device management and application management. Use the Microsoft Intune component to manage users, devices, and applications. * [ServiceDesk Plus](https://prismatic.io/docs/components/servicedesk-plus.md) - ServiceDesk Plus is a comprehensive service desk software that offers a suite of IT Service management, IT asset management, CBDM, and more. Use the ServiceDesk Plus to efficiently manage Assets and Configuration Items for integrations. * [Databricks](https://prismatic.io/docs/components/databricks.md) - Databricks is an analytics and artificial intelligence platform where you can build, scale and govern data and AI, including generative AI and other machine learning models. Manage compute, workflow jobs, ML models, SQL queries and more within a Databricks workspace. * [Zendesk Knowledge](https://prismatic.io/docs/components/zendesk.md) - Zendesk Knowledge is a comprehensive solution that allows you to manage content such as articles, sections, categories, topics, and posts, as well as subscriptions and attachments in the Help Center of Zendesk. The Knowledge and Help Center actions have been added to our existing Zendesk Component. MAY 03, 2024 ##### New Component - April 2024[​](#new-component---april-2024 "Direct link to New Component - April 2024") We are pleased to announce the following component has been recently added to the Prismatic library: * [Contentful](https://prismatic.io/docs/components/contentful.md) - Contentful is a content management system (CMS) that allows developers to manage and deliver content across multiple platforms and devices. APRIL 15, 2024 ##### Code-Native Integrations - Reference components[​](#code-native-integrations---reference-components "Direct link to Code-Native Integrations - Reference components") You can now reference triggers, connections, and data sources of existing components when building code-native integrations. Both public and private components can be used in your code-native integrations. By leveraging an existing component, you can save time and effort by reusing existing functionality. To reference existing components you will need to upgrade to Prism 6.0.0 and Spectral 8.1.0. Prism 6.0.0 requires Node 18 or higher. For those of you that have already started building code-native integrations, note that type definitions now use strings rather than enums to minimize the number of imports required. Check out the [code-native documentation](https://prismatic.io/docs/integrations/code-native.md) and the video below to learn more. MARCH 29, 2024 ##### New Components - March 2024[​](#new-components---march-2024 "Direct link to New Components - March 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [ServiceTitan](https://prismatic.io/docs/components/servicetitan.md) - ServiceTitan is a comprehensive field service management solution that helps businesses manage their operations, workforce, and customer service. * [Yoti Sign](https://prismatic.io/docs/components/yoti-sign.md) - Yoti Sign is a digital identity and e-signature solution that allows users to verify their identity and sign documents electronically and securely. MARCH 07, 2024 ##### Code-Native Integrations[​](#code-native-integrations "Direct link to Code-Native Integrations") We've introduced a new option for building integrations called code-native! This is the perfect building experience for integration builders who prefer to write code rather than use a low-code designer. Code-native highlights: * Build integrations completely within your IDE rather than in the low-code designer. * Define triggers, connections, integration logic, and the customer-facing configuration experience all in code. * Define integration logic however you see fit rather than leveraging predefined logic steps. * More easily incorporate integrations into your existing CI/CD process and code repositories. Check out [this blog post](https://prismatic.io/blog/introducing-code-native-integrations/) as well as the video below for more information about the benefits of code-native integrations. Our [code-native documentation](https://prismatic.io/docs/integrations/code-native.md) is an excellent place to start when you're ready to start building your first code-native integration.
MARCH 01, 2024 ##### New Components - February 2024[​](#new-components---february-2024 "Direct link to New Components - February 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [ArcGIS](https://prismatic.io/docs/components/arcgis.md) - Esri ArcGIS is an online geographic information system providing and maintaining detailed information and tools for maps and locations. * [Aspose](https://prismatic.io/docs/components/aspose.md) - Aspose is a robust file manipulation service that can manage various document and image file formats. Use the Aspose component to create, edit, process, and convert file formats from several languages, and several platforms. FEBRUARY 05, 2024 ##### Extended i18n Support[​](#extended-i18n-support "Direct link to Extended i18n Support") You can now provide translations for dynamic phrases in your embedded marketplace. That means that you can translate things like the names of your integrations, config variables, flows, steps, config wizard page titles, and more. Update to the latest version of `@prismatic-io/embedded` and check out our [docs](https://prismatic.io/docs/embed/translations-and-internationalization.md) to take advantage of this new feature. JANUARY 26, 2024 ##### New Components - January 2024[​](#new-components---january-2024 "Direct link to New Components - January 2024") We are pleased to announce the following components have been recently added to the Prismatic library: * [Adobe Acrobat Sign](https://prismatic.io/docs/components/adobe-acrobat-sign.md) - Adobe Acrobat Sign is an e-signature management solution. Use the Adobe Acrobat Sign component to send, sign, track, and manage the signature process. * [DocuSign](https://prismatic.io/docs/components/docusign.md) - DocuSign provides intuitive solutions for sending and collecting signatures on documents. Use the DocuSign component to manage signature collection and document distribution. JANUARY 26, 2024 ##### New Components - December 2023[​](#new-components---december-2023 "Direct link to New Components - December 2023") We are pleased to announce the following component has been recently added to the Prismatic library: * [Sage Intacct](https://prismatic.io/docs/components/sage-intacct.md) - Industry-leading financial accounting software system with a broad set of functionalities for businesses across a number of different verticals. Use the Sage Intacct component to manage Invoices, Payments, Vendors, and more. DECEMBER 19, 2023 ##### New Integration Designer UX[​](#new-integration-designer-ux "Direct link to New Integration Designer UX") We've built a new integration designer experience! Many of our customers have already switched to the new designer, and we're hearing great things. The new designer makes it faster and easier to build integrations in our low-code environment. Here are the highlights: 1. Streamlined designer canvas that makes better use of space 2. Panning and zooming so you can move around quickly 3. A more intuitive experience for adding and configuring integration steps 4. ...and more enhancements coming soon! [New Designer UX](https://player.vimeo.com/video/894294812) To try out the new experience, toggle the Designer Beta setting you'll see at the top of your integration designer screen. (Note that any of your customers using the embedded designer won't see the beta version at this time.) We love it, and we hope you will too! DECEMBER 07, 2023 ##### Labels and Categories in Integration YAML[​](#labels-and-categories-in-integration-yaml "Direct link to Labels and Categories in Integration YAML") An integration's labels and category are now included in its YAML definition when it is exported as a YAML file. DECEMBER 04, 2023 ##### New Components - November 2023[​](#new-components---november-2023 "Direct link to New Components - November 2023") We are pleased to announce the following components have been recently added to the Prismatic library: * [Expensify](https://prismatic.io/docs/components/Expensify.md) - Programmatically download expense report data for analysis or insertion into your accounting package, provision accounts for new hires, and much more. * [Calendly](https://prismatic.io/docs/components/calendly.md) - Manage the scheduling of events; attendee availability; and retrieve pertinent data on users and attendees. * [Confluence](https://prismatic.io/docs/components/confluence.md) - Manage spaces, pages, and content properties on your Confluence workspaces. * [Qlik](https://prismatic.io/docs/components/qlik.md) - Manage your Data Sets, Assets, and Apps. NOVEMBER 29, 2023 ##### Connection templates[​](#connection-templates "Direct link to Connection templates") [Connection templates](https://prismatic.io/docs/integrations/connections/integration-specific.md#connection-templates) allow you to pre-populate input values for common connections used by you and your customers. For example, you can create an OAuth 2.0 connection template for Salesforce that pre-populate your OAuth client ID and client secret. When you (or your customers in embedded designer) create a Salesforce connection, they can select your Salesforce connection template and will not need to set up a client ID and secret themselves. OCTOBER 27, 2023 ##### New Components - October 2023[​](#new-components---october-2023 "Direct link to New Components - October 2023") We are pleased to announce the following components have been recently added to the Prismatic library: * [Paylocity](https://prismatic.io/docs/components/paylocity.md) - Performs tasks related to workforce management, payroll, and other HR tasks SEPTEMBER 29, 2023 ##### New Components - September 2023[​](#new-components---september-2023 "Direct link to New Components - September 2023") We are pleased to announce the following components have been recently added to the Prismatic library: * [Azure OpenAI Service](https://prismatic.io/docs/components/azure-openai-service.md) - Performs OpenAI functions using Microsoft Azure's OpenAI service models. * [Adobe I/O Events](https://prismatic.io/docs/components/adobe-io-events.md) - Facilitates trigger changes to content and data on Adobe's Experience Platform; or when predefined rules or thresholds have been met. * [ShipStation](https://prismatic.io/docs/components/shipstation.md) - This component allows you to list, create, update, and delete orders and shipments in your ShipStation account. * [Segment](https://prismatic.io/docs/components/segment.md) - Manage your Sources, Warehouses, and Destinations of your Segment account. * [Amazon Seller Central](https://prismatic.io/docs/components/amazon-seller-central.md) - Manage your catalog, orders, and shipping information of the managed stores on your amazon seller account. SEPTEMBER 26, 2023 ##### New Trigger Events[​](#new-trigger-events "Direct link to New Trigger Events") Custom triggers can now handle the following events: * `onInstanceDeploy` - when an instance is deployed, all triggers with an `onInstanceDeploy` function will execute that function. These functions are handy for setting up webhooks in third-party apps, or for updating your own API to let your team know that a customer has deployed an instance. * `onInstanceDelete` - when an instance is deleted, all triggers with an `onInstanceDelete` function will execute that function. These functions are handy for cleaning up configuration in third-party apps, or for updating your own API to let your team know that a customer has deleted an instance. Read more about these new events in our [docs](https://prismatic.io/docs/custom-connectors/triggers.md#instance-lifecycle-functions). SEPTEMBER 25, 2023 ##### Disabling Log and Step Result Retention[​](#disabling-log-and-step-result-retention "Direct link to Disabling Log and Step Result Retention") For compliance reasons your organization may need to disable the storage of logs and step results. You can now disable the storage of logs and step results on a per-instance basis. For more information, see [docs](https://prismatic.io/docs/monitor-instances/logging.md#disabling-logs-and-step-results). SEPTEMBER 20, 2023 ##### Custom Fonts in Embedded[​](#custom-fonts-in-embedded "Direct link to Custom Fonts in Embedded") You can now use custom fonts in your embedded marketplace and designer. This is handy if you want to match the fonts in your embedded marketplace to the fonts in your app. Prismatic currently supports any font available in the [Google Fonts](https://fonts.google.com/) catalog. Read more about [custom fonts](https://prismatic.io/docs/embed/theming.md#using-a-custom-font) in our docs. SEPTEMBER 05, 2023 ##### Customer-Scoped Custom Components[​](#customer-scoped-custom-components "Direct link to Customer-Scoped Custom Components") Customer users can now build and deploy their own custom components, and can use those custom components in integrations they build in your embedded marketplace. This is handy if your customers need to build custom components to interact with their own internal systems, or if they need to build custom components that interact with third-party apps that you don't already support. Read more about customer-scoped custom components on the [Embedded Designer](https://prismatic.io/docs/custom-connectors.md#customer-users-and-custom-components) article. SEPTEMBER 01, 2023 ##### New Components - September, 1 2023[​](#new-components---september-1-2023 "Direct link to New Components - September, 1 2023") We are pleased to announce the following components have been recently added to the Prismatic library: * [BigCommerce](https://prismatic.io/docs/components/bigcommerce.md) - Manage your Products, Brands, Categories and more. * [Domo](https://prismatic.io/docs/components/domo.md) - Manage your Projects, Streams and various other actions within your business's data sets. * [Gong](https://prismatic.io/docs/components/gong.md) - Manage your calls, users, libraries, to best collect insights from customer interactions. * [Google Cloud Pub/Sub](https://prismatic.io/docs/components/google-cloud-pub-sub.md) - Subscribe to topics and configure push notifications for your various Google integrations. * [Google Docs](https://prismatic.io/docs/components/google-docs.md) - Manage and share documents from your Google cloud. * [Mixpanel](https://prismatic.io/docs/components/mixpanel.md) - Manage your custom reports and measure user engagement with collected data. * [Sage HR](https://prismatic.io/docs/components/sage-hr.md) - Manage employees, teams, projects, and more in this robust Human Resources solution. * [ShipBob](https://prismatic.io/docs/components/shipbob.md) - Manage orders, shipments, and generate labels with this fulfillment services solution. * [Square](https://prismatic.io/docs/components/square.md) - Manage your total point of sale system including payments, refunds, and inventory. * [Zendesk Sell](https://prismatic.io/docs/components/zendesk-sell.md) - Manage your sales force automation including leads, orders, and deals. AUGUST 15, 2023 ##### Integration Runner Moving to NodeJS 18.x[​](#integration-runner-moving-to-nodejs-18x "Direct link to Integration Runner Moving to NodeJS 18.x") **Platform Announcement**: We are pleased to announce that we will be transitioning our integration runner from NodeJS 14.x to 18.x beginning on Monday, August 21st. Running on the latest LTS version of NodeJS has huge advantages, including performance improvements, continued support and security patches, and a baked-in [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/fetch). This change will not require any action on your part. Your flows and components will transition seamlessly to their new environment. We will let you know once the upgrade is complete, so you can take advantage of any new features NodeJS 18.x has to offer. AUGUST 10, 2023 ##### Embedded designer improvements[​](#embedded-designer-improvements "Direct link to Embedded designer improvements") Two new features were added to embedded designer: 1. You can now issue a `prismatic.showDashboard()` to give users a holistic view of their integrations, instances, executions, logs, and more. See [docs](https://prismatic.io/docs/embed/additional-screens/show-dashboard.md) for more information. ![Open the customer dashboard in embedded](/docs/assets/images/show-dashboard-b897845438c2be124d28bb01459b6e95.png) 2. You can now filter the list of components that are available to your customers. See [docs](https://prismatic.io/docs/embed/workflow-builder/designer#filtering-components) for more information. AUGUST 09, 2023 ##### Embedded Designer[​](#embedded-designer "Direct link to Embedded Designer") Your customers may want to build integrations for themselves between your product and the other apps and services they use. Embedded designer allows your customers to log in to your app and build integrations using Prismatic's integration designer. You can provision your customers access to private components that you have built, and can provide your customers with "templates" that they can use as a starting point for building their own integrations. [Embedded Designer Announcement](https://player.vimeo.com/video/852820250) Read more about the embedded designer on [Embedding Integration Designer](https://prismatic.io/docs/embed/workflow-builder.md) docs page. If you're interested in using embedded designer, please [contact support](mailto:support@prismatic.io) to discuss enabling embedded designer for your organization. ##### New Embedded SDK[​](#new-embedded-sdk "Direct link to New Embedded SDK") Along with embedded designer comes a new embedded SDK. If you are currently using [@prismatic-io/marketplace](https://www.npmjs.com/package/@prismatic-io/marketplace) for embedded marketplace, swap it out for the new [@prismatic-io/embedded](https://www.npmjs.com/package/@prismatic-io/embedded) NodeJS package to begin using the embedded designer features. `@prismatic-io/marketplace` will continue to work, but `@prismatic-io/embedded` is a superset of features and new features and enhancements will only be added to the new embedded SDK. ##### Embedded SDK v2.0.0[​](#embedded-sdk-v200 "Direct link to Embedded SDK v2.0.0") The newest version of the embedded SDK changes the Marketplace default of `screenConfiguration.marketplace.configuration` to `allow-details` instead of `always-show-details`. See [docs](https://prismatic.io/docs/embed/marketplace.md#integration-configuration-detail-screen) for information on the integration configuration detail screen options. JULY 26, 2023 ##### Linking Execution Replays[​](#linking-execution-replays "Direct link to Linking Execution Replays") [Replays](https://prismatic.io/docs/monitor-instances/retry-and-replay.md) of executions are now linked to the original execution. This makes it easier to query for failed executions, and replay only those executions that don't have a subsequent replay that succeeded. [Linking Execution Replays](https://player.vimeo.com/video/848852783) See [docs](https://prismatic.io/docs/monitor-instances/retry-and-replay.md) for more information, and the [Examples repo](https://github.com/prismatic-io/examples/tree/main/api/replay-failed-executions) in GitHub for the script mentioned in the above video. JULY 25, 2023 ##### New Components - July 2023[​](#new-components---july-2023 "Direct link to New Components - July 2023") Some exciting new components landed this month: * [Google Content Shopping](https://prismatic.io/docs/components/google-content-shopping.md) - Update Google Content Shopping feeds, products, and more * [Azure Service Bus](https://prismatic.io/docs/components/azureServiceBus.md) - Manage Azure Service Bus queues and topics * [SAP S4/HANA](https://prismatic.io/docs/components/sapS4Hana.md) - Maintain ERP records in SAP S4/HANA * [Google BigQuery](https://prismatic.io/docs/components/google-cloud-bigquery.md) - Manage Google BigQuery datasets, tables, and more JUNE 30, 2023 ##### New Components - June 2023[​](#new-components---june-2023 "Direct link to New Components - June 2023") This month we released several new components to help you build integrations for your customers: * [Algolia](https://prismatic.io/docs/components/algolia.md) - Update Algolia indexes and records and perform search operations. * [ClickUp](https://prismatic.io/docs/components/click-up.md) - Manage Click Up users, projects and teams within your customers' Click Up workspaces. * [Greenhouse](https://prismatic.io/docs/components/greenhouse.md) - Manage Greenhouse jobs, candidates, applications, and more. * [Postmark](https://prismatic.io/docs/components/postmark.md) - Send and receive emails using Postmark's email API. * [SMTP](https://prismatic.io/docs/components/smtp.md) - Send emails using SMTP. * [Snowflake](https://prismatic.io/docs/components/snowflake.md) - Manage Snowflake databases, warehouses, schemas, tables, and more. JUNE 21, 2023 ##### Hide the Instance Details Configuration Page[​](#hide-the-instance-details-configuration-page "Direct link to Hide the Instance Details Configuration Page") You now have options for configuring how and if a marketplace user should access the integrations configuration details screen. This includes the ability to prevent a marketplace user from accessing the instance configuration details screen. This is useful if your customers should not access the **Test**, **Executions**, **Monitors** or **Logs** functionality. Check out our [docs](https://prismatic.io/docs/embed/marketplace.md#integration-configuration-detail-screen) for more information. MAY 30, 2023 ##### New Components - May 2023[​](#new-components---may-2023 "Direct link to New Components - May 2023") This month we released a new component to connect to [Adobe Analytics](https://prismatic.io/docs/components/adobe-analytics.md), so your customers can manage companies, report suites, metrics and more. MAY 16, 2023 ##### Multiple Instances of Integrations in Embedded Marketplace[​](#multiple-instances-of-integrations-in-embedded-marketplace "Direct link to Multiple Instances of Integrations in Embedded Marketplace") Your customers can now enable multiple instances of an integration through embedded marketplace. This allows your customers to set up several copies of an integration of themselves, each with different configurations. Check out our [docs](https://prismatic.io/docs/embed/marketplace.md#multiple-instances-of-one-integration-in-marketplace) for more information. MAY 15, 2023 ##### Embedded Config Variable Settings[​](#embedded-config-variable-settings "Direct link to Embedded Config Variable Settings") Granular config variable and connection input settings allow your org to better control who can see and set integration configurations values. Read more in [docs](https://prismatic.io/docs/integrations/config-wizard/config-variables.md#config-variable-visibility). ##### Endpoint Security Settings[​](#endpoint-security-settings "Direct link to Endpoint Security Settings") New Endpoint security settings allow your org to configure which endpoints should have API keys and who should set them (org vs customer). Read more in [docs](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md#securing-endpoints-with-api-keys). APRIL 28, 2023 ##### New Components - April 2023[​](#new-components---april-2023 "Direct link to New Components - April 2023") A couple of new components are available! A full catalog is available [here](https://prismatic.io/docs/components.md). This past month, we added: * [Gusto](https://prismatic.io/docs/components/gusto.md) - Manage payroll, benefits, and human resource within Gusto * [Notion](https://prismatic.io/docs/components/notion.md) - Manage Notion pages, databases, and users Additionally, the [Airtable](https://prismatic.io/docs/components/airtable.md) component was updated to handle OAuth connections, as API key connections are being deprecated early next year. APRIL 18, 2023 ##### Advanced Embedded Marketplace Filtering[​](#advanced-embedded-marketplace-filtering "Direct link to Advanced Embedded Marketplace Filtering") You can now use logical operators like `and`, `or`, `startsWith`, `notEqual` and more to filter the integrations in your embedded marketplace. For example, if you would like to show all integrations that have a category "ERP" and label "paid", and would also like your Dropbox and Slack integrations to be displayed, a filter could look like: ```ts [ BooleanOperator.or, [ BooleanOperator.and, [TermOperator.equal, "category", "ERP"], [TermOperator.in, "labels", "paid"], ], [TermOperator.equal, "name", "Dropbox"], [TermOperator.equal, "name", "Slack"], ]; ``` Read more about advanced filters in the [docs](https://prismatic.io/docs/embed/marketplace.md#advanced-integration-filters). MARCH 30, 2023 ##### New Components - March 2023[​](#new-components---march-2023 "Direct link to New Components - March 2023") Our list of built-in components continues to grow. A full catalog is available [here](https://prismatic.io/docs/components.md). This past month, we added: * [Microsoft Graph API](https://prismatic.io/docs/components/ms-graph-api.md) - Interact with the Microsoft Graph API * [OpenAI](https://prismatic.io/docs/components/openai.md) - Interact with OpenAI models, including Chat GPT and DALL·E Additionally, * [Microsoft Outlook](https://prismatic.io/docs/components/ms-outlook.md) - Added actions to interact with mailboxes, folders and email messages FEBRUARY 22, 2023 ##### New Components - February 2023[​](#new-components---february-2023 "Direct link to New Components - February 2023") We added a few additional components in February: * [Arena PLM](https://prismatic.io/docs/components/arena-plm.md) - Interact with items and resources in Arena PLM * [Google Analytics - GA4](https://prismatic.io/docs/components/google-analytics-ga4.md) - Manage Google Analytics GA4 accounts and data * [HTML Utils](https://prismatic.io/docs/components/html-utils.md) - Helpful HTML-related functions for building HTML documents and HTML-based emails. JANUARY 26, 2023 ##### New Components - January 2023[​](#new-components---january-2023 "Direct link to New Components - January 2023") Our list of built-in components continues to grow. A full catalog is available [here](https://prismatic.io/docs/components.md). This past month, we added: * [HTML Utils](https://prismatic.io/docs/components/html-utils.md) - Helpful HTML-related functions for building HTML documents and HTML-based emails * [JSON Forms](https://prismatic.io/docs/components/jsonforms.md) - Create powerful custom forms for the configuration wizard * [Marketo](https://prismatic.io/docs/components/marketo.md) - Manage Marketo records * [MessagePack](https://prismatic.io/docs/components/messagepack.md) - Efficiently serialize or deserialize data into a JSON-like format * [Microsoft Outlook](https://prismatic.io/docs/components/ms-outlook.md) - Read and manage Microsoft Outlook calendars * [NetSuite](https://prismatic.io/docs/components/netsuite.md) - Manage NetSuite records DECEMBER 29, 2022 ##### JSON Forms Config Variables[​](#json-forms-config-variables "Direct link to JSON Forms Config Variables") We've integrated [JSON Forms](https://jsonforms.io/) into our configuration wizard to give you more control over your users' integration configuration experience. You can build a static JSON form using the built-in [JSON Forms](https://prismatic.io/docs/components/jsonforms.md) component, or create dynamic configuration experiences by adding JSON Forms [data sources](https://prismatic.io/docs/custom-connectors/data-sources.md) to your custom components. ![JSON Forms in the configuration wizard](/docs/assets/images/jsonforms-ce21a45acba4083ab26491ef439ea7dd.png) DECEMBER 14, 2022 ##### Internationalization (i18n) Support[​](#internationalization-i18n-support "Direct link to Internationalization (i18n) Support") Not all of your customers speak English. You can now offer translations for embedded marketplace, so your customers can enable integrations in their native language. See [Embedding Marketplace](https://prismatic.io/docs/embed/translations-and-internationalization.md) for information on how to offer i18n support to your customers. NOVEMBER 29, 2022 ##### New Components - October / November 2022[​](#new-components---october--november-2022 "Direct link to New Components - October / November 2022") Our component team has been busy! A full catalog is available [here](https://prismatic.io/docs/components.md). In October and November we added: * [GraphQL](https://prismatic.io/docs/components/graphql.md) - Make GraphQL requests (queries and mutations) to a GraphQL-based API * [Microsoft Bing Ads](https://prismatic.io/docs/components/ms-bing-ads.md) - Manage Microsoft Bing Ad Customer Services * [Microsoft Bot Framework](https://prismatic.io/docs/components/ms-bot-framework.md) - Manage conversational interactions across platforms using Microsoft Bot Framework * [Microsoft Outlook](https://prismatic.io/docs/components/ms-outlook.md) - Read and manage Microsoft Outlook calendars * [Odoo](https://prismatic.io/docs/components/odoo.md) - Manage records in an Odoo database * [Zoho](https://prismatic.io/docs/components/zoho.md) - Manage records, users, and more in your Zoho CRM and Books apps NOVEMBER 09, 2022 ##### User Level Configuration[​](#user-level-configuration "Direct link to User Level Configuration") We're proud to introduce a new instance configuration option - **User-Level Configuration** (ULC). **What is ULC**? ULC helps when multiple users of a single customer all need an integration. It allows you to configure a single instance of an integration for a customer, but collect configuration from multiple users and execute using user-specific configuration. **Why use ULC**? ULC is handy if your integration requires user-specific config variables and credentials. For example, suppose your app needs to write data to several users' private Dropbox folders. With ULC, you can collect connection information for several users within a customer, and integrate with each of their individual Dropbox accounts. **How does ULC work**? At a high level, a single instance of an integration is deployed to a customer, and is configured with some customer-wide config variables. Individual users within the customer, then, go through a ULC config wizard and supply user-specific credentials and config variables. When the instance runs, it pulls in user-specific configuration depending on some rules you set. Read more about ULC in our [docs](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md). NOVEMBER 03, 2022 ##### Persisting Data for Integrations[​](#persisting-data-for-integrations "Direct link to Persisting Data for Integrations") You can now persist data between instances of the same integration. This is handy if your customers need to share some state, or if you need to persist a customer mapping for [preprocess flows](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md). See the [Persist Data](https://prismatic.io/docs/components/persist-data.md) docs, or write your own component that [persists integration data](https://prismatic.io/docs/custom-connectors/actions.md#execution-instance-and-cross-flow-state) OCTOBER 04, 2022 ##### Source Code for Prism and Marketplace[​](#source-code-for-prism-and-marketplace "Direct link to Source Code for Prism and Marketplace") Source code for the `@prismatic-io/prism` CLI tool and the embedded marketplace library, `@prismatic-io/marketplace`, have been added to public repositories on GitHub. `prism` wraps the [Prismatic API](https://prismatic.io/docs/api.md) and provides users a way to perform CRUD (create, read, update, delete) operations on a variety of Prismatic resources (components, integrations, instances, customers, etc.) from the command line. Having it publicly available provides a great reference for developers looking to wrap the Prismatic API themselves. The two projects join the custom component SDK, `@prismatic-io/spectral`, which was already publicly available: * `@prismatic-io/prism` - * `@prismatic-io/marketplace` - * `@prismatic-io/spectral` - * `@prismatic-io/examples` - SEPTEMBER 28, 2022 ##### New Components - September 2022[​](#new-components---september-2022 "Direct link to New Components - September 2022") Our list of built-in components continues to grow. A full catalog is available [here](https://prismatic.io/docs/components.md). This past month, we added: * [Fluent Commerce](https://prismatic.io/docs/components/fluent-commerce.md) - Manage orders within Fluent Commerce * [Google Analytics](https://prismatic.io/docs/components/google-analytics.md) - Manage Google Analytics accounts * [Gmail](https://prismatic.io/docs/components/google-gmail.md) - Fetch, read and manage messages in Gmail * [UUID](https://prismatic.io/docs/components/uuid.md) - Generate UUIDs and GUIDs SEPTEMBER 19, 2022 ##### Re-imagined Instance Configuration Wizard[​](#re-imagined-instance-configuration-wizard "Direct link to Re-imagined Instance Configuration Wizard") We've overhauled the way deploy-time configuration management works, making it much more flexible and dynamic. It's a wizard now, so you can split complex configuration pages in a way that's intuitive to your customers. Any existing configuration pages will simply become part of a one page wizard, so everything will continue to work as-is. Read more about the configuration wizard designer on the [Config Wizard](https://prismatic.io/docs/integrations/config-wizard.md) docs page. ![Embedded Instance Configuration Experience ](/docs/assets/images/old-instance-config-b031a27e9f9866b60c0a9c7e846b4c97.png)
![Embedded Instance Configuration Experience ](/docs/assets/images/new-instance-config-f6b3e2867cdca0c05b36497afbb81bdb.png) SEPTEMBER 19, 2022 ##### UI Redesign[​](#ui-redesign "Direct link to UI Redesign") You've probably noticed that the Prismatic UI has gotten a facelift! Based on everything we've learned over the last couple of years, we've improved the UI to feel better and be more intuitive. ![UI ](/docs/assets/images/old-ui-cd6c509d45877afaf32b6e1a770183b8.png)
![UI ](/docs/assets/images/new-ui-086d156f1215dd88a0bd7d0569dc9c4e.png) JULY 29, 2022 ##### New Components - July 2022[​](#new-components---july-2022 "Direct link to New Components - July 2022") This month we added a new utility component for zipping and unzipping files: * [Zip](https://prismatic.io/docs/components/zip.md) - Provides utility methods for working with zip files JUNE 29, 2022 ##### Access Instance Metadata from an Action[​](#access-instance-metadata-from-an-action "Direct link to Access Instance Metadata from an Action") You can now access additional information about the currently running execution from your custom component including: * The name and ID of the running instance * The name, ID and external ID of the customer the instance is deployed to * Webhook URLs for all flows of the running instance This is handy if you need to know information about the current run context, or if you're building actions that configure or delete webhooks in a third-party app. To access new `context` properties, update your custom component's `@prismatic-io/spectral` version `6.6.0`. Read more about the expanded [context parameter](https://prismatic.io/docs/custom-connectors/actions.md#the-context-parameter). JUNE 28, 2022 ##### New Components - June 2022[​](#new-components---june-2022 "Direct link to New Components - June 2022") Our list of built-in components continues to grow! A full catalog is available [here](https://prismatic.io/docs/components.md). This past month, we added: * [GitHub](https://prismatic.io/docs/components/github.md) - Manage users, repositories, licenses, and more on GitHub * [Hash](https://prismatic.io/docs/components/hash.md) - Compute hashes of strings using common hash functions * [Pipedrive](https://prismatic.io/docs/components/pipedrive.md) - Manage leads, companies, activities, and more on the Pipedrive platform * [Rippling](https://prismatic.io/docs/components/rippling.md) - Rippling makes it easy to manage your company's Payroll, Benefits, HR, and IT - all in one, modern platform JUNE 06, 2022 ##### Additional Control Over Marketplace UI[​](#additional-control-over-marketplace-ui "Direct link to Additional Control Over Marketplace UI") You now have more control over the UI elements that appear to your customers in your embedded marketplace. If you would like to hide the **Back to Marketplace** link, or the **Test**, **Executions**, **Logs**, or **Monitors** tabs on an instance configuration screen [you can](https://prismatic.io/docs/embed/marketplace.md#hiding-ui-elements-in-marketplace)! Bump your `@prismatic-io/marketplace` version to `3.1.0`, and add a `screenConfiguration` code block to your marketplace. MAY 24, 2022 ##### New Components - May 2022[​](#new-components---may-2022 "Direct link to New Components - May 2022") We have five new components this month (including a component to Prismatic itself - how meta!). * [IMAP](https://prismatic.io/docs/components/imap.md) - Fetch and manage email via IMAP * [Intercom](https://prismatic.io/docs/components/intercom.md) - Manage companies, contacts and tags on the Intercom platform * [Microsoft Sharepoint](https://prismatic.io/docs/components/ms-sharepoint.md) - Interact with sites, drives, and items within Microsoft Sharepoint * [Pretty Good Privacy (PGP)](https://prismatic.io/docs/components/pgp.md) - Create and translate encrypted messages * [Prismatic](https://prismatic.io/docs/components/prismatic.md) - Interact with the Prismatic API to manage customers, integrations, instances, etc. A full catalog is available [here](https://prismatic.io/docs/components.md). MAY 17, 2022 ##### Improvements to Custom Component Development[​](#improvements-to-custom-component-development "Direct link to Improvements to Custom Component Development") We've made several improvements to the custom component development experience. To highlight a few: * You can now [`clean`](https://prismatic.io/docs/custom-connectors/inputs.md#cleaning-inputs) your reusable inputs, which helps ensure type safety and catches problems with inputs before they reach the `perform` function. * You can now add a [global error handler](https://prismatic.io/docs/custom-connectors/error-handling.md#global-error-handlers) to your component, which helps you capture and display more informative errors if they're thrown. * The improved [testing harness](https://prismatic.io/docs/spectral/spectral-6-upgrade-guide.md#new---spectral-testing-harness) gives you more flexibility when unit testing your actions and triggers. * The `prism` CLI tool can now fetch existing integration connections (including OAuth 2.0 access tokens) and store them in environment variables, so you can [use them for unit testing](https://prismatic.io/docs/cli/prism.md#componentsdevrun). Update to the latest [`@prismatic-io/spectral`](https://www.npmjs.com/package/@prismatic-io/spectral) 6.x version to take advantage of these new features! MAY 04, 2022 ##### Cross-Flow State Storage[​](#cross-flow-state-storage "Direct link to Cross-Flow State Storage") You can now store and load across flows of an instance. One flow can save state, and another flow can load that saved state. Check out our [Persist Data](https://prismatic.io/docs/components/persist-data.md) for documentation on the new "Cross Flow" actions, and see our [docs](https://prismatic.io/docs/custom-connectors/actions.md#execution-instance-and-cross-flow-state) to build state storage into your custom components. MAY 04, 2022 ##### Step-Level Error Handling[​](#step-level-error-handling "Direct link to Step-Level Error Handling") Sometimes a step in an integration throws an error. This can be caused by a variety of external factors - temporary network connectivity issues, brief third-party API outages, etc. You can now configure how the integration runner handles errors on each step. You can choose to stop the instance execution (that's the current default behavior), you can ignore the error and continue the run, or you can choose to wait and retry the step at a later time. Read more in our [docs](https://prismatic.io/docs/integrations/low-code-integration-designer/error-handling.md). MAY 03, 2022 ##### Instance Remove Trigger[​](#instance-remove-trigger "Direct link to Instance Remove Trigger") A new management trigger - [Instance Remove](https://prismatic.io/docs/components/management-triggers.md#instanceremove) has been added to the [Management Triggers](https://prismatic.io/docs/components/management-triggers.md) component. Flows that use the instance remove trigger are run when an instance is deleted. This new trigger is handy for cleaning up configuration created by the integration. For example, you can remove webhook configuration in third-party apps, or update our own API so your team knows that a customer removed an integration. MAY 02, 2022 ##### Set Config Variables from Marketplace[​](#set-config-variables-from-marketplace "Direct link to Set Config Variables from Marketplace") You can now set values for configuration variables from your app within your embedded marketplace. This is helpful if you know some information about your customer (their API key, a special endpoint they use, data mapping configuration, etc), and would like to set a config variable value so they don't need to. Read more about [Dynamically Setting Config Variables in Marketplace](https://prismatic.io/docs/embed/marketplace.md#dynamically-setting-config-variables-in-marketplace) in our docs. APRIL 28, 2022 ##### New Components - April 2022[​](#new-components---april-2022 "Direct link to New Components - April 2022") We added some new components to [our catalog](https://prismatic.io/docs/components.md) in April. This past month, we added: * [Facebook Marketing](https://prismatic.io/docs/components/facebook-marketing.md) - Interact with ads and ad sets in your Facebook Marketing account * [Google Ads](https://prismatic.io/docs/components/google-ads.md) - Manage Google Ad campaigns * [WooCommerce](https://prismatic.io/docs/components/woo-commerce.md) - Easily manage your customers, orders, and products in your WooCommerce platform APRIL 28, 2022 ##### Sending Data Through URL Path[​](#sending-data-through-url-path "Direct link to Sending Data Through URL Path") Some popular SaaS applications append URL paths to the webhooks that they're configured to use. So, given a webhook endpoint `https://hooks.prismatic.io/trigger/EXAMPLE==` they might send data to `https://hooks.prismatic.io/trigger/EXAMPLE==/order/created`. You can now send data to webhook triggers four ways: * Request body * Request headers * URL parameters * URL path (added) Check out our [Sending data to webhook triggers](https://prismatic.io/docs/integrations/triggers/webhook/sending-data.md) article for more information. APRIL 26, 2022 ##### Improved Shared Endpoint Configuration[​](#improved-shared-endpoint-configuration "Direct link to Improved Shared Endpoint Configuration") Instance-specific endpoints (meaning all flows in an instance share one webhook URL) and shared endpoints (meaning all instances of an integration share one webhook URL) are now easier to configure, test and troubleshoot. Check out our [Endpoint Configuration](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md) article for details. APRIL 19, 2022 ##### Using the GET HTTP Verb to Invoke Instances[​](#using-the-get-http-verb-to-invoke-instances "Direct link to Using the GET HTTP Verb to Invoke Instances") Instance webhook triggers can now be invoked using the GET HTTP verb in addition to the POST verb. Some third-party apps (notably [Dropbox](https://prismatic.io/docs/components/dropbox.md) among others) verify that a webhook endpoint is ready to receive requests with a GET request. They then send webhook payloads with POST requests. This change was made to support the initial verification GET requests. MARCH 29, 2022 ##### New Components - March 2022[​](#new-components---march-2022 "Direct link to New Components - March 2022") Our list of built-in components continues to grow. A full catalog is available [here](https://prismatic.io/docs/components.md). This past month, we added: * [Collection Tools](https://prismatic.io/docs/components/collection-tools.md) - Perform common operations on collections * [Microsoft OneDrive](https://prismatic.io/docs/components/ms-onedrive.md) - Interact with files and drives inside Microsoft OneDrive MARCH 24, 2022 ##### Cloning Flows[​](#cloning-flows "Direct link to Cloning Flows") If you need to add a flow that is similar to another flow you've already built, it's helpful to be able to **clone** (make a copy of) a flow. You can now clone an existing flow from the flow menu in the integration designer. ![Clone integration flow in Prismatic app](/docs/assets/images/clone-flow-9395b81e33e85d4a9aed5d6d2fbe285b.png) For more information, see our [Building Integrations](https://prismatic.io/docs/integrations/low-code-integration-designer/flows.md#cloning-a-flow) article. MARCH 21, 2022 ##### Filter Marketplace Integrations[​](#filter-marketplace-integrations "Direct link to Filter Marketplace Integrations") You can now filter the integrations that you show in your embedded marketplace by [category](https://prismatic.io/docs/integrations/low-code-integration-designer.md#categorizing-integrations) or [label](https://prismatic.io/docs/integrations/low-code-integration-designer.md#assigning-labels-to-an-integration). This gives you the flexibility to show specific types of integrations to specific users or customers. To get started with filtering your embedded marketplace, update your [@prismatic-io/marketplace](https://www.npmjs.com/package/@prismatic-io/marketplace) package to version 1.1.2, and add a `filters` attribute to your `prismatic.showMarketplace()` invocation - see [docs](https://prismatic.io/docs/embed/marketplace.md#filtering-integrations) for details. MARCH 16, 2022 ##### Labels for Customers, Integrations and Instances[​](#labels-for-customers-integrations-and-instances "Direct link to Labels for Customers, Integrations and Instances") You can now assign labels to your [customers](https://prismatic.io/docs/customers/managing-customers.md#customer-labels), [integrations](https://prismatic.io/docs/integrations/low-code-integration-designer.md#assigning-labels-to-an-integration) and [instances](https://prismatic.io/docs/instances/deploying.md). This helps you keep your Prismatic account organized so you can find what you need quickly. ![Assign labels to customers, integrations and instances in Prismatic app](/docs/assets/images/labels-7ceebeb9296626e75c35ffa9296be811.png) FEBRUARY 28, 2022 ##### Improvements to Embedded Theming[​](#improvements-to-embedded-theming "Direct link to Improvements to Embedded Theming") You can now create custom themes for your embedded marketplace for both dark and light mode users of your application. Check out our [embedded marketplace docs](https://prismatic.io/docs/embed/theming.md) for information on how to theme your embedded marketplace to match your app's dark and light mode look-and-feel. FEBRUARY 23, 2022 ##### New Components - February 2022[​](#new-components---february-2022 "Direct link to New Components - February 2022") We have a couple new components this month! A full catalog is available [here](https://prismatic.io/docs/components.md). This past month, we added: * [Math](https://prismatic.io/docs/components/math.md) - Perform common math operations on numbers or lists of numbers * [Microsoft Dynamics 365](https://prismatic.io/docs/components/ms-dynamics.md) - Query, create, update or delete Microsoft Dynamics 365 API records JANUARY 21, 2022 ##### New Components - January 2022[​](#new-components---january-2022 "Direct link to New Components - January 2022") We have some new components to show off this month: * [QuickBooks Time](https://prismatic.io/docs/components/quickbooks-time.md) - Manage employee time tracking within Intuit QuickBooks Time * [Sage](https://prismatic.io/docs/components/sage.md) - Manage contacts and others connected to your Sage account * [SOAP](https://prismatic.io/docs/components/soap.md) - Easily interact with SOAP-based APIs JANUARY 20, 2022 ##### Integrate Faster with SOAP APIs[​](#integrate-faster-with-soap-apis "Direct link to Integrate Faster with SOAP APIs") It's now easier to integrate with SOAP-based APIs. For quick one-off calls, you can use our built-in [SOAP component](https://prismatic.io/docs/components/soap.md) to fetch WSDL definitions and make requests to an API's SOAP methods. For more complex SOAP APIs, you can leverage our custom component SDK to wrap SOAP methods into a series of component actions. JANUARY 05, 2022 ##### Simpler, More Flexible Authentication[​](#simpler-more-flexible-authentication "Direct link to Simpler, More Flexible Authentication") We've revamped the way that components connect to third-party apps and services. The new concept is called **connections**, and they make authentication *simpler*, more *flexible*, and *easier to support*. We'll be updating our built-in components to use connections in the coming weeks, and credentials will eventually be phased out in favor of connections (but don't worry - credentials won't be sunset immediately!). For a full run-down how connections improve integration development and support and customer self-deployment, check out our [blog announcement.](https://prismatic.io/docs/blog/simpler-more-flexible-authentication) Here's a quick summary: * Component developers have *more flexibility* when declaring what information their components need to connect to a third party. They can define [custom connections](https://prismatic.io/docs/custom-connectors/connections.md) that include any number of fields, like username, password, API key, tenant ID, endpoint URL or other fields that are unique to the service they're integrating with. * The [OAuth 2.0 flow](https://prismatic.io/docs/integrations/connections/oauth2.md) got much *simpler* and cleaner for both integration builders and customers who deploy the integration - customers see a single button to click when they need to authenticate with OAuth. * Authorization got *simpler* in general - connections live within the integration designer or a deployed instance. You don't need to create credentials from the organization or customer settings pages, nor juggle credential types. Components know what connections they're compatible with, and can only be paired with those connection config variables. * Connections are *easier to support*. You can now configure alert monitors to [notify you](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md#alerting-on-connection-errors) when connections (OAuth or otherwise) expire or fail to authenticate in an integration. DECEMBER 22, 2021 ##### New Components - December 2021[​](#new-components---december-2021 "Direct link to New Components - December 2021") We created new components for three popular SaaS apps this month: * [BambooHR](https://prismatic.io/docs/components/bamboohr.md) - Keep track of employees' HR needs * [Xero](https://prismatic.io/docs/components/xero.md) - Create and manage invoices, items, accounts, payments and more objects within a Xero account * [Zoom](https://prismatic.io/docs/components/zoom.md) - Manage Zoom users, meetings and webinars A full catalog of all of our components is available [here](https://prismatic.io/docs/components.md). DECEMBER 14, 2021 ##### Looping and Pagination[​](#looping-and-pagination "Direct link to Looping and Pagination") The [loop component](https://prismatic.io/docs/components/loop.md) has been improved to facilitate easily looping over a paginated API. Many third-party APIs limit the number of records you can fetch at once, and let you load a batch (page) of records at a time. You can now more easily loop over paged records that you fetch from an external API, and you can break out of a loop whenever you've paged over all available records. Check out our [quickstart](https://prismatic.io/docs/integrations/common-patterns/loop-over-paginated-api.md) for a tutorial on how to loop over pages of records in an integration. NOVEMBER 24, 2021 ##### New Components - November 2021[​](#new-components---november-2021 "Direct link to New Components - November 2021") We have a bunch of new built-in components this month. A full catalog is available [here](https://prismatic.io/docs/components.md). This past month, we added: * [Asana](https://prismatic.io/docs/components/asana.md) - Manage users, projects, and teams in your Asana workspace * [Monday](https://prismatic.io/docs/components/monday.md) - Manage boards, items, and columns inside your Monday account * [Microsoft Project](https://prismatic.io/docs/components/ms-project.md) - Make queries to reporting data from a Project Web App instance * [New Relic](https://prismatic.io/docs/components/new-relic.md) - Easily manage metrics, logs, and events * [Tableau](https://prismatic.io/docs/components/tableau.md) - Manage projects and workbooks in your Tableau site * [Zendesk](https://prismatic.io/docs/components/zendesk.md) - Manage Tickets and users in Zendesk NOVEMBER 16, 2021 ##### Stream Logs to External Logging Services[​](#stream-logs-to-external-logging-services "Direct link to Stream Logs to External Logging Services") Customers on enterprise plans can now stream logs and metrics to external logging services (like DataDog or New Relic). This is useful, since you likely already use a logging service to collect logs from your various applications. Now, your integration logs can live alongside the rest of your applications' logs. Read more on our [logging article](https://prismatic.io/docs/monitor-instances/logging/streaming-logs-externally.md). NOVEMBER 10, 2021 ##### New Step Input: Expressions[​](#new-step-input-expressions "Direct link to New Step Input: Expressions") You can now reference multiple config variables, step results, and static strings for step input using templated [inputs](https://prismatic.io/docs/integrations/low-code-integration-designer/passing-data-between-steps.md#template-inputs). This lets you concatenate config variables, text, and step results together, without needing an additional step to do the concatenation. It's helpful for dynamically generating URLs, queries, messages, and more. ![Expressions for step inputs in Prismatic app](/docs/assets/images/expression-input-16c5699848592fce50fc1a925937dcdc.png) **Update:** As of 2022-05-25, "Expression inputs" have been renamed "Template inputs" OCTOBER 20, 2021 ##### New Components - October 2021[​](#new-components---october-2021 "Direct link to New Components - October 2021") Our list of built-in components continues to grow. A full catalog is available [here](https://prismatic.io/docs/components.md). This past month, we added: * [AWS Glue](https://prismatic.io/docs/components/aws-glue.md) - Perform data transformation through AWS Glue * [AWS Lambda](https://prismatic.io/docs/components/aws-lambda.md) - Manage and invoke AWS Lambdas * [CSV](https://prismatic.io/docs/components/csv.md) - Build and parse CSV files to and from JavaScript arrays * [Firebase](https://prismatic.io/docs/components/firebase.md) - Create, read, update, and delete documents in a Firebase Cloud Firestore database collection * [Google Calendar](https://prismatic.io/docs/components/google-calendar.md) - Manage calendars and events in Google Calendar * [Hubspot](https://prismatic.io/docs/components/hubspot.md) - Manage objects and associations in the Hubspot CRM platform * [Jira](https://prismatic.io/docs/components/atlassian-jira.md) - Manage Jira issues, comments, projects and users * [Mailchimp](https://prismatic.io/docs/components/mailchimp.md) - Interact with email campaign lists and e-commerce resources * [Microsoft Excel](https://prismatic.io/docs/components/ms-excel.md) - Parse and build xlsx files (spreadsheets) * [Microsoft Teams](https://prismatic.io/docs/components/ms-teams.md) - Manage the teams, groups, channels, and messages associated with your Microsoft Teams account * [Redis](https://prismatic.io/docs/components/redis.md) - Manage items in a Redis database OCTOBER 18, 2021 ##### Write Your Own Triggers[​](#write-your-own-triggers "Direct link to Write Your Own Triggers") The vast majority of integrations are triggered in one of two ways: they either run on a schedule (i.e "At 15 minutes past each hour") or they're invoked by an HTTP request to a [webhook](https://prismatic.io/docs/integrations/triggers/webhook.md). Not all apps and services that you integrate with are the same, though, and some require additional functionality or validation. For example, Salesforce [outbound messages](https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_om_outboundmessaging_understanding.htm) (webhooks) require a special XML-formatted acknowledgement (ACK) response to a webhook request, and Amazon's Simple Notification Service (SNS) requires that integrations send an HTTP POST request to AWS to [confirm an SNS subscription](https://docs.aws.amazon.com/sns/latest/dg/SendMessageToHttp.prepare.html). With those considerations in mind, we've extended our [custom component SDK](https://www.npmjs.com/package/@prismatic-io/spectral) to allow you to write your own triggers for your components. Your triggers can handle things like: * Replying to webhook requests with custom responses * Validating webhook headers and payload data * Transforming and processing XML, CSV, or proprietary data formats so the rest of your integration can easily reference data that comes in If you've [written your own actions](https://prismatic.io/docs/custom-connectors/actions.md), writing a trigger will feel very familiar. Check out our docs on [writing triggers](https://prismatic.io/docs/custom-connectors/triggers.md) to get started. ##### Configurable Webhook Triggers[​](#configurable-webhook-triggers "Direct link to Configurable Webhook Triggers") The general webhook trigger is now more configurable. You can now specify the HTTP code, headers, response type, and response body that the webhook trigger returns to a webhook caller. This helps you handle APIs that require custom responses, and allows you to redirect webhook callers as needed. ![Configure webhook trigger in Prismatic app](/docs/assets/images/webhook-trigger-responses-050d574a6e98ecaeaec54a860878608f.png) SEPTEMBER 16, 2021 ##### Improved Embedded Marketplace Experience[​](#improved-embedded-marketplace-experience "Direct link to Improved Embedded Marketplace Experience") We've significantly enhanced Prismatic's embedded marketplace experience, enabling you to provide your customers a seamless, native integration experience with minimal engineering effort. You can now embed Prismatic's integration marketplace into your application with a few lines of code. You can choose to display the sleek marketplace UX directly within your application or as a popover, and apply [custom theming](https://prismatic.io/docs/embed/theming.md) to make your integration marketplace look native to your application. The embedded marketplace showcases your integration offerings and allows customers to self-activate and configure the integrations they need. As previously, you can specify which integrations appear in your marketplace, which ones can be self-activated, and define each integration's configuration screen. Your customers do not need to juggle another set of credentials to access your embedded marketplace. Instead, you can sign JSON web tokens (JWTs) for your users, which can be used to automatically authenticate them for the marketplace. Check out our [docs](https://prismatic.io/docs/embed/marketplace.md) to get started. SEPTEMBER 14, 2021 ##### Configurable Webhook Endpoints[​](#configurable-webhook-endpoints "Direct link to Configurable Webhook Endpoints") You now have more control over how webhook endpoints are configured for deployed instances. You already had the option to create a webhook endpoint for each flow of each deployed instance (**Instance and Flow-Specific**). You can now select two other configuration options: * **Instance-Specific**: Create a single webhook endpoint for each instance. Identify which of the instances' flows should run based on data in the webhook request. * **Shared**: Create a single webhook that is shared by all customers who have a particular integration. Route the webhook request to a flow in a specific customer's instance based on data in the webhook request. Both of these additional configuration options allows you to route webhook requests to a particular customer and flow based on the data that comes in to the webhook. If the data that comes in needs additional processing, or if you need to look up a flow's name or customer's ID, you can assign one of your integration's flows to be a **Preprocess Flow** - a flow that's run when a webhook is invoked and aids in making sure the request gets to the right place. For more information, check out our [webhook docs](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md). SEPTEMBER 01, 2021 ##### Granular Permissions for Third Parties[​](#granular-permissions-for-third-parties "Direct link to Granular Permissions for Third Parties") You can now invite third-party vendors to log in to Prismatic with limited access to your integrations, custom components, and customers. This is helpful if you need to collaborate on a new integration with a third-party vendor. You can grant them **view** or **edit** access to a particular integration or set of custom components, which allows them to test the integration against their app or service. You can debug and iterate faster on integration and custom component development, and can have one central place to view logs and test runs. You can also view logs of each test a third-party vendor performs to give you a sense of how their side of the integration development is progressing. Permissions are granular - third-party users only see what they've been given permissions to see. So, if you're integrating with two competing companies, or even with one of your competitors, they are not given insight into the other integrations, custom components, or customers you have in your Prismatic account. Read more about the third-party user role in our [docs](https://prismatic.io/docs/configure-prismatic/organization-users.md#third-party-users). AUGUST 25, 2021 ##### Integration Categories and Icons[​](#integration-categories-and-icons "Direct link to Integration Categories and Icons") You can now assign a category and icon to each of your integrations. This helps your team manage and filter integrations and improves the way you present them to customers. ![Assign integration to category in Prismatic app](/docs/assets/images/assign-category-0979d284701cf3be8cc0567cd7453e20.png) ![Filter integrations by category in Prismatic app](/docs/assets/images/filter-by-category-1bb56adf0c7faeec6077740218ab0e27.png) Check out our docs on [categorizing integrations](https://prismatic.io/docs/integrations/low-code-integration-designer.md#categorizing-integrations) and [assigning an icon](https://prismatic.io/docs/integrations/low-code-integration-designer.md#assigning-an-icon-to-an-integration) for more info. AUGUST 24, 2021 ##### Multi-Flow Integrations[​](#multi-flow-integrations "Direct link to Multi-Flow Integrations") Prismatic now provides full support for multi-flow integrations! Integrations can now include multiple **flows**. (A flow is a trigger and a series of steps.) This enables you to provide your customers with a complex third-party integration that performs multiple related tasks, but is packaged and deployed as a single integration. For example, you might integrate with an ERP that sends a variety of data via webhooks to your application (a webhook when inventory is updated, a webhook when customer info is updated, and so on). Rather than constructing integrations with complex branches or assembling multiple integrations, you can now create a single integration with flows that handle each type of webhook payload. You would create a flow to handle inventory updates, another flow to handle customer updates, and deploy all of those flows together as a single instance to a customer. Each flow has its own trigger (so it gets its own webhook URL), and flows are tested and run independently of one another. When customers or customer-facing teams deploy a multi-flow integration, they configure and deploy all of the flows at once using a single configuration screen. Your existing integrations will continue to operate as expected. Please note: 1. The **Selected Test Run** dropdown has been moved to the input reference selector. When configuring an input for a step, you can select which test run to preview outputs for within the **Reference** tab. ![Select test run in Prismatic app](/docs/assets/images/selected-test-run-f7b07f610d9b5a33b67f62c9c8a0eeab.png) 2. Prismatic's CLI, Prism, has been updated to version 3.0.0 to account for this change. To install the latest Prism, run: ```bash npm install --global @prismatic-io/prism ``` 3. The YAML that defines integrations has been updated. Check out our [building integrations](https://prismatic.io/docs/integrations/low-code-integration-designer/flows.md) page for more information. ##### Deploy-Time Triggers[​](#deploy-time-triggers "Direct link to Deploy-Time Triggers") You can now configure integration flows with triggers that are invoked when an instance is deployed to a customer. This is helpful if you have a set of "initialization" tasks that need to be completed *once* to set up a customer's instance. A deploy-time flow could enable features in a third-party app, set up third-party users or permissions, create a directory structure in a file storage system, or even set up webhooks in a third-party application to point to the instance's other flows. Check out our [deploy trigger](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger) docs for more info. AUGUST 16, 2021 ##### New Components - August 2021[​](#new-components---august-2021 "Direct link to New Components - August 2021") We've continued to expand our built-in component offering. A full catalog is available [here](https://prismatic.io/docs/components.md). This month, we added: * [Airtable](https://prismatic.io/docs/components/airtable.md) - List, create, delete, and update records in an Airtable Base * [Amazon SES](https://prismatic.io/docs/components/aws-ses.md) - Send email through Amazon's Simple Email Service (SES) * [Google Drive](https://prismatic.io/docs/components/google-drive.md) - Manage files that are stored in a Google Drive account * [Google Sheets](https://prismatic.io/docs/components/google-sheets.md) - Create, read and modify spreadsheets in a Google Drive account * [Mongo DB](https://prismatic.io/docs/components/mongo.md) - Create, read, update and delete documents inside a NoSQL MongoDB collection * [Microsoft Power BI](https://prismatic.io/docs/components/ms-power-bi.md) - Interact with datasets and data schemas within Microsoft's data visualization and business analytics service * [MySQL](https://prismatic.io/docs/components/mysql.md) - Query and manage data in a MySQL database * [Shopify](https://prismatic.io/docs/components/shopify.md) - Interact with Shopify's Access Service API * [Stripe](https://prismatic.io/docs/components/stripe.md) - Interact with Stripe's payment platform The Shopify and Stripe components were both generated from OpenAPI definitions using Prism's [component generator tool](https://prismatic.io/docs/custom-connectors/initializing.md#custom-connectors-from-wsdls-or-openapi-specs). AUGUST 05, 2021 ##### Per-Action Authorization in Components[​](#per-action-authorization-in-components "Direct link to Per-Action Authorization in Components") You can now configure authorization settings per *action* (as opposed to per *component*). This is helpful if you are building a component with multiple actions and only some of your actions require authorization. Read about how to upgrade your component to use per-action authorization on our [Spectral 3.x Upgrade Guide](https://prismatic.io/docs/spectral/spectral-3-upgrade-guide.md). JULY 14, 2021 ##### New Components - July 2021[​](#new-components---july-2021 "Direct link to New Components - July 2021") Several new components have been added to our [catalog](https://prismatic.io/docs/components.md) of built-in components: * [Amazon DynamoDB](https://prismatic.io/docs/components/aws-dynamodb.md) - Create, update, fetch, or delete items in an Amazon DynamoDB database * [Amazon SNS](https://prismatic.io/docs/components/aws-sns.md) - Manage subscriptions, topics, and messages within Amazon SNS * [Amazon SQS](https://prismatic.io/docs/components/aws-sqs.md) - Send, receive and manage messages within an Amazon SQS queue * [AMQP](https://prismatic.io/docs/components/amqp.md) - Send and receive messages on an AMQP-based message broker * [Apache Kafka](https://prismatic.io/docs/components/kafka.md) - Publish messages to an Apache Kafka event stream * [Customer.io](https://prismatic.io/docs/components/customer-io.md) - Manage customers on the Customer.io platform * [Microsoft SQL Server](https://prismatic.io/docs/components/ms-sql-server.md) - Query and manage data in a Microsoft SQL Server Database * [MQTT](https://prismatic.io/docs/components/mqtt.md) - Send and receive messages on an MQTT-based queue * [PostgreSQL](https://prismatic.io/docs/components/postgres.md) - Query and manage data in a PostgreSQL database JULY 08, 2021 ##### Spectral 2.x Released[​](#spectral-2x-released "Direct link to Spectral 2.x Released") Prismatic's custom component TypeScript library, `@prismatic-io/spectral`, has been expanded and updated to improve the developer experience for [building custom components](https://prismatic.io/docs/custom-connectors.md). Updated syntax for creating components, actions, and inputs helps to catch common errors at compile time (rather than runtime), and new utility functions help to guarantee that you pass the correct variable types to third party SDKs and APIs. For info on upgrading an existing 1.x custom component to 2.x, see our [Upgrade Guide](https://prismatic.io/docs/spectral/spectral-2-upgrade-guide.md). You can dive in to the Spectral code on [GitHub](https://github.com/prismatic-io/spectral). JUNE 16, 2021 ##### Enhanced Versioning for Components, Integrations, and Instances[​](#enhanced-versioning-for-components-integrations-and-instances "Direct link to Enhanced Versioning for Components, Integrations, and Instances") Versioning has been improved for components, integrations, and instances to give you more fine-grained control over exactly what code is deployed to customers. **Components** are now assigned an integer version that increments each time the component is published. If a custom component is at "version 3" and you publish a new component definition, that new definition gets "version 4". This allows you to update or extend components without unintentionally impacting existing integrations that use them, ensuring your integrations remain stable. Integration builders can then update the component versions used in their integrations, or roll back to a previous versions when desired, and will be notified when newer versions of components are available. Read more about [Versioning of Components](https://prismatic.io/docs/custom-connectors/publishing.md#component-versioning) and [Choosing Components Versions in Integrations](https://prismatic.io/docs/integrations/low-code-integration-designer/steps.md#choosing-component-versions). **Integration** versioning has been improved, giving you more control over what versions of integrations you deploy to customers. When you publish new changes to an integration, similar to components, your integration is assigned a new version number. Then, when you deploy an instance to a customer, you can choose which version of the integration to use. That means you can have some customers on version 1, and others on version 2 as needed, giving you control over which customers have what, and allowing you to test a new integration version with a small subset of your customer base before deploying it broadly. Rolling back an instance deployment is a breeze - if you deploy a new version of an integration to a customer and something seems off, you can easily roll back your instance to a known working version of the integration with a couple of clicks. As always, updating customers' instances can be scripted, so you don't need to manually deploy a new version of an integration to each customer. Read more about [Publishing an Integration](https://prismatic.io/docs/integrations/low-code-integration-designer.md#publishing-an-integration). MAY 17, 2021 ##### Generate Custom Components From API Specs[​](#generate-custom-components-from-api-specs "Direct link to Generate Custom Components From API Specs") APIs often have hundreds of unique endpoints that you can interact with. With the release of [Prism](https://prismatic.io/docs/cli.md) version 1.0.8, you can now generate a custom component from a WSDL or OpenAPI file. That means you can have a custom component for a third-party service with hundreds of actions with a single CLI command. Read more on our [Writing Custom Components](https://prismatic.io/docs/custom-connectors/initializing.md#custom-connectors-from-wsdls-or-openapi-specs) article. APRIL 27, 2021 ##### Customer Self-Service[​](#customer-self-service "Direct link to Customer Self-Service") It's now easier for your customers to manage instances of integrations that have been deployed to them. Customer users with **admin** permissions can update config variables and credentials that are associated with their instances. So, if their config or credentials for a third-party service change, they can log in and make the change without needing your help. For more information on customer user roles and permissions, see the [users article](https://prismatic.io/docs/customers/customer-users.md). ##### Custom Theming[​](#custom-theming "Direct link to Custom Theming") Organizations with an enterprise plan can now create a custom theme for the Prismatic web application. This takes Prismatic's white-label capabilities to the next level by allowing you to customize the color scheme and other UI elements to match your branding. Once you apply a custom theme, it will be displayed for both your team members and customers. For more information, see our [custom theming docs](https://prismatic.io/docs/embed/theming.md). MARCH 30, 2021 ##### Configure Instances to Run on a Per-Customer Schedule[​](#configure-instances-to-run-on-a-per-customer-schedule "Direct link to Configure Instances to Run on a Per-Customer Schedule") It's now much easier to configure instances of your integrations to run on a unique schedule for each of your customers. For example, Customer A could be set up to run the integration each day at 4:00PM, while Customer B could be set up to run the integration hourly, depending on their needs. ![Configure instances to run on customer schedule via Prismatic app](/docs/assets/images/schedule-config-variable-254858b76e40b13eec5c982bc9f32e25.png) For more information, check out our [integrations article](https://prismatic.io/docs/integrations/triggers/schedule.md). MARCH 25, 2021 ##### Intuitive Instance Deployment[​](#intuitive-instance-deployment "Direct link to Intuitive Instance Deployment") Significant improvements have been made to credentials, integration configuration and instance deployment. Integration builders now have the ability to create an easy-to-use configuration page for customer-facing teams. Builders can define config variable names, give hints as to what sort of data is expected, add headers, etc., giving their customer-facing teams an intuitive experience when it comes to deploying an integration. ![Integration configuration and config variables via Prismatic app](/docs/assets/images/intuitive-instance-deployment-dba44239f316b02fd063652ffeac0df5.webp) This ultimately makes for easier and faster deployment of integrations, without the need for developer intervention. Read more about setting up config variables on our [integrations](https://prismatic.io/docs/integrations/config-wizard/config-variables.md) article, and about the new instance configuration experience on the [instances](https://prismatic.io/docs/instances/deploying.md) article. MARCH 12, 2021 ##### Persisting Instance State[​](#persisting-instance-state "Direct link to Persisting Instance State") Small amounts of data (state) can now be stored between instance executions. This is handy if you want to save some information about one instance execution to use later in a subsequent execution. Prismatic handles several common state persistence scenarios for you through the new [Persist Data](https://prismatic.io/docs/components/persist-data.md) and [Process Data](https://prismatic.io/docs/components/process-data.md) components. Check out the [Integrations](https://prismatic.io/docs/integrations/persist-data.md) article to learn how to leverage state persistence in your integrations, or read the [Writing Custom Components](https://prismatic.io/docs/custom-connectors/actions.md#execution-instance-and-cross-flow-state) article to incorporate state persistence into your custom components. MARCH 04, 2021 ##### Terraform Provider[​](#terraform-provider "Direct link to Terraform Provider") You can now publish Prismatic integrations and custom components using the Prismatic Terraform Provider. This helps you incorporate Prismatic into your existing CI/CD pipeline, and push changes to integrations and custom components automatically when pull requests are approved. JANUARY 21, 2021 ##### Retry and Replay[​](#retry-and-replay "Direct link to Retry and Replay") Organizations with a professional or enterprise plan can now configure instances to [automatically retry](https://prismatic.io/docs/monitor-instances/retry-and-replay/automatic-retry.md) if an execution fails. You can control how many times an instance attempts to run with the same input, and how long it should wait between failed attempts. If you have an integration that relies on a flaky third-party API, for example, this minimizes interruptions for both your customers and your team. You can also [replay](https://prismatic.io/docs/monitor-instances/retry-and-replay/replaying-failed-executions.md) - manually retry - a specific failed execution of an instance. ##### Invoking Instances Synchronously[​](#invoking-instances-synchronously "Direct link to Invoking Instances Synchronously") You can now choose to invoke your instances [synchronously or asynchronously](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md). When you invoke an instance synchronously, your request says open until the instance completes, and results from the instance's run are returned as an HTTP response. --- ### Prism CLI Overview The Prismatic CLI tool enables programmatic interaction with the Prismatic API, allowing you to build, deploy, and support integrations from the command line. The CLI tool is built on the [Prismatic API](https://prismatic.io/docs/api.md), so any action that can be completed through the web application or API can also be completed through the CLI tool. #### Installing the CLI tool[​](#installing-the-cli-tool "Direct link to Installing the CLI tool") [![Prism NPM version](https://badge.fury.io/js/@prismatic-io%2Fprism.svg)](https://www.npmjs.com/package/@prismatic-io/prism) Prismatic's CLI tool, `prism`, is available at and can be installed using `npm` or `yarn`: ```bash npm install -g @prismatic-io/prism # OR yarn global add @prismatic-io/prism ``` Prism's source code is available on [GitHub](https://github.com/prismatic-io/prism) and serves as an excellent example of how to wrap the [Prismatic API](https://prismatic.io/docs/api.md). #### Authenticating with the CLI tool[​](#authenticating-with-the-cli-tool "Direct link to Authenticating with the CLI tool") Once `prism` has been installed, log in by running: ```text prism login ``` This will open a web browser for you to authenticate with your Prismatic credentials. Once you authenticate, your CLI tool will store an authentication token for subsequent `prism` commands. If your user is associated with multiple tenants in a single region, you will be prompted to select which tenant to use for your CLI session. You can switch between tenants within a region by running `prism login:switch`. To verify that you are logged in, run `prism me` to view information about your account and active profile. ```text prism me Name: Alex Cooper Email: alexander.cooper@progix.io Organization: Progix Software Endpoint URL: https://app.prismatic.io Profile: default ``` To view the authentication token that your CLI tool uses, run `prism me:token`. ```text prism me:token eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik5lVV9aYzFNdFRrSE93bXB1T2ZlUCJ9.eyJodHRwczovL3ByaXNtYXRpYy5pby9lbWFpbCI6InRlc3QudXNlckBlbWFpbC5jb20iLCJodHRwczovL3ByaXNtYXRpYy5pby9sYXN0X2xvZ2luIjoiMzAyMS0wMS0wMVQwMDowMDowMC4wMDFaIiwiaXNzIjoiaHR0cHM6Ly9wcmlzbWF0aWMtaW8udXMuYXV0aDAuY29tLyIsImF1ZCI6WyJodHRwczovL3ByaXNtYXRpYy5pby9hcGkiLCJodHRwczovL3ByaXNtYXRpYy1pby51cy5hdXRoMC5jb20vdXNlcmluZm8iXSwiaWF0IjoxNjEyMjA4NjQyLCJleHAiOjE2MTIyOTUwNDIsInNjb3BlIjoib3BlbmlkIHByb2ZpbGUgZW1haWwgb2ZmbGluZV9hY2Nlc3MifQ.iKQWx95vUWTxF62O3-mZFqHPgfapH7TQjsy-BunqWWDJrhk88byJpJQYy__hJE779qAahkEtZD914zgpZ8UnjGW0i_PUcCf5nZsDJBR-jfTEARCLmeVYge3Hy40BAFzj3eCcCouDFqxMNaD3oeXSjfizO9Cy_P-XKEkDdIOJ-rk ``` To clear your token from memory and log out, run `prism logout`. ##### Authentication profiles[​](#authentication-profiles "Direct link to Authentication profiles") Profiles let you stay logged in to multiple Prismatic stacks or tenants. Each profile stores its endpoint, tenant, and credentials. Prism uses the default profile unless you select another one. ```bash # Create or update a named profile PRISMATIC_URL=https://app.eu-west-1.prismatic.io prism login --profile eu # Use a profile for one command prism integrations:list --profile eu # Use a profile for the current shell export PRISM_PROFILE=eu # Change the default profile prism profiles:use eu ``` Run `prism profiles:list` to view saved profiles and `prism profiles:delete ` to delete one. The `--profile` flag takes precedence over `PRISM_PROFILE` and the saved default. `PRISM_ACCESS_TOKEN` and `PRISM_REFRESH_TOKEN` take precedence over saved profile credentials. When you use environment credentials, Prism also reads the endpoint and tenant from `PRISMATIC_URL` and `PRISMATIC_TENANT_ID` rather than from the selected profile. Logging in to other regions By default, your data is stored in the US commercial region and `prism` authenticates against that region. If your plan includes additional regions or private cloud, you will need to configure `prism` to point to that region. See [Integrations in Multiple Regions](https://prismatic.io/docs/configure-prismatic/deployment-regions.md#using-prism-cli-with-multiple-regions). #### Autocomplete in Prism[​](#autocomplete-in-prism "Direct link to Autocomplete in Prism") Enable autocomplete in `prism` by running `prism autocomplete` and then follow the displayed instructions. Instructions differ depending on which shell you use (bash, zsh, etc.). #### Running CLI commands and getting help[​](#running-cli-commands-and-getting-help "Direct link to Running CLI commands and getting help") All Prismatic CLI commands generally follow the form `prism COMMAND`. For example, you can run `prism customers:list` to list all customers, or `prism integrations:create` to create an integration. A complete list of `prism` commands can be found on the [Prismatic CLI Command Reference](https://prismatic.io/docs/cli/prism.md) page. Running `prism --help` will also list top-level commands that you can execute. ```text $ prism --help Build, deploy, and support integrations in Prismatic from the comfort of your command line VERSION @prismatic-io/prism/7.6.4 darwin-arm64 node-v22.11.0 USAGE $ prism [COMMAND] TOPICS alerts Manage Alerting resources components Manage, create, and publish Components customers Manage Customers executions Fetch results of Instance executions or Integration test runs instances Manage Instances integrations Manage and import Integrations logs List Log Severities for use by Alert Triggers me Print your user profile information on-prem-resources Delete an On-Premise Resource organization Manage your Organization translations Generate Dynamic Phrases for Embedded Marketplace COMMANDS autocomplete Display autocomplete installation instructions. help Display help for prism. login Log in to your Prismatic account logout Log out of your Prismatic account me Print your user profile information ``` To view subcommands of top-level commands, run `prism COMMAND --help`. For example, to see customer management options, run: ```text $ prism customers --help Manage Customers USAGE $ prism customers:COMMAND TOPICS customers:users Manage Customer Users COMMANDS customers:create Create a new Customer customers:delete Delete a Customer customers:list List your Customers customers:update Update a Customer ``` For a list of all required arguments for a command, run `prism COMMAND:SUBCOMMAND --help`. For example, to view the required arguments for creating a customer, run: ```text Create a new Customer USAGE $ prism customers:create -n [--print-requests] [--quiet] [-d ] [-e ] [-l ] FLAGS -d, --description= longer description of the customer -e, --externalId= external ID of the customer from your system -l, --label=... a label to apply to the customer -n, --name= (required) short name of the new customer GLOBAL FLAGS --print-requests Print all GraphQL requests that are issued --quiet Reduce helpful notes and text DESCRIPTION Create a new Customer EXAMPLES Apply multiple labels to a customer $ prism customers:create --name "Widgets Inc" --externalId "abc-123" --label "Prod Customers" --label "Beta \ Testers" ``` --- ### Bash Scripting with Prism #### Using the Prismatic CLI in Bash scripts[​](#using-the-prismatic-cli-in-bash-scripts "Direct link to Using the Prismatic CLI in Bash scripts") You can combine multiple `prism` commands to manage Prismatic resources. For example, to create an instance, you need the integration ID and the customer ID for deployment. You can use `customers:list`, `integrations:list`, and `instances:create` commands together to create a new instance. ```bash # Get the Customer ID CUSTOMER_ID=$( prism customers:list \ --columns id \ --filter 'Name=^FTL Rockets$' \ --no-header) # Get the Integration ID INTEGRATION_ID=$( prism integrations:list \ --columns id \ --filter 'name=^Acme$' \ --no-header) # Get the integration's latest version ID VERSION_ID=$( prism integrations:versions ${INTEGRATION_ID} \ --columns id \ --latest-available \ --no-header) # Create the instance prism instances:create \ --customer ${CUSTOMER_ID} \ --integration ${VERSION_ID} \ --name 'Acme ERP' \ --description 'Sync data with Acme ERP' ``` #### Headless prism usage for CI/CD pipelines[​](#headless-prism-usage-for-cicd-pipelines "Direct link to Headless prism usage for CI/CD pipelines") To use `prism` on a headless (no GUI) server for CI/CD or scripting purposes, you must log in on a system with a web browser and then transfer your "refresh token" to the headless system. Refresh tokens do *not* expire and are used to generate short-lived access tokens for Prismatic's API. After logging into `prism` with `prism login`, retrieve your refresh token with `prism me:token --type refresh`. Note your token and the API endpoint you are currently using (view this with `prism me`). Now, on your headless CI/CD system, set an environment variable `PRISM_REFRESH_TOKEN` with the retrieved value: ```bash export PRISM_REFRESH_TOKEN=my-refresh-token ``` If you're working with [regions](https://prismatic.io/docs/configure-prismatic/integrations-multiple-regions.md) other than the default US commercial region, you can also specify an endpoint: ```bash export PRISM_REFRESH_TOKEN=my-refresh-token export PRISMATIC_URL=https://app.eu-west-1.prismatic.io ``` If your user is associated with multiple tenants in a single region, you should also set the `PRISMATIC_TENANT_ID` environment variable to specify which tenant to use, which you can view by running `prism me`: ```bash export PRISMATIC_TENANT_ID=my-tenant-id ``` If your app uses a white-labeled domain (like integrations.my-company.com), you can use that endpoint instead for `PRISMATIC_URL`. **Note**: For PowerShell on Windows, you can set an environment variable using this syntax: ```powershell $ENV:PRISMATIC_URL="https://app.eu-west-1.prismatic.io" ``` Use GitHub Actions If you use GitHub, consider using Prismatic's integration and component [GitHub actions](https://prismatic.io/docs/api/github-actions.md). --- ### Custom Connector and Integration Testing with Prism You can test both [code-native integrations](https://prismatic.io/docs/integrations/code-native.md) and [custom connectors](https://prismatic.io/docs/custom-connectors.md) using Prism CLI. To test a code-native integration's flow from the command line, use `prism integrations:flows:test`. See [Testing Code-Native Integrations](https://prismatic.io/docs/integrations/code-native/testing.md#testing-a-code-native-integration-from-the-cli) for more information. To test a custom connector, use `prism components:dev:test` and `prism components:dev:run`. See [Unit Testing Custom Connectors](https://prismatic.io/docs/custom-connectors/unit-testing.md#testing-components-from-the-cli) for more information. --- ### Listing Resources with Prism CLI You can list all types of Prismatic resources (customers, components, integrations, instances, actions, etc.) using `:list` subcommands. By default, list commands display basic information like name and description, but you can also display additional information like resource ID. You can optionally select exactly which attributes of resources you want to list, filter the results, and format the results as CSV, JSON, or YAML. ```text prism components:list Label Public Description Version Category ─────────────────────── ────── ───────────────────────────────────────────────────────────────────────────────────────────── ─────── ────────────────────── Acme ERP false Interact with Acme ERP's inventory and customer systems 2 null Airtable true Manage items (records) in an Airtable Base 2 Data Platforms Amazon DynamoDB true Create, update, fetch, or delete items in an Amazon (AWS) DynamoDB database 5 Data Platforms Amazon S3 true Manage files within an Amazon (AWS) S3 bucket 34 Data Platforms Amazon SES true Send Emails through Amazon (AWS) SES 5 Application Connectors Amazon SNS true Manage subscriptions, topics, and messages within Amazon (AWS) SNS 7 Data Platforms Amazon SQS true Send, receive and manage messages within an Amazon (AWS) SQS queue 10 Data Platforms AMQP true Send and receive messages on an AMQP-based message broker 5 Data Platforms ``` ##### Listing resource IDs[​](#listing-resource-ids "Direct link to Listing resource IDs") All Prismatic resources have unique IDs. IDs are not displayed by default through `list` subcommands, but can optionally be displayed with the `--extended` flag. For example, to display IDs for components, run: ```text prism components:list --extended Id Key Label Public Description Version Category ──────────────────────────────────────────────────────────────── ─────────────────────── ─────────────────────── ────── ───────────────────────────────────────────────────────────────────────────────────────────── ─────── ────────────────────── Q29tcG9uZW50OjI3ZWM4ODlmLTI1ODUtNDFiMy05MDdlLWI2YWExNTg5ZGNhNA== acmeerp Acme ERP false Interact with Acme ERP's inventory and customer systems 2 null Q29tcG9uZW50OmVkMjcwNmExLThiMTEtNDI0YS05MjM0LTgzZjU4NDBmNzA3NQ== airtable Airtable true Manage items (records) in an Airtable Base 2 Data Platforms Q29tcG9uZW50Ojg3NzE3YThhLTFiODktNDY5My1hYmZlLWRjY2VkMjMxM2RlZg== aws-dynamodb Amazon DynamoDB true Create, update, fetch, or delete items in an Amazon (AWS) DynamoDB database 5 Data Platforms Q29tcG9uZW50OjE3NmRjYWU3LWEzMzktNDQ2NC1iYmJkLTU4ODllNzdmOWJjYQ== aws-s3 Amazon S3 true Manage files within an Amazon (AWS) S3 bucket 34 Data Platforms Q29tcG9uZW50Ojg3NjlhODE1LTY1OTEtNDliZC1hMGQ5LTNhMWNlYjUxZmZkYQ== aws-ses Amazon SES true Send Emails through Amazon (AWS) SES 5 Application Connectors Q29tcG9uZW50OjNkMzFkYjYxLWFlYzItNDRjZS05NGNkLTVhZWJjMjIxNjlhZg== aws-sns Amazon SNS true Manage subscriptions, topics, and messages within Amazon (AWS) SNS 7 Data Platforms Q29tcG9uZW50OmQ5ZmJkYzViLTFhMGUtNDRlMS1hNDcxLTNjMWE0NzFhYzAwNQ== aws-sqs Amazon SQS true Send, receive and manage messages within an Amazon (AWS) SQS queue 10 Data Platforms Q29tcG9uZW50OmFmYjNlMTNmLTg0NDctNGJmMC05MWIyLTAxNGQ1OTliYThkYg== amqp AMQP true Send and receive messages on an AMQP-based message broker 5 Data Platforms ``` ##### Configuring columns of a list to display[​](#configuring-columns-of-a-list-to-display "Direct link to Configuring columns of a list to display") You can optionally choose which resource attributes to display using the `--columns` flag. For example, to retrieve the Key, Label, and ID of all components, run: ```text prism components:list --columns "key,label,id" Key Label Id ──────────── ─────────────── ──────────────────────────────────────────────────────────────── acmeerp Acme ERP Q29tcG9uZW50OjI3ZWM4ODlmLTI1ODUtNDFiMy05MDdlLWI2YWExNTg5ZGNhNA== airtable Airtable Q29tcG9uZW50OmVkMjcwNmExLThiMTEtNDI0YS05MjM0LTgzZjU4NDBmNzA3NQ== aws-dynamodb Amazon DynamoDB Q29tcG9uZW50Ojg3NzE3YThhLTFiODktNDY5My1hYmZlLWRjY2VkMjMxM2RlZg== aws-s3 Amazon S3 Q29tcG9uZW50OjE3NmRjYWU3LWEzMzktNDQ2NC1iYmJkLTU4ODllNzdmOWJjYQ== aws-ses Amazon SES Q29tcG9uZW50Ojg3NjlhODE1LTY1OTEtNDliZC1hMGQ5LTNhMWNlYjUxZmZkYQ== aws-sns Amazon SNS Q29tcG9uZW50OjNkMzFkYjYxLWFlYzItNDRjZS05NGNkLTVhZWJjMjIxNjlhZg== aws-sqs Amazon SQS Q29tcG9uZW50OmQ5ZmJkYzViLTFhMGUtNDRlMS1hNDcxLTNjMWE0NzFhYzAwNQ== amqp AMQP Q29tcG9uZW50OmFmYjNlMTNmLTg0NDctNGJmMC05MWIyLTAxNGQ1OTliYThkYg== ``` ##### Filtering list output[​](#filtering-list-output "Direct link to Filtering list output") You can filter the output that a `:list` subcommand displays using the `--filter` flag. For example, to show only the component with the key "aws-s3", run: ```text prism components:list --filter 'key=^aws-s3$' Label Public Description Version Category ───────── ────── ───────────────────────────────────────────── ─────── ────────────── Amazon S3 true Manage files within an Amazon (AWS) S3 bucket 34 Data Platforms ``` Filter uses regex The `--filter` flag uses [regex](https://regexr.com/) pattern matching, hence the "start of string" `^` character and "end of string" `$` character. In a bash script, you can combine the `--filter` flag with the `--columns` and `--no-header` flags to retrieve the ID of a specific resource: ```text AWS_S3_COMPONENT_ID=$(prism components:list --filter 'key=aws-s3' --no-header --columns id) echo ${AWS_S3_COMPONENT_ID} Q29tcG9uZW50OjJlMDcyMGU4LTFjNTUtNDY1Ni04NzY0LTI1N2RmZDVhNTE3Mw== ``` ##### Formatting list output[​](#formatting-list-output "Direct link to Formatting list output") Lists can be optionally formatted as CSV, JSON, or YAML using the `--output` flag. This flag can be combined with the `--columns` and `--filter` flags as well. For example, to retrieve the ID and key of all components in CSV format, run: ```text prism components:list --output csv --columns "id,key" Id,Key Q29tcG9uZW50OjNiODQ1NGVkLTE5MjEtNGYxNS04MDhmLTBlZjkxNDEzNGRhZA==,airtable Q29tcG9uZW50OjE5YWYzMzQzLTU2OWQtNDY0Yy1iNTAwLWUzM2RhNjg3YmQxYQ==,aws-dynamodb Q29tcG9uZW50OjQ1ZGVkMzEyLTE2ZmUtNGY0Mi04OWVlLWZhOTIzNTQ0ZDEyYQ==,aws-s3 Q29tcG9uZW50OmEyNjRjMTVkLThjM2QtNGY0Yi1hNDNkLWEzYzMzZjgxZGY0MQ==,aws-ses Q29tcG9uZW50OjRmNTM5MWVkLWE3ZDEtNDljZi1hNjViLTE4ZGNmMTRmNGJlMA==,aws-sns Q29tcG9uZW50OmQ0YjhmOTllLWU3YTYtNDUxMS04YWIxLWNkOGQ1M2QyNDJiZg==,aws-sqs ``` --- ### Prismatic CLI Command Reference The Prismatic CLI tool allows you to interact with the Prismatic API programmatically so you can build, deploy, and support integrations from the command line. This page lists the subcommands of `prism` that you can invoke. For an introduction on using the Prismatic CLI tool, see the CLI [usage page](https://prismatic.io/docs/cli.md). *** #### Alerts CLI Commands[​](#alerts-cli-commands "Direct link to Alerts CLI Commands") ##### alerts:events:list[​](#alertseventslist "Direct link to alerts:events:list") List Alert Events for an Alert Monitor ```bash prism alerts:events:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `alertMonitorId` | | ID of an alert monitor | true | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### alerts:groups:create[​](#alertsgroupscreate "Direct link to alerts:groups:create") Create an Alert Group ```bash prism alerts:groups:create [--print-requests] [--quiet] [--profile ] --name [--users ] [--webhooks ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | -------------------------------------------------- | -------- | | `--name` | `-n` | name of the group to be created | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--users` | `-u` | JSON-formatted list of Prismatic user IDs to alert | false | | `--webhooks` | `-w` | JSON-formatted list of Alert Webhook IDs to alert | false | Create a group for 'DevOps': ```text prism alerts:groups:create --name DevOps --users "[\"$(prism organization:users:list --columns id --filter 'Name=John Doe' --no-header)\"]" ``` ##### alerts:groups:delete[​](#alertsgroupsdelete "Direct link to alerts:groups:delete") Delete an Alert Group ```bash prism alerts:groups:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `group` | | ID of the group to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### alerts:groups:list[​](#alertsgroupslist "Direct link to alerts:groups:list") List Alert Groups in your Organization ```bash prism alerts:groups:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | Fetch the ID and Name of all alert groups in JSON format, sorted descending by name: ```text prism alerts:groups:list --columns "id,name" --output json --sort name ``` ##### alerts:monitors:clear[​](#alertsmonitorsclear "Direct link to alerts:monitors:clear") Clear an Alert Monitor ```bash prism alerts:monitors:clear [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `monitor` | | ID of the monitor to clear | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### alerts:monitors:create[​](#alertsmonitorscreate "Direct link to alerts:monitors:create") Create an Alert Monitor by attaching an Alert Trigger and a set of users and webhooks to an Instance. While individual users and webhooks can be tied to alert monitors, it is recommended that you create alert groups and attach alert groups to alert monitors. This helps in the case that you need to add a user to a set of monitors: it's simpler to edit a single alert group than to edit dozens of alert monitors. ```bash prism alerts:monitors:create [--print-requests] [--quiet] [--profile ] --name --instance --triggers [--duration ] [--log-severity ] [--groups ] [--users ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ---------------------------------------------------------------------------- | -------- | | `--duration` | `-d` | greatest time allowed (in seconds) for time-based triggers | false | | `--groups` | `-g` | JSON-formatted list of group IDs to alert | false | | `--instance` | `-i` | ID of the instance to monitor | true | | `--log-severity` | `-s` | greatest log level (debug, info, warn, error) allowed for log-based triggers | false | | `--name` | `-n` | name of the alert monitor to be created | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--triggers` | `-t` | JSON-formatted list of trigger IDs that should trigger this monitor | true | | `--users` | `-u` | JSON-formatted list of Prismatic user IDs alert | false | Get the ID of an alert group named 'DevOps': ```text ALERT_GROUP_ID=$(prism alerts:groups:list --columns id --filter 'name=^DevOps$' --no-header) ``` Get the ID of an instance named 'My Instance': ```text INSTANCE_ID=$(prism instances:list --columns id --filter 'name=^My Instance$' --no-header) ``` Get the ID of an execution duration trigger: ```text TRIGGER_ID=$(prism alerts:triggers:list --columns id --filter 'name=^Execution Duration Matched or Exceeded$' --no-header) ``` Create an alert monitor that alerts the DevOps group when an instance execution takes longer than 10 seconds: ```text prism alerts:monitors:create --name "Alert Devops of slow execution" --instance ${INSTANCE_ID} --triggers "[\"${TRIGGER_ID}\"]" --duration 10 --groups "[\"${ALERT_GROUP_ID}\"]" ``` ##### alerts:monitors:delete[​](#alertsmonitorsdelete "Direct link to alerts:monitors:delete") Delete an Alert Monitor ```bash prism alerts:monitors:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `monitor` | | ID of the monitor to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### alerts:monitors:list[​](#alertsmonitorslist "Direct link to alerts:monitors:list") List Alert Monitors for Customer Instances ```bash prism alerts:monitors:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### alerts:triggers:list[​](#alertstriggerslist "Direct link to alerts:triggers:list") List Alert Triggers ```bash prism alerts:triggers:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### alerts:webhooks:create[​](#alertswebhookscreate "Direct link to alerts:webhooks:create") Create an Alert Webhook ```bash prism alerts:webhooks:create [--print-requests] [--quiet] [--profile ] --name --url [--headers ] --payloadTemplate ``` | Flag | Shorthand | Description | Required | | ------------------- | --------- | ------------------------------------------------------------------------------------ | -------- | | `--headers` | `-h` | JSON-formatted object of key/value pairs to include in the request header | false | | `--name` | `-n` | name of the webhook to be created | true | | `--payloadTemplate` | `-p` | template string that will be used as the request body, see documentation for details | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--url` | `-u` | URL that will receive a POST request for an alert | true | ##### alerts:webhooks:delete[​](#alertswebhooksdelete "Direct link to alerts:webhooks:delete") Delete an Alert Webhook ```bash prism alerts:webhooks:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `webhook` | | ID of the webhook to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### alerts:webhooks:list[​](#alertswebhookslist "Direct link to alerts:webhooks:list") List Alert Webhooks ```bash prism alerts:webhooks:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | *** #### Components CLI Commands[​](#components-cli-commands "Direct link to Components CLI Commands") ##### components:actions:list[​](#componentsactionslist "Direct link to components:actions:list") List Actions that Components implement ```bash prism components:actions:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] [--public] [--private] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `componentKey` | | The key of the component to show actions for (e.g. 'salesforce') | true | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--private` | | Show actions for the private component with the given key. Use this flag when you have a private component with the same key as a public component. | false | | `--profile` | | Use a profile | false | | `--public` | | Show actions for the public component with the given key. Use this flag when you have a private component with the same key as a public component. | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | Get the ID of the GET action of the HTTP component by action key: ```text prism components:actions:list --columns id --filter 'key=^httpGet$' --no-header http ``` Get actions related to the SFTP component: ```text prism components:actions:list sftp ``` ##### components:data-sources:list[​](#componentsdata-sourceslist "Direct link to components:data-sources:list") List Data Sources that Components implement ```bash prism components:data-sources:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] [--public] [--private] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `componentKey` | | The key of the component to show data sources for (e.g. 'salesforce') | true | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--private` | | Show data sources for the private component with the given key. Use this flag when you have a private component with the same key as a public component. | false | | `--profile` | | Use a profile | false | | `--public` | | Show data sources for the public component with the given key. Use this flag when you have a private component with the same key as a public component. | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | Get data sources related to the Salesforce component: ```text prism components:data-sources:list salesforce ``` ##### components:delete[​](#componentsdelete "Direct link to components:delete") Delete a Component ```bash prism components:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `component` | | ID of the component to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### components:dev:run[​](#componentsdevrun "Direct link to components:dev:run") Fetch an integration's active connection and execute a CLI command with that connection's fields as an environment variable. After specifying an integration ID and connection config variable name, this command executes a CLI command with that connection's fields saved as a config variable named PRISMATIC\_CONNECTION\_VALUE. ```bash prism components:dev:run -i -c -- /command/to/run ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------------------------- | -------- | | `--connectionKey` | `-c` | Key of the connection config variable to fetch meta/state for | true | | `--instanceId` | | Instance ID. | false | | `--integrationId` | `-i` | Integration ID | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | To simply print an integration's basic auth config variable named "My Connection" and pipe the resulting JSON to jq, run: ```text prism components:dev:run --integrationId SW50ZWexample --connectionKey "My Connection" -- printenv PRISMATIC_CONNECTION_VALUE | jq ``` If one of your integrations has an authenticated OAuth 2.0 config variable "Slack Connection", you could run your component's unit tests with that environment variable: ```text prism components:dev:run -i SW50ZWexample -c "Slack Connection" -- yarn run test ``` If you would like to fetch a connection from an instance deployed to one of your customers, specify the --instanceId flag instead ```text prism components:dev:run --instanceId SW50ZWexample -c "Slack Connection" -- yarn run test ``` ##### components:dev:test[​](#componentsdevtest "Direct link to components:dev:test") Run an action of a component within a test integration in the integration runner ```bash prism components:dev:test [--print-requests] [--quiet] [--profile ] [--envPath ] [--[no-]build] [--output-file ] [--print-results] [--clean-up] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------------------------------------- | -------- | | `--[no-]build` | `-b` | Build the component prior to testing | false | | `--clean-up` | | Clean up the integration and temporary component after running the action | false | | `--envPath` | `-e` | Path to dotenv file to load for supplying testing values | false | | `--output-file` | `-o` | Output the results of the action to a specified file | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--print-results` | | Print the results of the action to stdout | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### components:init[​](#componentsinit "Direct link to components:init") Initialize a new Component ```bash prism components:init [--print-requests] [--quiet] [--profile ] [--wsdl-path ] [--open-api-path ] [--verbose] [--toolchain {modern,legacy}] ``` | Flag | Shorthand | Description | Required | | --------------------------- | --------- | ----------------------------------------------------------------------------------------------- | -------- | | `name` | | Name of the new component to create (alphanumeric characters, hyphens, and underscores) | true | | `--open-api-path` | | The path to an OpenAPI Specification file (JSON or YAML) used to generate a Component | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--toolchain modern,legacy` | | Toolchain to scaffold: 'modern' (tsdown + vitest + Biome) or 'legacy' (webpack + jest + eslint) | false | | `--verbose` | | Output more verbose logging from Component generation | false | | `--wsdl-path` | | Path to the WSDL definition file used to generate a Component | false | Initialize a new component directory for a component named 'send-customer-invoices': ```text prism components:init send-customer-invoices ``` Initialize a component from a WSDL definition file, then install dependencies, build, and publish it: ```text prism components:init --wsdl-path ./example.wsdl "Example WSDL" cd "Example WSDL" && yarn install && yarn build prism components:publish ``` ##### components:init:component[​](#componentsinitcomponent "Direct link to components:init:component") Initialize a new Component ```bash prism components:init:component [--print-requests] [--quiet] [--profile ] [--name ] [--description ] [--toolchain {modern,legacy}] ``` | Flag | Shorthand | Description | Required | | --------------------------- | --------- | ------------------------------------------ | -------- | | `--description` | `-d` | Description for the component | false | | `--name` | `-n` | Name of the component | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--toolchain modern,legacy` | | | false | ##### components:list[​](#componentslist "Direct link to components:list") List available Components ```bash prism components:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] [--showAllVersions] [--search ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ---------------------------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--search` | `-s` | Search components by label first, then by key (case insensitive) | false | | `--showAllVersions` | `-a` | If specified this command returns all versions of all components rather than only the latest version | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### components:publish[​](#componentspublish "Direct link to components:publish") Publish a Component to Prismatic ```bash prism components:publish [--print-requests] [--quiet] [--profile ] [--comment ] [--[no-]confirm] [--[no-]check-signature] [--skip-on-signature-match] [--customer ] [--commitHash ] [--commitUrl ] [--repoUrl ] [--pullRequestUrl ] [--include-source] ``` | Flag | Shorthand | Description | Required | | --------------------------- | --------- | --------------------------------------------------------------------------- | -------- | | `--[no-]check-signature` | | Check signature of existing component and confirm publish if matched | false | | `--comment` | `-c` | Comment about changes in this Publish | false | | `--commitHash` | | Commit hash corresponding to the component version being published | false | | `--commitUrl` | | URL to the commit details for this component version | false | | `--[no-]confirm` | | Interactively confirm publish | false | | `--customer` | | ID of customer with which to associate the component | false | | `--include-source` | | Include source code in the component publish | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--pullRequestUrl` | | URL to the pull request that modified this component version | false | | `--quiet` | | Reduce helpful notes and text | false | | `--repoUrl` | | URL to the repository containing the component definition | false | | `--skip-on-signature-match` | | Skips component publish if the new signature matches the existing signature | false | Build and publish a component: ```text npm run build && prism components:publish ``` ##### components:signature[​](#componentssignature "Direct link to components:signature") Generate a Component signature ```bash prism components:signature [--print-requests] [--quiet] [--profile ] [--skip-signature-verify] ``` | Flag | Shorthand | Description | Required | | ------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------- | -------- | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--skip-signature-verify` | | This consistently returns a signature, regardless of whether the corresponding component has been published to the platform or not. | false | ##### components:triggers:list[​](#componentstriggerslist "Direct link to components:triggers:list") List Triggers that Components implement ```bash prism components:triggers:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] [--public] [--private] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `componentKey` | | The key of the component to show triggers for (e.g. 'salesforce') | true | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--private` | | Show actions for the private component with the given key. Use this flag when you have a private component with the same key as a public component. | false | | `--profile` | | Use a profile | false | | `--public` | | Show actions for the public component with the given key. Use this flag when you have a private component with the same key as a public component. | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | Get the ID of the Webhook trigger of the Webhook Triggers component by key: ```text prism components:triggers:list --columns id --filter 'key=^webhook$' --no-header webhook-triggers ``` Get triggers related to the Management Triggers component: ```text prism components:triggers:list management-triggers ``` *** #### Customers CLI Commands[​](#customers-cli-commands "Direct link to Customers CLI Commands") ##### customers:create[​](#customerscreate "Direct link to customers:create") Create a new Customer ```bash prism customers:create [--print-requests] [--quiet] [--profile ] --name [--description ] [--externalId ] [--label ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | -------------------------------------------- | -------- | | `--description` | `-d` | longer description of the customer | false | | `--externalId` | `-e` | external ID of the customer from your system | false | | `--label` | `-l` | a label to apply to the customer | false | | `--name` | `-n` | short name of the new customer | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | Apply multiple labels to a customer ```text prism customers:create --name "Widgets Inc" --externalId "abc-123" --label "Prod Customers" --label "Beta Testers" ``` ##### customers:delete[​](#customersdelete "Direct link to customers:delete") Delete a Customer ```bash prism customers:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `customer` | | ID of the customer to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### customers:list[​](#customerslist "Direct link to customers:list") List your Customers ```bash prism customers:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### customers:update[​](#customersupdate "Direct link to customers:update") Update a Customer ```bash prism customers:update [--print-requests] [--quiet] [--profile ] [--name ] [--description ] [--externalId ] [--label ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | -------------------------------------------- | -------- | | `customer` | | ID of a customer | true | | `--description` | `-d` | description of the customer | false | | `--externalId` | `-e` | external ID of the customer from your system | false | | `--label` | `-l` | a label to apply to the customer | false | | `--name` | `-n` | name of the customer | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | Apply multiple labels to a customer (note: previously set labels will be overwritten) ```text prism customers:update Q3VzdG9tZXI6MmUzZDllOTUtMWIyMy00N2FjLTk3MjUtMzU1OTA2YzgyZWZj --label "Prod Customers" --label "Beta Testers" ``` ##### customers:users:create[​](#customersuserscreate "Direct link to customers:users:create") Create a User for the specified Customer ```bash prism customers:users:create [--print-requests] [--quiet] [--profile ] --email --role --customer [--name ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ----------------------------------------------- | -------- | | `--customer` | `-c` | ID of the customer this user is associated with | true | | `--email` | `-e` | email address | true | | `--name` | `-n` | name of the new user | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--role` | `-r` | ID of the role to assign the user | true | Get the ID of a customer named 'My First Customer': ```text CUSTOMER_ID=$(prism customers:list --columns id --no-header --filter 'name=^My First Customer$') ``` Get the ID of the 'Member' role: ```text ROLE_ID=$(prism customers:users:roles --columns id --no-header --filter 'name=^Member$') ``` Add a new 'Member' user for the customer: ```text prism customers:users:create --email 'bar@email.com' --name 'Thomas Bar' --customer ${CUSTOMER_ID} --role ${ROLE_ID} ``` ##### customers:users:delete[​](#customersusersdelete "Direct link to customers:users:delete") Delete a Customer User ```bash prism customers:users:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `user` | | ID of the user to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### customers:users:list[​](#customersuserslist "Direct link to customers:users:list") List Customer Users ```bash prism customers:users:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `customer` | | ID of the customer | true | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### customers:users:roles[​](#customersusersroles "Direct link to customers:users:roles") List Roles you can grant to Customer Users ```bash prism customers:users:roles [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### customers:users:update[​](#customersusersupdate "Direct link to customers:users:update") Update a User ```bash prism customers:users:update [--print-requests] [--quiet] [--profile ] [--name ] [--phone ] [--dark-mode ] [--dark-mode-os-sync ] ``` | Flag | Shorthand | Description | Required | | --------------------- | --------- | ---------------------------------------------- | -------- | | `user` | | ID of a user | true | | `--dark-mode` | `-d` | whether the user should have dark mode enabled | false | | `--dark-mode-os-sync` | `-o` | whether dark mode should sync with OS settings | false | | `--name` | `-n` | name of the user | false | | `--phone` | `-p` | phone number of the user | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | *** #### Executions CLI Commands[​](#executions-cli-commands "Direct link to Executions CLI Commands") ##### executions:step-result:get[​](#executionsstep-resultget "Direct link to executions:step-result:get") Gets the Result of a specified Step in an Instance Execution. This command can be used to pull down step results for both integration tests and instance executions. ```bash prism executions:step-result:get [--print-requests] [--quiet] [--profile ] --executionId --stepName [--outputPath ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ---------------------------------------------------------------------------- | -------- | | `--executionId` | `-e` | ID of an Execution | true | | `--outputPath` | `-p` | Output result to a file. Output will be printed to stdout if this is omitted | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--stepName` | `-s` | Name of an Integration Step | true | Run a test of a flow to get an execution ID: ```text prism integrations:flows:test ${FLOW_ID} ``` Get step results from a specific execution: ```text prism executions:step-result:get --executionId SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6MWFkZTYwMGQtMjg2Ni00ZTljLWI2N2EtYmUxNzgwOWY4ODI4 --stepName "Fetch Invoice Info" ``` *** #### Graphql CLI Commands[​](#graphql-cli-commands "Direct link to Graphql CLI Commands") ##### graphql:query[​](#graphqlquery "Direct link to graphql:query") Execute an arbitrary GraphQL query against the Prismatic API ```bash prism graphql:query [--print-requests] [--quiet] [--profile ] [--file] [--variables ] [--output {json,yaml,table}] [--columns ] [--data-path ] [--raw] ``` | Flag | Shorthand | Description | Required | | -------------------------- | --------- | ------------------------------------------------------------------------- | -------- | | `query` | | GraphQL query string (omit to read from stdin) | true | | `--columns` | `-c` | Comma-separated field paths for table columns (required for table output) | false | | `--data-path` | `-d` | Dot-notation path to array data in result (e.g., 'customers.nodes') | false | | `--file` | `-f` | Treat query argument as file path | false | | `--output json,yaml,table` | `-o` | Output format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--raw` | `-r` | Output raw JSON without pretty-printing | false | | `--variables` | `-v` | JSON string or @file.json containing query variables | false | Direct query string ```text prism graphql:query 'query { customers { nodes { id name } } }' ``` From file ```text prism graphql:query --file query.graphql ``` From stdin ```text cat query.graphql | prism graphql:query ``` With variables ```text prism graphql:query --file query.graphql --variables '{"id":"Q3VzdG9tZXI6..."}' ``` Variables from file ```text prism graphql:query 'query($id: ID!) { customer(id: $id) { name } }' --variables @vars.json ``` YAML output ```text prism graphql:query 'query { customers { nodes { id name } } }' --output yaml ``` Table output with nested data ```text prism graphql:query 'query { customers { nodes { id name } } }' --output table --data-path customers.nodes --columns id,name ``` *** #### Instances CLI Commands[​](#instances-cli-commands "Direct link to Instances CLI Commands") ##### instances:config-vars:list[​](#instancesconfig-varslist "Direct link to instances:config-vars:list") List Config Variables used on an Instance ```bash prism instances:config-vars:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `instance` | | ID of an instance | true | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### instances:create[​](#instancescreate "Direct link to instances:create") Create an Instance ```bash prism instances:create [--print-requests] [--quiet] [--profile ] --name --integration --customer [--description ] [--config-vars ] [--label ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | --------------------------------------------------------------------------------- | -------- | | `--config-vars` | `-v` | config variables to bind to steps of your instance | false | | `--customer` | `-c` | ID of customer to deploy to | true | | `--description` | `-d` | longer description of the instance | false | | `--integration` | `-i` | ID of the integration or a specific integration version ID this is an instance of | true | | `--label` | `-l` | a label or set of labels to apply to the instance | false | | `--name` | `-n` | name of your new instance. | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | Get the ID of the integration you want to deploy: ```text INTEGRATION_ID=$(prism integrations:list --columns id --no-header --filter 'name=Acme Inc') ``` Get the version ID of the latest available published version: ```text VERSION_ID=$(prism integrations:versions ${INTEGRATION_ID} --latest-available --columns id --no-header) ``` Set up connection credentials (must be escaped): ```text CREDENTIALS='[{"name":"username","type":"value","value":"my.username"},{"name":"password","type":"value","value":"Pa$$W0Rd"}]' ``` Create an instance with config variables and labels: ```text prism instances:create --name 'Acme Inc' --description 'Acme Inc instance for Smith Rocket Co' --integration ${VERSION_ID} --customer ${CUSTOMER_ID} --config-vars '[{"key":"My Endpoint","value":"https://example.com/api"},{"key":"Do Thing?","value":"true"},{"key":"Acme Basic Auth","values":"${CREDENTIALS}"}]' --label 'Production' --label 'Paid' ``` ##### instances:delete[​](#instancesdelete "Direct link to instances:delete") Delete an Instance ```bash prism instances:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `instance` | | ID of the instance to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### instances:deploy[​](#instancesdeploy "Direct link to instances:deploy") Deploy an Instance ```bash prism instances:deploy [--print-requests] [--quiet] [--profile ] [--force] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | -------------------------------------------------------------------------------------- | -------- | | `instance` | | ID of an instance | true | | `--force` | `-f` | Force deployment even when there are certain conditions that would normally prevent it | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### instances:disable[​](#instancesdisable "Direct link to instances:disable") Disable an Instance ```bash prism instances:disable [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `instance` | | ID of an instance | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### instances:enable[​](#instancesenable "Direct link to instances:enable") Enable an Instance ```bash prism instances:enable [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `instance` | | ID of an instance | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### instances:flow-configs:list[​](#instancesflow-configslist "Direct link to instances:flow-configs:list") List Instance Flow Configs ```bash prism instances:flow-configs:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `instance` | | ID of an Instance | true | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### instances:flow-configs:test[​](#instancesflow-configstest "Direct link to instances:flow-configs:test") Test a Flow Config of an Instance ```bash prism instances:flow-configs:test [--print-requests] [--quiet] [--profile ] [--extended] [--columns ] [--tail] [--payload ] [--contentType ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------------------------ | -------- | | `flowConfig` | | ID of a Flow Config to test | true | | `--columns` | | only show provided columns (comma-separated) | false | | `--contentType` | `-c` | Optional content-type for the test payload | false | | `--extended` | `-x` | show extra columns | false | | `--payload` | `-p` | Optional JSON-formatted data payload to submit with the test | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--tail` | `-t` | Tail logs of the flow config test run | false | ##### instances:list[​](#instanceslist "Direct link to instances:list") List Instances ```bash prism instances:list [--print-requests] [--quiet] [--profile ] [--customer ] [--integration ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--customer` | `-c` | ID of a customer | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--integration` | `-i` | ID of an integration | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### instances:update[​](#instancesupdate "Direct link to instances:update") Update an Instance ```bash prism instances:update [--print-requests] [--quiet] [--profile ] [--name ] [--description ] [--version ] [--deploy] [--label ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------------- | -------- | | `instance` | | ID of an instance | true | | `--deploy` | | Deploy the instance after updating | false | | `--description` | `-d` | Description for the instance | false | | `--label` | `-l` | a label or set of labels to apply to the instance | false | | `--name` | `-n` | Name of the instance | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--version` | `-v` | ID of integration version | false | *** #### Integrations CLI Commands[​](#integrations-cli-commands "Direct link to Integrations CLI Commands") ##### integrations:available[​](#integrationsavailable "Direct link to integrations:available") Mark an Integration version as available or unavailable ```bash prism integrations:available [--print-requests] [--quiet] [--profile ] --[no-]available ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `integration` | | ID of an integration version | true | | `--[no-]available` | `-a` | Version is available or unavailable | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### integrations:convert[​](#integrationsconvert "Direct link to integrations:convert") Convert a Low-Code Integration's YAML file into a Code Native Integration ```bash prism integrations:convert [--print-requests] [--quiet] [--profile ] [--registryPrefix ] [--registryUrl ] [--includeComments] ``` | Flag | Shorthand | Description | Required | | ------------------- | --------- | -------------------------------------------------------- | -------- | | `integration` | | ID of the low-code integration to convert | true | | `--includeComments` | `-c` | Whether to include inline comments in the generated code | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--registryPrefix` | `-r` | The registry prefix to use for the converted integration | false | | `--registryUrl` | `-u` | The registry URL to use for the converted integration | false | ##### integrations:create[​](#integrationscreate "Direct link to integrations:create") Create an Integration ```bash prism integrations:create [--print-requests] [--quiet] [--profile ] --name --description [--customer ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------------------ | -------- | | `--customer` | `-c` | ID of customer with which to associate the integration | false | | `--description` | `-d` | longer description of the integration | true | | `--name` | `-n` | name of the integration to create | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### integrations:delete[​](#integrationsdelete "Direct link to integrations:delete") Delete an Integration ```bash prism integrations:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `integration` | | ID of the integration to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### integrations:export[​](#integrationsexport "Direct link to integrations:export") Export an integration to YAML definition ```bash prism integrations:export [--print-requests] [--quiet] [--profile ] [--latest-components] [--version ] ``` | Flag | Shorthand | Description | Required | | --------------------- | --------- | -------------------------------------------------------------- | -------- | | `integration` | | ID of an integration to export | true | | `--latest-components` | `-l` | Use the latest available version of each Component upon import | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--version` | `-v` | Define the definition version to export. | false | ##### integrations:flows:list[​](#integrationsflowslist "Direct link to integrations:flows:list") List Integration Flows ```bash prism integrations:flows:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `integration` | | ID of an Integration | true | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### integrations:flows:listen[​](#integrationsflowslisten "Direct link to integrations:flows:listen") Listen for webhook executions on a flow and save the payload to a file ```bash prism integrations:flows:listen [--print-requests] [--quiet] [--profile ] --integration-id [--flow-id ] [--flow-name ] [--output ] [--timeout ] [--no-prompt] [--reset] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ----------------------------------------------------------------------------------- | -------- | | `--flow-id` | `-f` | ID of the flow to listen to. If not provided, you will be prompted to select. | false | | `--flow-name` | `-n` | Name of the flow to listen to. | false | | `--integration-id` | `-i` | ID of the integration containing the flow to listen to. | true | | `--no-prompt` | `-n` | For flows using polling triggers, automatically poll without a confirmation prompt. | false | | `--output` | `-o` | Output directory for the payload file. Defaults to ./payloads | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--reset` | `-r` | Manually turn off listening mode for a given integration. | false | | `--timeout` | `-t` | Timeout in seconds to stop listening. | false | ##### integrations:flows:test[​](#integrationsflowstest "Direct link to integrations:flows:test") Run a test execution of a flow ```bash prism integrations:flows:test [--print-requests] [--quiet] [--profile ] [--flow-id ] [--flow-name ] [--flow-url ] [--integration-id ] [--payload ] [--payload-content-type ] [--sync] [--tail-results] [--tail-logs] [--cni-auto-end] [--timeout ] [--result-file ] [--jsonl] [--debug] [--apiKey ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `--apiKey` | | Optional API key for flows with secured endpoints. | false | | `--cni-auto-end` | | Automatically stop polling activity once an CNI flow execution completes. Some logs & results may not be returned this way. DOES NOT WORK FOR LOW-CODE FLOWS. | false | | `--debug` | | Enables debug mode on the test execution. | false | | `--flow-id` | `-f` | ID of the flow to test. Base64 encoded. | false | | `--flow-name` | `-n` | Name of the flow to test. | false | | `--flow-url` | `-u` | URL of the flow to test. Prefer to use flow-id instead, if possible. | false | | `--integration-id` | `-i` | ID of the integration containing the flow to test. Base64 encoded. | false | | `--jsonl` | | Optionally format the step and tail results output into JSON Lines. | false | | `--payload` | `-p` | Optional file containing a payload to run the flow with. | false | | `--payload-content-type` | `-c` | Optional Content-Type for the test payload. | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--result-file` | `-r` | Optional file to append tailed execution result data to. Results are saved into JSON Lines. | false | | `--sync` | | Forces the flow to run synchronously. | false | | `--tail-logs` | | Tail logs from the test execution until user interrupt or timeout. | false | | `--tail-results` | | Tail step results from the test execution until user interrupt or timeout. | false | | `--timeout` | | Optionally set a timeout (in seconds) to stop tail activity. Compatible with both low-code and CNI flows. | false | Test an integration flow with a payload file and tail the logs and step results: ```text prism integrations:flows:test -p=some_payload_file.xml -c=application/xml --tail-logs --tail-results ``` ##### integrations:fork[​](#integrationsfork "Direct link to integrations:fork") Fork an Integration ```bash prism integrations:fork [--print-requests] [--quiet] [--profile ] --name --description ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | -------------------------------------------- | -------- | | `parent` | | ID of the Integration to fork | true | | `--description` | `-d` | longer description of the forked integration | true | | `--name` | `-n` | name of the forked integration | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### integrations:import[​](#integrationsimport "Direct link to integrations:import") Import an Integration using a YAML definition file or a Code Native Integration ```bash prism integrations:import [--print-requests] [--quiet] [--profile ] [--path ] [--integrationId ] [--icon-path ] [--open] [--replace] [--test-api-key ] [--[no-]confirm] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ----------------------------------------------------------------------------------------------------------------------- | -------- | | `--[no-]confirm` | | Interactively confirm the import when using --replace | false | | `--icon-path` | | If supplied, the path to the PNG icon for the integration. Not applicable for Code Native Integrations. | false | | `--integrationId` | `-i` | The ID of the integration being imported | false | | `--open` | `-o` | If supplied, open the Designer for the imported integration | false | | `--path` | `-p` | If supplied, the path to the YAML definition of the integration to import. Not applicable for Code Native Integrations. | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--replace` | `-r` | If supplied, allows replacing an existing integration regardless of code-native status. Requires integrationId. | false | | `--test-api-key` | | Provide test API keys for flows in the format flowName="API\_KEY". Can be specified multiple times. | false | ##### integrations:init[​](#integrationsinit "Direct link to integrations:init") Initialize a new Code Native Integration ```bash prism integrations:init [--print-requests] [--quiet] [--profile ] [--clean] [--toolchain {modern,legacy}] ``` | Flag | Shorthand | Description | Required | | --------------------------- | --------- | ----------------------------------------------------------------------------------------------- | -------- | | `name` | | Name of the new integration to create (alphanumeric characters, hyphens, and underscores) | true | | `--clean` | | Generate clean scaffold without example code | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--toolchain modern,legacy` | | Toolchain to scaffold: 'modern' (tsdown + vitest + Biome) or 'legacy' (webpack + jest + eslint) | false | Initialize a new directory for a Code Native Integration: ```text prism integrations:init acme-integration ``` Install dependencies: ```text npm install ``` Build the integration: ```text npm run build ``` Import the integration into Prismatic: ```text prism integrations:import ``` ##### integrations:list[​](#integrationslist "Direct link to integrations:list") List Integrations ```bash prism integrations:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] [--showAllVersions] [--customer ] [--org-only] [--search ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------------------------------ | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--customer` | `-c` | If specified this command returns only integrations that are available to the specified customer ID | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--org-only` | `-o` | If specified this command returns only org integrations | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--search` | `-s` | If specified, search for integrations by name (case insensitive). | false | | `--showAllVersions` | `-a` | If specified this command returns all versions of all integrations rather than only the latest version | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### integrations:marketplace[​](#integrationsmarketplace "Direct link to integrations:marketplace") Make a version of an Integration available in the Marketplace ```bash prism integrations:marketplace [--print-requests] [--quiet] [--profile ] --[no-]available [--[no-]deployable] [--[no-]allow-multiple-instances] [--overview ] ``` | Flag | Shorthand | Description | Required | | --------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------- | -------- | | `integration` | | ID of an integration version to make marketplace available | true | | `--[no-]allow-multiple-instances` | `-m` | Allow a customer to deploy multiple instances of this integration | false | | `--[no-]available` | `-a` | Mark this Integration version available in the marketplace | true | | `--[no-]deployable` | `-d` | Mark this Integration version as deployable in the marketplace; does not apply if not also marked available | false | | `--overview` | `-o` | Overview to describe the purpose of the integration | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### integrations:open[​](#integrationsopen "Direct link to integrations:open") Open the Designer for the specified Integration ```bash prism integrations:open [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `integrationId` | | ID of the integration to open | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### integrations:publish[​](#integrationspublish "Direct link to integrations:publish") Publish a version of an Integration for use in Instances ```bash prism integrations:publish [--print-requests] [--quiet] [--profile ] [--comment ] [--commitHash ] [--commitUrl ] [--repoUrl ] [--pullRequestUrl ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | -------------------------------------------------------------------- | -------- | | `integration` | | ID of an integration to publish | true | | `--comment` | `-c` | comment about changes in this publication | false | | `--commitHash` | | Commit hash corresponding to the integration version being published | false | | `--commitUrl` | | URL to the commit details corresponding to this integration version | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--pullRequestUrl` | | URL to the pull request that modified this integration version | false | | `--quiet` | | Reduce helpful notes and text | false | | `--repoUrl` | | URL to the repository containing the definition for this integration | false | ##### integrations:set-debug[​](#integrationsset-debug "Direct link to integrations:set-debug") Set debug mode on or off for an integration's test instance. ```bash prism integrations:set-debug [--print-requests] [--quiet] [--profile ] [--integration-id ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------------------------------------------------ | -------- | | `debug` | | Boolean value to set whether globalDebug should be enabled for the given integration | true | | `--integration-id` | `-i` | ID of the integration containing the flow to test. | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### integrations:update[​](#integrationsupdate "Direct link to integrations:update") Update an Integration's name or description ```bash prism integrations:update [--print-requests] [--quiet] [--profile ] [--name ] [--description ] [--customer ] [--test-config-vars ] ``` | Flag | Shorthand | Description | Required | | -------------------- | --------- | ------------------------------------------------------ | -------- | | `integration` | | ID of an integration | true | | `--customer` | `-c` | ID of customer with which to associate the integration | false | | `--description` | `-d` | new description to give the integration | false | | `--name` | `-n` | new name to give the integration | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--test-config-vars` | | JSON-formatted config variables to be used for testing | false | ##### integrations:validate-yaml[​](#integrationsvalidate-yaml "Direct link to integrations:validate-yaml") Validate a YAML integration definition without importing it ```bash prism integrations:validate-yaml [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `path` | | Path to YAML file (use '-' for stdin) | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | Validate a YAML file ```text prism integrations:validate-yaml path/to/integration.yml ``` Validate from stdin ```text cat integration.yml | prism integrations:validate-yaml - ``` Validate from stdin (alternative) ```text prism integrations:validate-yaml - < integration.yml ``` ##### integrations:versions[​](#integrationsversions "Direct link to integrations:versions") List Integration versions ```bash prism integrations:versions [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] [--latest-available] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `integration` | | ID of an integration | true | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--latest-available` | `-l` | Show only the latest available version | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | *** #### Login CLI Commands[​](#login-cli-commands "Direct link to Login CLI Commands") ##### login[​](#login "Direct link to login") Log in to your Prismatic account ```bash prism login [--print-requests] [--quiet] [--profile ] [--force] [--url] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | --------------------------------------------------------------- | -------- | | `--force` | `-f` | re-authenticate, even if you are already logged in | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--url` | `-u` | returns a challenge url without automatically opening a browser | false | ##### login:switch[​](#loginswitch "Direct link to login:switch") Switch between organization tenants ```bash prism login:switch [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | *** #### Logout CLI Commands[​](#logout-cli-commands "Direct link to Logout CLI Commands") ##### logout[​](#logout "Direct link to logout") Log out of your Prismatic account ```bash prism logout [--print-requests] [--quiet] [--profile ] [--browser] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------------------ | -------- | | `--browser` | `-b` | additionally log out of your default browser's session | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | *** #### Logs CLI Commands[​](#logs-cli-commands "Direct link to Logs CLI Commands") ##### logs:severities:list[​](#logsseveritieslist "Direct link to logs:severities:list") List Log Severities for use by Alert Triggers ```bash prism logs:severities:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | *** #### Me CLI Commands[​](#me-cli-commands "Direct link to Me CLI Commands") ##### me[​](#me "Direct link to me") Print your user profile information ```bash prism me [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### me:token[​](#metoken "Direct link to me:token") Print your authorization tokens ```bash prism me:token [--print-requests] [--quiet] [--profile ] [--type {access,refresh}] ``` | Flag | Shorthand | Description | Required | | ----------------------- | --------- | ------------------------------------------ | -------- | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--type access,refresh` | `-t` | Which token type to print | false | ##### me:token:revoke[​](#metokenrevoke "Direct link to me:token:revoke") Revoke all refresh tokens for your user ```bash prism me:token:revoke [--print-requests] [--quiet] [--profile ] [--[no-]confirm] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------------------------------------- | -------- | | `--[no-]confirm` | | Prompt for confirmation before revoking tokens. Use --no-confirm to skip. | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | *** #### On-prem-resources CLI Commands[​](#on-prem-resources-cli-commands "Direct link to On-prem-resources CLI Commands") ##### on-prem-resources:delete[​](#on-prem-resourcesdelete "Direct link to on-prem-resources:delete") Delete an On-Premise Resource ```bash prism on-prem-resources:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `resource` | | ID of the On-Premise Resource to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### on-prem-resources:list[​](#on-prem-resourceslist "Direct link to on-prem-resources:list") List On-Premise Resources ```bash prism on-prem-resources:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] [--customer ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ----------------------------------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--customer` | `-c` | If specified this command returns only On-Premise Resources that are available to the specified customer ID | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### on-prem-resources:registration-jwt[​](#on-prem-resourcesregistration-jwt "Direct link to on-prem-resources:registration-jwt") Create a JWT that may be used to register an On-Premise Resource. ```bash prism on-prem-resources:registration-jwt [--print-requests] [--quiet] [--profile ] [--customerId ] [--orgOnly] [--resourceId ] [--rotate] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | -------------------------------------------------------------------------------------------- | -------- | | `--customerId` | `-c` | The ID of the customer for which to create the JWT. Only valid for Organization users. | false | | `--orgOnly` | | Register a Resource available to Organization users only. Only valid for Organization users. | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--resourceId` | `-r` | An optional ID of an existing On-Premise Resource for which to generate a new JWT. | false | | `--rotate` | | Invalidate all JWTs for the On-Premise Resource and get a new JWT. | false | *** #### Organization CLI Commands[​](#organization-cli-commands "Direct link to Organization CLI Commands") ##### organization:connections:list[​](#organizationconnectionslist "Direct link to organization:connections:list") List all integration-agnostic connections available to the organization ```bash prism organization:connections:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] [--managed-by {org,customer}] ``` | Flag | Shorthand | Description | Required | | --------------------------- | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--managed-by org,customer` | | Filter connections by management type | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### organization:signing-keys:delete[​](#organizationsigning-keysdelete "Direct link to organization:signing-keys:delete") Delete an embedded marketplace signing key ```bash prism organization:signing-keys:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `signingKeyId` | | ID of the signing key to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### organization:signing-keys:generate[​](#organizationsigning-keysgenerate "Direct link to organization:signing-keys:generate") Generate an embedded marketplace signing key. The RSA public key is saved in Prismatic, and the private key is returned and immediately removed from Prismatic. Once the private key is returned, it cannot be retrieved again. ```bash prism organization:signing-keys:generate [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### organization:signing-keys:import[​](#organizationsigning-keysimport "Direct link to organization:signing-keys:import") Import a RSA public key for use with embedded marketplace. You can use openssl to generate a new RSA key pair and import the public key. ```bash prism organization:signing-keys:import [--print-requests] [--quiet] [--profile ] --public-key-file ``` | Flag | Shorthand | Description | Required | | ------------------- | --------- | ------------------------------------------ | -------- | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--public-key-file` | `-p` | public key file | true | | `--quiet` | | Reduce helpful notes and text | false | Generate an RSA private key using openssl: ```text openssl genrsa -out my-private-key.pem 4096 ``` Generate the associated RSA public key: ```text openssl rsa -in my-private-key.pem -pubout > my-public-key.pub ``` Import the public key: ```text prism organization:signing-keys:import -p my-public-key.pub ``` ##### organization:signing-keys:list[​](#organizationsigning-keyslist "Direct link to organization:signing-keys:list") List embedded signing keys for embedded marketplace ```bash prism organization:signing-keys:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### organization:update[​](#organizationupdate "Direct link to organization:update") Update your Organization ```bash prism organization:update [--print-requests] [--quiet] [--profile ] [--name ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `--name` | `-n` | name of the organization | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### organization:updateAvatarUrl[​](#organizationupdateavatarurl "Direct link to organization:updateAvatarUrl") Update your Organization Avatar URL ```bash prism organization:updateAvatarUrl [--print-requests] [--quiet] [--profile ] --organizationId [--avatarUrl ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `--avatarUrl` | `-n` | Url of the organization avatar | false | | `--organizationId` | | ID of an organization | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### organization:users:create[​](#organizationuserscreate "Direct link to organization:users:create") Create a User for your Organization ```bash prism organization:users:create [--print-requests] [--quiet] [--profile ] [--name ] --email --role ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `--email` | `-e` | email address of the user | true | | `--name` | `-n` | name of the user | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--role` | `-r` | role the user should assume | true | Get the ID of the 'Integrator' role: ```text ROLE_ID=$(prism organization:users:roles --columns id --no-header --filter 'name=^Integrator$') ``` Create an organization user and assign the role: ```text prism organization:users:create --email 'foo@email.com' --name 'Susan Foo' --role ${ROLE_ID} ``` ##### organization:users:delete[​](#organizationusersdelete "Direct link to organization:users:delete") Delete an Organization User ```bash prism organization:users:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `user` | | ID of the user to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### organization:users:list[​](#organizationuserslist "Direct link to organization:users:list") List Users of your Organization ```bash prism organization:users:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### organization:users:roles[​](#organizationusersroles "Direct link to organization:users:roles") List Roles you can grant to other users in your Organization ```bash prism organization:users:roles [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### organization:users:update[​](#organizationusersupdate "Direct link to organization:users:update") Update a User ```bash prism organization:users:update [--print-requests] [--quiet] [--profile ] [--name ] [--phone ] [--dark-mode ] [--dark-mode-os-sync ] ``` | Flag | Shorthand | Description | Required | | --------------------- | --------- | ---------------------------------------------- | -------- | | `user` | | ID of a user | true | | `--dark-mode` | `-d` | whether the user should have dark mode enabled | false | | `--dark-mode-os-sync` | `-o` | whether dark mode should sync with OS settings | false | | `--name` | `-n` | name of the user | false | | `--phone` | `-p` | phone number of the user | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | *** #### Profiles CLI Commands[​](#profiles-cli-commands "Direct link to Profiles CLI Commands") ##### profiles:delete[​](#profilesdelete "Direct link to profiles:delete") Delete a profile ```bash prism profiles:delete [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `name` | | Profile to delete | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### profiles:list[​](#profileslist "Direct link to profiles:list") List profiles ```bash prism profiles:list [--print-requests] [--quiet] [--profile ] [--columns ] [--csv] [--extended] [--filter ] [--no-header] [--no-truncate] [--output {csv,json,yaml}] [--sort ] ``` | Flag | Shorthand | Description | Required | | ------------------------ | --------- | ------------------------------------------------------------------------------- | -------- | | `--columns` | | only show provided columns (comma-separated) | false | | `--csv` | | output is csv format \[alias: --output=csv] | false | | `--extended` | `-x` | show extra columns | false | | `--filter` | | filter property by regex, ex: name=^foo (prefix key with - to invert) | false | | `--no-header` | | hide table header from output | false | | `--no-truncate` | | do not truncate output to fit screen | false | | `--output csv,json,yaml` | | output in a more machine friendly format | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--sort` | | property to sort by, comma-separated for multi-key (prepend '-' for descending) | false | ##### profiles:use[​](#profilesuse "Direct link to profiles:use") Set the default profile ```bash prism profiles:use [--print-requests] [--quiet] [--profile ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------ | -------- | | `name` | | Profile to use by default | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | *** #### Translations CLI Commands[​](#translations-cli-commands "Direct link to Translations CLI Commands") ##### translations:list[​](#translationslist "Direct link to translations:list") Generate Dynamic Phrases for Embedded Marketplace ```bash prism translations:list [--print-requests] [--quiet] [--profile ] [--output-file ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ---------------------------------------------------- | -------- | | `--output-file` | `-o` | Output the results of the action to a specified file | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | *** #### Workflows CLI Commands[​](#workflows-cli-commands "Direct link to Workflows CLI Commands") ##### workflows:export[​](#workflowsexport "Direct link to workflows:export") Export an embedded workflow or workflow template YAML definition ```bash prism workflows:export [--print-requests] [--quiet] [--profile ] [--[no-]latest-components] ``` | Flag | Shorthand | Description | Required | | -------------------------- | --------- | --------------------------------------------------------------------------------- | -------- | | `workflow` | | ID of the workflow to export | true | | `--[no-]latest-components` | `-l` | Use the latest available version of each component upon import. Defaults to true. | false | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | ##### workflows:import[​](#workflowsimport "Direct link to workflows:import") Import an embedded workflow or workflow template YAML definition ```bash prism workflows:import [--print-requests] [--quiet] [--profile ] --path [--workflow ] [--customer ] ``` | Flag | Shorthand | Description | Required | | ------------------ | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | `--customer` | `-c` | The ID of the customer to associate with the imported workflow. This will overwrite the existing workflow. If omitted, the workflow will be imported as a template. | false | | `--path` | `-p` | The path to the YAML definition of the workflow to import | true | | `--print-requests` | | Print all GraphQL requests that are issued | false | | `--profile` | | Use a profile | false | | `--quiet` | | Reduce helpful notes and text | false | | `--workflow` | `-w` | The ID of the workflow being imported. If omitted, a new workflow will be created. | false | --- ### Troubleshooting and FAQ If you misconfigure [WSL](https://docs.microsoft.com/en-us/windows/wsl/) or [Node.js](https://nodejs.org/), `prism` may behave unexpectedly. Here are solutions to common problems: #### Error: spawn cmd.exe ENOENT[​](#error-spawn-cmdexe-enoent "Direct link to Error: spawn cmd.exe ENOENT") When you run `prism login` from within Windows Subsystem for Linux (WSL), you might encounter `Error: spawn cmd.exe ENOENT`. This is an issue with the WSL distribution's `PATH` environment variable. Ensure that your `PATH` environment variable contains `System32`, which is where `cmd.exe` is located. Typically, you can run something like this: ```bash export PATH=$PATH:/mnt/c/Windows/System32 ``` #### Error: spawn xdg-open ENOENT[​](#error-spawn-xdg-open-enoent "Direct link to Error: spawn xdg-open ENOENT") If you run `prism login` in a headless Linux environment (a Linux environment without a desktop or web browser, such as an Ubuntu server or Docker container), `prism` will be unable to open a GUI web browser to authenticate you. You might encounter an error that reads `Error: spawn xdg-open ENOENT` or `Error: Exited with code 3`. You'll need to authenticate on a computer with a desktop environment and web browser, then set the refresh token you receive as an environment variable on the headless server. See [Headless prism Usage for CI/CD Pipelines](https://prismatic.io/docs/cli/bash-scripting.md#headless-prism-usage-for-cicd-pipelines). #### prism: command not found[​](#prism-command-not-found "Direct link to prism: command not found") If you have followed the [installation instructions](https://prismatic.io/docs/cli.md#installing-the-cli-tool) to install `prism`, but then encounter `prism: command not found` on Linux or MacOS, or `'prism' is not recognized as an internal or external command, operable program or batch file` on Windows when you run `prism`, you likely don't have your NodeJS `PATH` configured correctly. Ensure that your `PATH` environment variable contains the `bin/` directory of your NodeJS installation. --- ### Organization Overview To clarify terminology, Prismatic's customers (i.e. *you*) are referred to as **organizations**, while your customers are referred to as **customers** throughout the documentation. #### Creating your organization[​](#creating-your-organization "Direct link to Creating your organization") To create an organization within Prismatic, first [sign up](https://prismatic.io/docs/free-trial). For large enterprises with multiple distinct divisions, consider creating a separate organization for each division. note If your company has already created an organization, ask your organization's administrator to create a user account for you. Registering with Prismatic again will create a new, separate organization. #### Setting your organization's avatar[​](#setting-your-organizations-avatar "Direct link to Setting your organization's avatar") To change your organization's avatar icon, navigate to the **Theme** tab. Logos are cropped and resized to 512 x 512 pixels and must be image files. Transparent, square PNG images typically yield the best results. #### Renaming your organization[​](#renaming-your-organization "Direct link to Renaming your organization") * CLI * API To rename your organization, use the `prism organization:update` subcommand: ```bash prism organization:update --name "New Organization Name" ``` To update your organization's name programmatically, use the [updateOrganization](https://prismatic.io/docs/api/schema/mutations.md#updateorganization)) mutation: ```graphql mutation { updateOrganization(input: { name: "New Organization Name" }) { organization { id } } } ``` #### Swapping between tenants[​](#swapping-between-tenants "Direct link to Swapping between tenants") If your email address is bound to multiple organizations in Prismatic, you can switch between them by clicking the **Tenant Picker** on the left-hand sidebar. ![Screenshot of multi-tenant switching in Prismatic](/docs/assets/images/multi-tenant-switching-d46a0cc43b6311c22cbe3153b3130335.png) The **Tenant Picker** displays all tenants associated with your email address including tenants in other regions. #### Deleting your organization[​](#deleting-your-organization "Direct link to Deleting your organization") You can delete your organization from the **Subscription** tab within the organization **Settings**. Note that deleting your organization is permanent. --- ### Custom Domains Feature Availability The custom domains feature is available to customers on specific pricing plans. Refer to your pricing plan or contract, or contact the Prismatic support team for more information. You can configure access to the Prismatic platform to operate through a custom domain that you control. For example, you might want to white-label Prismatic so your team members and customers access `https://integrations.your-company.com` instead of [https://app.prismatic.io](https://app.prismatic.io/). To configure a custom domain, please contact our [support team](mailto:support@prismatic.io), and specify the subdomain (like `https://integrations.your-company.com`) you would like to use. Setup is a three-step process: 1. We'll create an [SOA record](https://www.cloudflare.com/learning/dns/dns-records/dns-soa-record/) for that subdomain and provide you with a list of four nameservers. 2. After we prepare those records, you'll need to create an [NS record](https://www.cloudflare.com/learning/dns/dns-records/dns-ns-record/) pointing to the list of nameservers we provided. *Note:* Some DNS providers represent nameserver lists as separate NS records (one server per record), while others use single NS records with multiple values. Verify that your NS records propagate correctly by entering your custom subdomain in a tool like [DNS Checker](https://dnschecker.org/#NS/) or by using the `dig` command: ```bash dig NS integrations.your-company.com +short ``` 3. After you create the NS records, notify the Prismatic support team so we can finalize the setup on our end. We'll verify the DNS records and issue an SSL certificate for your domain. We'll also create several A and CNAME records for web app access, webhook invocation, OAuth 2.0 callbacks, MCP, and on-prem agent connectivity: * `https://integrations.your-company.com` - web app and embedded URL * `https://hooks.integrations.your-company.com` - webhooks * `https://oauth2.integrations.your-company.com` - OAuth 2.0 callback * `https://mcp.integrations.your-company.com` - MCP endpoint for [agentic flows](https://prismatic.io/docs/ai/model-context-protocol.md) * `https://onprem.integrations.your-company.com` - [on-prem agent](https://prismatic.io/docs/integrations/connections/on-prem-agent.md) connectivity Note: Prismatic will handle all additional DNS records for these services; you don't need to create any. Once your domain is white-labeled, your OAuth 2.0 applications can use `https://oauth2.integrations.your-company.com/callback` as a callback URL. Webhook and OAuth 2.0 URLs are derived from the URL you visit Once a subdomain is configured, you can access your tenant through either the custom domain or the default Prismatic domain. Custom domains are not tenant-specific. If you have production and development tenants, both tenants can use the custom domain you establish. * If you access or embed Prismatic via , you will see webhook and OAuth URLs with the Prismatic domain. * If you access or embed Prismatic via your custom domain like `https://integrations.your-company.com`, you will see webhook and OAuth URLs with your custom domain. --- ### Deployment Regions #### Why use multiple regions?[​](#why-use-multiple-regions "Direct link to Why use multiple regions?") There are several reasons you might choose to host Prismatic integrations in multiple regions: * GDPR compliance requirements, ensuring EU-based customer data remains in the EU and US-based customer data remains in the US * CJIS or ITAR compliance requirements necessitating customer data storage in GovCloud * Ensuring low-latency between where your application is hosted and Prismatic * Private cloud hosting to fulfill specific customer requirements Regardless of the requirement, when hosting integrations across multiple Prismatic stacks, you must maintain [component and integration synchronization](https://prismatic.io/docs/configure-prismatic/integrations-multiple-regions.md) between regions. #### Logging in to additional regions[​](#logging-in-to-additional-regions "Direct link to Logging in to additional regions") Prismatic's public regions are accessible through the following URLs: | Region | App URL | | -------------------- | ----------------------------------------- | | US Commercial (Ohio) | | | US GovCloud | | | Europe (Ireland) | | | Europe (London) | | | Canada (Central) | | | Australia (Sydney) | | | Africa (Cape Town) | | If your email address is associated with multiple regions, you can [switch between them](https://prismatic.io/docs/configure-prismatic.md#swapping-between-tenants) by clicking the **Tenant Picker** on the left-hand sidebar after logging in. If your tenant is hosted in a private cloud deployment, contact Prismatic support for your endpoint URL. #### Prismatic IP allowlist (whitelist)[​](#prismatic-ip-allowlist-whitelist "Direct link to Prismatic IP allowlist (whitelist)") If your integration connects to an external application that restricts connections by IP address, add the following IPs to your allowlist: | Region | App URL | IP Addresses | OAuth 2.0 IP Addresses † | | -------------------- | ------------------------------- | ------------------------------------- | ---------------------------------------------------------------------------- | | US Commercial (Ohio) | app.prismatic.io | `3.132.205.204`
`3.139.185.169` | `3.23.192.20`
`3.150.210.213`
`18.224.210.244`
`52.14.40.203` | | US GovCloud | app.us-gov-west-1.prismatic.io | `15.200.86.230`
`15.205.78.158` | | | Australia (Sydney) | app.ap-southeast-2.prismatic.io | `52.65.181.77`
`54.252.173.54` | | | Canada (Central) | app.ca-central-1.prismatic.io | `35.182.143.99`
`3.99.22.244` | | | Europe (Ireland) | app.eu-west-1.prismatic.io | `54.78.26.19`
`54.246.201.85` | | | Europe (London) | app.eu-west-2.prismatic.io | `18.132.171.185`
`18.134.91.207` | | | Africa (Cape Town) | app.af-south-1.prismatic.io | `13.245.178.8`
`13.247.255.128` | | † *Execution runners will use **IP Addresses** listed above.* *US Commercial has four additional IP addresses used by its OAuth 2.0 service.* For private cloud deployments, contact Prismatic support for the appropriate allowlist. **Note**: Integrations in Prismatic **send** data from these IP addresses to third-party applications and services. Webhook URLs for invoking integrations use name-based routing (e.g., hooks.prismatic.io) and IPs are subject to change. #### Private regions[​](#private-regions "Direct link to Private regions") For private cloud deployments, Prismatic's DevOps team will deploy a Prismatic stack in your AWS account within your chosen region. Contact for your endpoint URL and IP allowlist. #### Multi-region user access[​](#multi-region-user-access "Direct link to Multi-region user access") By default, new Prismatic accounts are provisioned in the US Commercial (Ohio) region, [app.prismatic.io](https://app.prismatic.io). Access to additional regions can be enabled by Prismatic for enterprise customers whose contracts include additional regions. User setup required per region User authentication spans all regions, allowing you to use the same email and password across regions. However, user data is not shared between regions. After Prismatic support enables your organization in an additional region, you must invite your team members to the new region for them to access your tenant there. #### Using Prism CLI with multiple regions[​](#using-prism-cli-with-multiple-regions "Direct link to Using Prism CLI with multiple regions") The [prism](https://prismatic.io/docs/cli/prism.md) CLI tool interacts with the US Commercial region by default. Create a profile for each region you use: * bash * powershell Create and use a regional profile ```bash $ PRISMATIC_URL=https://app.eu-west-1.prismatic.io prism login --profile eu Press any key to open prismatic.io in your default browser: Login complete! $ prism me --profile eu Name: John Doe Email: john.doe@example.com Organization: Example Corp - EU Region Endpoint URL: https://app.eu-west-1.prismatic.io Profile: eu ``` Add `--profile eu` to a command, set `PRISM_PROFILE=eu` for a shell session, or run `prism profiles:use eu` to make it the default. Create and use a regional profile ```powershell PS C:\> $env:PRISMATIC_URL="https://app.eu-west-1.prismatic.io" PS C:\> prism login --profile eu Press any key to open prismatic.io in your default browser: Login complete! PS C:\> Remove-Item Env:PRISMATIC_URL PS C:\> prism me --profile eu Name: John Doe Email: john.doe@example.com Organization: Example Corp - EU Region Endpoint URL: https://app.eu-west-1.prismatic.io Profile: eu ``` When automating deployments across regions in CI/CD pipelines, use the `PRISMATIC_URL` and `PRISMATIC_TENANT_ID` environment variables to target specific regions and tenants. See [Headless prism Usage](https://prismatic.io/docs/cli/bash-scripting.md#headless-prism-usage-for-cicd-pipelines). --- ### Integrations in Multiple Regions #### Syncing integrations between regions[​](#syncing-integrations-between-regions "Direct link to Syncing integrations between regions") An integration can be represented as a YAML definition that specifies the flows, steps, configuration, and config variables comprising the integration. Integrations can be replicated between regions by downloading an integration's YAML definition from one region and importing the YAML file into another region. ##### Exporting an integration's YAML definition[​](#exporting-an-integrations-yaml-definition "Direct link to Exporting an integration's YAML definition") To access an integration's YAML definition from the integration builder, click the **Integration Details** gear icon on the left side of the integration designer and select **Save to file**. To access an integration's YAML definition using `prism`, identify the integration's ID by running `prism integrations:list --extended`, or by copying the `SW5.....` portion of your integration's URL when it's open. Then, run `prism integrations:export`: Save an integration's YAML definition to my-file.yaml ```bash prism integrations:export SW50ZWdyYXRpb246YmE0NGU5NmQtYWMzOS00MDMxLTg4MmUtMWQyNzA5ZjY5MDg0 > my-file.yaml ``` Component Versions Across Regions Component versions may not be consistent across regions. For example, you might publish an "Acme Inc" component 50 times during testing on the US Commercial stack, but only twice on your Europe tenant. In this case, "v50" in the US stack would correspond to "v2" in the Europe stack. A YAML file specifying "Acme Inc v50" would be meaningless to the Europe stack. To avoid version mismatches, export your YAML definition using the **Save to file with latest component versions** button in the UI, or by adding the `--latest-components` flag to your `prism` command: ```text prism integrations:export --latest-components SW50Z... ``` When you import your integration in another region, the latest versions of each component will be used. ##### Importing an integration's YAML definition[​](#importing-an-integrations-yaml-definition "Direct link to Importing an integration's YAML definition") Once you have the integration's YAML definition, you can import the integration through the UI or using `prism`. In the integration designer, open the **Integration details** modal again, then select **Import**. Using prism, run `prism integrations:import --path ./my-file.yaml` to import your YAML file. **Troubleshooting**: If you encounter errors, ensure that the custom components used in your source stack are also deployed to your destination stack. You may have an older version of your custom component that lacks some actions, inputs, etc. ##### Renaming flows and config variables in YAML definitions[​](#renaming-flows-and-config-variables-in-yaml-definitions "Direct link to Renaming flows and config variables in YAML definitions") Use caution when modifying flow names or config variable names in YAML If you modify the name of a flow or config variable in the YAML definition, you should include a `renameAttributes` section in your YAML. Your integration consists of flows, steps, config variables, and additional metadata, and can be represented as a YAML file. When a YAML file is imported, the Prismatic API attempts to match flows and config variables in the YAML with flows and config variables in the existing integration. If a match is found, the existing config variable or flow will remain. If no match is found, a new config variable or flow will be created and the old flow or config variable will be removed. This creates problems when you want to rename flows or config variables in your YAML definition: * If you rename a **flow**, the Prismatic API will not be able to match the flow in the YAML with a flow in the existing integration, and will create a new flow and delete the old one. Your integration will function identically, but the flow's ID will be different. Instances of your integration will be assigned new webhook URLs for the renamed flow when the instance is updated to the latest integration version. Different webhook URLs can cause issues if you already have webhooks configured using the old flow's URL. * If you rename a **config variable**, the Prismatic API will not be able to match the config variable in the YAML with the config variable in the existing integration, and will create a new config variable and delete the old one. This will cause issues if you have already configured instances of your integration. When a customer updates their instance, their old config variables will be removed and new ones will need to be reconfigured (this includes OAuth 2.0 connections - the customer user will need to reauthenticate their connection). To ensure that the Prismatic API matches the flow or config variable in the YAML with the flow or config variable in the existing integration, you should include a `renameAttributes` section in your YAML. This section includes `requiredConfigVars` and `flows` sections, which specify the old and new names of config variables and flows, respectively. ```yaml configPages: - elements: - type: configVar value: SFDC Connection - type: configVar value: SFDC Record Type name: Page 1 userLevelConfigured: false definitionVersion: 7 endpointType: flow_specific flows: - description: "" endpointSecurityType: customer_optional isSynchronous: false name: SFDC Data Import steps: [] name: SFDC Integration requiredConfigVars: - key: SFDC Record Type description: "" dataType: string orgOnly: false defaultValue: "" - key: SFDC Connection description: "" dataType: string orgOnly: false defaultValue: "" renameAttributes: requiredConfigVars: - newName: SFDC Connection oldName: Salesforce Connection - newName: SFDC Record Type oldName: Salesforce Record Type flows: - newName: SFDC Data Import oldName: Salesforce Data Import ``` In the above example, we renamed the flow `Salesforce Data Import` to `SFDC Data Import`, and renamed the config variable `Salesforce Connection` to `SFDC Connection` and `Salesforce Record Type` to `SFDC Record Type`. With this block included, the Prismatic API will know to match the old flow and config variable names with the new flow and config variable names, and will not replace flows or config variables with new ones. #### Syncing components between regions[​](#syncing-components-between-regions "Direct link to Syncing components between regions") When you publish a component, it is published to a single region. To publish a built component to multiple regions, you must log in to each region and run `prism components:publish`. For local development, create a profile for each region and select it when you publish: Publish a component with regional profiles ```bash prism components:publish --profile us prism components:publish --profile eu ``` For CI/CD, store a [refresh token](https://prismatic.io/docs/cli.md#authenticating-with-the-cli-tool) for each region and provide the corresponding endpoint: Publish a component to multiple regions ```bash # Publish to EU PRISM_REFRESH_TOKEN=your-eu-refresh-token PRISMATIC_URL=https://app.eu-west-1.prismatic.io prism components:publish # Publish to GovCloud by exporting environment variables export PRISM_REFRESH_TOKEN=your-govcloud-refresh-token export PRISMATIC_URL=https://app.us-gov-west-1.prismatic.io prism components:publish ``` Note that component versions increment independently in each region. If you've published your custom component 50 times to US Commercial and 10 versions to the Sydney region, the same component code will be versioned as `v50` and `v10` respectively. #### Building multi-region deployment into a CI/CD pipeline[​](#building-multi-region-deployment-into-a-cicd-pipeline "Direct link to Building multi-region deployment into a CI/CD pipeline") To develop integrations and custom components in one tenant and automatically deploy them to another, you can store your custom component code and integration YAML in a version control system (like a git repository), and configure a CI/CD pipeline to automatically deploy new versions of components and integrations when code passes QA and is merged to your production branch. Since integrations depend on components, [build and publish](https://prismatic.io/docs/custom-connectors/publishing.md) your components first, then update your integrations. See [above](#syncing-components-between-regions) for information on publishing components across multiple regions. Once components are published, you can use the same `prism` authentication you used for components to [import integrations](#importing-an-integrations-yaml-definition). The `prism integrations:import` command will return an **integration ID** (SW50Z....). Using that integration ID, you can **publish** a new version of your integration: ```text prism integrations:publish SW50Z... --comment "My publication comment" ``` The `integrations:publish` command will return an **integration version ID** (SW50Z...). You can use that ID with the [`updateInstance`](https://prismatic.io/docs/api/schema/mutations.md#updateinstance)) GraphQL mutation to update deployed instances to the latest published version of your integration. --- ### Organization Team Members Organization users are team members employed by *your* company. They are responsible for building, deploying, and supporting integrations for your customers, and can be granted permissions based on their assigned role. #### Organization team member roles[​](#organization-team-member-roles "Direct link to Organization team member roles") [What are Team Member Roles?](https://player.vimeo.com/video/502283103) Organization users can be assigned a variety of *roles*: * An organization **owner** is a super-user who can manage all aspects of an organization (users, customers, integrations, etc.). * An organization **admin** has all the permissions of an owner, except the ability to make changes to the organization itself. This role is typically granted to user management teams (like your IT team). * An organization **integrator** can manage customers, integrations, and instances. Most developers, DevOps, and implementation technicians will have this role. * An organization **restricted integrator** can manage customers and their connections, integrations, and instances, but cannot view instance logs or step results. This role is suitable for team members who need to manage integrations but should not have access to sensitive runtime data or customer credentials. * An organization **guest** is a read-only user who can view information about customer instances but cannot modify anything. This is suitable for support technicians who need to view logs but should not modify instance configurations. * An organization **customer manager** has limited permissions and can manage customers but cannot view or manage their instances. This is appropriate for support users who should not have access to customer instance configurations. * An organization **third-party** user is used when you are integrating with a third-party app or service and would like to grant limited access to a user from that third-party to specific integrations, components, or customers. The **third-party** role is described in more detail [below](#third-party-users). | | Owner | Admin | Integrator | Restricted Integrator | Guest | Customer Manager | Third-Party | | ------------------------------- | ----- | ----- | ---------- | --------------------- | ----- | ---------------- | ----------- | | View Customers | x | x | x | x | x | x | ? | | View Customer Users | x | x | x | x | x | x | | | View Customer Instances | x | x | x | x | x | | | | View Alert Monitors | x | x | x | x | x | | | | Manage Customers | x | x | x | x | | x | | | Manage Customer Users | x | x | x | x | | x | | | Manage Components | x | x | x | x | | | ? | | Manage Instances | x | x | x | x | | | ? | | Manage Integrations | x | x | x | x | | | ? | | View instance logs \*\* | x | x | x | | | | | | View instance step results \*\* | x | x | x | | | | | | View scoped connections \*\* | x | x | x | | | | | | Manage Organization Users | x | x | | | | | | | Configure Embedded Themes | x | x | | | | | | | Manage Embedded Settings | x | x | | | | | | | Configure Log Streaming | x | x | | | | | | | Manage Organization | x | | | | | | | \*\* If you would like to prevent access to logs, step results, and customer-scoped connections for *all* users, reach out to Prismatic Support to enable the "Restrict Sensitive Data Access" organization setting. #### Managing organization users[​](#managing-organization-users "Direct link to Managing organization users") Only organization users with **admin** or **owner** roles can manage organization users. To manage organization users in the web app, click **Settings** on the left-hand sidebar, and select the **Team Members** tab. ##### Listing organization users[​](#listing-organization-users "Direct link to Listing organization users") * Web App * CLI * API Organization users are listed under the **Team Members** tab. You can filter users by typing a name into the search bar at the top of the page. You can also filter by email address by clicking the **Filter** link to the right of the search bar. You can list users via CLI using the `organization:users:list` subcommand. List all organization users ```bash prism organization:users:list Name Email ──────────────── ────────────────────────── James Patton james.patton@progix.io Samantha Johnson samantha.johnson@progix.io Ed Davis edward.davis@progix.io Kristin Henry kristin.henry@progix.io Alex Cooper alexander.cooper@progix.io ``` List users by querying the `users` field on [organization](https://prismatic.io/docs/api/schema/queries.md#organization)): ```graphql query { organization { users { nodes { id name email } } } } ``` ##### Adding organization users[​](#adding-organization-users "Direct link to Adding organization users") * Web App * CLI * API From the **Team Members** tab, click the **+ Add team member** button in the upper-right. Select an appropriate role for the new user (see above for permissions), and provide a name and email address for the user. ![Add team member in Prismatic app](/docs/assets/images/add-org-user-8a70521b28912be60dd17754442b2b9b.png) Management of organization users is performed through the `prism organization:users` subcommands. You can find the role IDs you are allowed to grant using `organization:users:roles`. Add 'Susan Smith' as a new 'integrator' ```bash ROLE_ID=$(prism organization:users:roles \ --columns id \ --no-header \ --filter 'name=Integrator') prism organization:users:create \ --email 'susan.smith@progix.io' \ --name 'Susan Smith' \ --role ${ROLE_ID} ``` To create an organization user, you will need the ID of the role you want to assign: ```graphql query listOrganizationRoles { authenticatedUser { grantableRoles(roleType: ORGANIZATION) { id name description } } } ``` Once you have the role ID, use the [createOrganizationUser](https://prismatic.io/docs/api/schema/mutations.md#createorganizationuser)) mutation to create a new organization user: ```graphql mutation { createOrganizationUser( input: { name: "Susan Smith" email: "susan.smith@progix.io" role: "Um9sZTpmYzE0ODIwNC1mZmQxLTQxMWUtYmRlYS1iNmFmYzM4YmViOGE=" } ) { user { id } } } ``` After creating the new user, they will receive a confirmation email with a link to set up their profile and password. ##### Changing an organization user's role, name, avatar, or phone number[​](#changing-an-organization-users-role-name-avatar-or-phone-number "Direct link to Changing an organization user's role, name, avatar, or phone number") From the **Team Members** tab, click the name of a user. You can change the user's role, name, phone number, or avatar under the **Details** tab. After making changes, click **Save** to apply your updates. ![Edit team member in Prismatic app](/docs/assets/images/edit-org-user-9e7cbce1c24ad498b4010140543cf403.png) ##### Deleting organization users[​](#deleting-organization-users "Direct link to Deleting organization users") * Web App * CLI * API From the **Team Members** tab, click the name of a user. Select the **Details** tab within that user's page and click the **Delete user** button at the bottom of the page. Enter the **Confirmation text** and click **Remove user** to confirm removal. Delete user 'Susan Smith' ```bash USER_ID=$(prism organization:users:list \ --columns id \ --no-header \ --filter 'email=susan.smith@progix.io') prism organization:users:delete ${USER_ID} ``` To delete an organization team member, use the [deleteUser](https://prismatic.io/docs/api/schema/mutations.md#deleteuser)) mutation: ```graphql mutation { deleteUser( input: { id: "VXNlcjpiMmNmNmY5MS1iMjljLTRlODUtOTc1My04NWE0NGM2ZDE2YzE=" } ) { user { id } } } ``` #### Third-party users[​](#third-party-users "Direct link to Third-party users") It is often necessary to involve people from third-party vendors as you build, test, and debug your integrations. Granting third-party vendors the ability to view and test specific integrations and components accelerates development and ensures all stakeholders remain aligned regarding development progress and data flow between systems. ##### Creating third-party users[​](#creating-third-party-users "Direct link to Creating third-party users") Organization users with **admin** or **owner** permissions can create new organization-level users with the **third-party** role. This role is highly restricted - by default, **third-party** users can only edit their own profile information and view built-in components. They cannot view information about your custom components, integrations, or customers. Once created, you can grant additional permissions to allow interaction with specific resources. You can create a third-party user as you would any other organization-level user: click **Settings** on the left-hand sidebar, then click **+ Add team member**. Assign the new user the **Third-Party** role. ![Add third-party team member in Prismatic app](/docs/assets/images/add-third-party-user-a1f549538d6cd91854c3814510fdc93c.png) ##### Granular access for third-party users[​](#granular-access-for-third-party-users "Direct link to Granular access for third-party users") To grant access to specific resources, like integrations, custom components or customers, click **Settings** on the left-hand sidebar and then **Team Members**. Select the third-party user you would like to grant access to, and click into the **Granular Access** tab. ![Set granular access for third-party team member in Prismatic app](/docs/assets/images/granular-access-tab-5a74b923ebaa17868bc3508076651dec.png) From here, you can grant the user access to specific **integrations**, **components**, or **customers** by clicking the **+ Add permission** button on the top-right. ![Add specific permissions to third-party team member in Prismatic app](/docs/assets/images/third-party-add-permission-dialogue-e90e05e3f0b51fe03ac5cd6c4cb64dae.png) ##### Integration access[​](#integration-access "Direct link to Integration access") The most common use case for third-party users is to allow a third-party vendor to view, and possibly edit and test an integration. That way, they can test invoking an integration in Prismatic from their third-party service and can verify that the data the integration receives is in the format you agreed upon. Giving integration access to a third-party vendor also allows you to see what sort of attempts are being made on their end to make sure the integration works. You can view logs of each test a third-party vendor performs to give you a sense of how their side of the integration development is progressing, and if and when you jump on calls with your mutual customer and the third party, you can test and debug issues quickly (rather than relying on email chains that drag on for weeks). To grant a third-party vendor access to a specific integration, select **Integration** from the **+ Add permission** dialog, then search for and select the integration you want to give permissions for. On the next screen select the types of permissions you would like to grant for that integration. If you would like the third-party user to be able to see the integration in their Integrations list view, select **View Integration**. If you would like the third-party user to be able to edit the integration, select **Edit Integration**. The third party user will then be able to see the integration that they've been granted permission to see, but all other integrations will remain hidden from them. This is handy if you are integrating with multiple competing vendors - the third-party vendors cannot see one another's integrations (or even know they exist). You must also grant access to relevant custom components If you give a third-party user access to an integration that uses custom components, you must also grant them access to those custom components. ##### Component access[​](#component-access "Direct link to Component access") Similar to integrations, you can grant third-party users access to specific custom components. By default, third-party users have access to Prismatic built-in public components, but you may not want third-party vendors to see all of the custom components you've published (especially if you integrate with several competing vendors). To grant a third-party user access to a custom component, select **Component** after opening the **+ Add permission** dialog, and then search for and select the component you would like to grant access to. You can grant a variety of component-related permissions to a third-party user. If they are assisting in the development of the custom component, they will need the **Edit Component** permission. Otherwise, to use the component in an integration they will just need the **View Component** permission. Custom components that are not granted to a user are not visible. This is once again handy if you are integrating with several competing companies, or your own competitors - their users will not be able to see what other custom components you've published. ##### Customer access[​](#customer-access "Direct link to Customer access") You can grant a third-party user access to a specific customer. This is handy if you and another vendor share a customer in common, and are working on an integration together for that customer. To grant permissions to a specific customer, select **Customer** after clicking **+ PERMISSION** and then select the customer you'd like to grant permissions for. Next, select the permissions on this customer you would like to grant. There are a variety of options, each with a description below them. You can elect to let the third-party user view or manage the customer, the customer's users, and the instances deployed to the customer. read-only access grants viewing of customer credentials Note that if you grant the **View Customer** permission on a customer to a third-party user, that user can view the customer's saved credentials. Permissions are scoped to a specific customer. That way, if you are developing an integration with a competing software vendor they will not be able to view information about the other customers in your system. --- ### Single Sign-On (SSO) Feature Availability The single sign-on feature is available to customers on specific pricing plans. Refer to your pricing plan or contract, or contact the Prismatic support team for more information. [Single Sign-On (SSO)](https://player.vimeo.com/video/1143921974?h=fbf2e1ec7c) You can configure authentication for your team members through your existing identity provider, like [Okta](https://www.okta.com/), [Google Workspaces](https://workspace.google.com/), or [Azure Active Directory](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id). If your team members authenticate to other applications through Active Directory, Active Directory Federation Services (ADFS), or Lightweight Directory Access Protocol (LDAP), we can configure your organization to authenticate to Prismatic using the same method. When you enable **single sign-on** (SSO), team members will be redirected to your identity provider from the login screen if they enter an email address matching your domain. Otherwise, they will be prompted for a password using standard authentication. ![Diagram showing paths and logic for SSO ](/docs/assets/images/sso-aa8108d41906e6da1929f58ad4d8ef0f.png) Once they are logged in through your identity provider, team members will be redirected back to Prismatic where they can [switch between tenants](https://prismatic.io/docs/configure-prismatic.md#swapping-between-tenants) if necessary. If you are interested in implementing Single Sign-On for Prismatic, please contact our [support](mailto:support@prismatic.io) team to configure and enable SSO for your organization. --- ### User Settings #### Managing your own user profile[​](#managing-your-own-user-profile "Direct link to Managing your own user profile") You can update your name, password, phone number, avatar image, and light/dark mode preferences. Begin by clicking your user avatar at the top-right of the screen, then select the **User settings** link. All user profile preferences are available in the **Password** and **Details** tabs. ##### Updating your password[​](#updating-your-password "Direct link to Updating your password") After clicking your avatar at the top-right of the screen and selecting **User settings**, open the **Password** tab. Enter your current password, then select a new password. Your password must contain: * At least 8 characters * At least one uppercase letter * At least one lowercase letter * At least one number ![Change own password in Prismatic app](/docs/assets/images/change-own-password-1256669a0baed87037f2d7b08aa47be3.png) **Note**: If your organization uses [single sign-on (SSO)](https://prismatic.io/docs/configure-prismatic/single-sign-on.md) to authenticate to Prismatic, you will not be able to change your password through Prismatic. ##### Resetting a forgotten password[​](#resetting-a-forgotten-password "Direct link to Resetting a forgotten password") If you have forgotten your password, navigate to . If you are not logged out, do so by clicking your avatar at the top-right of the screen and selecting **Logout**. Enter your email address, then click **Continue**. Click the **Forgot password?** button and then click **Continue** again. You will receive an email with a password reset link where you can create a new password. ##### Updating your name, avatar, or phone number[​](#updating-your-name-avatar-or-phone-number "Direct link to Updating your name, avatar, or phone number") After clicking your avatar at the top-right of the screen, select the **Details** tab. You can modify your name or phone number from this screen. If you provide a phone number, it can be used by your team members for monitoring and alerting purposes. If you modify your avatar image, Prismatic will resize and crop it to 500 x 500 pixels. Transparent PNG avatar images typically yield the best results. ##### Setting light or dark mode[​](#setting-light-or-dark-mode "Direct link to Setting light or dark mode") By default, the web application will present light or dark mode to match your operating system settings. To override the light/dark mode setting, click your avatar at the top-right of the screen, and click the **Light** or **Dark** button as needed. ![Set light/dark mode in Prismatic app](/docs/assets/images/dark-mode-f2c7d33c6c2aca4c75bbf2e7db1cdeaf.png) Light/dark mode settings are associated with your user account, so if you set a preference on one computer, that preference will be retained when you log in from another computer. --- ### What is a connector? A connector is a pre-built set of triggers, actions and connection logic that allows your integrations to interact with a specific third-party application or service. Connectors abstract away the complexities of interacting with various APIs, handling authentication, providing configuration options, and dealing with data transformations. For example, the [Salesforce](https://prismatic.io/docs/components/salesforce.md) connector manages OAuth 2.0 authentication and provides actions to create, update, and query Salesforce records. This allows you to easily integrate Salesforce into your workflows without needing to write custom code for its API. Prismatic provides hundreds of [built-in connectors](https://prismatic.io/docs/components.md) for popular applications and services, enabling you to quickly build integrations without needing to write custom code for each API. If you need to connect to an application or service that doesn't have a built-in connector, you can create a [custom connector](https://prismatic.io/docs/custom-connectors.md) using our TypeScript SDK. #### FAQ[​](#faq "Direct link to FAQ") ##### What is the difference between a Prismatic connector and a component?[​](#what-is-the-difference-between-a-prismatic-connector-and-a-component "Direct link to What is the difference between a Prismatic connector and a component?") We use the terms "connector" and "component" interchangeably. A connector is a type of component specifically designed to interact with third-party applications or services. All connectors are components, but not all components are connectors. Components can also include data transformation functions, control flow elements, and other building blocks used to create integrations. ##### What types of third-party services can I build a connector for?[​](#what-types-of-third-party-services-can-i-build-a-connector-for "Direct link to What types of third-party services can I build a connector for?") If a service has an API that can be accessed programmatically, you can build a connector for it. This includes RESTful APIs, SOAP APIs, GraphQL APIs, databases, message queues, and more. Basically, if you can write TypeScript code to interact with a service, you can build a connector for it. ##### Can I build custom connectors for the embedded workflow builder?[​](#can-i-build-custom-connectors-for-the-embedded-workflow-builder "Direct link to Can I build custom connectors for the embedded workflow builder?") Yes! Custom connectors you create using our TypeScript SDK can be used in both standard integrations and in workflows built by your customers with the [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md). --- ### Customers Overview Customers in Prismatic represent *your* organization's customers. Your organization creates customers and [integrations](https://prismatic.io/docs/integrations.md) in Prismatic. Your customers can either enable instances of your integrations themselves through your [embedded marketplace](https://prismatic.io/docs/embed/marketplace.md), or your team members can deploy [instances](https://prismatic.io/docs/instances/deploying.md) to them. You can create [users](https://prismatic.io/docs/customers/customer-users.md) for your customers and assign them [roles](https://prismatic.io/docs/customers/customer-users.md#customer-user-roles) that determine how they manage and gain insight into their deployed instances. Like all Prismatic resources, you can manage customers through the [Prism CLI](https://prismatic.io/docs/cli.md) or through the web app by clicking the **Customers** link on the left-hand sidebar. --- ### Customer Users #### Customer users[​](#customer-users "Direct link to Customer users") **Customer users** are users associated with your customers. Their permissions are limited in scope to their specific customer account. They can view and manage users and instances of integrations that have been deployed to their customer, but they cannot access other customers' resources. You can grant customer users permission to update instance configurations, enabling them to modify config variables and credentials tied to their instances without requiring direct support from your team. #### Customer user roles[​](#customer-user-roles "Direct link to Customer user roles") You can assign customer users one of two roles: * A customer **admin** can manage their users and the instances deployed to them. Assign this role to users who should be able to deploy, modify, enable, or disable instances of integrations. * A customer **member** has read-only access to view information about users and instances deployed to them. This role is suitable for users who need to view logs but should not have permission to modify instances. * **A customer created through an [embedded marketplace](https://prismatic.io/docs/embed/marketplace.md)** automatically receives the **Marketplace** role, which allows them to deploy and manage their integrations. A **Marketplace User** can only configure their own [user-level configuration](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md) for an instance. Note: **marketplace** users cannot log in to Prismatic directly; they can only access it through the embedded integration marketplace. | | Admin | Member | Marketplace Admin | Marketplace User | | --------------------- | ----- | ------ | ----------------- | ---------------- | | View Customer Users | x | x | | | | Manage Customer Users | x | | | | | View Instances | x | x | x | | | View Instance Logs | x | x | x | | | Configure Instances | x | | x | | | Test Instances | x | | x | | | Configure ULC | | | x | x | #### Managing customer users[​](#managing-customer-users "Direct link to Managing customer users") You Do Not Need to Create Embedded Customer Users Note that it's uncommon to create customer users manually, and you should only do so if you intend for your customer users to log in to Prismatic's web app. Most of the time, your users will interact with Prismatic through an [embedded marketplace](https://prismatic.io/docs/embed/marketplace.md). You do not need to create embedded users manually - a user will be automatically created when they authenticate within your app. Only customer users with the **admin** role or organization users with **integrator**, **admin**, or **owner** roles can manage customer users. To manage customers users in the web app, click **Customers** on the left-hand sidebar, select a customer from that list and open the customer's **Users** tab. If you are a customer user with the **admin** role, click the **Team Members** link on the left-hand sidebar to manage your users. ##### Listing customer users[​](#listing-customer-users "Direct link to Listing customer users") * Web App * CLI * API The main customer's **Users** tab lists users for a specific customer. Filter users by typing a name into the search bar at the top of the page. You can also filter by email address by clicking the **Filter** link to the right of the search bar. To list users with the Prismatic CLI tool, use the `prism customers:users:list` subcommand. List all users of customer 'FTL Rockets' ```bash # Get your customer's ID CUSTOMER_ID=$(prism customers:list \ --columns=id \ --filter 'Name=^FTL Rockets$' \ --no-header) # List that customer's users prism customers:users:list --customer ${CUSTOMER_ID} Name Email ─────────── ─────────────────────────── Jim Simms james.simms@ftl-rockets.com Lisa Nguyen lisa.nguyen@ftl-rockets.com ``` To list all customers query [customers](https://prismatic.io/docs/api/schema/queries.md#customers)), or query [customer](https://prismatic.io/docs/api/schema/queries.md#customer)) for a specific customer's users: ```graphql query { customers { nodes { name users { nodes { name email } } } } } ``` ##### Adding a customer user[​](#adding-a-customer-user "Direct link to Adding a customer user") * Web App * CLI * API From the customer's **Users** tab, click the **+ User** button in the upper-right. Select an appropriate role for the new user (see above for permissions) and provide their name and email address. ![Add customer user in Prismatic app](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAIGBAMAAAB6zOxoAAAAIVBMVEXQ0dHKy8vl6ezFxca/v8D3+Pn///9sbGyZmpw6OTkvz5aiuhCYAAAajUlEQVR4AeyVzVPbyNbGUaI4zM4qaZKaHePCBpZ65cxUdlNjD4FdyhUJsiOeUcPyvb5JjHe5SQB7N8OFMv3f3tPdalsCfRxPyO1c+/wQ8of64/Tjp59eI/4WdQtYq8u/O1jwV3fqjmPBzVIX3ADxxMofUT0SjVSz3FnVvFbZ5LpVdvrbV8ksySRJf3mDxvlTFbIxH85R9eZNKtcs5hKN6unqxKvqeUuztARJcy3GfNS6FlKPl0yelUzqrSazMtPnyuWsOVZRd12+pce6VVEVD2ZDyrrFRGrMVOmJjPXZ7wL/6oLPjm5ST1qnSBYhGko/yJUkuqWVFM/VkHU9q7OWbSa+ShpZxZcYQXST06SLh7tqMvcW/It3ORWljZ6ScmYsOfy8aOlSJ2dBd9EPsj3UW+d2N2u+iGQpmTGSV9l7NpbSPKmrCksLat36yZzcFVh3K8qQCHYL8aVYrS5Miy+H0x4qp54SLRH3bh8raabVrxcqMLOjGtNZCDWN/o3rFfLK67ZkyZ5XRs3sQiFV3kD1u0v2ncXJ9pG+wazYWlCiOyo4ODsWN5JqiQzNkOyaqkW7LlwCx5/hlvUQD0XrwPVLVHblnxisavo7iF7zN05xMe68u6zGx/7q8rfNnKxWIq4vFxaIud2UI3wxF3wL9SiFgnm5fgCIDtmFCZHkQ9UaKoVW8LWfXpBYomghh5bjBnNZ/YxKcqC7iEJSFcEALvQT06S6O5mC9U8t5hLFlEmc6FVPqTXTSgwZ+EGqkOSXkAWJVQkp4RIPxQdZnHwU6PWoLjOV5NeiS7IaaDorOkimUGPJu+oza570ScqRw4g1io7yRw2S6kQV8vn8BRSRv4WbKBrIOlT3pKvsq1ab3itpErlmuQWRJbXydS26Km1YWdwcfzbrfH7ZJvM7y26ubqJH0CVqfdMju/Ph1c8jB5Eyylsys5uZP0PSKBFhpqP+LaRt9TRqSmkAVxeTQ6LXPLdkXMnObSDodLrtoBt01Id28l1HPoBHnS7cu+pduw138ZIg2+k3Etkemole4lnQ1aMmTWRzOUQ3GUh8pdp2dCs1uppZXaKLGj19pUoRPdvBrA54IucQL6qsdNt2MK84g/4VfGdmLTBWvbHReDC/Mh+Krwf31g491EZqRGyX25NgO6tX+GtsWE597qxHYRTG84ulP+RdsgGraKSv6nYMN5SYlcWzERlq9vQkqbLlOOWXeoW/MNpwrPrMWCFRSt9RqVUHtf7PdDHfPFaiFqQ9iVWFpSK+Xiexqnno1EXGW47jklhVPHSEtcRZ6PxqupZvHhBLhBbsQp+cVYV2FmxDclYVIJbILMdyfBKrioc+JLx0VkBiVQFigbPELqRtWMnDQG7DOjkLgev7kPBiF5JYlQhnUWYhcQMILXIWjoeBC5kF7nJJrEo86aw6OQuDLZ1lOS45qxrlLIechcEO4DhcW6PMwuDBNnRoG+KYB3ybxKoCnOULZ1FmIbCls8Q2JGdV4smAJ2ehUJlFYqEAsSizsOjM8uk0rAYyC5zlQMCTsyqZZxY5qxJbnobgLMqsamAbusJZrkvOqsQjZ+FR25CchQIC3idnIVHb0HJ8clY1NjkLj0eZhQfEImdhgW1ImYWFnLUAlFkLYEuxyFkowFkuZRYSj5yFx6bMwuMFPp2GWPRpWJxZcfLK4vl9RbGxzuoNz+Del/dVxavKrJPhuXzt8Usw1RG/Nl3x16OnXpqFDUCsUmdFO/ydfNPnAynWO9NL+mpE28/k/bfCFrANSzMrmvC/RExFUiyWFYvJFirFliDLNj/BrTeuFTaodNaIT0EHttGHbcgaSizh1ybo9yCCBi8fxfBF/5HppX458d4zMNaHuEys0sw64iPeCqMtfsoH7DHcQaxochUfTFtHI34aRocjfsZ+uJlcLoG1Xn0K+yXGgm0oxCp21gkf810ILOBS3iHgo9FN/JrXvoNPZ+GE82m0z2X8/8+z91OZsYSz3JLMAkv9yN+DZFcTPpD3xFkg1ujmR/7XH/zmBT9/zqeDZRBr83OZsZJtWOis6Pk0Hv0J0vwEabXDa78rsW7iA94a/cX2Phzz3T5//5w/M73OeyHeKzNWsg0LMysaXbHJDduZtvr83XfTUAW8ctaEv22xHf55zK9BUtPrvBd6409lj73ALzsN+/ymO5lGIFOfX2uxhLNArANIKxlc/PKHqell3gtwFO6VbRF9GhZk1iuhBX8CMvWyzjrgNbY+4oMdPhwOPz5fDrHgKHxVZi273FnHUqzzHS62Idy1s9hr3uzWXo6ujkG0mO0vhVhgrDgss5ZXmlnRd/yXNZtfHvPBFh8c849bSqxpY8If8Es2ujrh/2hc1JbDWT1xFG5+KhWrxFkgC2Px6OqNTKbfxV2IJYOqNeFDfhmPhPOWQyxhLDgQi60F27Ass/gN9IeE3+c3YKQJ3K/h2xMOOtUORcBHW3BvLcc23KzJe7FY5c4KpYKbnbC/98j+Pjzaq3nfwxfR+r9edpqRPf4+DmP7cy3e/MX0Qu+PuPhReWaFjAlt4AYpHsMbcZdfy+/kB3EXn1YAW4pV6CwiDTjLLcksIo1HzsJjl2cWkcYLfJ+chUSfhpRZCGxyFh6PMgsPiEXOwgLbkDILCzlrASizFsCWYpGzUICzXMosJB45C49NmYXHC3w6DbHo05AyC4FNzsLjUWbhAbHIWVhgG1JmYSFnLQBl1gLYUixyFgpwlkuZhcQjZ+GxKbPweIFPpyEWfRpSZiGwyVl4PMosPCAWOQsLbEPKLCwVzopYrF5j04V+C1Rk1stGU8gUbX80Xei3gC3FKnJWtM/5mXizc0nWks5ySzJr/3R0E7MoBrFiZrpW43gVzjo/mbLHp62dy/7Fh5U3l12eWUKsN3w0eH15wHnNdLGm8QK/5DSMYBv++/jq5Ob19f75/rnpYk2jT8OizOJ8d+f6zfT15WQ4em+6WNPYFc56NzmXYl2PTsdnpos1jVeVWTt/Hl8dwjacDLZbpos1DYhV6qz3J9MjzlXA75ou1jSwDUsyK1r/rT9urZ+2Dj/GF59M12qccmeFURyykLFYvDLTtRqnIrOINLYUq9BZRBpwllucWUQGj5yFx6bMwuMFvk/OQqJPQ8osBDY5C49HmYUHxCJnYaHMWgBy1gJUZlaj11zFK0+KqMJZvcaqkuusthCrMLMabEWJm/nb0C1xVst00cbo5YnVUc7Kz6xeZLpmY/Tzt2GJs0is284KijOLxMqK1Q18n5yFFCtxFmUWSixIeHIWUqxuELiUWXfp5YjVJmflU+QsnzILJ5ZwlkvOwomlnUWZhRCr3e0EATkLJ5bXbcvTkDILIZZwFp2GSLGEswLKrLv0vjSzNlZIuyJn+dWZ9epc3H9OPm7XTC/FjFhIZ20N0mIdtkwvxYxY2lkVmfXilLH40dOo+bLVf8TiqNlrml6NAbGQznr7ImKPhxfR6fDtxbC1Vbu4OF26/Irb3W73WZlYuMw6GmzV4tPeRTRsTs62zreeXPz29Inpxd072+PxuFUmVrsbIJx1cn5y/mYAmfWWPa6dvAdnsYNd02u7d+Lx+FdWJhYusw4+rA/+eKfEav0BYo3Z8fKJxTY/t0rFwjnr5+HwLYj1VIolnDVeRmex/txYeWJFqMyKx4xd9P+fvVhyZ2WE+7vO6v+TQVZdjIetxFlPljKzKsXyQKzKzDo6g4yvvRqvRx/YduvN7mGrDZ9NL+fr0stzVqeDyCxFZHoB/03KnFWaWatIbmZ1umhnrRR/O7NWkfzTkJyFFosyawGxKLMWEIsyq4AeZRYeyqwvFIsyawGxKLMWEKvd7QQuOQsnFmVWAbmnYadDmZVHjrMi4SynMLPClumajVHkLL/QWWHDdM2miJsFmeUWZlbYa6wqYWHAFzor7K0oeVqEHuzCkswKo5CYUeUsIoV2VkFmEWnAWa5DzsLhBeWZRaQAschZWLw2ZRYacJZDzkKitiFlFgoV8OQsFLbchpRZKMhZC6ACnjILhQp4chYKtQ0ps1BQZi2A16bMQkPOWgAV8JRZKLxuEJCzkHgddRpSZiGAgCdnYdHOosxCQM5aAHBWQJmFRJyGPjkLh8gsnzILR4Wzojhksekavxm0s/IzK3p6Fr3YNV3jl/Af9sqguWksa8PVlGKz/PisSahZpU0kh92YgNNLykXsZAfqSAo7R/G99tIImyvv0iElOTvoTpXj7Sz5lXNkx7RDS1dHELiAzkOCY8lWSW+95zm3+iNvlnNwyS9ChzOHcwYVc7jq+1VKhrMOZuZF6D0znz96bOqmc/i00DOZ1axZeBEezN73pjMxe+eNZ6HqG1aJ3Fn2gXh3EQpt2ptuTHevelcbfxa5WlnNOp1ehFuT6dFf/avulXvVvix0WNCsexJnhRcwiOI6rIezWbHDeryzc0/SrDcPZ2dT7zqsl395a6pvWCXLZqU56013djaLpu4lhDXtzyKfmpXerNAehwdTsf2+f3lyxS6me6pvWCU6ZCVxFsCYbcKLE/8yo8jFisOqpDeLuAGMocxZxCrQrDo1C0m2s4iPkLNyoF03i5yFgJyVAw2yImchoWblQCdn4aFm5WDZrDRnOQX9lxZWRdYsq1pMjMSwHsdjmOosq2obVhF/EtPKcFaVF5WUsGTOMlXfszKs5LAkzrIc1fesDC8pLKmzKKwczqKwcjirwGGRs3JAzvrSsMhZucIiZ+UIi5yFDouclSMsclausMhZSVjULDxJzdIgK3IWMixqVo6wNJyzklP7+yj8wZqqn++rh7UQfFazuiJISOvvo96AmWyo+vm+RViVbGe1m+XRPy7H4Gh4/eeTjl+AsDRUswLoTsfxSnzrCefaGq++eDQ/2hl0njeZZjJja2gMt0qqn/Arh6WjnBU5zKiVuqOuEOZREDjtSJjxUW64kZgI3/PboiFEoPoJbxErMaw6YhvWghKPw3JDNyybtfV2o9aKj5rcDfuXLIDe+Uw4bVP1I94esm0odxZrCzMOq+fD3tvcaLWdHtjKg6Pu+onPo3lYQ17+ycPCNQtaNIrD8qIhPwoiCKsfq52VR27jeFSgsBDOYmfcexWHxbzdsG32Fs3y4OjAbRSoWTCG2c1i8d5zW/03tVL/TeSUF82Coyc+NKs4YcEYIpxVC6KwH4hRP4ga5UCE0dxZZTjqNrrzsJhoFCEsRLM4++2Us/1mg98/5d75ZukB75QWRw9Nb4//wpr8bukR33JUP+LXDUvDOOsmTPVzfBOsVMFnb8PCkTKGlWxnFZD0MaRmocLS8zurGCSHVa9Ts5BhLbchOQsRFjUrV1jkrESs5DGkZiWRMobkLHxY1Cx0WBo5Cx/WQvDULGxYFXIWMixN3ixT9T0rw0odwzRn2Ybqe1ZGNTGsumQb2pZhF5PEB19uw2Rn2Xa1oCSWJKNZxCoZziJWgTGkZmGBMZQ6i1hBp2bh0chZeBaCp2ahgLAq5CwkGjULj07OwqPv1GkbYlluQ3IWAmpWDshZOYAxpGZhgTEkZ2HRqVl4NHIWnoXgqVkoIKwKOQuJRs3Co5Oz8Og7ddqGWJbbkJyFgJqVA3JWDmAM8c1iDlN9v0qBMUQ7y1rb2lN9v0rRpc1yDsYz/2Obeu9e/lnoamlyZx1Mhc8549x2OOuePfQLHdZC8OnNCjl7dvds+5QdTkq/Nx8Wu1kQVkXirAO/6oxn0/HMGM/e9d6/LHaztIxmzWbh+HR8Nm4G/SsIq/DNkjormKyPzXHjItyOpoVvlr5Tl27DkNvj0rh1cTYW08I3a7kN05wV2ouwzqddalZWs2Yzfx7W6WxMzZI7y9kVwo/MaL3dagfB0cD1Vd+vUmAMJc2yHc4Zt7nt2MzhDnMKXax4DGXOIlbR5c0iVtGkziJusBA8NQsFhFUhZyHRqFl4dHIWHn2nTtsQy3IbkrMQULNyQM7KAYyhvFlOQf8lAWModVa1oBjJYUmbZRmqq6+IxLQ0ubMMzrnDWfxfsV55NSGNheDTmmWZvKh4yWFV0p3lOarv+bsKS5M3i8L65ximOYvC+iSsep2ahQxruQ3r1KzssKhZucIiZyViJY8hNSuJlDEkZ+HDomahw9LIWfiwFoKnZmHDqpCzkGFp1Cx8WDo5KxkrMaz6F2xDA36fpZ04vXHkqPTJzahOIwPZNvwsZ3kDtsaD+K9OFOytJgEn2PDGZ93W4nXz+n2kOo3PCOsLm+UN+GT+6M0H0cpnmdHxPwmr1roZ0o/YLJyzvEmJ7fEHvHNuwrutt87Jr2+t/RJ7sh2UJttN3h1x3g/51h/89wd/ePGJu0FpeHeP//9jvg3juPUWmnXcYPvrx6LJfmtCnJvxRVUnkjMsGENEs8qTIXSozXcnPlxGRKEbRJPJa29wN2hFgXBq5tE5G1yfiCaDjn8/WBdBwEXQCaKQxSda7fWjIOiLs34gIPIovqjqRHKGBWOIcFbE9z2ftb1X8dD1Rx2/Fx5e8sAbQISRWSvt88lG6bw/Oh65Yf+SwQkYw4C3TeG44fGgP/IG7s4ruEytBCGV3AZcEs7vmqojyRsWplntJvd83u6+/j8wUycwYag6Po/mYU3gzfnJm15p/wRO1NaPfT6ZhzXkZTPgGyYf9lr8jhu1WPDLRivi7ml81Qg+17v22PeIbBvKnXUY+XGzukEQwJH7Yn0eVjsOi8VhDfqha+7DCSjN8TzFj2G1HR7ETXJFPI0RhOVFQ7hIBJ7rhaojScdKFXxWswwvmjdrxOLMnSO/d6NZjUGvVTPP4cRoHtZqs8qLZhnu6Ws+hK9HvMo2oFERXMD9wZoFYVWynQUFsYdMeEOuMQOcNZiHtdKsXqttnvbDY99tnCzCGlyH5YZdP/6G24rMyDw0IxhMN7Tmzmr/YM7SUM0qiyGPhOC74nUHlp4Ir531iokQ8mhNuiKahB04UYvDgsox0VqEBQcbLIq/0R8dicApD/pCmG0HRC9eqU4kZ1g6ylmebvJOc5N7dZOt8edPeSd+rcLvVqkKb8rOL/Z/+PyEszxh3uHPnSrnL5pwogkfste4tgaX4dq/+CavcqZ/x8VKCauO2IZZ9M9UP9s3CWu5DeXOyiSaTL7nmtxSWLfTLO49e6r66b5JWBhnFRAreQxvo1k/HyljeBvO+vlIGUNqFjYsjZyFD2sheGoWNqwKOQsZlkbNwoelk7OSsRLDqtM2TEK2DclZiLCoWbnCkjnLM1XfszKs5DGUNMs2VN+zMqrJYyhxlm0ZtmEX8adqJI+hrFl2taAkZfVxGyY7y7aZTXxkIfj0ZhErQFgVibOIVTRqFh49w1nECvpOvU7NQrLchuQsBNSsHJCzcgBjSM3CAmNIzsKiU7PwaOQsPAvBU7NQQFgVchYSjZqFRydn4dF36rQNsSy3ITkLATUrB+SsHMAYUrOwwBiSs7Do1Cw8GjkLz0Lw1CwUEFaFnIVEo2bh0clZePSdOm1DLMttSM5CQM3KATkrBzCG1CwsMIbkLCw6NQuPVmBnOTwdlvSFheAlzTIMy8r47wfF+fVeKpU7LDmsisRZVhWBofqxP48XH2SUEr6hyZuFyeoHTcv5tzSs/yZUS5c76ycOy5Zm9eFDwjf0nXq9mM3y8oe13IbJzrJ+4rAsahaez2iW3Fnfuln/a698XttGwjAsO95mycmTUWHxSTuxsyGnnYwpS0+7ceNufSqiDaWnEloffFoKi+xb6Q8Sn/dgHZNs7YP+yv1mxrLNVtaMVIgs53sjlSAqUB6e9527vPxcNfx+s1jRf3ie5DKL42ZlqCFuVtbTEDfLeuBxs2xhUdysTDXMZ1atWmZYyWb1zDU0blbzclj9Btaz0zLDWjHr5lj+O2vAb4N0WNzmNHw6HP+1HlbZN2v6Nob178DmNDRsVvDwRci8PxhzDvZ1/+DeGrMe+Xaw7Mw6CH12xS5H771gNGRHcDdHw1a5Ya2YxbpRdNs/bER9f/D9m9UMGfMPwh/D50Fn9MP4w/js2Yfxl3LDWjHr7WwvetB70JgeT001tDBLwoIK1kIveDk+DVpHp6OT3b+3ZbOuB7evowuoYbdnHHiLzdKwLoPQG7YAFmuxIBi93xazpsfXg+gNwHpkPA1tzPp54rPhq9ADsxQsjwWdzn65YS3N6vb7FwpWN/pqdRqmbxYbnx6Fr/55ocw6G588/TI+az4sN6ylWYfyakXdxmzv1mLgTWax3cnoYzMIJt4EYO0Gk+ovk9HZtmzWmyiaPZ75543ri5YJFjVvFmNXnxn789MV+8x+q3pXH+HBJ/bT/naYtRdFN72o8jWKfu89NtfQaFZsEPNh3JkPEOFqxY9LCWtpVnISXnHtNis9pYTl54HFub1Z61L0H54nOcyKT0OOZt2RWaWEhZuVIX6uGuJmZaghblaGGuJmZT0NcbOsB/4+mmWC9S4RFl2/WdsM6zyd1U0CrFq6WdDDahomr7SH4cvz16mwpgmvuKmbZadWKcWC9NLESnrBFTztNNzqvOunJOmF+DRcs1mY1dxnszLHtFmYlUAN0SzbQA1xs2zjoln2qeFm2UcPPJplFYBFcbMsU0Oz7OPiZtnHFRxPQ9vEpyFulkXQrAzRm1XHzbIJnoYZomtYAVpoljG6hhU0yyY1wQmaZZm4hmiWRdyFWQjLmIVZCMscvVl1QgRuljGxWQQ3yxw3NgtraE5NCIKbZRlXUDTLNnrgcbOs4kIL605F0kJYplBVQ2DFEZYxO8osB82yCV1sFkVYpuxwZRYchxxhmbKzHPiTor9l4yPNqiizyK9Ff8vGh3JSkQNfgdE6LPpjNjzPoYXE0WbR/aK/ZrPjH1CYLG0WhYORCpm26MCFUQESCoogtMM1LKWWgOdPMIkBbILrfXfq8jgErzqiU/RnbWTabXBr3kJNi2paiOv/aasqKrGkWXK0COHwqAMQZVeftPGCSwZ4zFkRJZZWS9ESMTG82kKh0kwotJA4mlYFJp5C5rQEBtJe/Ka8Io5qYVxEwnnRH7iZoWSxWIqVpEWRVkI4cCHxUegsighPZTe5oEV/4GYEMAAOQAJe1ZdmQR2JKqKsItz4I38kCbXsZLnu+jx06vX5Y0lT/a/7fpMYFIg19+o/wAylQ3syf6gAAAAASUVORK5CYII=) To add a new customer, use the `customers:users:create` subcommand. You can use `customers:list` to get a customer's ID, and `customers:users:roles` to get a desired role ID. Add a new 'Admin' user for customer 'FTL Rockets' ```bash # Get your customer's ID CUSTOMER_ID=$(prism customers:list \ --columns=id \ --filter 'Name=^FTL Rockets$' \ --no-header) # Get the ID of the "Admin" role ROLE_ID=$(prism customers:users:roles \ --columns id \ --no-header \ --filter 'name=Admin') # Create a user "Lisa Nguyen" that has the "Admin" role prism customers:users:create \ --email 'lisa.nguyen@ftl-rockets.com' \ --name 'Lisa Nguyen' \ --customer ${CUSTOMER_ID} \ --role ${ROLE_ID} ``` To create a new customer user, use the [createCustomerUser](https://prismatic.io/docs/api/schema/mutations.md#createcustomeruser)) mutation. You will need to know the ID of the role you want to assign the user, as well as the ID of the customer: ```graphql mutation { createCustomerUser( input: { name: "Lisa Nguyen" email: "lisa.nguyen@ftl-rockets.com" role: "Um9sZTplMTRlZjUzNC0yOTZiLTQ4MjAtYjhmNS1jZjQ1Zjg4N2I0YjM=" customer: "Q3VzdG9tZXI6ZDIyOGUwNjItYzc0NC00NDFkLWE0MDMtNjQ1NTU4MDQ1OTZk" } ) { user { id } } } ``` ##### Changing a customer user's role, name, avatar picture or phone number[​](#changing-a-customer-users-role-name-avatar-picture-or-phone-number "Direct link to Changing a customer user's role, name, avatar picture or phone number") From the customer's **Users** tab, click a user's name. Like organization users, you can change a customer user's role under the **Details** tab. You can also change their name, avatar picture, or phone number under the **Details** tab. ![Edit customer user in Prismatic app](/docs/assets/images/edit-customer-user-6829210c0f00730d5c9b6c636f49cef6.png) ##### Deleting a customer user[​](#deleting-a-customer-user "Direct link to Deleting a customer user") * Web App * CLI * API From the specific customer's **Users** tab, click the name of a user. Open the **Details** tab, and click the **Delete user** button on the bottom of the page. Enter the **Confirmation text** and click the **Remove user** button to confirm the removal. ![Delete customer user in Prismatic app](/docs/assets/images/delete-customer-user-ef5c6fd792b1436581d2fae529fe33b9.png) Find the user ID to delete with the `customers:users:list` subcommand, then delete the user with the `customers:users:delete` subcommand. Delete customer user 'Lisa Nguyen' ```bash USER_ID=$(prism customers:users:list \ --customer EXAMPLEtZXI6NTRlNDQyMDgtNTJiNi00ZGVhLTgyODYtOWRkNDU4MTA2ZTYw \ --columns id \ --no-header \ --filter 'Email=lisa.nguyen@ftl-rockets.com') prism customers:users:delete ${USER_ID} ``` ```graphql mutation { deleteUser( input: { id: "VXNlcjpmYjFiNDMyZC0zN2Y5LTQyZTUtOTljNy1hNjc1ZWIzZGUyNTA=" } ) { user { id } } } ``` ##### Searching all customer users[​](#searching-all-customer-users "Direct link to Searching all customer users") You can search for users on a per-customer basis from the customer's **Users** tab. To search users of all customers, click the **Users** link on the left-hand sidebar. To search for a user by name, enter their name in the search bar on the top of the page. To search for a user by email, click the **Filter** link on the top of the page. --- ### Managing Customers #### Creating new customers[​](#creating-new-customers "Direct link to Creating new customers") * Web App * CLI * API After you click the **Customers** link on the left-hand sidebar, click the **+ Add Customer** button on the upper-right. Enter an appropriate name and description for your customer. ![Add customer in Prismatic app](/docs/assets/images/add-customer-cc41b133a8e1a99b6bb20a97557dff62.png) To create new customers, use the `prism customers:create` subcommand. ```bash prism customers:create \ --name 'FTL Rockets' \ --description 'Faster-Than-Light Rocket Inc' ``` Create a new customer using the [createCustomer](https://prismatic.io/docs/api/schema/mutations.md#createcustomer)) mutation: ```graphql mutation { createCustomer( input: { name: "FTL Rockets", description: "Faster-Than-Light Rocket Inc" } ) { customer { id } } } ``` When Using Embedded, You Do Not Need to Create Customers or Customer Users Manually If you are using [embedded](https://prismatic.io/docs/embed.md) to present an integration marketplace or workflow builder in your app, you do not have to create customers or customer users manually. Customers and customer user records are automatically created for you when you [sign a JWT](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md) for authentication. Embedded customer users are different from standard customer users - standard customer users require a valid email address and receive Prismatic-branded confirmation emails. Embedded customer users can have any value (like a UUID) for their unique identifier and do not receive transactional emails. #### Searching customers[​](#searching-customers "Direct link to Searching customers") * Web App * CLI * API After clicking the **Customers** link on the left-hand sidebar, you can enter a portion of a customer's name into the search bar to filter customers by name. To filter customers by **description**, **[external ID](#customer-external-ids)**, or **[label](#customer-labels)** instead, click the **Filter** button to the right of the search bar. You can list customers using `prism customers:list`, and you can `--filter` or `--sort` the results: ```bash prism customers:list --filter "Name=Smith Rocket Company" ``` Query [customers](https://prismatic.io/docs/api/schema/queries.md#customers)) for a list of customers: ```graphql query { customers { nodes { id name description } } } ``` #### Modifying customers[​](#modifying-customers "Direct link to Modifying customers") After you click the **Customers** link on the left-hand sidebar, you'll see a list of your customers. When you click a customer's name, you'll see the customer's page. This page contains a menu with options to manage instances assigned to the customer, alert monitors, logs, customer users, and file attachments. ##### Editing customer name, description and logo[​](#editing-customer-name-description-and-logo "Direct link to Editing customer name, description and logo") * Web App * CLI * API From the customer's page, you can click the **Details** tab at the top of the page to change the customer's name or modify the longer customer description. To modify the customer's avatar icon, click the **Upload a photo** link on the **Details** tab. The avatar icon you upload will be resized and cropped to 500 x 500 pixels. Transparent PNG images tend to look the best. ![Edit customer details in Prismatic app](/docs/assets/images/details-629c5fd8e6a27cff817c0bc5e6167e9f.png) Update the customer name or description from the command line using the `prism customers:update` subcommand with the customer's ID: ```bash prism customers:update \ EXAMPLEtZXI6M2JkYzcwNTAtZTU2ZS00ZGJkLThmMzQtNWI0MDdhOTEXAMPLE \ --name "New Customer Name" \ --description "New Customer Description" ``` Use the [updateCustomer](https://prismatic.io/docs/api/schema/mutations.md#updatecustomer)) mutation to update a customer: ```graphql mutation { updateCustomer( input: { id: "Q3VzdG9tZXI6ZDIyOGUwNjItYzc0NC00NDFkLWE0MDMtNjQ1NTU4MDQ1OTZk" name: "New Customer Name" description: "New customer description" } ) { customer { id } } } ``` ##### Customer labels[​](#customer-labels "Direct link to Customer labels") Labels help you keep your customers organized. You can assign any number of labels to a customer from the customer's **Details** tab, and you can then [search](#searching-customers) for customers by label. Note: Prismatic automatically converts labels to lowercase for consistency. #### Deleting customers[​](#deleting-customers "Direct link to Deleting customers") Deleting a Customer Is Permanent When you delete a customer, Prismatic also deletes all associated users and instances. Use caution when deleting customers. * Web App * CLI * API After you click the **Customers** link on the left-hand sidebar, click the name of the customer you'd like to delete, and then select the **Details** tab. Verify that the name shown matches the customer you wish to delete, and click the **Delete customer** button. Confirm your choice by typing the customer name exactly, and then you can click **Remove customer**. ![Delete customer in Prismatic app](/docs/assets/images/delete-customer-5b06ac0d4f68e2a8c10ddbd0a84ec041.png) To delete a customer, use the `prism customers:delete` subcommand. ```bash # Get the customer's ID CUSTOMER_ID=$(prism customers:list \ --columns id \ --filter 'Name=^FTL Rockets$' \ --no-header) prism customers:delete ${CUSTOMER_ID} ``` Delete a customer using the [deleteCustomer](https://prismatic.io/docs/api/schema/mutations.md#deletecustomer)) mutation: ```graphql mutation { deleteCustomer( input: { id: "Q3VzdG9tZXI6ZDIyOGUwNjItYzc0NC00NDFkLWE0MDMtNjQ1NTU4MDQ1OTZk" } ) { customer { id } } } ``` #### Customer external IDs[​](#customer-external-ids "Direct link to Customer external IDs") You can assign a customer an **External ID** - a unique identifier from an external system. So, if you know "Smith Rocket Company" as a customer with an ID of `abc-123` in another system you use, you can assign "Smith Rocket Company" in Prismatic the `externalId` of `abc-123`. This is helpful if you need to quickly look up or associate customers in Prismatic with customers in your external system. You can set external IDs programmatically (see the next section), or edit them within the Prismatic web app by clicking the **Customers** link on the left-hand sidebar, selecting a customer, and then clicking on the **Details** tab. ![Set customer external IDs in Prismatic app](/docs/assets/images/external-id-23e9e35dbb38b26d2440691dfe3fae21.png) External IDs are required if you want to route webhook requests to instances deployed to specific customers using [shared](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md) webhook triggers. #### Per-customer execution concurrency[​](#per-customer-execution-concurrency "Direct link to Per-customer execution concurrency") Enterprise feature Per-customer execution concurrency limits are available on Enterprise plans. You can limit the number of simultaneous executions allowed for a specific customer. This prevents a single customer's high-volume integrations from consuming a disproportionate share of your organization's total execution capacity. When a customer reaches their limit, additional execution attempts return a 429 "too many requests" response. The number of executions rejected in the last 24 hours is visible from your organization's **Settings** > **Execution Limits**. ![Execution Limits listview screen](/docs/assets/images/execution-limits-screen-a6efbb65883c81a632338c56d00f2b13.png) Existing customers default to **Unrestricted** - they are limited only by your organization's overall concurrency cap. Total allocation can exceed your org limit You can allocate more total concurrency across customers than your organization's maximum. It's unlikely all customers would hit their limits simultaneously, but if they do, the organization's overall concurrency cap still applies. ##### Setting a concurrency limit[​](#setting-a-concurrency-limit "Direct link to Setting a concurrency limit") * Web App * API You can set concurrency limits for customers two places: * From a customer's **Utilization** tab you can manage that customer's concurrency limit directly. * From your organization's **Settings** > **Execution Limits** page, you can manage all customers' limits. When editing a customer's concurrency limit, you can choose between two options: * **Unrestricted** - The customer shares your organization's overall concurrency pool with no individual cap. * **Set Limit** - Enter a maximum number of concurrent executions for this customer. ![Edit concurrency limit dialog](/docs/assets/images/edit-concurrency-limit-bfd22dc9893dd79998c97200737de77b.png) Use the `updateCustomer` mutation and set `concurrentExecutionLimit` to the desired maximum number of concurrent executions. To remove a limit, set `concurrentExecutionLimit` to `null`. ```graphql mutation { updateCustomer( input: { id: "Q3VzdG9tZXI6ZDIyOGUwNjItYzc0NC00NDFkLWE0MDMtNjQ1NTU4MDQ1OTZk" concurrentExecutionLimit: 25 } ) { customer { id concurrentExecutionLimit } } } ``` You can also specify a concurrency limit when creating customers automatically via the `concurrent_execution_limit` [embedded JWT claim](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md#create-and-sign-a-jwt). --- ### Sync Customers Programmatically When you embed Prismatic into your app, you need to [authenticate](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md) your customer users as users of specific customers in Prismatic. You can establish these customer records in one of two ways: 1. **Create them programmatically through the API.** When authenticating embedded customer users, ensure that your customer record's `externalId` matches your embedded JWT's `customer` claim. 2. **Specify both `externalId` and `name` as embedded JWT claims.** With this approach, customer records will be automatically created if they don't already exist. This guide focuses on option 1, demonstrating how to programmatically create and manage customer records rather than relying on automatic creation from your embedded application. You'll use **External IDs**, which provide a way to link your external customer identifiers with Prismatic customer records. #### Setting up[​](#setting-up "Direct link to Setting up") This tutorial uses Python, though you can adapt the same ideas to any language that has a [GraphQL client library](https://graphql.org/code/). Full code shown in this tutorial can be found on [GitHub](https://github.com/prismatic-io/examples/blob/main/api/customers/customers.py). To start, add `gql` to your dependencies for your Python project: ```bash pip install gql ``` Next, import the necessary libraries and create a GraphQL client so you can run queries against Prismatic's API. Define the transport layer for getting to the API (HTTP), which will include an authorization header to authenticate your client against Prismatic: Initialize GraphQL client with authentication ```python import json import os import sys from gql import gql, Client from gql.transport.requests import RequestsHTTPTransport token = os.environ['PRISMATIC_API_KEY'] api_endpoint = "https://app.prismatic.io/api/" transport = RequestsHTTPTransport( url=api_endpoint, headers={'Authorization': f'Bearer {token}'} ) client = Client(transport=transport) ``` This code assumes that an environment variable, `PRISMATIC_API_KEY`, has been set (it's best practice not to hard-code API keys). To get an API key into your environment variables, you can use the `me:token` [prism subcommand](https://prismatic.io/docs/cli/prism.md#metoken): ```bash export PRISMATIC_API_KEY=$(prism me:token) ``` Now that you have a working client, you can write some GraphQL queries and mutations to manage customers within Prismatic: #### GraphQL queries and mutations[​](#graphql-queries-and-mutations "Direct link to GraphQL queries and mutations") To sync customers, you'll use the [customers](https://prismatic.io/docs/api/schema/queries.md#customers)) query and [createCustomer](https://prismatic.io/docs/api/schema/mutations.md#createcustomer)) and [deleteCustomer](https://prismatic.io/docs/api/schema/mutations.md#deletecustomer)) mutations. GraphQL Queries and Mutations In GraphQL lingo, a **query** is similar to a `SELECT` statement in SQL. You query for a particular set of information in a read-only way. A **mutation** is similar to an `INSERT`, `UPDATE`, or `DELETE` statement in SQL. You mutate data by creating new records, or updating or deleting existing records. #### List all customers[​](#list-all-customers "Direct link to List all customers") First, write a function that gets a list of all your customers, including their `name`, `description`, Prismatic `id`, and `externalId`: Query all customers ```python def getCustomers(): query = gql(""" query { customers { nodes { id name description externalId } } } """) result = client.execute(query) return result['customers']['nodes'] ``` This function runs a [customers](https://prismatic.io/docs/api/schema/queries.md#customers)) query against Prismatic's API. The query returns an object with a `customers` key, which has (in GraphQL lingo) has a series of **nodes** (customers). The function returns `result['customers']['nodes']` so that just a list of customer objects are returned. Invoke this function and use `json.dumps()` for readability: ```python print(json.dumps(getCustomers())) ``` ```json [ { "id": "Q3VzdG9tZXI6MThjZTBjM2EtYmQ5NS00MWJiLWIyMjUtN2MwYjVjMDg3YmE4", "name": "Mars Missions", "description": "Mars Missions Corp", "externalId": "abc-123" }, { "id": "Q3VzdG9tZXI6MDllZDQyNTctMTNkMS00YTY4LWFiNTktY2Y5NzNmZGUyOTQy", "name": "Eastern Space Flight", "description": "Eastern Space Flight - Houston, TX", "externalId": "xyz-456" } ] ``` You can use the output of this function to figure out which customers have not been synced into Prismatic, or to sync Prismatic customers back to an external system. ##### GraphQL pagination[​](#graphql-pagination "Direct link to GraphQL pagination") Before moving on, let's address pagination. By default, the Prismatic API returns the first 100 results for a query. So, the [customers](https://prismatic.io/docs/api/schema/queries.md#customers)) query only returns 100 customers, even if you have more than 100 in the system. If you want to download **all** customers, you'll need to update your `getCustomers()` function a bit. Update your query so that it requests pagination information (`pageInfo`). Request whether or not there are additional pages to fetch (`hasNextPage`), and a unique ID indicating where to start the next page (`endCursor`). Feed that `endCursor` back in to your query as a parameter `after`, so the GraphQL API knows where it last left off: Query all customers with pagination ```python def getCustomers(): query = gql(""" query ($startCursor: String){ customers(after: $startCursor) { nodes { id name description externalId } pageInfo { hasNextPage endCursor } } } """) cursor = "" hasNextPage = True customers = [] # Used to accumulate customer objects while hasNextPage: result = client.execute(query, variable_values={"startCursor": cursor}) customers += result['customers']['nodes'] hasNextPage = result['customers']['pageInfo']['hasNextPage'] cursor = result['customers']['pageInfo']['endCursor'] return customers ``` #### Fetch a specific customer by external ID[​](#fetch-a-specific-customer-by-external-id "Direct link to Fetch a specific customer by external ID") Now, you can look up a specific customer by their `externalId`. The [customers](https://prismatic.io/docs/api/schema/queries.md#customers)) query allows you to filter customers by `externalId`. The function passes in the `externalId` as a variable to the customers query using an object, `params`, that contains the GraphQL variables to use. The function also asserts that exactly one customer object is returned from the API: Fetch a customer by external ID ```python def getCustomerByExternalId(externalId): query = gql(""" query ($externalId: String!) { customers (externalId: $externalId) { nodes { id name description externalId } } } """) params = {"externalId": externalId} response = client.execute(query, variable_values=params) assert len(response["customers"]["nodes"]) == 1, f"No customer with external ID '{externalId}' exists." return response["customers"]["nodes"][0] ``` If you invoke this function now, you'll get a single customer based on the `externalId` that you provide: ```python print(json.dumps(getCustomerByExternalId("abc-123"))) ``` ```json { "id": "Q3VzdG9tZXI6MThjZTBjM2EtYmQ5NS00MWJiLWIyMjUtN2MwYjVjMDg3YmE4", "name": "Mars Missions", "description": "Mars Missions Corp", "externalId": "abc-123" } ``` #### Create a new customer[​](#create-a-new-customer "Direct link to Create a new customer") Next, write a function that creates a new customer given the customer's `name`, `description`, and `externalId`. To do that, use the [createCustomer](https://prismatic.io/docs/api/schema/mutations.md#createcustomer)) GraphQL mutation. Similar to the [getCustomerByExternalId](#fetch-a-specific-customer-by-external-id) function above, supply some input variables to your mutation with a `params` object: Create a new customer ```python def createCustomer(name, description="", externalId=""): mutation = gql(""" mutation($name: String!, $description: String, $externalId: String) { createCustomer( input: { name: $name, description: $description, externalId: $externalId } ) { customer { id name description externalId } errors { messages } } } """) params = { "name": name, "description": description, "externalId": externalId } result = client.execute(mutation, variable_values=params) if result["createCustomer"]["errors"]: raise Exception(result["createCustomer"]["errors"]) else: return result["createCustomer"]["customer"] ``` The `createCustomer` mutation will return `errors` if the `name` or `externalId` you specified is already in the Prismatic system. If the mutation does not throw an error, it will return a customer object containing the new customer's `name`, `description`, `externalId` and generated Prismatic `id`. Let's try it out: ```python print( json.dumps( createCustomer( name="Rockets Rockets Rockets", description="Rockets^3", externalId="456-xyz" ))) ``` ```json { "id": "Q3VzdG9tZXI6NGQ3ZDc3ZTktOTllNy00NmJiLWFlNDktMTg1N2JlNWNiYjUz", "name": "Rockets Rockets Rockets", "description": "Rockets^3", "externalId": "456-xyz" } ``` #### Delete a customer by external ID[​](#delete-a-customer-by-external-id "Direct link to Delete a customer by external ID") For the last customer-related function, delete a customer given their external ID. To do that, use the [deleteCustomer](https://prismatic.io/docs/api/schema/mutations.md#deletecustomer)) mutation. First, the function looks up the customer to delete using the [getCustomerByExternalId](#fetch-a-specific-customer-by-external-id) function written previously. Then, the function feeds the Prismatic ID into the `deleteCustomer` mutation: Delete a customer by external ID ```python def deleteCustomer(externalId): customer = getCustomerByExternalId(externalId) mutation = gql(""" mutation ($id: ID!) { deleteCustomer( input: { id: $id } ) { customer { id name description externalId } errors { messages } } } """) params = {"id": customer["id"]} result = client.execute(mutation, variable_values=params) if result["deleteCustomer"]["errors"]: raise Exception(result["deleteCustomer"]["errors"]) else: return result["deleteCustomer"]["customer"] ``` If you supply an external ID that is not attached to a customer, the `getCustomerByExternalId()` function will throw an error indicating that. --- ### Prism MCP Dev Server The **Prism MCP Server** is a local Model Context Protocol (MCP) server that helps AI assistants work with the Prismatic API for code-native integration and custom component development. Source code for the Prism MCP server is available on [GitHub](https://github.com/prismatic-io/prism-mcp). Not to be confused with Prismatic's MCP flow server! The Prism MCP server is different from Prismatic's MCP flow server. * This tool, the Prism MCP server, is a development tool for use with AI coding assistants to help you build [custom connectors](https://prismatic.io/docs/custom-connectors.md) and [code-native integrations](https://prismatic.io/docs/integrations/code-native.md). * [Prismatic's MCP flow server](https://prismatic.io/docs/ai/model-context-protocol.md) is a hosted service that lets AI agents interact with workflows deployed on the Prismatic platform. You'd use this server for building integrations and connectors, and the Prismatic MCP flow server for interacting with deployed workflows. #### Features[​](#features "Direct link to Features") This MCP server provides several tools, organized into categories. You may register whatever set of tools are most relevant to your use case. ![Claude Code using the Prism MCP server to assist with code-native integration development](/docs/assets/images/claude-vscode-04f4da4ca85bfb074d5d90fbda515143.png) ##### General tools (always available)[​](#general-tools-always-available "Direct link to General tools (always available)") * **prism\_me**: Check login status and display current user profile information * **prism\_components\_list**: List all available components with version options ##### Integration tools (toolset: "integration")[​](#integration-tools-toolset-integration "Direct link to Integration tools (toolset: \"integration\")") ###### Utilities[​](#utilities "Direct link to Utilities") * **prism\_integrations\_list**: List all integrations * **prism\_integrations\_init**: Initialize a new Code Native Integration * **prism\_integrations\_convert**: Convert an existing Low-Code Integration to Code Native * **prism\_integrations\_flows\_list**: List flows for an integration * **prism\_integrations\_flows\_test**: Test a flow in an integration * **prism\_integrations\_flows\_listen**: Set a flow to "listening mode" to capture webhook payloads or polling trigger responses, saving them as payloads for `prism_integrations_flows_test` * **prism\_integrations\_import**: Import an integration from a specific directory ###### Code generation[​](#code-generation "Direct link to Code generation") * **prism\_install\_component\_manifest**: Generate component manifest in CNI src directory (requires spectral@10.6.0 or greater) * **prism\_install\_legacy\_component\_manifest**: Generate line to add to a CNI's devDependencies for legacy component manifest installation * **prism\_integrations\_generate\_flow**: Generate boilerplate file for a CNI flow * **prism\_integrations\_generate\_config\_page**: Generate boilerplate code for a CNI config page * **prism\_integrations\_generate\_config\_var**: Generate boilerplate code for a config variable * **prism\_integrations\_add\_connection\_config\_var**: Returns path to connection wrapper function if available, otherwise generates boilerplate code for a connection config variable * **prism\_integrations\_add\_datasource\_config\_var**: Returns path to datasource wrapper function if available, otherwise generates boilerplate code for a datasource config variable ##### Component tools (toolset: "component")[​](#component-tools-toolset-component "Direct link to Component tools (toolset: \"component\")") * **prism\_components\_init**: Initialize a new Component (supports WSDL/OpenAPI generation) * **prism\_components\_publish**: Publish a component from a specific directory * **prism\_components\_generate\_manifest**: Generate the manifest for a Prismatic component ##### Toolset configuration[​](#toolset-configuration "Direct link to Toolset configuration") Tools are organized into **toolsets**, which you enable by listing their names as arguments after the working directory (see [General configuration](#general-configuration)): * **`integration`** - Enables all integration-related tools * **`component`** - Enables all component-related tools * **General tools** are always available regardless of toolset configuration If you pass no toolset arguments, the server registers all tools by default. If you pass an unrecognized toolset name, the server reports a startup error and exits. ![Claude Code using prism\_components\_list to discover MS Dynamics 365 components, then prism\_install\_component\_manifest to add the manifest to a CNI project](/docs/assets/images/prism-mcp-b073fe22b32d501193aa1de8e54af27e.png) #### Prerequisites[​](#prerequisites "Direct link to Prerequisites") Install the Prism CLI and log in. The CLI writes your credentials to a shared configuration file that the MCP server reads automatically: ```bash npm install --global @prismatic-io/prism prism login ``` #### Usage[​](#usage "Direct link to Usage") ##### General configuration[​](#general-configuration "Direct link to General configuration") Configuration location and methods vary slightly depending on the AI tool you are using, but the following is relatively standard. More specific instructions are below. Example setup: ```json { "mcpServers": { "prism": { "type": "stdio", "command": "npx", "args": ["-y", "@prismatic-io/prism-mcp", "."], "env": { "PRISMATIC_URL": "https://app.prismatic.io" } } } } ``` If you would like the MCP server to run in a different directory than the currently open workspace, replace the `.` path argument with your working directory, ```json { "args": ["-y", "@prismatic-io/prism-mcp", "/path/to/code-native/integration"] } ``` Command-line arguments: * First argument: **Required.** Working directory path that determines where Prism CLI commands are run from. Most coding agents (like Cursor and Claude Code) will interpret `.` as the current workspace directory. If your coding agent does not support this, you can specify an absolute path of your code-native integration or custom component project instead. * Remaining arguments: **Optional.** Toolsets to enable (`integration`, `component`). If you specify no toolsets, the server registers all tools by default. Being selective about toolsets may improve performance. For example, to enable only integration-related tools: ```json { "args": ["-y", "@prismatic-io/prism-mcp", ".", "integration"] } ``` Optional environment variable options: * `PRISMATIC_URL`: `https://app.prismatic.io` by default. If your Prismatic tenant is hosted in a different region, or if you use a private stack deployment, set this variable to your Prismatic URL. * `MCP_PRISM_PATH`: An absolute path to a Prism CLI for the server to run. ##### Installing with Claude Desktop[​](#installing-with-claude-desktop "Direct link to Installing with Claude Desktop") Add the above JSON config to your `claude_desktop_config.json` file. ##### Installing with Claude Code[​](#installing-with-claude-code "Direct link to Installing with Claude Code") To use this MCP server with Claude code, add the above config to your working directory's `.mcp.json` configuration file. Alternatively, run ```bash claude mcp add-json prism '{"type":"stdio","command":"npx","args":["-y","@prismatic-io/prism-mcp","."],"env":{"PRISMATIC_URL":"https://app.prismatic.io"}}' ``` ##### Installing with Cursor[​](#installing-with-cursor "Direct link to Installing with Cursor") You can configure available MCP Servers via `Cursor Settings` > `MCP Tools`, then add the above config to your `mcp.json` file. Or, click this link to install automatically: [Add MCP Server to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=prism\&config=ewogICJ0eXBlIjogInN0ZGlvIiwKICAiY29tbWFuZCI6ICJucHgiLAogICJhcmdzIjogWwogICAgIkBwcmlzbWF0aWMtaW8vcHJpc20tbWNwIiwKICAgICIuIgogIF0sCiAgImVudiI6IHsKICAgICJQUklTTUFUSUNfVVJMIjogImh0dHBzOi8vYXBwLnByaXNtYXRpYy5pbyIKICB9Cn0%3D) ##### Installing with VS Code / GitHub Copilot[​](#installing-with-vs-code--github-copilot "Direct link to Installing with VS Code / GitHub Copilot") Add the above config to the `.vscode/mcp.json` in your workspace, or the global `mcp.json` file (accessible via the "Add MCP Server..." option in the Command Palette). Or, click this link to install automatically: [Add MCP Server to VS Code](vscode:mcp/install?%7B%22name%22%3A%22prism%22%2C%22type%22%3A%22stdio%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22%40prismatic-io%2Fprism-mcp%22%2C%22.%22%5D%2C%22env%22%3A%7B%22PRISMATIC_URL%22%3A%22https%3A%2F%2Fapp.prismatic.io%22%7D%7D) ##### Other tools[​](#other-tools "Direct link to Other tools") If your agent of choice is not listed, please reference their official documentation for setup instructions. #### Related tools[​](#related-tools "Direct link to Related tools") * To get started with the Prism CLI itself, see [Set Up Your Dev Environment](https://prismatic.io/docs/custom-connectors/get-started/setup.md). * If you use Claude Code, [Prismatic Skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) provides a dedicated plugin with specialized agents and knowledge bases for building custom connectors and code-native integrations. * The [Prismatic VS Code extension](https://prismatic.io/docs/dev-tools/vscode-extension.md) provides test runners, config wizards, and execution result views directly in VS Code or Cursor. --- ### Prismatic Extension for VS Code & Cursor An extension for VSCode & Cursor that improves the developer experience around Code-Native Integrations (CNI) by enabling test execution, integration imports, instance configuration, and inspection of execution results directly within the IDE. #### Purpose[​](#purpose "Direct link to Purpose") The main intent of this extension is to offer: 1. **Seamless Development Workflow Integration:** This extension bridges the gap between local development and the Prismatic platform by providing direct access to integration testing, configuration, and debugging tools within your IDE. Instead of constantly switching between your code editor and the Prismatic web interface, developers can manage their entire CNI development lifecycle from VS Code, reducing context switching and improving productivity. 2. **Real-time Testing and Debugging:** The extension provides immediate feedback on integration performance through real-time test execution and detailed step-by-step output streaming. This allows developers to quickly identify issues, debug problems, and iterate on their integrations without leaving their development environment, significantly reducing the feedback loop between coding and testing. 3. **Unified Configuration Management:** The Config Wizard provides a guided interface for setting up integration instances, while maintaining synchronization with the Prismatic platform, ensuring that local development configurations stay aligned with production environments. #### Features[​](#features "Direct link to Features") * **Authentication**: Secure login and token management with multi-tenant support. * **Config Wizard**: Configure integration instances with a guided interface. * **Execution Results**: View detailed step-by-step outputs and logs. * **Integration Import**: Direct import of integrations from Prismatic via the Prism CLI. * **Status Bar**: Displays the current organization and active integration at a glance. * **Integrations Sidebar**: Tree view listing all Code-Native Integrations in your workspace. * **Integration Details**: Sidebar panel showing configuration state, flows, and connections. * **Flow Payloads**: Tree view of flows and their test payload files. #### Prerequisites[​](#prerequisites "Direct link to Prerequisites") * A [Prismatic account](https://prismatic.io). * VSCode [version 1.96.0 or higher](https://code.visualstudio.com/updates/v1_96) or [Cursor](https://www.cursor.com/). * A [Prismatic Code-Native Integration (CNI) project](https://prismatic.io/docs/integrations/code-native.md). * The Prismatic CLI ([Prism](https://prismatic.io/docs/cli.md#installing-the-cli-tool)) installed globally. #### Installation[​](#installation "Direct link to Installation") Install the Prismatic extension for VS Code by visiting the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=prismatic.prismatic-io) or by searching for "Prismatic" in the VS Code Extensions view. #### Usage[​](#usage "Direct link to Usage") The extension provides commands and webview panels that can be accessed through the VS Code command palette: 1. Press `Cmd+Shift+P` (Mac) or `Ctrl+Shift+P` (Windows/Linux) 2. Type "Prismatic" to see available commands. ##### Available Commands[​](#available-commands "Direct link to Available Commands") ![Available Commands](/docs/assets/images/marketplace-commands-dc6f5a4e133c805b580f7e41c675d747.png) ###### `Prismatic: Config Wizard`[​](#prismatic-config-wizard "Direct link to prismatic-config-wizard") Launches the Config Wizard to edit configuration values for your integration instance. ###### `Prismatic: Import Integration`[​](#prismatic-import-integration "Direct link to prismatic-import-integration") Imports your local Code-Native Integration into the Prismatic platform. ###### `Prismatic: Test Integration`[​](#prismatic-test-integration "Direct link to prismatic-test-integration") Runs a test for your Code-Native Integration and streams step outputs and logs for debugging. ###### `Prismatic: Create Flow Payload`[​](#prismatic-create-flow-payload "Direct link to prismatic-create-flow-payload") Generates a sample flow payload you can use to simulate data inputs during testing. ###### `Prismatic: Login`[​](#prismatic-login "Direct link to prismatic-login") Opens your browser to authenticate with Prismatic. If your account has access to multiple tenants, you'll be prompted to select one. ###### `Prismatic: Logout`[​](#prismatic-logout "Direct link to prismatic-logout") Clears your stored authentication session. ###### `Prismatic: Switch Tenant`[​](#prismatic-switch-tenant "Direct link to prismatic-switch-tenant") Switches between Prismatic organizations without logging out and back in. ###### `Prismatic: Prismatic URL`[​](#prismatic-prismatic-url "Direct link to prismatic-prismatic-url") Sets the Prismatic instance URL used by the extension. This allows you to change your Prismatic stack environment. ###### `Prismatic: Select Integration`[​](#prismatic-select-integration "Direct link to prismatic-select-integration") Switches the active integration from a list of Code-Native Integrations found in your workspace. ###### `Prismatic: Open Integration in Browser`[​](#prismatic-open-integration-in-browser "Direct link to prismatic-open-integration-in-browser") Opens the active Code-Native Integration in the Prismatic web app. ###### `Prismatic: Reveal in Explorer`[​](#prismatic-reveal-in-explorer "Direct link to prismatic-reveal-in-explorer") Reveals the active integration's directory in the VS Code file explorer. ###### `Prismatic: Me`[​](#prismatic-me "Direct link to prismatic-me") Displays details about the currently authenticated Prismatic user, including name, organization, and Prismatic stack URL. ###### `Prismatic: Focus on Execution Results View`[​](#prismatic-focus-on-execution-results-view "Direct link to prismatic-focus-on-execution-results-view") Displays test results, including step results and logs. ##### Available Webviews[​](#available-webviews "Direct link to Available Webviews") ###### `Prismatic: Focus on Execution Results View`[​](#prismatic-focus-on-execution-results-view-1 "Direct link to prismatic-focus-on-execution-results-view-1") Displays test results, including step results and logs. ![Execution Results](/docs/assets/images/marketplace-execution-results-299c4b0b707dd098bba988c56d9cf036.png) ###### `Prismatic: Config Wizard`[​](#prismatic-config-wizard-1 "Direct link to prismatic-config-wizard-1") Displays the Config Wizard to edit configuration values for your integration instance. ![Config Wizard](/docs/assets/images/marketplace-config-wizard-abeea1ed332dc141ac075183a5d502b6.png) #### Extension settings[​](#extension-settings "Direct link to Extension settings") ##### `prismatic.prismCliPath`[​](#prismaticprismclipath "Direct link to prismaticprismclipath") Path to the Prism CLI executable. If not specified, the extension searches for `prism` in your system PATH, falling back to npx. ##### `prismatic.npmCliPath`[​](#prismaticnpmclipath "Direct link to prismaticnpmclipath") Path to the `npm` executable. If not specified, the extension searches for `npm` in your PATH and common locations (Homebrew, nvm, asdf, etc.). ##### `prismatic.debugMode`[​](#prismaticdebugmode "Direct link to prismaticdebugmode") Debug logging level for command execution. | Value | Description | | --------- | ---------------------------------------------------------------------------- | | `off` | No debug logging (default) | | `basic` | Logs command, working directory, and Node version | | `verbose` | Logs command, working directory, Node version, and all environment variables | Debug output appears in the Prismatic output channel (**View** > **Output** > "Prismatic"). #### Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") If you encounter issues: 1. Enable debug mode (`basic` or `verbose`) in VS Code settings and check the Prismatic output channel (**View** > **Output** > "Prismatic") for detailed logs. 2. Ensure the CLI is installed globally: `npm install -g @prismatic-io/prism` 3. Verify the installation: `prism --version` 4. Check your PATH environment variable includes the npm global bin directory. 5. Try reinstalling the extension. #### Related tools[​](#related-tools "Direct link to Related tools") * To set up the Prism CLI, see [Set Up Your Dev Environment](https://prismatic.io/docs/custom-connectors/get-started/setup.md). * The [Prism MCP dev server](https://prismatic.io/docs/dev-tools/prism-mcp.md) connects AI coding assistants to your Prismatic tenant. * [Prismatic Skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) provides a Claude Code plugin for building custom connectors and integrations end-to-end. --- ### Get Started with Prismatic Build Integrations Build productized integrations that customers can deploy for themselves using our code-native SDK + AI or our low-code builder. Embed a Marketplace Embed an integration marketplace within your product to offer a seamless deployment experience for your customers. Embed a Workflow Builder Enable customers to build their own workflows through natural language using an AI Copilot, and a low-code builder. Connect AI Agents Leverage existing integrations to connect your product's chatbots and AI tools to apps and services your customers use. Get started Get acquainted with Prismatic's code-native SDK and AI tooling by building a simple integration in TypeScript, or build using our low-code integration designer. [First code-native integration with AI](https://prismatic.io/docs/get-started/build-integrations/first-integration-code-native.md) [First low-code integration](https://prismatic.io/docs/get-started/build-integrations/first-integration-low-code.md) Prepare your product Ensure your app and API are ready for integrations by supporting event-driven workflows and developing a custom connector. [Custom connector basics](https://prismatic.io/docs/get-started/build-integrations/custom-connector-basics.md) [Build a custom connector with AI](https://prismatic.io/docs/get-started/build-integrations/wrap-api-custom-connector.md) [Prepare your API for integrations](https://prismatic.io/docs/get-started/build-integrations/prepare-api-for-integrations.md) [Configure a Git Repository for Prismatic](https://prismatic.io/docs/get-started/build-integrations/configure-git-repository.md) Connect Connect your integrations to third-parties (and your own app!), and ensure that the connections you create are reusable across integrations. [What are connections?](https://prismatic.io/docs/get-started/build-integrations/what-are-connections.md) [Crash course in OAuth 2.0](https://prismatic.io/docs/get-started/build-integrations/oauth.md) Self-service integrations Ensure your customers can onboard and configure your integrations without needing to contact your support team. [Crash course in config wizards](https://prismatic.io/docs/get-started/build-integrations/config-wizards.md) [Dynamic data sources](https://prismatic.io/docs/get-started/build-integrations/dynamic-data-sources.md) [Intro to JSON Forms](https://prismatic.io/docs/get-started/build-integrations/json-forms-intro.md) [Field mappers with JSON Forms](https://prismatic.io/docs/get-started/build-integrations/field-mappers-with-json-forms.md) Event-driven integrations Build integrations that react to events in your app or third-party apps. [Crash course in webhooks](https://prismatic.io/docs/get-started/build-integrations/webhooks.md) [Trigger lifecycle events](https://prismatic.io/docs/get-started/build-integrations/lifecycle-events.md) [Secure webhooks with HMAC](https://prismatic.io/docs/get-started/build-integrations/webhook-security.md) Handling Large Data Sets Ensure your integrations are robust, maintainable, and easy to use. [Large Data Syncs](https://prismatic.io/docs/get-started/build-integrations/large-data-syncs.md) Offer your integrations to users Once your integrations are built, offer a self-service marketplace for your customers to discover and deploy them. Check out "Embed Marketplace" above --- ### Authenticate Customers with MCP After building agentic flows, you can invoke those flows via MCP. Authentication with the [Prismatic MCP Flow Server](https://prismatic.io/docs/ai/model-context-protocol.md) uses the same JWTs that you generate for your embedded users. The video below walks you through how to authenticate embedded users. Use Claude Code to set up embedding The [Prismatic Skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) plugin for Claude Code can walk you through signing key setup, JWT backend generation, and frontend authentication interactively. It has reference material for JWT generation in a variety of backends. Run `/prismatic-skills:embedded` to get started. #### Authenticating users[​](#authenticating-users "Direct link to Authenticating users") One advantage of embedding the Prismatic embedded marketplace or embedded workflow builder is that users don't need to remember an additional set of credentials. They can log in to your application, and you can provide them with an authentication token that allows them to interact with Prismatic. You do this by signing a JSON Web Token (JWT) using a unique private key that you get from Prismatic. The JWT contains information about the authenticated user, like their name, external ID, and customer they're associated with. #### JWT signing keys[​](#jwt-signing-keys "Direct link to JWT signing keys") Before you can generate a JWT, you'll need a valid **signing key** from Prismatic. In Prismatic, click your organization name at the bottom of the left-hand sidebar, then open the **Embedded** tab. Click the **+ Add signing Key** button. note You must be an [owner or admin](https://prismatic.io/docs/configure-prismatic/organization-users.md#organization-team-member-roles) to create a signing key. You will be presented with a private signing key. Store this key somewhere safe - it's the key you'll use to sign JWTs for users in your application. ![Get signing key in Prismatic app](/docs/assets/images/example-private-key-0ea08c3d0624b47f74bc6b548ddacf23.png) Private keys are not stored in Prismatic Prismatic does not store the private signing key that is generated. Instead, we only save the last 8 characters so you can easily match up a private key you have with one in our system. We store the corresponding public key to verify signatures of JWTs you send. Save the private key that you generate somewhere safe. If it's ever compromised or you lose it, you can deactivate old keys and generate a new one. ##### Importing your own private signing key[​](#importing-your-own-private-signing-key "Direct link to Importing your own private signing key") You can also import your own private signing key for embedded authentication. The OpenSSL CLI tool is most commonly used for generating public/private key pairs yourself: ```bash # Generate a private key with 4096 bit encryption openssl genrsa -out my-private-key.pem 4096 # Generate the corresponding public key openssl rsa -in my-private-key.pem -pubout > my-public-key.pub ``` This will generate two files - a private key called `my-private-key.pem` and a public key called `my-public-key.pub`. Your public key will look like this: ```text -----BEGIN PUBLIC KEY----- EXAMPLE -----END PUBLIC KEY----- ``` Import the public key using the [Prism CLI](https://prismatic.io/docs/cli/prism.md#organizationsigning-keysimport): ```bash prism organization:signing-keys:import -p my-public-key.pub ``` #### Create and sign a JWT[​](#create-and-sign-a-jwt "Direct link to Create and sign a JWT") Now that you have a signing key, you can create and sign a JSON web token (JWT). Your backend API (not your frontend) should generate a JWT for your users. Your frontend client should request this JWT from your backend API. Do your JWT generation on the backend Generate JWT tokens on your backend. If you bake JWT generation (including the signing key) into your frontend, you create a security problem - someone with the signing key could sign their own JWT and pretend to be any user. Most programming languages offer JWT libraries for generating tokens - see [jwt.io](https://jwt.io/). The JWT that you generate for a user should have the following required claims (properties): | Field | Description | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `sub` | Unique user ID, typically a UUID | | `organization` | Your organization ID, found on the **Embedded** tab in Prismatic | | `customer` | The [external ID](https://prismatic.io/docs/customers/managing-customers.md#customer-external-ids) of the customer the user belongs to | | `iat` | Signing time as a Unix timestamp. It's a good idea to use (current time) minus a small buffer to account for clock skew. | | `exp` | Token expiration time as a Unix timestamp | You can also include these optional JWT claims: | Field | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `external_id` | External ID of the user in Prismatic; typically matches `sub` | | `name` | The user's name | | `customer_name` | If a customer with the given `customer` external ID doesn't exist, creates one with this name. Ignored if the customer already exists. | | `concurrent_execution_limit` | Maximum concurrent executions for this customer (integer). See [per-customer execution concurrency](https://prismatic.io/docs/customers/managing-customers.md#per-customer-execution-concurrency). | | `role` | Only for [user level configuration](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md) (ULC). `"admin"` can deploy ULC instances; `"user"` can supply user configuration. Defaults to `"admin"`. | For example, a JWT payload might look like this: Example JWT Payload ```json { "sub": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", "external_id": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", "name": "Phil Embedmonson", "organization": "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw==", "customer": "abc-123", "customer_name": "Hooli", "concurrent_execution_limit": 25, "role": "admin", "iat": 1631676917, "exp": 1631680517 } ``` The JWT's **header** should indicate that it's signed with RSA SHA-256, and should read: ```json { "alg": "RS256", "typ": "JWT" } ``` Use unique identifiers as JWT subjects The `sub` (subject) within the JWT identifies the user who is logged in to your system. The `sub` value can be any unique identifier - usually a UUID. A customer user with that identifier will be created in Prismatic if it doesn't already exist, and will be granted permissions to configure and deploy instances to the customer they're assigned to (you assign the user to a customer in the examples below). ##### Example JWT signing code snippets[​](#example-jwt-signing-code-snippets "Direct link to Example JWT signing code snippets") Here are a couple of code snippets for JavaScript and Python that would create a valid JWT to authenticate a user in Prismatic: * JavaScript Example * Python Example * .Net (C#) Example ```javascript import jsonwebtoken from "jsonwebtoken"; /* This is for illustrative purposes only; Obviously don't hard-code a signing key in your code. */ const signingKey = `-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP3+OrT0IXqCu4 EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEE c5R7QVzxgmGRXjPZGPf5huA1 -----END PRIVATE KEY-----`; const currentTime = Math.floor(Date.now() / 1000); const token = jsonwebtoken.sign( { sub: "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", // Some unique identifier for the user external_id: "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", // Generally matches sub name: "Phil Embedmonson", // Optional organization: "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw==", customer: "abc-123", // This is an external ID of a customer customer_name: "Hooli", // The optional name to use if we need to create a new customer record iat: currentTime - 60, // Issued 1 minute ago to account for clock skew exp: currentTime + 60 * 60, // 1 hour from now }, signingKey, // Store this somewhere safe { algorithm: "RS256" }, ); ``` ```python import jwt import math from time import time # This is for illustrative purposes only; # Obviously don't hard-code a signing key in your code. signing_key = '''-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP3+OrT0IXqCu4 EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEE c5R7QVzxgmGRXjPZGPf5huA1 -----END PRIVATE KEY-----''' current_time = math.floor(time()) token = jwt.encode( { "sub": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", # Some unique identifier for the user "external_id": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", # Generally matches sub "name": "Phil Embedmonson", # Optional "organization": "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw==", "customer": "abc-123", # This is an external ID of a customer "customer_name": "Hooli", # The optional name to use if we need to create a new customer record "iat": current_time, "exp": current_time + 60 * 60, # 1 hour from now }, signing_key, algorithm="RS256") ``` ```csharp using Microsoft.IdentityModel.Tokens; using System.Security.Cryptography; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; /* This is for illustrative purposes only; Obviously don't hard-code a signing key in your code. */ var pem = @"-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP3+OrT0IXqCu4 EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEE c5R7QVzxgmGRXjPZGPf5huA1 -----END PRIVATE KEY-----"; Task GetToken() { using var rsa = RSA.Create(); rsa.ImportFromPem(pem); var descriptor = new SecurityTokenDescriptor(); descriptor.SigningCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256) { CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false } }; var claims = new List { new Claim("sub", "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1"), // Some unique identifier for the user new Claim("external_id", "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1"), // Generally matches sub new Claim("name", "Phil Embedmonson"), // Optional new Claim("organization", "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw=="), new Claim("customer", "abc-123"), // This is an external ID of a customer new Claim("customer_name", "Hooli") // The optional name to use if we need to create a new customer record }; descriptor.Subject = new ClaimsIdentity(claims); descriptor.IssuedAt = DateTime.UtcNow; descriptor.Expires = DateTime.UtcNow.AddHours(1); // Expire 1 hour from now var token = new JwtSecurityTokenHandler().CreateEncodedJwt(descriptor); return Task.FromResult(token); } var token = await GetToken(); ``` An example NextJS implementation of JWT generation is available in [GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/pages/api/prismatic-auth.tsx). #### Use the JWT to authenticate the user[​](#use-the-jwt-to-authenticate-the-user "Direct link to Use the JWT to authenticate the user") Now that a user in your application has a signed JWT from the backend, you can authenticate them with the Prismatic library using the `prismatic.authenticate()` function in your frontend application: ```javascript // Some function that fetches the JWT from your API: const token = getJwtToken(); try { await prismatic.authenticate({ token }); } catch (error) { console.error(`Authentication failed with error ${error}`); } ``` If your customer or organization ID in your JWT are incorrect, if your JWT is not signed correctly, or if the JWT is expired, `prismatic.authenticate()` will throw an error. For an example React hook that wraps the `prismatic.authenticate()` function, see the [GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/src/usePrismaticAuth.ts#L66). ##### What does `prismatic.authenticate()` do?[​](#what-does-prismaticauthenticate-do "Direct link to what-does-prismaticauthenticate-do") When you call `prismatic.authenticate({ token })`, your client will make an authenticate call to Prismatic with the JWT you provide. The request is equivalent to this curl command: ```bash curl -X POST 'https://app.prismatic.io/embedded/authenticate' \ --header 'authorization: Bearer eyJhb...' \ ``` The request returns an HTTP 200 if the token is valid, and an error if it's not. If the user or customer you specified in the JWT doesn't already exist in Prismatic, they will be created automatically. #### Refreshing an embedded JWT[​](#refreshing-an-embedded-jwt "Direct link to Refreshing an embedded JWT") If a customer user's JWT expires, the customer user will see a 404 in their embedded iframe. To reauthenticate a user prior to expiration, ensure that your frontend app fetches a new token for your user and then run `prismatic.authenticate({ token })` with the new token. Existing iframes and the embedded client will be updated to use the new token. --- ### Code-Native Agentic Flows In this video we add two agentic flows to existing code-native Dropbox and Slack integrations, giving our built-in chat bot the ability to fetch files from our customers' Dropbox accounts, and send notifications to our customers' Slack workspaces. For additional information on building code-native agentic flows, see [Code-Native Agentic Flows](https://prismatic.io/docs/ai/agentic-flows/code-native.md). Code built in the video can be referenced below: * Dropbox * Slack dropbox/searchAndFetchFiles.ts ```ts import { flow } from "@prismatic-io/spectral"; import { createDropboxClient } from "../dropboxClient"; interface Match { metadata: { metadata: { name: string; path_display: string; }; }; } interface ResponseItem { name: string; path_display: string; presigned_url: string; } export const searchAndFetchFiles = flow({ name: "Search and Fetch Files from Dropbox", description: "Search for files in Dropbox and return a presigned URL for each matching file", isAgentFlow: true, isSynchronous: true, stableKey: "searchAndFetchFiles", schemas: { invoke: { $schema: "https://json-schema.org/draft/2020-12/schema", title: "search-and-fetch-files", $comment: "Search for files in Dropbox by file name. Returns a list of files that match including a presigned URL for each file.", type: "object", properties: { filename: { type: "string", description: "The name of the file to search for in Dropbox. This can be a partial or full file name.", }, folder: { type: "string", description: "The folder path in Dropbox to search for files. If not provided, the root folder will be searched.", }, }, required: ["filename"], }, }, onExecution: async (context, { onTrigger }) => { const { filename, folder } = onTrigger.results.body.data as { filename: string; folder?: string; }; if (!filename) { throw new Error("Filename is required to search for files in Dropbox."); } const dbxClient = createDropboxClient( context.configVars["Dropbox Connection"], ); const searchResponse = await dbxClient.post<{ matches: Match[] }>( "/files/search_v2", { query: filename, options: { path: folder || "", }, }, ); const files: ResponseItem[] = []; for (const match of searchResponse.data.matches) { const tempLinkResponse = await dbxClient.post<{ link: string }>( "/files/get_temporary_link", { path: match.metadata.metadata.path_display, }, ); files.push({ name: match.metadata.metadata.name, path_display: match.metadata.metadata.path_display, presigned_url: tempLinkResponse.data.link, }); } return { data: files }; }, }); ``` slack/sendMessage.ts ```ts import { flow } from "@prismatic-io/spectral"; import { createSlackClient } from "../slackClient"; interface SendMessageInput { message: string; } export const sendMessage = flow({ name: "Send Message", description: "Send a message to a Slack channel", stableKey: "send-message", isAgentFlow: true, isSynchronous: true, schemas: { invoke: { $schema: "https://json-schema.org/draft/2020-12/schema", title: "send-slack-message", $comment: "Send a message to Slack", type: "object", properties: { message: { description: "The message to send to Slack. Reference https://docs.slack.dev/messaging/formatting-message-text.md for formatting documentation.", type: "string", }, }, required: ["message"], }, }, onExecution: async (context, { onTrigger }) => { const message = (onTrigger.results.body.data as SendMessageInput).message; if (!message) { throw new Error("Message is required"); } const slackClient = createSlackClient( context.configVars["Slack Connection"], ); const response = await slackClient.post("/chat.postMessage", { channel: context.configVars["Notification Channel"], text: message, }); return { data: response.data }; }, }); ``` --- ### Connect the AI SDK Vercel's [AI SDK](https://ai-sdk.dev/) brings together the power of LLMs with MCP servers. Using the AI SDK, you can connect your agentic flows, giving your customers the ability to query for data in their third-party systems from within your app. This video walks you through how to connect the AI SDK to Prismatic's MCP Flow Server and present your agentic flows as MCP tools. For more information on connecting to the MCP Flow server, see [Connect Your App to Agentic Flows](https://prismatic.io/docs/ai/connect-ai-agent.md) --- ### Build Your First Agentic Flow In this guide we'll build a simple agentic flow that can be invoked by an AI agent as an MCP tool. This flow will take a user's query for first and last name, and return a list of people with matching names. Available as a template If you'd like to skip the build steps, create a new integration and select **View Templates** under **Low-Code**. Select the **Generic MCP Example** template. A code-native version of this example is also available in [GitHub](https://github.com/prismatic-io/integration-templates/tree/main/generic-mcp-example). #### Build your agentic flow[​](#build-your-agentic-flow "Direct link to Build your agentic flow") ##### Step 1: Create a new flow with invocation schema[​](#step-1-create-a-new-flow-with-invocation-schema "Direct link to Step 1: Create a new flow with invocation schema") Create a new integration that begins with a generic **Universal Webhook** trigger. Click your trigger. Within the step configuration panel, open the **Schemas** tab. Add an **Invoke Schema** with the following JSON schema: ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "search-people-in-acme", "$comment": "Given a first and last name of a person, search for matching people in Acme CRM", "type": "object", "properties": { "first": { "description": "A person's first name", "type": "string" }, "last": { "description": "A person's last name", "type": "string" } } } ``` This invocation schema tells an AI agent that this flow can be invoked with a JSON object containing a `first` and `last` string property, which represent a person's first and last name. Neither property is *required*, so the agent can choose to provide either or both when invoking the flow. For example, the flow may receive a webhook request with the following body: ```json { "first": "Leanne", "last": "Graham" } ``` ##### Step 2: Fetch data from an API[​](#step-2-fetch-data-from-an-api "Direct link to Step 2: Fetch data from an API") Add an HTTP - GET step to your flow that makes a request to `https://jsonplaceholder.typicode.com/users` to fetch a list of users. Run your flow and verify that the HTTP step returns an array of 10 user objects. ##### Step 3: Filter data with a code step[​](#step-3-filter-data-with-a-code-step "Direct link to Step 3: Filter data with a code step") Next, add a code step to filter the list of users based on the `first` and `last` properties from the invocation schema: Filter results with a code step ```javascript module.exports = async ({ logger, configVars }, stepResults) => { // Validate and extract the search parameters from trigger const { first: firstNameSearch, last: lastNameSearch } = stepResults.trigger.results.body.data; if (!firstNameSearch && !lastNameSearch) { throw new Error("You must specify at least a first name or last name"); } // Filter the people based on the search parameters const matchingPeople = stepResults.getRequest.results.filter((person) => { const [firstName, lastName] = person.name.split(" "); if (firstNameSearch) { if (!firstName.toLowerCase().includes(firstNameSearch.toLowerCase())) { return false; } } if (lastNameSearch) { if (!lastName.toLowerCase().includes(lastNameSearch.toLowerCase())) { return false; } } return true; }); return { data: matchingPeople }; }; ``` Run your flow again with a webhook body of `{"first": "Clem"}` and verify that the code step returns two matching people: `Clementine Bauch` and `Clementina DuBuque`. ##### Step 4: Enable MCP tool access[​](#step-4-enable-mcp-tool-access "Direct link to Step 4: Enable MCP tool access") To enable AI agents to discover your flow as an MCP tool, it needs invocation schema (which we added in Step 1) and it needs to be marked as a **Tool-Enabled Flow**. Open the MCP settings on the left-hand navigation and enable your flow as a tool. #### Test your flow from an AI agent[​](#test-your-flow-from-an-ai-agent "Direct link to Test your flow from an AI agent") Now that we've built an agentic flow with an invocation schema, we can test invoking it from an AI agent. This can be done by adding the integration's MCP endpoint (found in the same tab where you enabled your flow as a tool) to any agent that supports invoking MCP tools. For example, if we're using Claude CLI we can run: Enable Claude to invoke our flow ```bash claude mcp add \ --transport http \ people-searcher \ https://mcp.prismatic.io/SW5EXAMPLE/mcp ``` Then, we can ask Claude to enable and authenticate the MCP server and use the MCP server to query our flow. ![Claude invoking our flow as an MCP tool ](/docs/assets/images/claude-invoke-flow-51b2a71120bc6baeac05ed0a4f741064.png) Documentation for setting up other agents, including your own via the Node AI SDK for chat bots in your app, is available [here](https://prismatic.io/docs/ai/connect-ai-agent.md). If your agent doesn't support OAuth 2.0 authentication, you can create a Prismatic access token with `prism me:token` and use that as a Bearer token when interacting with the MCP server. #### Next Up[​](#next-up "Direct link to Next Up") * [Build a code-native agentic flow](https://prismatic.io/docs/get-started/agentic-flows/code-native-agentic-flows.md) * [Build a low-code agentic flow](https://prismatic.io/docs/get-started/agentic-flows/low-code-agentic-flows.md) * [Incorporate Prismatic's MCP Flow Server in your own app](https://prismatic.io/docs/get-started/agentic-flows/connect-ai-agent.md) --- ### Flow Invocation Schemas A [flow invocation schema](https://prismatic.io/docs/ai/flow-invocation-schema.md) gives the Prismatic MCP server information about using your workflow as an MCP tool, including a description of what the flow does and what inputs it requires. This video covers how to create a flow invocation schema and mark your flow as an MCP tool. While we cover some things you can do with schema, like `enums` and constraints, our video is not exhaustive to everything you can do with input schema. Additional information about `inputSchema` is available on Specification Enhancement Proposal [SEP-2106](https://modelcontextprotocol.io/seps/2106-json-schema-2020-12). --- ### Low-Code Agentic Flows In this video we add two agentic flows to existing low-code Dropbox and Slack integrations, giving our built-in chat bot the ability to fetch files from our customers' Dropbox accounts, and send notifications to our customers' Slack workspaces. Here is the schema for the two flows we built in the video: * Dropbox * Slack ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "search-fetch-dropbox-files", "$comment": "Search for files in Dropbox by file name. Returns list of matching files and a presigned URL where the file can be fetched.", "type": "object", "properties": { "filename": { "description": "Partial or full name of a file", "type": "string" }, "folder": { "description": "Subfolder to search for the file", "type": "string" } }, "required": ["filename"] } ``` ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "send-slack-message", "$comment": "Send a message to Slack", "type": "object", "properties": { "message": { "description": "The message to send to Slack. Reference https://docs.slack.dev/messaging/formatting-message-text.md for formatting documentation.", "type": "string" } }, "required": ["message"] } ``` For additional information on building agentic flows in low-code, see [Low-Code Agentic Flows](https://prismatic.io/docs/ai/agentic-flows/low-code.md). --- ### Crash Course in Config Wizards When your customers visit your [integration marketplace](https://prismatic.io/docs/embed/marketplace.md) and configure an instance of an integration you've built, they walk through a [config wizard](https://prismatic.io/docs/integrations/config-wizard.md) experience that you've designed. By entering their own connection credentials and selecting options that are relevant to them, they can create an instance of the integration that is configured to fit their needs. In this video we walk through how to use Prismatic's config wizards to create a self-service integration experience for your customers. We examine how to add config variables to your code-native or low-code integration, and how to reference those variables in your flows. **Also See:** * [Config Wizard Overview](https://prismatic.io/docs/integrations/config-wizard.md) * [Dynamic Data Sources](https://prismatic.io/docs/get-started/build-integrations/dynamic-data-sources.md) --- ### Configure Git Repository When building integrations and custom connectors (also called *custom components*) with Prismatic, it's important to organize your repository in a way that promotes code reuse, maintainability, and efficient CI/CD workflows. This guide will walk you through setting up a repository structure that supports both [code-native integrations](https://prismatic.io/docs/integrations/code-native.md) and [custom connectors](https://prismatic.io/docs/custom-connectors.md). Click [here](https://vimeo.com/1129318603) to watch a webinar on this topic. #### Example repository[​](#example-repository "Direct link to Example repository") For a complete working example, check out our [example project structure repository](https://github.com/prismatic-io/example-project-structure) on GitHub. This repository demonstrates best practices for organizing your Prismatic projects and includes CI/CD automation using GitHub Actions. #### Recommended project structure[​](#recommended-project-structure "Direct link to Recommended project structure") A well-organized Prismatic repository typically includes the following directories: ```text my-prismatic-project/ ├── .github/ │ └── workflows/ # CI/CD pipelines for automation │ ├── components.yml │ └── integrations.yml ├── components # Custom components for low-code integrations │ ├── acme │ └── todoist ├── integrations # Code-native integrations │ ├── slack │ └── todoist └── shared-libs # Shared libraries for CNI + Components │ ├── acme │ └── todoist └── package.json ``` ##### Components directory[​](#components-directory "Direct link to Components directory") The `components/` directory contains your custom connectors - reusable building blocks that can be used across multiple integrations. Each component should be in its own subdirectory with its own `package.json`, source code, and tests. ```text components/ ├── acme-crm/ │ ├── src/ │ │ ├── index.ts │ │ ├── actions.ts │ │ └── connections.ts │ ├── package.json │ └── tsconfig.json └── todoist/ ├── src/ │ └── index.ts └── package.json ``` Components are published to your Prismatic tenant and can then be used in both low-code and code-native integrations. ##### Integrations directory[​](#integrations-directory "Direct link to Integrations directory") The `integrations/` directory contains your [code-native integrations](https://prismatic.io/docs/integrations/code-native.md) - complete integration solutions built entirely in TypeScript. Like components, each integration should have its own subdirectory with its dependencies and configuration. Each integration should be initialized using the Prismatic CLI tool by running `prism integrations:init`. ```text integrations/ └── slack/ ├── src/ │ ├── index.ts │ └── flows.ts │ └── configPages.ts ├── package.json └── tsconfig.json ``` ##### Shared libraries directory[​](#shared-libraries-directory "Direct link to Shared libraries directory") The `shared-libs/` directory contains reusable TypeScript packages that can be shared across both components and integrations. This promotes code reuse and keeps your codebase DRY (Don't Repeat Yourself). Using shared libraries offers several advantages: 1. **Faster iterations**: Updates to shared code immediately benefit all dependent projects 2. **Local code visibility**: All code remains in your repository for easier navigation and debugging 3. **Code reusability**: Common logic (API clients, utilities, types) centralizes in one location ```text shared-libs/ ├── acme-client/ │ ├── src/ │ │ ├── index.ts │ │ └── types.ts │ ├── package.json │ └── tsconfig.json └── common-utils/ ├── src/ │ └── index.ts └── package.json ``` Shared libraries can be referenced in your components and integrations as local dependencies in their `package.json` files: components/acme-crm/package.json ```json { "dependencies": { "@prismatic-io/spectral": "^9.0.0", "acme-client": "file:../../shared-libs/acme-client" } } ``` Why use shared libraries? When building both custom components and code-native integrations that interact with the same external APIs, you have two options for sharing code: 1. Abstract common logic into shared libraries 2. Publish custom component and install the component's [manifest](https://prismatic.io/docs/integrations/code-native/existing-components.md#adding-component-manifests-to-your-code-native-project) into your code-native project. Using shared libraries is often the better choice because it allows for faster iterations and easier debugging. When you update a shared library, all components and integrations that depend on it immediately benefit from the changes without needing to republish components. #### Publishing components and integrations[​](#publishing-components-and-integrations "Direct link to Publishing components and integrations") ##### Publishing from the command line[​](#publishing-from-the-command-line "Direct link to Publishing from the command line") You can manually publish components and integrations using the Prism CLI: ```bash # Publish a component cd components/my-component npm run build prism components:publish # Publish a code-native integration cd integrations/my-integration npm run build export INTEGRATION_ID=$(prism integrations:import) prism integrations:publish ${INTEGRATION_ID} ``` ##### Publishing in a CI/CD pipeline[​](#publishing-in-a-cicd-pipeline "Direct link to Publishing in a CI/CD pipeline") For automated publishing, you can integrate the Prism CLI into your CI/CD pipeline. The CLI supports authentication via refresh tokens, making it easy to automate deployments. If you're using **GitHub Actions**, Prismatic provides pre-built actions that make publishing even easier. See our [GitHub Actions guide](https://prismatic.io/docs/api/github-actions.md) for detailed instructions on: * Setting up authentication with GitHub secrets * Publishing components automatically when code changes * Publishing integrations automatically when code changes * Ensuring components are published before integrations that depend on them * Linking component and integration versions to pull requests For other CI/CD systems (GitLab CI, Jenkins, CircleCI, Azure DevOps, etc.), you can use the Prism CLI directly. See [Publishing components in a CI/CD pipeline](https://prismatic.io/docs/custom-connectors/publishing.md#publishing-components-in-a-cicd-pipeline) for details. #### Managing multiple environments[​](#managing-multiple-environments "Direct link to Managing multiple environments") If you have multiple Prismatic tenants (for example, a development environment and production environments in different regions), you can manage them in your CI/CD pipeline by: 1. Creating separate refresh tokens for each environment 2. Storing them as secrets in your CI/CD system (e.g., `PRISM_REFRESH_TOKEN_DEV`, `PRISM_REFRESH_TOKEN_PROD`) 3. If your user is associated with multiple tenants in a single region, also storing the corresponding `PRISMATIC_TENANT_ID` for each environment 4. Storing the Prismatic URL for each environment as variables (e.g., `PRISMATIC_URL_DEV`, `PRISMATIC_URL_PROD`) 5. Creating separate workflow jobs or branches for each environment See the [Example Project Structure](https://github.com/prismatic-io/example-project-structure) repo for a complete example of publishing to multiple regions. The example repo leverages GitHub Actions' [Environments](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment) feature to manage secrets for different Prismatic tenants. #### Best practices[​](#best-practices "Direct link to Best practices") ##### Use version control[​](#use-version-control "Direct link to Use version control") Always commit your component and integration source code to version control (Git). This allows you to track changes, collaborate with team members, and roll back if needed. ##### Organize by domain[​](#organize-by-domain "Direct link to Organize by domain") If you have lots of custom components, group related components and integrations together. For example, if you have multiple components related to your CRM system, consider placing them in a `components/crm/` subdirectory. ##### Document your code[​](#document-your-code "Direct link to Document your code") Add README files to your components and integrations explaining: * What the component or integration does * How to install dependencies * How to build and test locally * Any configuration required ##### Test in a dev environment before publishing[​](#test-in-a-dev-environment-before-publishing "Direct link to Test in a dev environment before publishing") When possible, test your components and integrations in a development Prismatic tenant before publishing to production. This helps catch issues early and ensures a smoother deployment process. ##### Leverage monorepo tools[​](#leverage-monorepo-tools "Direct link to Leverage monorepo tools") For larger projects with many components and integrations, consider using monorepo tools like: * [npm workspaces](https://docs.npmjs.com/cli/v7/using-npm/workspaces) * [yarn workspaces](https://yarnpkg.com/features/workspaces) * [pnpm workspaces](https://pnpm.io/workspaces) * [bun workspaces](https://bun.com/docs/install/workspaces) These tools make it easier to manage dependencies, run scripts across multiple packages, and optimize build times. #### Next steps[​](#next-steps "Direct link to Next steps") Now that you have your repository set up, you're ready to start building: * [Build your first code-native integration](https://prismatic.io/docs/get-started/build-integrations/first-integration-code-native.md) * [Write a custom component](https://prismatic.io/docs/custom-connectors.md) * [Set up GitHub Actions for automated publishing](https://prismatic.io/docs/api/github-actions.md) * [Explore the Prism CLI](https://prismatic.io/docs/cli.md) --- ### Custom Connector Basics In this tutorial we introduce the concept of a custom connector, and walk through the steps to build one. We cover how to set up your development environment, create a new connector project, and implement basic functionality. We build the connection line-by-line by hand to give you a deeper understanding of how custom connectors work, but if you want to see how our Claude Skills can accelerate custom connector development, skip ahead to [Build a Custom Connector with AI](https://prismatic.io/docs/get-started/build-integrations/wrap-api-custom-connector.md). Prerequisites * Install a recent version of [Node.js](https://nodejs.org/en) * Install [Prismatic's CLI tool](https://prismatic.io/docs/cli.md) and [authenticate it](https://prismatic.io/docs/cli.md#authenticating-with-the-cli-tool) against your Prismatic tenant Initialize a new connector project Initialize a new connector project ```bash prism components:init my-first-connector ``` This will create a new directory called `my-first-connector` which contains your connector project. Open this directory in your code editor, and then install the project's dependencies: Install dependencies ```bash npm install ``` Finally, delete all of the files in `src/` except for `src/index.ts` and `src/actions.ts`. Replace the contents of `src/index.ts` with the following boilerplate code: src/index.ts ```ts import { component } from "@prismatic-io/spectral"; import actions from "./actions"; export default component({ key: "my-first-connector", public: false, display: { label: "My First Connector", description: "This is my first custom connector", iconPath: "icon.png", }, actions, }); ``` Create your first action Next, let's add an action to our connector that fetches data from a public API. Replace `src/actions.ts` with the following code: src/actions.ts ```ts import axios from "axios"; // Popular HTTP client import { action } from "@prismatic-io/spectral"; const listItems = action({ display: { label: "List Items", description: "List all items in the system", }, inputs: {}, // No inputs for this action perform: async (context, inputs) => { const response = await axios.get( "https://my-json-server.typicode.com/prismatic-io/placeholder-data/items", ); return { data: response.data }; // Return the list of items }, }); export default { listItems }; ``` Build, publish and test your connector Now let's test our action to make sure it works as expected. First, build the connector: Build the connector ```bash npm run build ``` Then, publish the connector to your Prismatic tenant: Publish the connector ```bash prism components:publish ``` Finally, navigate to the Prismatic UI and create a new flow that uses your connector's "List Items" action to fetch and display the list of items. ![Testing the connector in a flow](/docs/assets/images/test-list-items-d8813236b048eee25eed77ed8588c714.png) Add actions with inputs Our first action took no inputs. Let's add two more actions that take inputs. src/actions.ts ```ts import axios from "axios"; // Popular HTTP client import { action, input, util } from "@prismatic-io/spectral"; const listItems = action({ display: { label: "List Items", description: "List all items in the system", }, inputs: {}, // No inputs for this action perform: async (context, inputs) => { const response = await axios.get( "https://my-json-server.typicode.com/prismatic-io/placeholder-data/items", ); return { data: response.data }; // Return the list of items }, }); const getItemById = action({ display: { label: "Get Item By ID", description: "Get a specific item by its ID", }, inputs: { itemId: input({ label: "Item ID", type: "string", required: true, }), }, perform: async (context, inputs) => { const response = await axios.get( `https://my-json-server.typicode.com/prismatic-io/placeholder-data/items/${inputs.itemId}`, ); return { data: response.data }; // Return the specific item }, }); const createItem = action({ display: { label: "Create Item", description: "Create a new item in the system", }, inputs: { name: input({ label: "Item Name", type: "string", required: true, }), quantity: input({ label: "Quantity", type: "string", clean: (value) => util.types.toNumber(value), // Convert input to a number required: true, }), }, perform: async (context, inputs) => { const response = await axios.post( "https://my-json-server.typicode.com/prismatic-io/placeholder-data/items", { name: inputs.name, quantity: inputs.quantity, }, ); return { data: response.data }; // Return the created item }, }); export default { listItems, getItemById, createItem }; ``` Re-run the build and publish commands, and test the new actions that have inputs: ```bash npm run build prism components:publish ``` ![Testing the new actions with inputs in a flow](/docs/assets/images/test-actions-with-inputs-026301a8241934f670b9c30bf00bf439.png) Add authentication and a reusable HTTP client While our dummy API doesn't require authentication, most real APIs do. Let's add support for an API key and use that API key as a bearer token in the Authorization header of our HTTP requests. We'll also refactor our code to use a reusable HTTP client that automatically includes the API key in each request. src/connections.ts ```ts import { connection, input } from "@prismatic-io/spectral"; const acmeApiKey = connection({ key: "acmeApiKey", display: { label: "Acme API Key", description: "API key for authenticating with the Acme API", }, inputs: { baseUrl: input({ label: "Base URL", type: "string", required: true, default: "https://my-json-server.typicode.com/prismatic-io/placeholder-data", }), apiKey: input({ label: "API Key", comments: "Generate an API key from your Acme account (settings tab) and enter it here", type: "password", required: true, }), }, }); // Connections are represented as an array in a component definition export default [acmeApiKey]; ``` Update `src/index.ts` to import the connection and include it in the component definition: src/index.ts ```diff diff --git a/src/index.ts b/src/index.ts index 4589d73..29fdb7b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { component } from "@prismatic-io/spectral"; import actions from "./actions"; +import connections from "./connections"; export default component({ key: "my-first-connector", @@ -10,4 +11,5 @@ export default component({ iconPath: "icon.png", }, actions, + connections, }); ``` Now, add a reusable HTTP client that uses the connection's base URL and API key: src/client.ts ```ts import { createClient } from "@prismatic-io/spectral/dist/clients/http"; import { Connection, util } from "@prismatic-io/spectral"; export const acmeClient = (connection: Connection) => { return createClient({ baseUrl: util.types.toString(connection.fields.baseUrl), headers: { Authorization: `Bearer ${connection.fields.apiKey}`, }, // Enable debug mode to log request and response details // Disable before deploying to production debug: true, }); }; ``` Finally, update `src/actions.ts` to use the reusable HTTP client, and add a connection input to each step: src/actions.ts ```diff diff --git a/src/actions.ts b/src/actions.ts index f8828b9..425ab73 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -1,16 +1,21 @@ -import axios from "axios"; // Popular HTTP client +import { acmeClient } from "./client"; import { action, input, util } from "@prismatic-io/spectral"; +const connectionInput = input({ + label: "Acme Connection", + type: "connection", + required: true, +}); + const listItems = action({ display: { label: "List Items", description: "List all items in the system", }, - inputs: {}, // No inputs for this action + inputs: { connection: connectionInput }, perform: async (context, inputs) => { - const response = await axios.get( - "https://my-json-server.typicode.com/prismatic-io/placeholder-data/items", - ); + const client = acmeClient(inputs.connection); + const response = await client.get("/items"); return { data: response.data }; // Return the list of items }, }); @@ -21,6 +26,7 @@ const getItemById = action({ description: "Get a specific item by its ID", }, inputs: { + connection: connectionInput, itemId: input({ label: "Item ID", type: "string", @@ -28,9 +34,8 @@ const getItemById = action({ }), }, perform: async (context, inputs) => { - const response = await axios.get( - `https://my-json-server.typicode.com/prismatic-io/placeholder-data/items/${inputs.itemId}`, - ); + const client = acmeClient(inputs.connection); + const response = await client.get(`/items/${inputs.itemId}`); return { data: response.data }; // Return the specific item }, }); @@ -41,6 +46,7 @@ const createItem = action({ description: "Create a new item in the system", }, inputs: { + connection: connectionInput, name: input({ label: "Item Name", type: "string", @@ -54,13 +60,11 @@ const createItem = action({ }), }, perform: async (context, inputs) => { - const response = await axios.post( - "https://my-json-server.typicode.com/prismatic-io/placeholder-data/items", - { - name: inputs.name, - quantity: inputs.quantity, - }, - ); + const client = acmeClient(inputs.connection); + const response = await client.post("/items", { + name: inputs.name, + quantity: inputs.quantity, + }); return { data: response.data }; // Return the created item }, }); ``` Build and publish once more. You'll notice that an integration-specific connection is automatically created for your steps, and that you'll need to go through the config wizard to set up the connection before you can test the steps. **Tip**: Enable logs in the step result drawer to see the details of the HTTP requests being made by your connector's actions, which can be helpful for debugging. ![Testing the connector with authentication and reusable HTTP client in a flow](/docs/assets/images/test-connector-with-authentication-f1e14b48eabb9e945b28f0d7711b678d.png) **Next steps**: * [Ensure your API is ready for integrations](https://prismatic.io/docs/get-started/build-integrations/prepare-api-for-integrations.md) * [Build a Custom Connector with AI](https://prismatic.io/docs/get-started/build-integrations/wrap-api-custom-connector.md) --- ### Dynamic Data Sources In the [Config Wizard Overview](https://prismatic.io/docs/integrations/config-wizard.md) video, we showed how to create config variables that allow your customers to enter connection credentials and select options when configuring an instance of your integration. But, your customers may want to select from dynamic options that are fetched from an external source, such as a list of accounts or projects in a third-party system. You can create config variables that fetch data from external sources, allowing your customers to select from dynamic options when configuring an instance of your integration. In this video we walk through how to create a data source-backed config variable - we start with a simple data source using a placeholder API, and then build a more complex data source that interacts with data in Salesforce using an established Salesforce connection. We also show how to test the data source locally in our IDE. **Also See:** * [Writing Custom Data Sources](https://prismatic.io/docs/custom-connectors/data-sources.md) --- ### Field Mappers with JSON Forms Your customers will come to you with custom third-party configurations. One customer may store their Lead information in a vanilla Salesforce **Lead** object, another might customize the **Lead** object with additional custom fields, and a third might use an entirely custom `my_lead__c` record type to store their Lead information. You need to know where your users are storing their data, and which of their fields map to fields in your app. A field mapper JSON Forms data source allows you to prompt your users to map fields between your app and their third-party system, and then reference those mappings in your flows. If you have not yet, we strongly recommend reviewing [Dynamic Data Sources](https://prismatic.io/docs/get-started/build-integrations/dynamic-data-sources.md) and [Intro to JSON Forms](https://prismatic.io/docs/get-started/build-integrations/json-forms-intro.md) before watching this video. **Also See:** * [Prismatic VS Code Extension](https://prismatic.io/docs/dev-tools/vscode-extension.md) that we used in this video * [JSON Form validation](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md) to learn how to add validation to your JSON Forms --- ### Build with Code and AI This "Hello, World" style guide demonstrates how to build a code-native integration using Prismatic's TypeScript SDK and AI skills. Recommended: Use Claude to build your integration If you use an AI coding assistant like Claude Code you can leverage [Prismatic's AI skills](https://github.com/prismatic-io/prismatic-skills) to build your integration faster. These skills provide your AI agent with example code snippets, efficient documentation lookup, and best practice guidance. Install Prismatic's AI skills in your coding assistant Open `claude` and run the following commands to install Prismatic's marketplace and skills Install Prismatic's AI marketplace and skills ```text /plugin marketplace add prismatic-io/prismatic-skills /plugin install prismatic-skills@prismatic-skills /reload-plugins ``` Invoke the build-integration skill with a prompt describing your integration Next, invoke your newly-installed `build-integration` command and skill with a prompt describing the integration you want to build Prompt Claude to build your integration ```text /prismatic-skills:build-integration Build a Prismatic code-native integration named `TODO List Slack Notifications`. This integration will fetch an array of todo items from a REST API endpoint. Each item will have a `completed` boolean property and `task` string property. For each item that is not completed, the integration will post a message to a Slack channel with the task details. This integration should: - Use a configuration variable for the REST API endpoint URL, which defaults to `https://my-json-server.typicode.com/prismatic-io/placeholder-data/todo` - Use a Slack OAuth connection for authentication - the build-only OAuth connection is fine for now - Prompt a user to select one of their Slack channels from a dropdown menu - Run on a schedule each day at 8:00 AM UTC ``` Your AI assistant will scaffold a new project, install dependencies, write the flows, configure the Slack OAuth connection, and import the integration into your Prismatic tenant for testing. See full [agent skills documentation](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md). #### Get started from scratch[​](#get-started-from-scratch "Direct link to Get started from scratch") If you prefer to learn by building the integration step-by-step yourself, follow the steps below to scaffold a new code-native integration. Our example integration will: 1. Be configurable with a REST API endpoint config variable and a Slack channel dropdown menu 2. Fetch a list of TODO items from a REST API endpoint 3. Check each item for a `completed` boolean property 4. For each incomplete item, post a message to a Slack channel with the task details Prerequisites * Install a recent version of [Node.js](https://nodejs.org/en) * Install [Prismatic's CLI tool](https://prismatic.io/docs/cli.md) and [authenticate it](https://prismatic.io/docs/cli.md#authenticating-with-the-cli-tool) against your Prismatic tenant * Authenticate a Slack connection by navigating to **Components** > **Slack** > **Connections** in the Prismatic web app, selecting the **Build Only** connection, and clicking **Connect**. Initialize a new integration project Use the [prism CLI tool](https://prismatic.io/docs/cli.md) to scaffold a new code-native integration project: ```bash prism integrations:init todo-slack-notifications ``` This creates a new directory called `todo-slack-notifications`. Open this directory in your code editor and then install the project's dependencies ```bash npm install ``` Finally, delete `src/flows.test.ts` and `src/client.ts` - we'll cover unit testing and creating reusable HTTP clients in another guide. Remove extraneous files ```bash rm src/client.ts src/flows.test.ts ``` Install the Slack component manifest Our integration will rely on the built-in [Slack](https://prismatic.io/docs/components/slack.md) component, so we need to install the Slack component manifest into our project: ```bash npx cni-component-manifest slack ``` This will create a set of files in `src/manifests/slack` that define the Slack component's actions, connections, triggers, and data sources. Next, add Slack to your component registry: src/componentRegistry.ts ```ts import { componentManifests } from "@prismatic-io/spectral"; import slack from "./manifests/slack"; export const componentRegistry = componentManifests({ slack }); ``` Create a configuration experience For this integration, we want our users to: 1. Provide the endpoint for the REST API that the integration will fetch todo items from 2. Select a Slack channel from a dropdown menu to post incomplete tasks to We'll add a basic config variable for the REST API, and we'll use a [data source](https://prismatic.io/docs/integrations/data-sources.md) to fetch the list of Slack channels and present them as a picklist dropdown menu. For simplicity, we'll leverage a [build-only](https://prismatic.io/docs/integrations/connections.md#build-only-connections) connection, which is a connection that exists for development and testing purposes - for production, you'd want to create your own Slack OAuth app. src/configPages.ts ```ts import { configPage, configVar } from "@prismatic-io/spectral"; import { slackReusableConnection } from "./manifests/slack/connections"; import { slackSelectChannels } from "./manifests/slack/dataSources/selectChannels"; export const configPages = { Configuration: configPage({ elements: { "Slack Connection": slackReusableConnection( // Replace this with your own UUID. See src/manifests/slack/connections/index.ts "12345678-0000-0000-0000-000000000000", ), // Include some helper text to guide users through the configuration experience _helperText0: "

Integration Configuration

", _helperText1: "

Please provide the API endpoint for your todo items and select a Slack " + "channel where you'd like to receive notifications of incomplete tasks.

", "TODO API Endpoint": configVar({ stableKey: "todo-api-endpoint", dataType: "string", defaultValue: "https://my-json-server.typicode.com/prismatic-io/placeholder-data/todo", }), "Slack Channel": slackSelectChannels("slack-channel", { connection: { configVar: "Slack Connection" }, }), }, }), }; ``` Finding your build-only connection UUID Each organization has a distinct build-only connection ID for testing. Look at `src/manifests/slack/connections/index.ts` to find the `slackReusableConnection`'s `stableKey` for your tenant. Build a flow to fetch TODO items and post incomplete ones to Slack Now that our integration is configurable, we'll build a flow that runs on a schedule, fetches the list of TODO items from the configured REST API endpoint, and uses the Slack component to post messages to the configured Slack channel for each incomplete task. src/flows.ts ```ts import { flow } from "@prismatic-io/spectral"; import axios from "axios"; interface TodoItem { id: number; completed: boolean; task: string; } export const checkTodoItems = flow({ name: "Check Todo Items", stableKey: "check-todo-items", description: "Fetch todo items from an API and send message for incomplete items.", schedule: { value: "0 8 * * *" }, // Run each day at 8:00 AM UTC onExecution: async (context) => { const response = await axios.get( context.configVars["TODO API Endpoint"], ); const todoItems = response.data; for (const todoItem of todoItems) { if (todoItem.completed) { context.logger.info( `Task ${todoItem.id} is completed: ${todoItem.task}`, ); } else { await context.components.slack.postMessage({ connection: context.configVars["Slack Connection"], channelName: context.configVars["Slack Channel"], message: `Task ${todoItem.id} is incomplete: ${todoItem.task}`, }); } } return { data: null }; }, }); export default [checkTodoItems]; ``` Build and import your integration Now it's time to build your integration Build the integration ```bash npm run build ``` Finally, import your integration and open it in the Prismatic web app Import and open the integration in Prismatic ```bash prism integrations:import --open ``` Test your integration Once the integration opens in your browser, navigate to **Test Configuration** > **Test Instance Configuration** and walk through the config wizard. ![Config wizard showing the REST API endpoint input and Slack channel dropdown](/docs/assets/images/config-wizard-455182cd859df096d9ea0d032f3f946b.png) After completing the configuration, trigger a test execution of your flow by clicking **Test** in the top right of the page. ![Test showing messages posted to Slack for the incomplete tasks](/docs/assets/images/slack-result-a158a161c27c0c92f5fad867d98b8ac3.png) --- ### Build with Low-Code When building integrations in Prismatic, you can choose to [write TypeScript code](https://prismatic.io/docs/get-started/build-integrations/first-integration-code-native.md) using our SDK and AI skills, or you can use our sophisticated low-code designer to construct workflows using pre-built components and custom code steps where necessary. In this "hello world-style" guide, we walk through the basics of building an integration using the low-code designer by building a simple workflow that fetches TODO items from a REST API and posts incomplete items to a Slack channel. --- ### Intro to JSON Forms Most config variables are singleton - that is, they prompt for a single string or boolean value, or a single selection from a dropdown. JSON Forms are a powerful tool for building dynamic user interfaces in your integrations. They allow you to create complex forms that can capture multiple pieces of information from your users in a structured way. This is especially useful when you need to collect data that has a specific format, such as an array of objects or a nested structure. In this video we create a JSON Form that prompts a user for several types of data, we present additional fields when the user selects a specific option, and we add some validation to ensure the user enters valid selections. **Also See:** * [JSON Forms Playground](https://prismatic.io/docs/jsonforms/playground) * [JSON Forms Reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference.md) * [Field Mappers with JSON Forms](https://prismatic.io/docs/get-started/build-integrations/field-mappers-with-json-forms.md) * [AI-Assisted Development](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md), which we used at the end of the video to help Claude build a JSON Form --- ### Large Data Syncs If you have a large set of data to sync from a source system to a destination system, you can use the `batchFlowTrigger` pattern to page of records from the source system and process them in batches. This pattern is useful for integrations that need to do an initial bulk import of data when first deployed, and then keep data up to date by processing incoming webhooks in real time. It can also be used to periodically re-sync data from a source system on a schedule. In this video, we build a basic code-native flow that pages through a set of records, breaks the data into smaller batches, and processes each batch in a single execution path. **Resources**: * [Large Data Sync Docs](https://prismatic.io/docs/integrations/common-patterns/large-data-sync.md) * [Example from Video](https://github.com/prismatic-io/integration-templates/tree/main/simple-initial-data-sync) * [Salesforce Example](https://github.com/prismatic-io/integration-templates/tree/main/salesforce-initial-data-sync) --- ### Trigger Lifecycle Events When a customer deploys or deletes an instance of your integration, you often need to do something in a third-party system - register a webhook, create a folder, or tear down resources. Prismatic lifecycle functions let you hook into those moments. #### The three lifecycle hooks[​](#the-three-lifecycle-hooks "Direct link to The three lifecycle hooks") **`onInstanceDeploy`** runs synchronously each time an instance is deployed (including re-deployments). Use it for short setup tasks like creating folders or custom record types. It must complete within 30 seconds. **`webhookLifecycleHandlers.create`** also runs on deploy, but is specifically for registering webhooks in third-party apps. It also runs when you enter [listening mode](https://prismatic.io/docs/integrations/triggers/listening-mode.md) in the integration designer, making it easy to test webhook-driven flows without a full deployment. You can also trigger it manually from the **Test Runner** drawer using **Test Deploy**. **`onInstanceDelete`** and **`webhookLifecycleHandlers.delete`** are the mirrors of their deploy counterparts - they run when an instance is deleted (or when you exit listening mode) so you can clean up whatever was created. For longer-running setup tasks (like an initial data sync), use an [Instance Deployed management trigger](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger) instead. Management triggers run as full flow executions and aren't subject to the 30-second limit. note `webhookLifecycleHandlers` functions don't have access to flow-scoped persisted data. Store any state you need (like a webhook ID) in `crossFlowState`, using the flow's stable ID to keep keys unique across flows. Write idempotent lifecycle functions Lifecycle functions run every time an instance is deployed - not just the first time. Make sure they can run multiple times safely. #### Learn more[​](#learn-more "Direct link to Learn more") See [Instance lifecycle](https://prismatic.io/docs/integrations/lifecycle.md) for the full execution order, code examples, and an FAQ on when to use each hook. --- ### OAuth 2.0 Crash Course The [OAuth 2.0 Authorization Code Grant Type](https://prismatic.io/docs/integrations/connections/oauth2/authorization-code-grant-type.md) is the most common way to authenticate with the other apps and services your customers use. In this video, we cover what OAuth 2.0 is and set up a couple of OAuth 2.0 apps from scratch to show you how to add OAuth 2.0 authentication to your integrations. Start the verification process early! Some apps require you to submit your OAuth 2.0 app for review before you can use it in production. This process can take time, so we recommend starting it as early as possible. --- ### Prepare Your API for Integrations As you prepare to build integrations between your app and the other tools your customers use, it's critical to design your API with integrations in mind. This document covers best practices for making your API easy to integrate with, including authentication setup, webhook design, and API features that support integration builders. **Checklist** * Set up seamless [authentication](https://prismatic.io/docs/get-started/build-integrations/prepare-api-for-integrations.md#authentication) on behalf of your customers * Implement [webhooks](https://prismatic.io/docs/get-started/build-integrations/prepare-api-for-integrations.md#webhooks) to notify integrations when records change in your app * \[x] [Design your API](https://prismatic.io/docs/get-started/build-integrations/prepare-api-for-integrations.md#api-design-for-integrations) with features that support integrations, like pagination and incremental endpoints * Build a [custom connector](https://prismatic.io/docs/get-started/build-integrations/prepare-api-for-integrations.md#build-a-custom-connector) so your team members and customers can interact with your API in a workflow #### Authentication[​](#authentication "Direct link to Authentication") Normally, the [OAuth 2.0 Authorization Code](https://prismatic.io/docs/integrations/connections/oauth2/authorization-code-grant-type.md) flow is the best choice for authentication for integrations because your end user is presented a simple **Connect** button and easy authorization experience. If third-party services are integrating with your API, support for OAuth is essential. Your users, though, will interact with your integration UI through the [embedded marketplace](https://prismatic.io/docs/embed/marketplace.md) or [workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) while logged in to your app. Clicking a **Connect** button to connect *to* your app, *from* your app, may feel like a silly experience to your users. We recommend supporting an authentication method that you, as the organization offering integrations, can set up on behalf of your customers. By supporting API keys, OAuth 2.0 Client Credentials, or another similar auth method, you as the organization can create an [org-activated customer connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md) for each of your customers for your app. If you activate a connection for your app for each of your customers, then when your customers go to set up an integration in your marketplace or workflow builder their authentication experience is seamless and simple - they're authenticated automatically by virtue of being logged into your app. #### Webhooks[​](#webhooks "Direct link to Webhooks") You have two options for keeping downstream systems up-to-date with changes in your app: * Your app can emit webhooks requests when things change * Your integrations can poll your API for changes Webhooks are the foundation of reliable, real-time integrations. Polling is slower and more resource-intensive. We strongly recommend implementing webhooks in your app. ##### Emit events for meaningful state changes[​](#emit-events-for-meaningful-state-changes "Direct link to Emit events for meaningful state changes") Emit a webhook event whenever a record is created, updated, or deleted. Think in terms of what downstream systems care about: if something changed that an integration might need to act on, emit an event for it. Each event should include: * **Event type** - a stable, namespaced identifier like `order.created` or `contact.updated` * **Timestamp** - in UTC ISO 8601 format * **Entity ID** - so integrations can fetch the full record if needed * **Payload** - include the changed data or at minimum the fields that changed * **Correlation / request ID** - useful for debugging ##### Provide a webhook registration API[​](#provide-a-webhook-registration-api "Direct link to Provide a webhook registration API") Integrations need to register and deregister webhook subscriptions programmatically. Expose endpoints to: * Create a webhook subscription * Allow callers to specify the URL where events should be sent * Callers should specify an array of event types (like `order.created`, `contact.updated`) that they care about - don't send every event by default * Your API should respond with a unique subscription ID that can be used to manage the subscription later * List active subscriptions * Delete a subscription by ID Prismatic's [instance lifecycle handlers](https://prismatic.io/docs/custom-connectors/triggers.md#example-app-event-trigger-using-webhooks) (`webhookLifecycleHandlers.create` and `.delete`) can call these endpoints automatically when customers enable or remove an integration or add a trigger to a workflow. This means webhook subscriptions are provisioned and cleaned up without any manual steps. ##### Sign your webhook payloads[​](#sign-your-webhook-payloads "Direct link to Sign your webhook payloads") Include an HMAC signature on every webhook delivery so integrations can verify the payload is authentic. The standard approach is to include a header like `X-Signature-256` with an HMAC-SHA256 hash of the request body, signed with a shared secret. Prismatic supports [HMAC signature verification](https://prismatic.io/docs/integrations/triggers/webhook/what-is-hmac.md) in webhook triggers, and your custom connector's trigger can include custom logic to handle your implementation of HMAC. #### API design for integrations[​](#api-design-for-integrations "Direct link to API design for integrations") A few API design choices make a significant difference when integrations are consuming your API at scale. ##### Support pagination and filtering[​](#support-pagination-and-filtering "Direct link to Support pagination and filtering") Integrations often fetch large record sets. Implement cursor-based or offset pagination, and support filtering by: * Last modified timestamp (essential for incremental sync) * Customer or tenant scope * Resource type Filtering by last modified timestamp allows integrations to fetch only records that changed since the last run, rather than pulling everything every time. ##### Provide incremental (delta) endpoints[​](#provide-incremental-delta-endpoints "Direct link to Provide incremental (delta) endpoints") A dedicated endpoint that returns records changed since a given cursor or timestamp is one of the most valuable things you can build for integrations. It enables efficient polling as a fallback when webhooks aren't available, and makes initial sync and reconciliation straightforward. ##### Publish an OpenAPI schema[​](#publish-an-openapi-schema "Direct link to Publish an OpenAPI schema") An OpenAPI (formerly Swagger) specification is useful for integration builders - it documents your endpoints, request/response shapes, and authentication requirements in a machine-readable format. It also makes it faster to build and update a custom connector or code-native integration. #### Build a custom connector[​](#build-a-custom-connector "Direct link to Build a custom connector") A custom connector is a reusable Prismatic component that wraps your API. It lets you (and your customers) build integrations using your app's actions and triggers in the low-code designer, without writing boilerplate HTTP calls in every integration. Building a connector for your own API is the best way to expose your platform's capabilities to integration builders. You define the actions (like "Create order" or "Update contact"), the authentication configuration, and the webhook triggers - and integration builders can use them like any other component. A good custom connector for your app typically includes: * [Actions](https://prismatic.io/docs/custom-connectors/actions.md) for your core create, read, update, and delete operations * [Triggers](https://prismatic.io/docs/custom-connectors/triggers.md) that receive and verify your webhook payloads, with lifecycle handlers to register and deregister subscriptions. You may have a "New Order" trigger that fires when your app emits an `order.created` event, for example. * A **connection** that supports [org-activated connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md) for seamless authentication See the [custom connector documentation](https://prismatic.io/docs/custom-connectors.md) to get started. The [wrap an API guide](https://prismatic.io/docs/get-started/build-integrations/wrap-api-custom-connector.md) walks through building a connector from scratch. --- ### Webhook Security When you build event-driven integrations, your webhook endpoints are publicly accessible - which means anyone on the internet can send requests to them. HMAC (hashed message authentication code) is the recommended way to verify that incoming requests are legitimate. #### How HMAC works[​](#how-hmac-works "Direct link to How HMAC works") HMAC generates a hash from the request body and a shared secret key. Before your source application sends a webhook request, it hashes the payload with the secret key and includes the resulting hash as a request header. When Prismatic receives the request, it hashes the body with the same key and compares the two hashes. If they match, the request is legitimate. The secret key is never sent in the payload - it's only used to generate and validate the signature. This means an attacker who intercepts a request cannot forge a valid hash without the key. #### Using HMAC in Prismatic[​](#using-hmac-in-prismatic "Direct link to Using HMAC in Prismatic") For apps that don't have an app-specific connector, you can use the built-in [HMAC Webhook Trigger](https://prismatic.io/docs/components/hash.md#hmacwebhooktrigger) from the Hash component. It handles validation automatically when: * The request body is hashed with a string secret key * The hash is included as a request header If you'd like to wrap HMAC logic into a reusable component, you can build a [custom webhook trigger](https://prismatic.io/docs/integrations/triggers/webhook/custom-hmac-trigger.md). #### Learn more[​](#learn-more "Direct link to Learn more") For a deeper explanation of HMAC, including code examples in Node.js, Python, PHP, and .NET, see [What is HMAC?](https://prismatic.io/docs/integrations/triggers/webhook/what-is-hmac.md) --- ### Crash Course in Webhooks Webhooks let external applications notify your integration the moment something happens - a new order, a form submission, a status change. Instead of polling for updates on a schedule, the source app sends an HTTP request to a webhook URL when an event occurs. In Prismatic, you configure a flow's trigger to listen for these incoming requests. When the webhook fires, the trigger receives the payload and your flow runs. In this video, we cover: * Sending webhook requests to Prismatic * Doing something with the request you received * Basic webhook setup with a popular app (GitHub) **Also See:** * [Triggers overview](https://prismatic.io/docs/integrations/triggers.md) - how to configure webhook triggers in your integration * [Webhook security](https://prismatic.io/docs/get-started/build-integrations/webhook-security.md) - how to validate incoming requests with HMAC --- ### What are Connections? [Connections](https://prismatic.io/docs/integrations/connections.md) allow you to send authenticated requests to third-party APIs and services. They are essential to interacting with the other apps your customers use. In this video we cover the [four types](https://prismatic.io/docs/integrations/connections.md#choosing-a-connection-type) of connections you can create in Prismatic, when to use each one, and how to create and use them in an integration. --- ### Build a Custom Connector with AI In [this previous session](https://prismatic.io/docs/get-started/build-integrations/custom-connector-basics.md) we introduced the concept of a custom connector, and walked through the steps to build a basic one. In this session, we'll build a fully functional custom connector that wraps a real API, includes data sources and actions, and connects to a third-party service with OAuth 2.0. For this example, we'll wrap [todoist](https://www.todoist.com/) - a popular task management app that has a well-documented REST API and supports OAuth 2.0 authentication. #### Use Prismatic Claude skills to help build your connector[​](#use-prismatic-claude-skills-to-help-build-your-connector "Direct link to Use Prismatic Claude skills to help build your connector") If you use an AI coding assistant like Claude Code you can leverage [Prismatic's AI skills](https://github.com/prismatic-io/prismatic-skills) to build your connector faster. These skills provide your AI agent with example code snippets, efficient documentation lookup, and best practice guidance. Install Prismatic's AI skills in your coding assistant Open `claude` and run the following commands to install Prismatic's marketplace and skills Install Prismatic's AI marketplace and skills ```text /plugin marketplace add prismatic-io/prismatic-skills /plugin install prismatic-skills@prismatic-skills /reload-plugins ``` Invoke the build-component skill with a prompt describing your connector Then, invoke your newly-installed `build-component` command and skill with a prompt describing the connector you want to build Prompt Claude to build your connector ```text /prismatic-skills:build-component My customers use Todoist, and I would like to interact with Todoist in a low-code integration. Build a custom component for Todoist. API documentation is available at https://developer.todoist.com/api/v1/ The component should include: - OAuth 2.0 for authentication - A data source where users can select a workspace from a dropdown menu - A data source where users can select a project from a dropdown menu - Actions for listing, creating, updating, closing and deleting tasks - Actions for listing projects and workspaces Remember to fetch a square PNG icon for the Todoist component and save it to assets/icon.png. ``` Your AI assistant will scaffold a custom connector project, install dependencies, write the data sources and actions and OAuth-based connection and import the connector into your Prismatic tenant for testing. See full [agent skills documentation](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md). --- ### Authenticate Embedded Users In [Embed the Marketplace](https://prismatic.io/docs/get-started/embedded-marketplace/get-started.md) we used Claude Code to build a simple embedded marketplace, adding a backend API to a a Next.js app to generate JWTs for a Next.js/React frontend. In this lesson, we'll dive deeper into how JWT-based authentication works for Prismatic's embedded marketplace and embedded workflow builder, and how to set it up using your own backend and frontend code. Use Claude Code to set up embedding The [Prismatic Skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) plugin for Claude Code can walk you through signing key setup, JWT backend generation, and frontend authentication interactively. It has reference material for JWT generation in a variety of backends. Run `/prismatic-skills:embedded` to get started. #### Authenticating users[​](#authenticating-users "Direct link to Authenticating users") One advantage of embedding the Prismatic embedded marketplace or embedded workflow builder is that users don't need to remember an additional set of credentials. They can log in to your application, and you can provide them with an authentication token that allows them to interact with Prismatic. You do this by signing a JSON Web Token (JWT) using a unique private key that you get from Prismatic. The JWT contains information about the authenticated user, like their name, external ID, and customer they're associated with. #### JWT signing keys[​](#jwt-signing-keys "Direct link to JWT signing keys") Before you can generate a JWT, you'll need a valid **signing key** from Prismatic. In Prismatic, click your organization name at the bottom of the left-hand sidebar, then open the **Embedded** tab. Click the **+ Add signing Key** button. note You must be an [owner or admin](https://prismatic.io/docs/configure-prismatic/organization-users.md#organization-team-member-roles) to create a signing key. You will be presented with a private signing key. Store this key somewhere safe - it's the key you'll use to sign JWTs for users in your application. ![Get signing key in Prismatic app](/docs/assets/images/example-private-key-0ea08c3d0624b47f74bc6b548ddacf23.png) Private keys are not stored in Prismatic Prismatic does not store the private signing key that is generated. Instead, we only save the last 8 characters so you can easily match up a private key you have with one in our system. We store the corresponding public key to verify signatures of JWTs you send. Save the private key that you generate somewhere safe. If it's ever compromised or you lose it, you can deactivate old keys and generate a new one. ##### Importing your own private signing key[​](#importing-your-own-private-signing-key "Direct link to Importing your own private signing key") You can also import your own private signing key for embedded authentication. The OpenSSL CLI tool is most commonly used for generating public/private key pairs yourself: ```bash # Generate a private key with 4096 bit encryption openssl genrsa -out my-private-key.pem 4096 # Generate the corresponding public key openssl rsa -in my-private-key.pem -pubout > my-public-key.pub ``` This will generate two files - a private key called `my-private-key.pem` and a public key called `my-public-key.pub`. Your public key will look like this: ```text -----BEGIN PUBLIC KEY----- EXAMPLE -----END PUBLIC KEY----- ``` Import the public key using the [Prism CLI](https://prismatic.io/docs/cli/prism.md#organizationsigning-keysimport): ```bash prism organization:signing-keys:import -p my-public-key.pub ``` #### Create and sign a JWT[​](#create-and-sign-a-jwt "Direct link to Create and sign a JWT") Now that you have a signing key, you can create and sign a JSON web token (JWT). Your backend API (not your frontend) should generate a JWT for your users. Your frontend client should request this JWT from your backend API. Do your JWT generation on the backend Generate JWT tokens on your backend. If you bake JWT generation (including the signing key) into your frontend, you create a security problem - someone with the signing key could sign their own JWT and pretend to be any user. Most programming languages offer JWT libraries for generating tokens - see [jwt.io](https://jwt.io/). The JWT that you generate for a user should have the following required claims (properties): | Field | Description | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `sub` | Unique user ID, typically a UUID | | `organization` | Your organization ID, found on the **Embedded** tab in Prismatic | | `customer` | The [external ID](https://prismatic.io/docs/customers/managing-customers.md#customer-external-ids) of the customer the user belongs to | | `iat` | Signing time as a Unix timestamp. It's a good idea to use (current time) minus a small buffer to account for clock skew. | | `exp` | Token expiration time as a Unix timestamp | You can also include these optional JWT claims: | Field | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `external_id` | External ID of the user in Prismatic; typically matches `sub` | | `name` | The user's name | | `customer_name` | If a customer with the given `customer` external ID doesn't exist, creates one with this name. Ignored if the customer already exists. | | `concurrent_execution_limit` | Maximum concurrent executions for this customer (integer). See [per-customer execution concurrency](https://prismatic.io/docs/customers/managing-customers.md#per-customer-execution-concurrency). | | `role` | Only for [user level configuration](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md) (ULC). `"admin"` can deploy ULC instances; `"user"` can supply user configuration. Defaults to `"admin"`. | For example, a JWT payload might look like this: Example JWT Payload ```json { "sub": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", "external_id": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", "name": "Phil Embedmonson", "organization": "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw==", "customer": "abc-123", "customer_name": "Hooli", "concurrent_execution_limit": 25, "role": "admin", "iat": 1631676917, "exp": 1631680517 } ``` The JWT's **header** should indicate that it's signed with RSA SHA-256, and should read: ```json { "alg": "RS256", "typ": "JWT" } ``` Use unique identifiers as JWT subjects The `sub` (subject) within the JWT identifies the user who is logged in to your system. The `sub` value can be any unique identifier - usually a UUID. A customer user with that identifier will be created in Prismatic if it doesn't already exist, and will be granted permissions to configure and deploy instances to the customer they're assigned to (you assign the user to a customer in the examples below). ##### Example JWT signing code snippets[​](#example-jwt-signing-code-snippets "Direct link to Example JWT signing code snippets") Here are a couple of code snippets for JavaScript and Python that would create a valid JWT to authenticate a user in Prismatic: * JavaScript Example * Python Example * .Net (C#) Example ```javascript import jsonwebtoken from "jsonwebtoken"; /* This is for illustrative purposes only; Obviously don't hard-code a signing key in your code. */ const signingKey = `-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP3+OrT0IXqCu4 EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEE c5R7QVzxgmGRXjPZGPf5huA1 -----END PRIVATE KEY-----`; const currentTime = Math.floor(Date.now() / 1000); const token = jsonwebtoken.sign( { sub: "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", // Some unique identifier for the user external_id: "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", // Generally matches sub name: "Phil Embedmonson", // Optional organization: "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw==", customer: "abc-123", // This is an external ID of a customer customer_name: "Hooli", // The optional name to use if we need to create a new customer record iat: currentTime - 60, // Issued 1 minute ago to account for clock skew exp: currentTime + 60 * 60, // 1 hour from now }, signingKey, // Store this somewhere safe { algorithm: "RS256" }, ); ``` ```python import jwt import math from time import time # This is for illustrative purposes only; # Obviously don't hard-code a signing key in your code. signing_key = '''-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP3+OrT0IXqCu4 EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEE c5R7QVzxgmGRXjPZGPf5huA1 -----END PRIVATE KEY-----''' current_time = math.floor(time()) token = jwt.encode( { "sub": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", # Some unique identifier for the user "external_id": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", # Generally matches sub "name": "Phil Embedmonson", # Optional "organization": "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw==", "customer": "abc-123", # This is an external ID of a customer "customer_name": "Hooli", # The optional name to use if we need to create a new customer record "iat": current_time, "exp": current_time + 60 * 60, # 1 hour from now }, signing_key, algorithm="RS256") ``` ```csharp using Microsoft.IdentityModel.Tokens; using System.Security.Cryptography; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; /* This is for illustrative purposes only; Obviously don't hard-code a signing key in your code. */ var pem = @"-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP3+OrT0IXqCu4 EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEE c5R7QVzxgmGRXjPZGPf5huA1 -----END PRIVATE KEY-----"; Task GetToken() { using var rsa = RSA.Create(); rsa.ImportFromPem(pem); var descriptor = new SecurityTokenDescriptor(); descriptor.SigningCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256) { CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false } }; var claims = new List { new Claim("sub", "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1"), // Some unique identifier for the user new Claim("external_id", "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1"), // Generally matches sub new Claim("name", "Phil Embedmonson"), // Optional new Claim("organization", "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw=="), new Claim("customer", "abc-123"), // This is an external ID of a customer new Claim("customer_name", "Hooli") // The optional name to use if we need to create a new customer record }; descriptor.Subject = new ClaimsIdentity(claims); descriptor.IssuedAt = DateTime.UtcNow; descriptor.Expires = DateTime.UtcNow.AddHours(1); // Expire 1 hour from now var token = new JwtSecurityTokenHandler().CreateEncodedJwt(descriptor); return Task.FromResult(token); } var token = await GetToken(); ``` An example NextJS implementation of JWT generation is available in [GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/pages/api/prismatic-auth.tsx). #### Use the JWT to authenticate the user[​](#use-the-jwt-to-authenticate-the-user "Direct link to Use the JWT to authenticate the user") Now that a user in your application has a signed JWT from the backend, you can authenticate them with the Prismatic library using the `prismatic.authenticate()` function in your frontend application: ```javascript // Some function that fetches the JWT from your API: const token = getJwtToken(); try { await prismatic.authenticate({ token }); } catch (error) { console.error(`Authentication failed with error ${error}`); } ``` If your customer or organization ID in your JWT are incorrect, if your JWT is not signed correctly, or if the JWT is expired, `prismatic.authenticate()` will throw an error. For an example React hook that wraps the `prismatic.authenticate()` function, see the [GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/src/usePrismaticAuth.ts#L66). ##### What does `prismatic.authenticate()` do?[​](#what-does-prismaticauthenticate-do "Direct link to what-does-prismaticauthenticate-do") When you call `prismatic.authenticate({ token })`, your client will make an authenticate call to Prismatic with the JWT you provide. The request is equivalent to this curl command: ```bash curl -X POST 'https://app.prismatic.io/embedded/authenticate' \ --header 'authorization: Bearer eyJhb...' \ ``` The request returns an HTTP 200 if the token is valid, and an error if it's not. If the user or customer you specified in the JWT doesn't already exist in Prismatic, they will be created automatically. #### Refreshing an embedded JWT[​](#refreshing-an-embedded-jwt "Direct link to Refreshing an embedded JWT") If a customer user's JWT expires, the customer user will see a 404 in their embedded iframe. To reauthenticate a user prior to expiration, ensure that your frontend app fetches a new token for your user and then run `prismatic.authenticate({ token })` with the new token. Existing iframes and the embedded client will be updated to use the new token. --- ### Marketplace with Custom UI Elements In this video we query Prismatic's API for a list of available marketplace integrations and present the data using custom native UI elements. This approach allows you to have complete control over the UI elements and user experience of your embedded marketplace, while still leveraging Prismatic's API and config wizard experience to power your integration marketplace. #### GraphQL query and TypeScript type used[​](#graphql-query-and-typescript-type-used "Direct link to GraphQL query and TypeScript type used") This is the GraphQL query and corresponding TypeScript type definition used in the video: Query for available marketplace integrations ```graphql query getMarketplaceIntegrations { marketplaceIntegrations( includeActiveIntegrations: true sortBy: [ { field: CATEGORY, direction: ASC } { field: NAME, direction: ASC } ] ) { nodes { id name allowMultipleMarketplaceInstances avatarUrl category description isCustomerDeployable marketplaceConfiguration overview documentation versionNumber instances { nodes { id name enabled deployedVersion } } deployedInstances deploymentStatus } } } ``` TypeScript types for marketplace integrations query ```typescript export interface MarketplaceIntegrationsResponse { marketplaceIntegrations: { nodes: MarketplaceIntegration[]; }; } export interface MarketplaceIntegration { id: string; name: string; allowMultipleMarketplaceInstances: boolean; avatarUrl?: string; category: string; description: string; isCustomerDeployable: boolean; marketplaceConfiguration: string; overview: string; documentation: string; versionNumber: number; instances: { nodes: Instance[] }; deployedInstances: "ZERO" | "ONE" | "MULTIPLE"; deploymentStatus: "ACTIVATED" | "PAUSED" | "UNCONFIGURED" | null; } export interface Instance { id: string; name: string; enabled: boolean; deployedVersion: number; } ``` **See Also**: * [Custom marketplace UI](https://prismatic.io/docs/embed/custom-marketplace-ui.md) * [Prismatic's GraphQL API](https://prismatic.io/docs/api.md) --- ### Embedded Marketplace Lifecycle Events You've embedded Prismatic's integration marketplace into your app. Now, you want to know what your customers are doing with the marketplace. There are many reasons why you might want to listen for marketplace events, including: * You want to know when a customer deploys an instance of your integration * You want to know if a customer begins to configure an instance, but never finishes * You want to set some config variables within the integration on your customer's behalf In this video we listen for marketplace events and respond to them from our web app. Also consider instance lifecycle events to track instances If your goal is to track deployment or removal of instances, you can also leverage on-deploy and on-delete instance workflows. Wire up a workflow to run when an instance is deployed or removed, and have that workflow send you a notification to let you know that the instance has been deployed or removed. See [Management Triggers](https://prismatic.io/docs/integrations/triggers/management.md). **See Also:** * [Listening to Marketplace Events](https://prismatic.io/docs/embed/marketplace.md#listening-to-marketplace-events) * [Dynamically Setting Config Variables in Marketplace](https://prismatic.io/docs/embed/marketplace.md#dynamically-setting-config-variables-in-marketplace) --- ### Embed the Marketplace When you embed the [integration marketplace](https://prismatic.io/docs/embed/marketplace.md) in your app, your customers can browse, configure and deploy integrations you've built right from your application. In this video, we walk through how to embed the Prismatic integration marketplace into your application, including adding seamless authentication and customizing theme to match your app's aesthetic. Recommended: Use AI to embed the marketplace in your app We recommend using Prismatic's [agent skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) to embed the integration marketplace in your app. The `embedded` skill is an expert in how Prismatic's authentication system works and best practices for embedding the marketplace in your app. Install Prismatic's AI marketplace and skills ```text /plugin marketplace add prismatic-io/prismatic-skills /plugin install prismatic-skills@prismatic-skills /reload-plugins ``` Then, invoke the `embedded` skill with a prompt describing your application and how you want to embed the marketplace Prompt Claude to build your embedded marketplace ```text /prismatic-skills:embedded Add the Prismatic embedded integration marketplace to my app. ``` --- ### Invoke Embedded Instances You've built an integration, and your customer has configured and deployed an instance for themselves. Now, you'd like to invoke that instance in response to some event in your app (someone clicks a button, etc). In this video, we demonstrate how to fetch the instance's webhook URLs and use those URLs to invoke the deployed instance's flows. This query was used in the video to fetch the webhook URLs for the Hubspot instance. You can modify it to fetch the webhook URLs for any of your embedded instances. Example GraphQL query to fetch Hubspot instance webhook URLs ```graphql query { instances(name: "Hubspot") { nodes { name integration { name } flowConfigs { nodes { flow { name } webhookUrl } } } } } ``` **See Also:** * [Prismatic's GraphQL API](https://prismatic.io/docs/api.md) * [Determining Webhook URLs Programmatically](https://prismatic.io/docs/api/common-queries/webhook-urls.md) --- ### Explore Marketplace Options In this video we explore the different options you have for embedding a marketplace experience in your app using Prismatic's embedded SDK. We cover embedding an iframe in an existing `
` and presenting the marketplace as a popover modal, as well as screen configuration options you have. **See Also**: * [Embedding marketplace](https://prismatic.io/docs/embed/marketplace.md) --- ### Authenticate Embedded Users In [Embed the Workflow Builder](https://prismatic.io/docs/get-started/embedded-workflow-builder/get-started.md) we used Claude Code to build a simple embedded marketplace, adding a backend API to a a Next.js app to generate JWTs for a Next.js/React frontend. In this lesson, we'll dive deeper into how JWT-based authentication works for Prismatic's embedded marketplace and embedded workflow builder, and how to set it up using your own backend and frontend code. Use Claude Code to set up embedding The [Prismatic Skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) plugin for Claude Code can walk you through signing key setup, JWT backend generation, and frontend authentication interactively. It has reference material for JWT generation in a variety of backends. Run `/prismatic-skills:embedded` to get started. #### Authenticating users[​](#authenticating-users "Direct link to Authenticating users") One advantage of embedding the Prismatic embedded marketplace or embedded workflow builder is that users don't need to remember an additional set of credentials. They can log in to your application, and you can provide them with an authentication token that allows them to interact with Prismatic. You do this by signing a JSON Web Token (JWT) using a unique private key that you get from Prismatic. The JWT contains information about the authenticated user, like their name, external ID, and customer they're associated with. #### JWT signing keys[​](#jwt-signing-keys "Direct link to JWT signing keys") Before you can generate a JWT, you'll need a valid **signing key** from Prismatic. In Prismatic, click your organization name at the bottom of the left-hand sidebar, then open the **Embedded** tab. Click the **+ Add signing Key** button. note You must be an [owner or admin](https://prismatic.io/docs/configure-prismatic/organization-users.md#organization-team-member-roles) to create a signing key. You will be presented with a private signing key. Store this key somewhere safe - it's the key you'll use to sign JWTs for users in your application. ![Get signing key in Prismatic app](/docs/assets/images/example-private-key-0ea08c3d0624b47f74bc6b548ddacf23.png) Private keys are not stored in Prismatic Prismatic does not store the private signing key that is generated. Instead, we only save the last 8 characters so you can easily match up a private key you have with one in our system. We store the corresponding public key to verify signatures of JWTs you send. Save the private key that you generate somewhere safe. If it's ever compromised or you lose it, you can deactivate old keys and generate a new one. ##### Importing your own private signing key[​](#importing-your-own-private-signing-key "Direct link to Importing your own private signing key") You can also import your own private signing key for embedded authentication. The OpenSSL CLI tool is most commonly used for generating public/private key pairs yourself: ```bash # Generate a private key with 4096 bit encryption openssl genrsa -out my-private-key.pem 4096 # Generate the corresponding public key openssl rsa -in my-private-key.pem -pubout > my-public-key.pub ``` This will generate two files - a private key called `my-private-key.pem` and a public key called `my-public-key.pub`. Your public key will look like this: ```text -----BEGIN PUBLIC KEY----- EXAMPLE -----END PUBLIC KEY----- ``` Import the public key using the [Prism CLI](https://prismatic.io/docs/cli/prism.md#organizationsigning-keysimport): ```bash prism organization:signing-keys:import -p my-public-key.pub ``` #### Create and sign a JWT[​](#create-and-sign-a-jwt "Direct link to Create and sign a JWT") Now that you have a signing key, you can create and sign a JSON web token (JWT). Your backend API (not your frontend) should generate a JWT for your users. Your frontend client should request this JWT from your backend API. Do your JWT generation on the backend Generate JWT tokens on your backend. If you bake JWT generation (including the signing key) into your frontend, you create a security problem - someone with the signing key could sign their own JWT and pretend to be any user. Most programming languages offer JWT libraries for generating tokens - see [jwt.io](https://jwt.io/). The JWT that you generate for a user should have the following required claims (properties): | Field | Description | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `sub` | Unique user ID, typically a UUID | | `organization` | Your organization ID, found on the **Embedded** tab in Prismatic | | `customer` | The [external ID](https://prismatic.io/docs/customers/managing-customers.md#customer-external-ids) of the customer the user belongs to | | `iat` | Signing time as a Unix timestamp. It's a good idea to use (current time) minus a small buffer to account for clock skew. | | `exp` | Token expiration time as a Unix timestamp | You can also include these optional JWT claims: | Field | Description | | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `external_id` | External ID of the user in Prismatic; typically matches `sub` | | `name` | The user's name | | `customer_name` | If a customer with the given `customer` external ID doesn't exist, creates one with this name. Ignored if the customer already exists. | | `concurrent_execution_limit` | Maximum concurrent executions for this customer (integer). See [per-customer execution concurrency](https://prismatic.io/docs/customers/managing-customers.md#per-customer-execution-concurrency). | | `role` | Only for [user level configuration](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md) (ULC). `"admin"` can deploy ULC instances; `"user"` can supply user configuration. Defaults to `"admin"`. | For example, a JWT payload might look like this: Example JWT Payload ```json { "sub": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", "external_id": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", "name": "Phil Embedmonson", "organization": "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw==", "customer": "abc-123", "customer_name": "Hooli", "concurrent_execution_limit": 25, "role": "admin", "iat": 1631676917, "exp": 1631680517 } ``` The JWT's **header** should indicate that it's signed with RSA SHA-256, and should read: ```json { "alg": "RS256", "typ": "JWT" } ``` Use unique identifiers as JWT subjects The `sub` (subject) within the JWT identifies the user who is logged in to your system. The `sub` value can be any unique identifier - usually a UUID. A customer user with that identifier will be created in Prismatic if it doesn't already exist, and will be granted permissions to configure and deploy instances to the customer they're assigned to (you assign the user to a customer in the examples below). ##### Example JWT signing code snippets[​](#example-jwt-signing-code-snippets "Direct link to Example JWT signing code snippets") Here are a couple of code snippets for JavaScript and Python that would create a valid JWT to authenticate a user in Prismatic: * JavaScript Example * Python Example * .Net (C#) Example ```javascript import jsonwebtoken from "jsonwebtoken"; /* This is for illustrative purposes only; Obviously don't hard-code a signing key in your code. */ const signingKey = `-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP3+OrT0IXqCu4 EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEE c5R7QVzxgmGRXjPZGPf5huA1 -----END PRIVATE KEY-----`; const currentTime = Math.floor(Date.now() / 1000); const token = jsonwebtoken.sign( { sub: "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", // Some unique identifier for the user external_id: "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", // Generally matches sub name: "Phil Embedmonson", // Optional organization: "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw==", customer: "abc-123", // This is an external ID of a customer customer_name: "Hooli", // The optional name to use if we need to create a new customer record iat: currentTime - 60, // Issued 1 minute ago to account for clock skew exp: currentTime + 60 * 60, // 1 hour from now }, signingKey, // Store this somewhere safe { algorithm: "RS256" }, ); ``` ```python import jwt import math from time import time # This is for illustrative purposes only; # Obviously don't hard-code a signing key in your code. signing_key = '''-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP3+OrT0IXqCu4 EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEE c5R7QVzxgmGRXjPZGPf5huA1 -----END PRIVATE KEY-----''' current_time = math.floor(time()) token = jwt.encode( { "sub": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", # Some unique identifier for the user "external_id": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", # Generally matches sub "name": "Phil Embedmonson", # Optional "organization": "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw==", "customer": "abc-123", # This is an external ID of a customer "customer_name": "Hooli", # The optional name to use if we need to create a new customer record "iat": current_time, "exp": current_time + 60 * 60, # 1 hour from now }, signing_key, algorithm="RS256") ``` ```csharp using Microsoft.IdentityModel.Tokens; using System.Security.Cryptography; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; /* This is for illustrative purposes only; Obviously don't hard-code a signing key in your code. */ var pem = @"-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDP3+OrT0IXqCu4 EXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEEXAMPLEE c5R7QVzxgmGRXjPZGPf5huA1 -----END PRIVATE KEY-----"; Task GetToken() { using var rsa = RSA.Create(); rsa.ImportFromPem(pem); var descriptor = new SecurityTokenDescriptor(); descriptor.SigningCredentials = new SigningCredentials(new RsaSecurityKey(rsa), SecurityAlgorithms.RsaSha256) { CryptoProviderFactory = new CryptoProviderFactory { CacheSignatureProviders = false } }; var claims = new List { new Claim("sub", "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1"), // Some unique identifier for the user new Claim("external_id", "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1"), // Generally matches sub new Claim("name", "Phil Embedmonson"), // Optional new Claim("organization", "T3JnYW5pemF0aW9uOmU5ZGVhZDU5LWU3YzktNDNkMi1hNjhhLWFhMjcyMzEyMTAxNw=="), new Claim("customer", "abc-123"), // This is an external ID of a customer new Claim("customer_name", "Hooli") // The optional name to use if we need to create a new customer record }; descriptor.Subject = new ClaimsIdentity(claims); descriptor.IssuedAt = DateTime.UtcNow; descriptor.Expires = DateTime.UtcNow.AddHours(1); // Expire 1 hour from now var token = new JwtSecurityTokenHandler().CreateEncodedJwt(descriptor); return Task.FromResult(token); } var token = await GetToken(); ``` An example NextJS implementation of JWT generation is available in [GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/pages/api/prismatic-auth.tsx). #### Use the JWT to authenticate the user[​](#use-the-jwt-to-authenticate-the-user "Direct link to Use the JWT to authenticate the user") Now that a user in your application has a signed JWT from the backend, you can authenticate them with the Prismatic library using the `prismatic.authenticate()` function in your frontend application: ```javascript // Some function that fetches the JWT from your API: const token = getJwtToken(); try { await prismatic.authenticate({ token }); } catch (error) { console.error(`Authentication failed with error ${error}`); } ``` If your customer or organization ID in your JWT are incorrect, if your JWT is not signed correctly, or if the JWT is expired, `prismatic.authenticate()` will throw an error. For an example React hook that wraps the `prismatic.authenticate()` function, see the [GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/src/usePrismaticAuth.ts#L66). ##### What does `prismatic.authenticate()` do?[​](#what-does-prismaticauthenticate-do "Direct link to what-does-prismaticauthenticate-do") When you call `prismatic.authenticate({ token })`, your client will make an authenticate call to Prismatic with the JWT you provide. The request is equivalent to this curl command: ```bash curl -X POST 'https://app.prismatic.io/embedded/authenticate' \ --header 'authorization: Bearer eyJhb...' \ ``` The request returns an HTTP 200 if the token is valid, and an error if it's not. If the user or customer you specified in the JWT doesn't already exist in Prismatic, they will be created automatically. #### Refreshing an embedded JWT[​](#refreshing-an-embedded-jwt "Direct link to Refreshing an embedded JWT") If a customer user's JWT expires, the customer user will see a 404 in their embedded iframe. To reauthenticate a user prior to expiration, ensure that your frontend app fetches a new token for your user and then run `prismatic.authenticate({ token })` with the new token. Existing iframes and the embedded client will be updated to use the new token. --- ### Curate Your Connector Allowlist There are many reasons why you might want to limit which connectors and actions are available to your customers in the embedded workflow builder. For example, you might want to: 1. Limit the set of connectors available within certain parts of your product to avoid overwhelming your customers with too many options. 2. Remove connectors to your competitors' products. 3. Offer access to certain connectors only to customers on specific plans. In this video we show you how to curate your connector allowlist to control which connectors and actions are available to your customers in the embedded workflow builder two different ways: 1. Using [filters](https://prismatic.io/docs/embed/workflow-builder/workflow-builder.md#filtering-components) when you call `prismatic.showWorkflows` to launch the workflow builder. 2. Defining a connector and action allowlist with a [workflow context](https://prismatic.io/docs/embed/workflow-builder/workflow-contexts.md) to control which connectors and actions are available to your customers in the embedded workflow builder. --- ### Custom Connector Basics A custom connector is a reusable component that wraps an API and can be used in multiple workflows. In this tutorial we introduce the concept of a custom connector, and walk through the steps to build one. We cover how to set up your development environment, create a new connector project, and implement basic functionality. We build the connection line-by-line by hand to give you a deeper understanding of how custom connectors work, but if you want to see how our Claude Skills can accelerate custom connector development, skip ahead to [Build a Custom Connector with AI](https://prismatic.io/docs/get-started/embedded-workflow-builder/wrap-api-custom-connector.md). Prerequisites * Install a recent version of [Node.js](https://nodejs.org/en) * Install [Prismatic's CLI tool](https://prismatic.io/docs/cli.md) and [authenticate it](https://prismatic.io/docs/cli.md#authenticating-with-the-cli-tool) against your Prismatic tenant Initialize a new connector project Initialize a new connector project ```bash prism components:init my-first-connector ``` This will create a new directory called `my-first-connector` which contains your connector project. Open this directory in your code editor, and then install the project's dependencies: Install dependencies ```bash npm install ``` Finally, delete all of the files in `src/` except for `src/index.ts` and `src/actions.ts`. Replace the contents of `src/index.ts` with the following boilerplate code: src/index.ts ```ts import { component } from "@prismatic-io/spectral"; import actions from "./actions"; export default component({ key: "my-first-connector", public: false, display: { label: "My First Connector", description: "This is my first custom connector", iconPath: "icon.png", }, actions, }); ``` Create your first action Next, let's add an action to our connector that fetches data from a public API. Replace `src/actions.ts` with the following code: src/actions.ts ```ts import axios from "axios"; // Popular HTTP client import { action } from "@prismatic-io/spectral"; const listItems = action({ display: { label: "List Items", description: "List all items in the system", }, inputs: {}, // No inputs for this action perform: async (context, inputs) => { const response = await axios.get( "https://my-json-server.typicode.com/prismatic-io/placeholder-data/items", ); return { data: response.data }; // Return the list of items }, }); export default { listItems }; ``` Build, publish and test your connector Now let's test our action to make sure it works as expected. First, build the connector: Build the connector ```bash npm run build ``` Then, publish the connector to your Prismatic tenant: Publish the connector ```bash prism components:publish ``` Finally, navigate to the Prismatic UI and create a new flow that uses your connector's "List Items" action to fetch and display the list of items. ![Testing the connector in a flow](/docs/assets/images/test-list-items-d8813236b048eee25eed77ed8588c714.png) Add actions with inputs Our first action took no inputs. Let's add two more actions that take inputs. src/actions.ts ```ts import axios from "axios"; // Popular HTTP client import { action, input, util } from "@prismatic-io/spectral"; const listItems = action({ display: { label: "List Items", description: "List all items in the system", }, inputs: {}, // No inputs for this action perform: async (context, inputs) => { const response = await axios.get( "https://my-json-server.typicode.com/prismatic-io/placeholder-data/items", ); return { data: response.data }; // Return the list of items }, }); const getItemById = action({ display: { label: "Get Item By ID", description: "Get a specific item by its ID", }, inputs: { itemId: input({ label: "Item ID", type: "string", required: true, }), }, perform: async (context, inputs) => { const response = await axios.get( `https://my-json-server.typicode.com/prismatic-io/placeholder-data/items/${inputs.itemId}`, ); return { data: response.data }; // Return the specific item }, }); const createItem = action({ display: { label: "Create Item", description: "Create a new item in the system", }, inputs: { name: input({ label: "Item Name", type: "string", required: true, }), quantity: input({ label: "Quantity", type: "string", clean: (value) => util.types.toNumber(value), // Convert input to a number required: true, }), }, perform: async (context, inputs) => { const response = await axios.post( "https://my-json-server.typicode.com/prismatic-io/placeholder-data/items", { name: inputs.name, quantity: inputs.quantity, }, ); return { data: response.data }; // Return the created item }, }); export default { listItems, getItemById, createItem }; ``` Re-run the build and publish commands, and test the new actions that have inputs: ```bash npm run build prism components:publish ``` ![Testing the new actions with inputs in a flow](/docs/assets/images/test-actions-with-inputs-026301a8241934f670b9c30bf00bf439.png) Add authentication and a reusable HTTP client While our dummy API doesn't require authentication, most real APIs do. Let's add support for an API key and use that API key as a bearer token in the Authorization header of our HTTP requests. We'll also refactor our code to use a reusable HTTP client that automatically includes the API key in each request. src/connections.ts ```ts import { connection, input } from "@prismatic-io/spectral"; const acmeApiKey = connection({ key: "acmeApiKey", display: { label: "Acme API Key", description: "API key for authenticating with the Acme API", }, inputs: { baseUrl: input({ label: "Base URL", type: "string", required: true, default: "https://my-json-server.typicode.com/prismatic-io/placeholder-data", }), apiKey: input({ label: "API Key", comments: "Generate an API key from your Acme account (settings tab) and enter it here", type: "password", required: true, }), }, }); // Connections are represented as an array in a component definition export default [acmeApiKey]; ``` Update `src/index.ts` to import the connection and include it in the component definition: src/index.ts ```diff diff --git a/src/index.ts b/src/index.ts index 4589d73..29fdb7b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { component } from "@prismatic-io/spectral"; import actions from "./actions"; +import connections from "./connections"; export default component({ key: "my-first-connector", @@ -10,4 +11,5 @@ export default component({ iconPath: "icon.png", }, actions, + connections, }); ``` Now, add a reusable HTTP client that uses the connection's base URL and API key: src/client.ts ```ts import { createClient } from "@prismatic-io/spectral/dist/clients/http"; import { Connection, util } from "@prismatic-io/spectral"; export const acmeClient = (connection: Connection) => { return createClient({ baseUrl: util.types.toString(connection.fields.baseUrl), headers: { Authorization: `Bearer ${connection.fields.apiKey}`, }, // Enable debug mode to log request and response details // Disable before deploying to production debug: true, }); }; ``` Finally, update `src/actions.ts` to use the reusable HTTP client, and add a connection input to each step: src/actions.ts ```diff diff --git a/src/actions.ts b/src/actions.ts index f8828b9..425ab73 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -1,16 +1,21 @@ -import axios from "axios"; // Popular HTTP client +import { acmeClient } from "./client"; import { action, input, util } from "@prismatic-io/spectral"; +const connectionInput = input({ + label: "Acme Connection", + type: "connection", + required: true, +}); + const listItems = action({ display: { label: "List Items", description: "List all items in the system", }, - inputs: {}, // No inputs for this action + inputs: { connection: connectionInput }, perform: async (context, inputs) => { - const response = await axios.get( - "https://my-json-server.typicode.com/prismatic-io/placeholder-data/items", - ); + const client = acmeClient(inputs.connection); + const response = await client.get("/items"); return { data: response.data }; // Return the list of items }, }); @@ -21,6 +26,7 @@ const getItemById = action({ description: "Get a specific item by its ID", }, inputs: { + connection: connectionInput, itemId: input({ label: "Item ID", type: "string", @@ -28,9 +34,8 @@ const getItemById = action({ }), }, perform: async (context, inputs) => { - const response = await axios.get( - `https://my-json-server.typicode.com/prismatic-io/placeholder-data/items/${inputs.itemId}`, - ); + const client = acmeClient(inputs.connection); + const response = await client.get(`/items/${inputs.itemId}`); return { data: response.data }; // Return the specific item }, }); @@ -41,6 +46,7 @@ const createItem = action({ description: "Create a new item in the system", }, inputs: { + connection: connectionInput, name: input({ label: "Item Name", type: "string", @@ -54,13 +60,11 @@ const createItem = action({ }), }, perform: async (context, inputs) => { - const response = await axios.post( - "https://my-json-server.typicode.com/prismatic-io/placeholder-data/items", - { - name: inputs.name, - quantity: inputs.quantity, - }, - ); + const client = acmeClient(inputs.connection); + const response = await client.post("/items", { + name: inputs.name, + quantity: inputs.quantity, + }); return { data: response.data }; // Return the created item }, }); ``` Build and publish once more. You'll notice that an integration-specific connection is automatically created for your steps, and that you'll need to go through the config wizard to set up the connection before you can test the steps. **Tip**: Enable logs in the step result drawer to see the details of the HTTP requests being made by your connector's actions, which can be helpful for debugging. ![Testing the connector with authentication and reusable HTTP client in a flow](/docs/assets/images/test-connector-with-authentication-f1e14b48eabb9e945b28f0d7711b678d.png) **Next steps**: * [Build a Custom Connector with AI](https://prismatic.io/docs/get-started/embedded-workflow-builder/wrap-api-custom-connector.md) --- ### Enable and Configure AI Co-Pilot The [AI Co-Pilot](https://prismatic.io/docs/embed/workflow-builder/ai-copilot.md) allows your customers to build workflows through natural language. If you want to enable the AI Co-Pilot for your customers programmatically, issue an [`updateCustomer`](https://prismatic.io/docs/api/schema/mutations.md#updatecustomer) mutation with the `allowWorkflowCopilot` field set to `true`. Enable AI Co-Pilot for a customer ```graphql mutation { updateCustomer( input: { id: "Q3V-YOUR-CUSTOMERS-ID" allowEmbeddedDesigner: true allowWorkflowCopilot: true } ) { customer { id } errors { field messages } } } ``` --- ### Enable the Embedded Workflow Builder The workflow builder is enabled on a per-customer basis, so you can offer it as a premium feature in your app. If you want to enable the embedded workflow builder for your customers programmatically, issue an [`updateCustomer`](https://prismatic.io/docs/api/schema/mutations.md#updatecustomer) mutation with the `allowEmbeddedDesigner` field set to `true`. Enable Embedded Workflow Builder for a customer ```graphql mutation { updateCustomer( input: { id: "Q3V-YOUR-CUSTOMERS-ID", allowEmbeddedDesigner: true } ) { customer { id } errors { field messages } } } ``` --- ### Listening for Workflow Builder Events There are two main way to track user behavior in the embedded workflow builder: 1. Listen for browser events from the embedded iframe. Events are emitted when a workflow is enabled or disabled. See [Listening for Marketplace Events](https://prismatic.io/docs/embed/marketplace.md#listening-to-marketplace-events) for examples of how to listen for browser events. 2. Subscribe to [event webhooks](https://prismatic.io/docs/webhooks.md) to receive notifications when workflows are created, updated, deleted, enabled or disabled. --- ### Embed the Workflow Builder When you embed the [workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) in your app, your customers can build and manage their own custom workflows right from your application. In this video, we walk through how to embed the Prismatic workflow builder into your application, including adding seamless authentication and customizing theme to match your app's aesthetic. Recommended: Use AI to embed the workflow builder in your app We recommend using Prismatic's [agent skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) to embed the workflow builder in your app. The `embedded` skill is an expert in how Prismatic's authentication system works and best practices for embedding the workflow builder in your app. Install Prismatic's AI marketplace and skills ```text /plugin marketplace add prismatic-io/prismatic-skills /plugin install prismatic-skills@prismatic-skills /reload-plugins ``` Then, invoke the `embedded` skill with a prompt describing your application and how you want to embed the workflow builder Prompt Claude to build your embedded workflow builder ```text /prismatic-skills:embedded Add the Prismatic embedded workflow builder to my app. ``` --- ### Provision Customer Connections to Your App Your [custom connector](https://prismatic.io/docs/custom-connectors.md) for your app likely requires authentication, but it feels strange to ask your customers to authenticate with your app when they're already logged in to your app. The solution is to create customer connections on behalf of your customers, so they don't have to authenticate with your app separately. If you'd like to provision customer connections programmatically, first create the connection and take note of its ID (it'll start with `U2N...`). Then, take note of your customer's ID (it'll start with `Q3V...`). With both IDs in hand, create a customer connection, filling `inputs` with the keys and values required by your custom connector. Here's an example GraphQL mutation: Create a Customer Connection ```graphql mutation { createCustomerConfigVariable( input: { scopedConfigVariable: "U2N-YOUR-CONNECTION-ID" customer: "Q3V-YOUR-CUSTOMER-ID" isTest: false inputs: [ { name: "api_key", type: "value", value: "my-customers-api-key" } ] } ) { customerConfigVariable { id } errors { field messages } } } ``` --- ### Reusable Customer Connections Your customers want to interact with dozens of external APIs, but OAuth setup can slow them down. In this video we create an OAuth app for Slack so embedded workflow builder users simply need to click **Connect**. Save your customers some time and give them reusable OAuth connections they can use across multiple workflows and integrations. **See also**: * [Customer Connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md) * [OAuth 2.0 Crash Course](https://prismatic.io/docs/get-started/build-integrations/oauth.md) --- ### Tune Your Connector for Copilot In order for the embedded workflow builder's AI copilot to understand how to use your connector's actions, you need to provide the copilot with enough information about your connector. This video focuses on two key recommendations for tuning your connector for the copilot: 1. Provide detailed descriptions for your connector's actions and their inputs. 2. Provide an `examplePayload` for your connector's actions to help the copilot understand how data can flow between steps. It's important that each action has a good description and example payload: Example action with description and example payload ```ts export const getPerson = action({ display: { label: "Get Person", description: "Get a person stored in the Acme People API by their ID", }, inputs: { connection: connectionInput, personId: input({ label: "Person ID", comments: "A numerical ID of a person stored in the Acme People API" type: "string", required: true, clean: util.types.toString, }), }, perform: async (_context, { connection, personId }) => { // Fetch the specific person using the selected ID const client = getPeopleClient(connection); const response = await client.get(`/users/${personId}`); return { data: response.data }; }, examplePayload: { data: { id: 1, name: "Leanne Graham", username: "Bret", email: "Sincere@april.biz", address: { street: "Kulas Light", suite: "Apt. 556", city: "Gwenborough", zipcode: "92998-3874", geo: { lat: "-37.3159", lng: "81.1496", }, }, phone: "1-770-736-8031 x56442", website: "hildegard.org", company: { name: "Romaguera-Crona", catchPhrase: "Multi-layered client-server neural-net", bs: "harness real-time e-markets", }, }, }, }); ``` --- ### Choose a Placement Strategy Decide where the workflow builder lives in your app. There are three common UX patterns: 1. A dedicated automations tab 2. A templates gallery 3. In-context "Automate this" launchers from product surfaces. Most implementations combine more than one. --- ### Providing Workflow Context When a customer creates a new workflow from the workflow listview screen, they are dropped into a blank canvas. If you can provide your customer with a preconfigured trigger, you can save them time and help them get started with their workflow. In this video, we show how you can create new workflows with preconfigured triggers from your product surfaces using workflow contexts. The simple example in the video added a `prismatic.createWorkflow()` call to a button in the product that, when clicked, created a new workflow and redirected the customer to the workflow builder with a preconfigured trigger. Create a new workflow with a preconfigured trigger ```ts "use client"; import * as React from "react"; import { SidebarInset } from "@/components/ui/sidebar"; import { AppHeader } from "@/components/app-header"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import prismatic from "@prismatic-io/embedded"; import { useRouter } from "next/navigation"; const RESOURCE_OPTIONS = [ { value: "invoice", label: "Invoice" }, { value: "lead", label: "Lead" }, { value: "ticket", label: "Ticket" }, ]; const EVENT_OPTIONS = [ { value: "created", label: "Created" }, { value: "updated", label: "Updated" }, { value: "deleted", label: "Deleted" }, ]; export default function SimpleExamplePage() { const router = useRouter(); const [resource, setResource] = React.useState(); const [event, setEvent] = React.useState(); async function handleClick(): Promise { const result = await prismatic.createWorkflow("my-new-workflow-context", { name: `Run when a ${resource} is ${event}`, contextData: { resourceType: resource, event: event, }, }); const workflowId = result.data.importWorkflow.workflow.id; router.push(`/resources/automate/${resource}/${workflowId}`); } return (
When a...
Is...
); } ``` --- ### Build a Custom Connector with AI In [this previous session](https://prismatic.io/docs/get-started/embedded-workflow-builder/custom-connector-basics.md) we introduced the concept of a custom connector, and walked through the steps to build a basic one. In this session, we'll build a fully functional custom connector that wraps a real API, includes data sources and actions, and connects to a third-party service with OAuth 2.0. For this example, we'll wrap [todoist](https://www.todoist.com/) - a popular task management app that has a well-documented REST API and supports OAuth 2.0 authentication. #### Use Prismatic Claude skills to help build your connector[​](#use-prismatic-claude-skills-to-help-build-your-connector "Direct link to Use Prismatic Claude skills to help build your connector") If you use an AI coding assistant like Claude Code you can leverage [Prismatic's AI skills](https://github.com/prismatic-io/prismatic-skills) to build your connector faster. These skills provide your AI agent with example code snippets, efficient documentation lookup, and best practice guidance. Install Prismatic's AI skills in your coding assistant Open `claude` and run the following commands to install Prismatic's marketplace and skills Install Prismatic's AI marketplace and skills ```text /plugin marketplace add prismatic-io/prismatic-skills /plugin install prismatic-skills@prismatic-skills /reload-plugins ``` Invoke the build-component skill with a prompt describing your connector Then, invoke your newly-installed `build-component` command and skill with a prompt describing the connector you want to build Prompt Claude to build your connector ```text /prismatic-skills:build-component My customers use Todoist, and I would like to interact with Todoist in a low-code integration. Build a custom component for Todoist. API documentation is available at https://developer.todoist.com/api/v1/ The component should include: - OAuth 2.0 for authentication - A data source where users can select a workspace from a dropdown menu - A data source where users can select a project from a dropdown menu - Actions for listing, creating, updating, closing and deleting tasks - Actions for listing projects and workspaces Remember to fetch a square PNG icon for the Todoist component and save it to assets/icon.png. ``` Your AI assistant will scaffold a custom connector project, install dependencies, write the data sources and actions and OAuth-based connection and import the connector into your Prismatic tenant for testing. See full [agent skills documentation](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md). --- ### Prismatic Labs Workshops #### Past events[​](#past-events "Direct link to Past events") ##### Handling Large Data Sets (2026-06-30)[​](#handling-large-data-sets-2026-06-30 "Direct link to Handling Large Data Sets (2026-06-30)") How do you move large data sets at scale without running into timeouts, rate limits, or other issues? In this session, we explore the challenges of moving large data sets and how to handle them with Prismatic's new features. ##### Product Automations: Solving the Long Tail Problem (2026-05-28)[​](#product-automations-solving-the-long-tail-problem-2026-05-28 "Direct link to Product Automations: Solving the Long Tail Problem (2026-05-28)") When customers ask for integrations that aren't on your roadmap, the usual answers ("use our API" or "try Zapier") fall short. There's a third option: embedded workflows with an AI Copilot and workflow context that let non-technical users build their own product automations in plain English, inside your app, while your team keeps full control. ##### Ship Smarter Field Mappers with JSON Forms and AI (2026-04-28)[​](#ship-smarter-field-mappers-with-json-forms-and-ai-2026-04-28 "Direct link to Ship Smarter Field Mappers with JSON Forms and AI (2026-04-28)") Field mapping is one of the most painful parts of integration configuration. Every one of your customers' CRMs, ERPs, or business systems is different, and manually mapping fields between systems is tedious, error-prone, and slows down every deployment. Taylor Reece builds a production-ready field mapper using JSON Forms with AI-powered matching. ##### AI Copilot for Embedded Workflow Builder (2026-03-30)[​](#ai-copilot-for-embedded-workflow-builder-2026-03-30 "Direct link to AI Copilot for Embedded Workflow Builder (2026-03-30)") If your customers struggle with the learning curve of building workflows, this one's for you. We demo how users can describe what they want in plain language and watch it build on the canvas in real time - no code, no complex configuration. ##### Building faster with AI (2026-02-24)[​](#building-faster-with-ai-2026-02-24 "Direct link to Building faster with AI (2026-02-24)") Building production-ready integrations usually means weeks of work: OAuth flows, rate limits, configurations, and everything else that breaks at scale. With an AI coding assistant, supplemented by Prismatic's [MCP dev server](https://prismatic.io/docs/dev-tools/prism-mcp.md) and new Claude (agent) [skills](https://github.com/prismatic-io/prismatic-skills/), you can go from idea to deployed integration in minutes, not months. ##### Instance Lifecycle Events (2026-01-27)[​](#instance-lifecycle-events-2026-01-27 "Direct link to Instance Lifecycle Events (2026-01-27)") Learn what happens when a customer deploys an instance of your integration, and how you can use Prismatic's instance lifecycle events to automate deployment tasks (like setting up webhooks, provisioning resources, or doing initial data syncs). ##### Subscribing to Events with Prismatic's API and Event Webhooks (2025-11-18)[​](#subscribing-to-events-with-prismatics-api-and-event-webhooks-2025-11-18 "Direct link to Subscribing to Events with Prismatic's API and Event Webhooks (2025-11-18)") Learn how to subscribe to Prismatic's event webhooks to monitor your integrations and workflows, and how to query Prismatic's GraphQL API to get real-time data about your integrations. ##### Incorporating code-native integrations into your CI/CD pipeline. (2025-10-21)[​](#incorporating-code-native-integrations-into-your-cicd-pipeline-2025-10-21 "Direct link to Incorporating code-native integrations into your CI/CD pipeline. (2025-10-21)") Learn how to organize your integration code repository, leverage GitHub Actions to deploy connectors and integrations, and include best practices for sharing logic between custom connectors and code-native integrations. ##### Integration Marketplace Best Practices (2025-09-16)[​](#integration-marketplace-best-practices-2025-09-16 "Direct link to Integration Marketplace Best Practices (2025-09-16)") Learn how to embed and deploy a marketplace in your instance ##### Productized Integrations (2025-08-19)[​](#productized-integrations-2025-08-19 "Direct link to Productized Integrations (2025-08-19)") Learn how Prismatic enables you to turn your integrations into products. ##### Incorporate AI in Your Integrations (2025-07-22)[​](#incorporate-ai-in-your-integrations-2025-07-22 "Direct link to Incorporate AI in Your Integrations (2025-07-22)") Learn about the new AI capabilities in Prismatic and how you can leverage them for your integrations. ##### Visibility features for efficient integrations (2025-06-17)[​](#visibility-features-for-efficient-integrations-2025-06-17 "Direct link to Visibility features for efficient integrations (2025-06-17)") Explore Prismatic's visibility features, including debugging tools recently added to the custom connector and code-native integration SDK. ##### Using a code-native approach to build on your low-code integration (2025-05-20)[​](#using-a-code-native-approach-to-build-on-your-low-code-integration-2025-05-20 "Direct link to Using a code-native approach to build on your low-code integration (2025-05-20)") Learn how to convert Prismatic's low-code integrations into code-native integrations (CNI) with our new CNI converter. This session includes a brief overview of CNI and a live demo of the converter. --- ### Instances Overview An **instance** of an [integration](https://prismatic.io/docs/integrations.md) is a copy of an integration that has been configured for a specific [customer](https://prismatic.io/docs/customers.md). When configuring an instance, you or your customers set up connections to third-party applications and services, along with customer-specific configuration variables, by walking through a [configuration wizard](https://prismatic.io/docs/integrations/config-wizard.md). You can deploy instances of your integrations [on behalf of your customers](https://prismatic.io/docs/instances/deploying.md), or your customers can enable instances themselves through the [integration marketplace](https://prismatic.io/docs/embed/marketplace.md). For your customers, the term **instance** doesn't have any specific meaning - they either have an integration or they don't. When customers log in to Prismatic, they see phrases such as "activate this integration" or "configure this integration." A customer "activates" an "integration" - which is equivalent to deploying an "instance" in organization user terminology. When a [flow](https://prismatic.io/docs/integrations/low-code-integration-designer/flows.md) within an instance is triggered, an [execution](https://prismatic.io/docs/monitor-instances/executions.md) of that instance's flow runs. #### What happens when an instance is deployed[​](#what-happens-when-an-instance-is-deployed "Direct link to What happens when an instance is deployed") When you deploy an instance, Prismatic invokes any triggers marked as [deploy triggers](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger). Additionally, Prismatic executes any [lifecycle functions](https://prismatic.io/docs/integrations/lifecycle.md) associated with the instance's flows to set up necessary resources. Prismatic generates webhooks for each flow, which become available for invocation after you deploy the instance. Prismatic registers schedule triggers with the scheduler, and they run at your specified intervals. Instances are billed based on how long the instance is enabled. You are not billed when an instance is paused. --- ### Deploying Instances #### Options for deploying instances[​](#options-for-deploying-instances "Direct link to Options for deploying instances") [Options for deploying an instance](https://player.vimeo.com/video/894996278) Once you've built and published an integration, it's time to configure and deploy an instance of your integration to a customer. You can either deploy the instance yourself, or grant your customer access to deploy the instance themselves. #### Option 1: Deploy an instance yourself[​](#option-1-deploy-an-instance-yourself "Direct link to Option 1: Deploy an instance yourself") As you work with your first few customers and build out an MVP, it may be easiest to [deploy instances yourself](#configuring-instances). **Advantages**: * You can quickly iterate on your integration and fix any production issues that arise. * No additional development work is required to support self-deployment. **Disadvantages**: * You'll need to manually deploy instances for each customer. * You'll possibly need to handle the customers' credentials for the third-party service as you configure the instance. #### Option 2: Grant customers access to deploy instances through the Prismatic app[​](#option-2-grant-customers-access-to-deploy-instances-through-the-prismatic-app "Direct link to Option 2: Grant customers access to deploy instances through the Prismatic app") You can invite your customers to log in to Prismatic and deploy instances themselves. You can do this by publishing your integration to the [integration marketplace](https://prismatic.io/docs/embed/marketplace.md) and then invite the [customer user](https://prismatic.io/docs/customers/customer-users.md) to your tenant. The customer user will only be able to see instances that are deployed to them (and not to your other customers). **Advantages**: * Your customer can configure and deploy instances themselves. * You don't need to handle the customers' credentials for the third-party service. **Disadvantages**: * Your customer will require an additional login to Prismatic. #### Option 3: Embed the integration marketplace in your app[​](#option-3-embed-the-integration-marketplace-in-your-app "Direct link to Option 3: Embed the integration marketplace in your app") You can [embed the integration marketplace](https://prismatic.io/docs/embed/marketplace.md) in your app so that your customers can deploy instances without leaving your app. **Advantages**: * Customers can deploy instances for themselves without leaving your app. * You can leverage your existing authentication system to authenticate customers. * You have some control over the fonts, colors and other styling of the marketplace, so you can make the embedded iframe match your app. **Disadvantages**: * Some development work is required to embed the marketplace in your app (though, the Prismatic embedded SDK makes this easy!). #### Option 4: Build your own UI for deploying instances[​](#option-4-build-your-own-ui-for-deploying-instances "Direct link to Option 4: Build your own UI for deploying instances") If you want to build a custom UI for deploying instances, you can use the Prismatic embedded SDK to [make API requests](https://prismatic.io/docs/embed/embedded-api-requests.md) to the Prismatic API on behalf of your customer user. You can query for integrations in the integration marketplace, components, etc., and can map those records to custom UI elements in your app. **Advantages**: * You have full control over the UI and can build a custom UI that fits your app's look and feel. **Disadvantages**: * This option requires the most development work. #### Configuring instances[​](#configuring-instances "Direct link to Configuring instances") When you or your customer deploy an instance, you will work through the [Configuration Wizard](https://prismatic.io/docs/integrations/config-wizard.md) that your integration builders created. This is where you can set values for connections and configuration variables for your deployed instance. If your integration developers set default values for the config variables, those will be set initially, but you can override them if you choose. Depending on config variable type, you'll have the option to toggle boolean values, enter string values, enter JSON or XML for a code config variable, or select options from a dropdown. ![Configure instance in Prismatic app](/docs/assets/images/configuration-4f74bfda58291e3723d0d6ac0d2d28a6.png) Be sure to click **Save and deploy** on the last page of the **Configuration Wizard** to save any changes you make. ##### Creating an unconfigured instance[​](#creating-an-unconfigured-instance "Direct link to Creating an unconfigured instance") The embedded marketplace shows only integrations explicitly added to marketplace. But, there are situations where you may want to provide a particular integration that is not part of marketplace to a customer. To do that, you will need to create an unconfigured instance for a customer and elect to [show all instances](https://prismatic.io/docs/embed/marketplace.md#showing-all-instances-in-marketplace) to your customer. Your customer will be able to log in to marketplace and configure that instance for themselves. To create an unconfigured instance, select the **Skip configuration** button when creating your instance. ![Skip instance configuration](/docs/assets/images/skip-configuration-362e6dbd224897e0bc7075518c7de851.png) An unconfigured instance will not be deployed until your customer enters their configuration information, and will not count towards your monthly instance count. ##### Setting integration version for an instance[​](#setting-integration-version-for-an-instance "Direct link to Setting integration version for an instance") When you publish an integration, Prismatic creates a new version. Instances of the integration can then be updated to use the new integration version. In the instance's configuration page, you'll see **New Version Available** if your instance can be updated. To update your instance, click the **Reconfigure** button at the top right of the page, and then select the latest version from the **Integration Version** field. ![Set instance version in Prismatic app](/docs/assets/images/set-integration-version-e9599cb00878cdbcacd92c08f81f41b2.png) You can pin instances to different integration versions Not all instances need to run the same version of the integration. For example, one customer might be running a legacy version of a third-party app. They can continue to run "version X" of an integration until they upgrade their third-party app, at which point their instance can be upgraded to "version Y". If an instance upgrade causes problems - suppose a new definition of an integration has a bug that an older one didn't have - you can always reconfigure the instance to run an older version of the integration by similarly clicking **Reconfigure** and choosing an older known working integration version. ##### Setting instance labels[​](#setting-instance-labels "Direct link to Setting instance labels") When deploying an instance, you can set labels to help you organize and filter your instances. ![Set instance labels in Prismatic app](/docs/assets/images/instance-labels-c86baf1a5c6acba3a0011c8bdfa18d68.png) --- ### What is Integration Marketplace? Prismatic's **integration marketplace** allows you to present your integrations to your customers. You can [embed](https://prismatic.io/docs/embed/marketplace.md) the marketplace within your app so your users can seamlessly deploy integrations natively using your existing authentication system. Within the integration marketplace, your customers can: * Explore your integration offerings in an attractive marketplace * Easily self-activate integrations that connect your app to third-party services they use * Monitor their active integrations using powerful logging and alerting tools You can choose which integrations to include in your integration marketplace, and how they're presented to your customers. Customers follow a simple configuration and deployment experience that [you create](https://prismatic.io/docs/integrations/config-wizard.md) when you build the integration. They enter configuration values and credentials, select a few options from some dropdown menus, and click "activate". Disambiguating "integrations" and "instances" For organization users like yourself, an **integration** refers to a general, productized and published integration that can be configured and deployed to multiple customers. An **instance** of an integration is a copy of the integration that has been configured and deployed to a specific customer. For your customers, **instance** has no meaning - they either have an integration or they don't. So, when customers log in to Prismatic they see phrases like "activate this integration", or "configure this integration". A customer "activates an integration" - which is the same as deploying an "instance" in your lingo as an organization user. #### Options for presenting the integration marketplace[​](#options-for-presenting-the-integration-marketplace "Direct link to Options for presenting the integration marketplace") You have a few options for presenting the integration marketplace to your customers. ##### Embed the marketplace as an iframe[​](#embed-the-marketplace-as-an-iframe "Direct link to Embed the marketplace as an iframe") You can [embed the integration marketplace](https://prismatic.io/docs/embed/marketplace.md) directly into your app as an iframe. This option allows your customers to activate integrations seamlessly within your app. You can [theme](https://prismatic.io/docs/embed/theming.md) the integration marketplace to match your app's design. ![Your customers log in to your app and activate integrations through an embedded iframe](/docs/assets/images/presenting-embed-iframe-453f362f89f4b04ddef98ddb3b05a5d0.png) ##### Embed a custom marketplace UI[​](#embed-a-custom-marketplace-ui "Direct link to Embed a custom marketplace UI") For a completely custom experience, you can use the [embedded SDK](https://prismatic.io/docs/embed/get-started/install-embedded-sdk.md) to [query Prismatic's API](https://prismatic.io/docs/embed/embedded-api-requests.md) for information on integrations. You can then use this information to build your own integration marketplace within your app. This option requires the most development effort, but allows you to build a completely custom integration marketplace experience. See [Custom Marketplace UI](https://prismatic.io/docs/embed/custom-marketplace-ui.md). ![Your customers log in to your app and activate integrations through a custom UI](/docs/assets/images/presenting-custom-ui-d0faa900417a402a65b169cc469a4357.png) ##### Let customers log in to Prismatic[​](#let-customers-log-in-to-prismatic "Direct link to Let customers log in to Prismatic") You can create Prismatic accounts for your customers and let them log in to Prismatic to activate integrations. This is the simplest option, and works well if you don't have an existing authentication system. However, this option requires your customers to log in to Prismatic to activate integrations, rather than activating integrations directly within your app. ![Your customers log in to Prismatic to activate integrations](/docs/assets/images/presenting-login-to-prismatic-d6972cdbf87d9c9eaf18d5244e423c72.png) --- ### Managing Instances #### Enabling and disabling instances[​](#enabling-and-disabling-instances "Direct link to Enabling and disabling instances") If you would like to stop a deployed instance from executing, click the **Enabled** button under **Status** on the instance's **Summary** tab. When disabled, your instance will not execute on a cron schedule (if configured to use [scheduled triggers](https://prismatic.io/docs/integrations/triggers/schedule.md)), nor respond to webhook invocations. ![Pause instance in Prismatic app](/docs/assets/images/pause-instance-c74ac91a530b9dcc89dd8a789ffe117c.png) To re-enable a disabled instance, click the yellow **Paused** button. #### Viewing instance execution results[​](#viewing-instance-execution-results "Direct link to Viewing instance execution results") It's useful for debugging purposes to be able to see execution results of instance invocations. Click the **Executions** tab from an instance's page to see the logs and step outputs of each execution of the instance. ![Instance execution results in Prismatic app](/docs/assets/images/execution-results-274849506a7323391947f59b5ac96e2f.png) If an instance fails to run to completion, you can review the data passed to the instance when it was invoked to help debug the issue. To view results for all instances and all customers, click the **Executions** link on the left-hand sidebar. To view results for a specific customer, click into the **Customer** and select their **Executions** tab. #### Instance execution retry and replay[​](#instance-execution-retry-and-replay "Direct link to Instance execution retry and replay") Executions can fail for many reasons - a third-party service might be down, or your integration might encounter an edge case it doesn't handle correctly. With **retry** and **replay** you can re-run failed executions so you don't miss important data. * **Retry** allows you to automatically re-run an execution if it fails to run to completion. This is useful if you want to handle temporary outages of third-party apps or services. Following a failure, your instance will re-attempt the execution a configurable number of times, with a configurable delay between each attempt. * **Replay** allows you to manually re-run an execution. This is useful if you want to fix a bug in your integration and then re-run an execution with the same payload that caused it to run initially. ##### Execution retry[​](#execution-retry "Direct link to Execution retry") Integrations can be configured to automatically [retry](https://prismatic.io/docs/monitor-instances/retry-and-replay/automatic-retry.md) in the event that an instance fails to run to completion. Information about instance retries can be found on the [execution results pages](#viewing-instance-execution-results). There, you will see when the instance last ran, and when it will attempt to run again. ![Instance execution details in Prismatic app](/docs/assets/images/execution-retry-d08c19d3b0a275b18bf1540653e15dbd.png) An icon beside an execution indicates that Prismatic automatically retried an execution that had previously failed. ##### Execution replay[​](#execution-replay "Direct link to Execution replay") To manually retry (i.e. "Replay") an invocation of an instance, click the icon beside any execution run. The instance will be run again with the same webhook payload that caused it to run initially. To programmatically retry many failed executions, use the [executionResults](https://prismatic.io/docs/api/schema/queries.md#executionresults)) query to find executions that failed to run to completion. You can then use the [replayExecution](https://prismatic.io/docs/api/schema/mutations.md#replayexecution)) mutation to replay the execution. For an example of how to bulk-replay failed executions, see our [examples repository](https://github.com/prismatic-io/examples/tree/main/api/replay-failed-executions) on GitHub or review the [Execution Retry & Replay](https://prismatic.io/docs/monitor-instances/retry-and-replay.md) documentation. Prismatic links replays with the original execution, so you can query for only original executions without a successful subsequent replay. #### Adding alert monitors to instances[​](#adding-alert-monitors-to-instances "Direct link to Adding alert monitors to instances") You can set up instance alert monitors to notify your team of various events, such as failed instance executions, slow executions, or instances in unexpected disabled states. They can be found by clicking the **Monitors** tab from the instance's page. **For more information**: [Creating Alert Monitors](https://prismatic.io/docs/monitor-instances/alerting.md). #### Viewing instance logs[​](#viewing-instance-logs "Direct link to Viewing instance logs") Logs for an instance can be viewed by clicking the **Logs** tab from the instance's page. You can search log message text through the **Search Logs** search bar on the top of the page, and you can filter logs by Log Severity or date range by clicking the **Filter** link to the right of the search bar. ![Filter instance logs in Prismatic app](/docs/assets/images/instance-logs-16c45b46accb7aefde3bce27e7c17eca.png) **For More Information**: [Logging](https://prismatic.io/docs/monitor-instances/logging.md), [Log Retention](https://prismatic.io/docs/monitor-instances/logging.md#log-retention) #### Deleting instances[​](#deleting-instances "Direct link to Deleting instances") Deleting an instance removes the instance and any associated data. Before choosing to delete an instance, consider if you want to [disable](#enabling-and-disabling-instances) the instance from running instead. If you choose to delete the instance, scroll to the bottom of the instance's **Details** tab. Click the **Delete instance** button, and type the name of the instance in the input field to confirm that you want to delete the instance. Click **Remove instance**. ![Delete instance in Prismatic app](/docs/assets/images/delete-instance-9be2bb937de1f9edc0506902cba267b2.png) --- ### Testing Instances After deploying an instance to a customer, you'll want to test it to ensure it functions correctly. This guide covers the various ways to invoke and test instances in different environments. #### Invoking instances[​](#invoking-instances "Direct link to Invoking instances") An instance's flows can be invoked one of four ways: 1. You can set up your integration to run [on a schedule](https://prismatic.io/docs/integrations/triggers/schedule.md) 2. You can invoke them [through a webhook](https://prismatic.io/docs/integrations/triggers/webhook.md) 3. You can configure your flow to run [on deployment](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger) or [on instance removal](https://prismatic.io/docs/integrations/triggers/management.md#instance-remove-trigger) 4. You can test a flow manually #### Testing instances from the web app[​](#testing-instances-from-the-web-app "Direct link to Testing instances from the web app") You can invoke an instance outside of its cron schedule or webhook invocations to ensure it functions properly. To run a test of an instance, open the **Test** tab. You can enter a test payload body and custom HTTP headers to simulate a webhook trigger payload. Click the **Run** button to invoke the test. Alternatively, look up the ID of a flow in an instance with `prism instances:flow-configs:list ${INSTANCE_ID}` and then run `prism instances:flow-configs:test ${FLOW_ID}` from the command line. ![Test instance in Prismatic app](/docs/assets/images/test-instance-6cba6d909b17ce2295ff964af7484e38.png) Logs from the test can be found by clicking the **Logs** tab. #### Invoking instances with webhook triggers[​](#invoking-instances-with-webhook-triggers "Direct link to Invoking instances with webhook triggers") If you choose to invoke your instance's flows with a webhook trigger, Prismatic generates webhook URLs for each flow when you deploy the instance. To invoke an instance's flow programmatically, you can send a POST request to the webhook URL with an optional payload. Here's an example using `curl`, though you can use any language you prefer: ```bash curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --location \ --header "Content-Type: application/json" \ --data '{"examplePayloadKey": "examplePayloadValue"}' ``` **More information**: [Webhook Triggers](https://prismatic.io/docs/integrations/triggers/webhook.md) --- ### AI agent integrations When your app includes an AI assistant or chatbot, customers will ask it questions that require data or actions from tools they already use - "How many open support tickets do I have in Zendesk?" or "Schedule a follow-up meeting in Google Calendar." Prismatic's [MCP Flow Server](https://prismatic.io/docs/ai/model-context-protocol.md) exposes your integration flows as tools that AI agents can discover and invoke through the standard MCP protocol. You define the flows once; your AI agent handles the rest. #### Design considerations[​](#design-considerations "Direct link to Design considerations") **Think in questions, not endpoints.** When deciding what flows to build, ask what questions your customers will direct at the AI. Each distinct question type or action becomes a flow. "Get open ticket count by status" is more useful to an AI than "list all tickets." **Write clear invocation schemas.** Your AI agent uses the [invocation schema](https://prismatic.io/docs/ai/flow-invocation-schema.md) to understand what a flow does and what parameters it needs. Treat schema descriptions like prompts - be specific about what the flow returns and when to call it. **Keep flows focused and fast.** Agentic flows run synchronously within an LLM conversation. A flow that does one thing quickly is better than one that handles multiple cases. Avoid long-running or batch-style logic. **Return structured, descriptive responses.** The LLM interprets your flow's output to compose a response for the user. Return clean JSON with meaningful field names rather than raw API responses. Include human-readable labels alongside IDs. **Handle errors gracefully.** If a flow fails or the third-party API is unavailable, return a clear error message the LLM can relay to the user rather than throwing an unhandled exception. **Scope by customer automatically.** Agentic flows run in the context of a customer's deployed instance, so they use that customer's credentials and configuration. You don't need to pass tenant identifiers. #### Getting started[​](#getting-started "Direct link to Getting started") 1. [Build your first agentic flow](https://prismatic.io/docs/get-started/agentic-flows/first-agentic-flow.md) 2. [Test with an MCP client](https://prismatic.io/docs/ai/test-mcp-clients.md) such as Postman, Claude, or ChatGPT 3. [Connect your AI agent to Prismatic's MCP server](https://prismatic.io/docs/ai/connect-ai-agent.md) --- ### Alerting and notification integrations Notification integrations fire when something important happens in your app and deliver messages to your customers through channels they already use - Slack, Microsoft Teams, email, SMS, and more. Build one integration per channel - a Slack integration, an email integration, an SMS integration, and so on. Customers enable whichever channels they want. #### Trigger[​](#trigger "Direct link to Trigger") Use a [webhook trigger](https://prismatic.io/docs/integrations/triggers/webhook.md) when your app can emit events in real time - this is the preferred approach. If your app can't push events, use a [scheduled polling trigger](https://prismatic.io/docs/integrations/triggers/schedule.md) to check for new conditions periodically. Your webhook payload should include enough context for the flow to compose a useful notification: what happened, which record it affects, severity, and any relevant IDs or links. #### Flow design[​](#flow-design "Direct link to Flow design") **One integration per channel.** Build a dedicated integration for each notification channel. Prismatic includes built-in components for Slack, Microsoft Teams, Twilio (SMS), SendGrid (email), and many others. Customers install only the channels they want to use. **Use config variables for recipient info.** Store webhook URLs, channel names, email addresses, and phone numbers in [config variables](https://prismatic.io/docs/integrations/config-wizard/config-variables.md) so each customer's instance can be configured independently. **Keep messages concise and actionable.** Notifications should tell the customer what happened and what to do next. Include a deep link back to the relevant record in your app where appropriate. #### Related documentation[​](#related-documentation "Direct link to Related documentation") * [Webhook triggers](https://prismatic.io/docs/integrations/triggers/webhook.md) * [Scheduled triggers](https://prismatic.io/docs/integrations/triggers/schedule.md) * [Config variables](https://prismatic.io/docs/integrations/config-wizard/config-variables.md) * [Flow concurrency and FIFO queues](https://prismatic.io/docs/integrations/triggers/fifo-queue.md) --- ### Data sync integrations Data sync is the most common integration type built on Prismatic. A sync integration has two phases: an initial backfill that loads existing records, and ongoing incremental updates that keep data current as things change. #### Initial sync[​](#initial-sync "Direct link to Initial sync") Use the [Instance Deploy trigger](https://prismatic.io/docs/integrations/triggers/management.md) to run a backfill when a customer enables the integration. Because this trigger also fires when upgrading integration versions, your flow must be **idempotent** - re-running it should not create duplicates. For large datasets that exceed the 15-minute execution limit, use [large data syncs](https://prismatic.io/docs/integrations/common-patterns/large-data-sync.md) to process data a few pages at a time. Each execution processes a hand-full of pages, then saves a cursor to [Cross-Flow State](https://prismatic.io/docs/integrations/persist-data.md) (for idempotency). Configure the initial sync flow to run only [one execution at a time](https://prismatic.io/docs/integrations/triggers/fifo-queue.md) to avoid concurrently processing the same set of pages. #### Incremental updates[​](#incremental-updates "Direct link to Incremental updates") **Webhooks (preferred).** Use [webhook triggers](https://prismatic.io/docs/integrations/triggers/webhook.md) for near-real-time updates. Register webhook subscriptions in [lifecycle handlers](https://prismatic.io/docs/custom-connectors/triggers.md#instance-lifecycle-functions) so subscriptions are created and cleaned up automatically. **Polling (fallback).** If webhooks aren't available, use [polling triggers](https://prismatic.io/docs/integrations/triggers/app-events.md#app-event-triggers-with-polling). Persist a cursor (last processed timestamp or ID) in [Flow State](https://prismatic.io/docs/integrations/persist-data.md) so each execution fetches only records that changed since the previous run. #### Best practices[​](#best-practices "Direct link to Best practices") * **Idempotency.** Use stable record identifiers and track processed keys to avoid duplicates across retries or restarts. * **Checkpoints.** Persist cursors in [Flow State or Cross-Flow State](https://prismatic.io/docs/integrations/persist-data.md) so the sync resumes cleanly after a failure. * **Rate limiting.** Use batching and concurrency controls to respect source and destination API quotas. * **Error handling.** Use retries with backoff for transient errors. Route persistent failures to a dead-letter flow for investigation. * **Observability.** Log records scanned, updated, skipped, and errored so sync behavior is visible and diagnosable. * **Handle large files.** Large files can consume lots of memory - use [streaming](https://prismatic.io/docs/custom-connectors/handling-large-files-in-custom-components.md) strategies. #### Related documentation[​](#related-documentation "Direct link to Related documentation") * [Management triggers](https://prismatic.io/docs/integrations/triggers/management.md) - run flows on instance deploy / remove * [Large data syncs](https://prismatic.io/docs/integrations/common-patterns/large-data-sync.md) - process large datasets across multiple executions * [Persisting state](https://prismatic.io/docs/integrations/persist-data.md) - store cursors and checkpoints between executions * [Handling large files](https://prismatic.io/docs/integrations/common-patterns/large-files.md) - strategies for file transfers * [Integration runner limits](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md) - memory, execution time, and payload constraints --- ### Event-driven integrations Event-driven integrations respond to things that happen in third-party systems - a new record is created, a payment completes, a file is uploaded. Rather than polling for changes, the external system pushes events to your integration via webhooks. #### Webhook lifecycle management[​](#webhook-lifecycle-management "Direct link to Webhook lifecycle management") Use [lifecycle handlers](https://prismatic.io/docs/custom-connectors/triggers.md#instance-lifecycle-functions) in your custom trigger to register and deregister webhook subscriptions automatically: * **`onInstanceDeploy`** - register the webhook subscription and persist the returned `webhookId` and `signingSecret` in [Cross-Flow State](https://prismatic.io/docs/integrations/persist-data.md). * **`onInstanceDelete`** - unsubscribe and clean up the webhook when the instance is removed. If webhook registration logic lives outside a custom trigger, use a [Management Trigger](https://prismatic.io/docs/integrations/triggers/management.md) flow as an alternative. #### Security[​](#security "Direct link to Security") Always verify webhook signatures using [HMAC](https://prismatic.io/docs/integrations/triggers/webhook/what-is-hmac.md) on every incoming request. Many built-in components handle this automatically; for custom triggers, implement verification manually. #### Ordering and deduplication[​](#ordering-and-deduplication "Direct link to Ordering and deduplication") For workflows where event order matters - financial transactions, inventory updates - enable [FIFO queues](https://prismatic.io/docs/integrations/triggers/fifo-queue.md) on your flow. FIFO queues process events one at a time in order and support [message deduplication](https://prismatic.io/docs/integrations/triggers/fifo-queue.md#message-deduplication) using a deduplication ID (such as a webhook event ID header) to prevent processing duplicates within a 10-minute window. #### Batched payloads[​](#batched-payloads "Direct link to Batched payloads") Many systems deliver arrays of events in a single webhook request. Loop over each event individually, validate and deduplicate per `eventId`, and dispatch to sub-flows based on event type as needed. Isolate failures so one bad event doesn't fail the entire batch. #### Error handling[​](#error-handling "Direct link to Error handling") Use retry steps with exponential backoff for transient failures. After a defined number of attempts, escalate to a dead-letter flow or alert and record minimal context (such as `eventId`) in [Flow State](https://prismatic.io/docs/integrations/persist-data.md) for targeted reprocessing. #### Best practices[​](#best-practices "Direct link to Best practices") * **Keep handlers fast.** Validate the request, deduplicate the event, then hand off to async processing. Avoid heavy logic in the trigger itself. * **Make handlers idempotent.** Design flows to be safe to retry. Deduplicate using stable event IDs. * **Configure triggers for reuse.** Expose inputs for object/topic selection and event type filtering with sensible defaults, so the same trigger can be reused across customers. #### Related documentation[​](#related-documentation "Direct link to Related documentation") * [Webhook triggers](https://prismatic.io/docs/integrations/triggers/webhook.md) * [Custom triggers](https://prismatic.io/docs/custom-connectors/triggers.md) * [FIFO queues and flow concurrency](https://prismatic.io/docs/integrations/triggers/fifo-queue.md) * [Management triggers](https://prismatic.io/docs/integrations/triggers/management.md) * [HMAC verification](https://prismatic.io/docs/integrations/triggers/webhook/what-is-hmac.md) --- ### File import and export integrations File integrations move files between systems - importing CSVs from an SFTP server, exporting reports to S3, or processing spreadsheets dropped in a cloud storage bucket. The main challenge is handling large files without running into memory limits or execution timeouts. #### Triggers[​](#triggers "Direct link to Triggers") * **Scheduled trigger** - poll an SFTP server or cloud storage bucket on a fixed interval. * **Webhook trigger** - receive a notification when a file is ready (some platforms push a file reference or upload event rather than the file itself). #### Handling large files[​](#handling-large-files "Direct link to Handling large files") Each flow execution has limited memory (1 GB) and a 15-minute execution limit. The most important design decision for file integrations is how to avoid loading entire files into memory. **Pass references, not content.** When moving files between storage systems (S3, Azure Blob, Dropbox), pass presigned URLs or file references from source to destination instead of downloading the file into the flow. See [handling large files](https://prismatic.io/docs/integrations/common-patterns/large-files.md) for patterns. **Stream or chunk large files.** If you must process file content - parsing CSV rows, transforming records - read the file line-by-line or in fixed-size chunks. Use [streaming in custom components](https://prismatic.io/docs/custom-connectors/handling-large-files-in-custom-components.md) for byte-level control. **Use large data syncs for very large datasets.** Fetch a page of records at a time and break the records into batches, processing each batch sequentially. See [large data syncs](https://prismatic.io/docs/integrations/common-patterns/large-data-sync.md). #### Best practices[​](#best-practices "Direct link to Best practices") * **Validate before processing.** Check file schema, headers, and encoding before ingesting. Move invalid files to a quarantine folder and emit a structured error rather than failing silently. * **Idempotency.** Use checksums, file IDs, or timestamps to detect files that have already been processed and skip them safely on retry. * **Config-driven paths and credentials.** Store SFTP hosts, bucket names, file paths, and credentials in config variables - never hardcode them. The same integration should deploy across many customers with different configurations. * **Observability.** Log records processed, skipped, and errored. Emit metrics for file counts, sizes, and processing duration. * **Separate transfer from business logic.** Keep the file retrieval step independent from transformation and ingestion so failures are easier to isolate. #### Related documentation[​](#related-documentation "Direct link to Related documentation") * [Handling large files](https://prismatic.io/docs/integrations/common-patterns/large-files.md) * [Streaming in custom components](https://prismatic.io/docs/custom-connectors/handling-large-files-in-custom-components.md) * [Large data syncs](https://prismatic.io/docs/integrations/common-patterns/large-data-sync.md) * [Persisting state](https://prismatic.io/docs/integrations/persist-data.md) * [Integration runner limits](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md) --- ### Monitoring Overview No system is perfect. The API you integrate with may go down temporarily, you might encounter an unexpected edge case when importing data, or a cosmic ray might [flip a bit](https://www.youtube.com/watch?v=o3Cx2wmFyQQ) in your system! Whatever the case, rapid detection, alerting and resolution of issues is critical. Prismatic provides three core monitoring capabilities for customer-deployed instances: 1. Comprehensive [logging](https://prismatic.io/docs/monitor-instances/logging.md) enables detailed analysis of each execution step. When anomalies occur, logs provide the primary source of information for understanding data flow and execution behavior within your instances. 2. Configurable [alerts](https://prismatic.io/docs/monitor-instances/alerting.md) notify your team (via webhook or email) of specific events, such as execution failures or missed execution schedules. 3. Automated [retry](https://prismatic.io/docs/monitor-instances/retry-and-replay/automatic-retry.md) mechanisms handle transient issues with third-party applications without manual intervention. For integration logic issues, you can update the integration and [replay](https://prismatic.io/docs/monitor-instances/retry-and-replay/replaying-failed-executions.md) previously failed executions. --- ### Alerting Effective monitoring and alerting are crucial for maintaining reliable integrations. When issues occur - such as a dependent REST endpoint becoming unavailable or an integration's performance degrading significantly - your incident response team needs immediate notification. Proactive monitoring ensures that your team can identify and address integration issues before they impact your customers. With properly configured monitoring and alerting you can put your mind at ease - no news is good news! Prismatic alert monitors are configurable. * Choose from multiple alert triggers including elevated log levels, execution time thresholds, and failed executions * Notify your integration team through various channels: * Email notifications * Integration with services like Slack and PagerDuty * Custom webhook support for any notification system [How to Alert People When an Instance has an Error](https://player.vimeo.com/video/499651619) #### Terminology[​](#terminology "Direct link to Terminology") * An **alert group** is a set of users and webhooks that you notify when an instance exhibits noteworthy or unexpected behavior, such as execution failures. * An **alert trigger** specifies the conditions that initiate an alert monitor. You can configure triggers for: * Performance issues (e.g., exceeded execution time thresholds) * Error conditions (e.g., error or warning log messages) * Status changes (e.g., successful runs or instance enablement) For a comprehensive list of available triggers, see [alert triggers](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md#alert-triggers). * An **alert monitor** combines an alert group with alert triggers for a specific [instance](https://prismatic.io/docs/instances.md). It defines which conditions should trigger alerts and which groups should be notified. * An **alert event** is what's generated when an alert trigger's conditions are met. For example, if an instance scheduled to run every 15 minutes fails, an alert event notifies the DevOps team. Subsequent failures generate new events until you resolve the issue. --- ### Alert Groups #### Alert groups[​](#alert-groups "Direct link to Alert groups") Alert groups let you efficiently manage notifications by grouping users who should be notified when specific integrations encounter issues. Instead of configuring notifications individually for each integration, you can create an **alert group** and assign it to multiple alert monitors. This centralized approach simplifies user management - when you add a new team member, such as a DevOps engineer, you can add them to the relevant alert group, and they'll automatically receive notifications for all associated alert monitors. Alert groups can include both **organization** team members and **customer** users. For optimal organization, we recommend creating separate alert groups for: * Your internal teams (e.g., DevOps, Support) * Each customer who needs notifications This structure allows you to: * Attach your team's alert group(s) to all relevant alert monitors * Associate customer-specific alert groups only with monitors for their respective instances ##### Creating alert groups[​](#creating-alert-groups "Direct link to Creating alert groups") * Web App * CLI * API Click **Settings** on the left-hand sidebar, and select the **Alert Groups** tab. Click the **+ Add alert group** button on the upper-right and give your alert group a name (e.g. "Progix DevOps Team"). From there, you can specify users to be notified and webhooks to be invoked when an alert triggers. ![Create alert group in Prismatic app](/docs/assets/images/create-alert-group-cae04c5582b59d1e24f29fe9edb97392.png) Use the `alerts:group:create` subcommand to create a new alert group. You can pass in JSON-formatted lists of user IDs and webhook IDs ```bash USER_IDS="[ \"$(prism organization:users:list --columns id --no-header --filter 'Email=edward.davis@progix.io')\", \"$(prism organization:users:list --columns id --no-header --filter 'Email=kristin.henry@progix.io')\", \"$(prism organization:users:list --columns id --no-header --filter 'Email=samantha.johnson@progix.io')\" ]" WEBHOOK_IDS="[\"$(prism alerts:webhooks:list --columns id --no-header --filter 'name=Devops Webhook')\"]" # Create an alert group to email your DevOps Team prism alerts:groups:create \ --name DevOps \ --users "${USER_IDS}" \ --webhooks "${WEBHOOK_IDS}" ``` You can take advantage of [jq](https://jqlang.org/) to process JSON on the command line to simplify your user IDs query. ```bash # Create an alert group to alert customer users at FTL Rockets CUSTOMER_ID=$(prism customers:list --columns id --no-header --filter 'Name=^FTL Rockets$') CUSTOMER_USER_IDS=$( prism customers:users:list \ --customer $CUSTOMER_ID \ --output json \ --columns id | jq '[.[].id]') prism alerts:groups:create \ --name 'Customer - FTL Rockets' \ --users "${CUSTOMER_USER_IDS}" ``` To create an alert group you will need to know the IDs of the users and webhooks who you would like to add to the group. You can look up user IDs grouped by customer name with this query: ```graphql query { customers { nodes { name users { nodes { id name } } } } } ``` Alert webhook IDs can be queried for using this query: ```graphql query { alertWebhooks { nodes { id name } } } ``` Once you have user IDs and alert webhook IDs, create an alert group using the [createAlertGroup](https://prismatic.io/docs/api/schema/mutations.md#createalertgroup)) mutation: ```graphql mutation createAlertGroup($name: String!, $users: [ID], $webhooks: [ID]) { createAlertGroup(input: { name: $name, users: $users, webhooks: $webhooks }) { alertGroup { id } } } ``` Query Variables ```json { "name": "DevOps", "users": [ "VXNlcjplZTI3N2I4My0zOTBmLTQ3ODAtOGU4ZS1iYmNjOGY1Y2RlMTk=", "VXNlcjpiNmZmNDJhNS1mOTM3LTRlOWEtYWMyYi0yNjNjYTFiYjgzYjQ=" ], "webhooks": [ "QWxlcnRXZWJob29rOjA2NmJkN2Q1LThiYTgtNGJlMi1hM2MyLTE3NzFlMzY3NmI3Zg==" ] } ``` ##### Editing existing alert groups[​](#editing-existing-alert-groups "Direct link to Editing existing alert groups") To modify an existing alert group, return to the same screen you saw when you created your alert group by clicking **Settings** on the left-hand sidebar and then selecting the **Alert Groups** tab. Click into an existing alert group. Within this screen, you can modify the name of the group by clicking the group's name at the top of the page. You can also modify the list of users and webhooks associated with the group. ##### Deleting alert groups[​](#deleting-alert-groups "Direct link to Deleting alert groups") * Web App * CLI * API To delete an alert group click the **Settings** link on the left-hand sidebar. Then, click the **Alert Groups** tab and select an alert group. Scroll to the bottom of the alert group's page and click **Delete alert group**. Click **Remove alert group** to confirm deletion. Find the ID of the alert group you want to delete using ```bash prism alerts:groups:list --extended ``` and then reference that ID using ```bash prism alerts:groups:delete ${ALERT_GROUP_ID} ``` Delete an alert group using the [deleteAlertGroup](https://prismatic.io/docs/api/schema/mutations.md#deletealertgroup)) mutation: ```graphql mutation { deleteAlertGroup( input: { id: "QWxlcnRHcm91cDo3MmU0OTMyNi1lMWYyLTRlNGEtYTNmZi00ZGIxZmY1NWViNmU=" } ) { alertGroup { id } } } ``` --- ### Alert Monitors #### Alert triggers[​](#alert-triggers "Direct link to Alert triggers") You can configure your alert monitors to respond to many different events: * **Execution Completed**: Triggers when an instance runs successfully * Use case: Notify customers of successful integration runs * **Execution Duration Matched or Exceeded**: Triggers when execution time exceeds a specified threshold * Example: Alert if an integration takes longer than 10 seconds when it typically takes 5 * **Execution Failed**: Triggers on instance execution failure * **Execution Failed, Retry Pending**: Triggers when an execution fails but is scheduled for retry * **Execution Overdue**: Triggers when an expected execution hasn't occurred within the specified interval * **Execution Started**: Triggers when an instance begins execution * **Instance Disabled**: Triggers when an instance is deactivated * **Instance Enabled**: Triggers when an instance becomes active * Use case: Notify project managers when an instance is ready for customer use * **Instance Removed**: Triggers when an instance is deleted * **Log Level Matched or Exceeded**: Triggers when logs meet or exceed specified severity levels * Monitors unexpected `error` or `warn` log entries * **Connection Threw an Exception**: Triggers on connection failures * Indicates expired credentials, invalid authentication, or API availability issues Note: Some triggers are instance-wide (like status changes), while others are flow-specific (like execution events). This allows you to configure monitoring at the granular level through [alert monitors](#alert-monitors). **For More Information**: [Log Levels](https://prismatic.io/docs/monitor-instances/logging.md#log-levels) #### Alert monitors[​](#alert-monitors "Direct link to Alert monitors") An **alert monitor** is a combination of an [alert group](https://prismatic.io/docs/monitor-instances/alerting/alert-groups.md) (users and webhooks) and an [alert trigger](#alert-triggers) that is configured for an [instance](https://prismatic.io/docs/instances.md). When you add an alert monitor to an instance, you specify when it should trigger and which alert group(s) to notify when a trigger fires. Alert monitors cannot be bound to preprocess flows Note that if your instances are configured to use a [shared endpoint](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md#shared-endpoint-configuration) and a **preprocess flow**, an alert monitor cannot be assigned to the preprocess flow since the preprocess flow runs independently of any deployed instance. ##### Creating an alert monitor[​](#creating-an-alert-monitor "Direct link to Creating an alert monitor") * Web App * CLI * API After selecting an instance from a customer's **Instances** tab or the **Instances** link on the left-hand sidebar, click the instance's **Monitors** tab. Click the **+ Add alert monitor** button on the top-right of the screen. Specify a name for the monitor and select a trigger. If you're in a customer's **Instances** tab, you'll also need to specify the instance. ![Create alert monitor in Prismatic app](/docs/assets/images/create-alert-monitor-e26a933639982a4668205742ed85e8c8.png) After creating the alert monitor, you'll land in the monitor's **Details** tab. Within this tab, you can add additional triggers to your alert monitor within the **Triggers** card. You can also choose the groups or users to notify and webhooks to trigger when an alert trigger fires. ![Configure alert monitor in Prismatic app](/docs/assets/images/configure-alert-monitor-6b1c97c8f272abbfd49ecee73e89025e.png) To create an alert monitor, gather the IDs of the trigger you want, the instance, and the group(s) to alert. Then, you can create a monitor using that trigger ID, instance ID, and group ID(s). ```bash # Get Trigger ID prism alerts:triggers:list --extended --filter 'name=^Execution Failed$' Id Name ──────────────────────────────────────────────────────────────────── ──────────────── QWxlcnRUcmlnZ2VyOjQyYmM2MDY5LTE5YTktNDE1MS04ZjAwLTQ4ZWExN2E3MzZjMQ== Execution Failed # Get Alert Group ID prism alerts:groups:list --extended --filter 'name=^DevOps$' Id Name ──────────────────────────────────────────────────────────────── ────── QWxlcnRHcm91cDplMzcwMzY2OC0yZWM4LTQ0MWEtODdlYS02OGZjYTg1N2U5N2E= DevOps INSTANCE_ID=$(prism instances:list --columns id --filter 'name=^Fabricate 3D Model for FTL Rockets$' --no-header) prism alerts:monitors:create \ --groups "[\"QWxlcnRHcm91cDplMzcwMzY2OC0yZWM4LTQ0MWEtODdlYS02OGZjYTg1N2U5N2E=\"]" \ --name 'Alert Devops on Failure' \ --instance ${INSTANCE_ID} \ --triggers "[\"QWxlcnRUcmlnZ2VyOjQyYmM2MDY5LTE5YTktNDE1MS04ZjAwLTQ4ZWExN2E3MzZjMQ==\"]" ``` To create an alert monitor for an instance, query for [alertTriggers](https://prismatic.io/docs/api/schema/queries.md#alerttriggers)) and select which types of triggers should result in an alert: ```graphql query listTriggers { alertTriggers { nodes { id name } } } ``` You'll also need the IDs of the instance, users, and groups to notify. Then, use the [createAlertMonitor](https://prismatic.io/docs/api/schema/mutations.md#createalertmonitor)) mutation to create the alert monitor: ```graphql mutation ( $name: String! $instance: ID! $triggers: [ID]! $groups: [ID] $users: [ID] ) { createAlertMonitor( input: { name: $name instance: $instance triggers: $triggers groups: $groups users: $users } ) { alertMonitor { id } } } ``` Query Variables ```json { "name": "Alert Alex and DevOps on Execution Failure", "instance": "SW5zdGFuY2U6OTc1YzgyMTEtYTIxZi00OTg1LThhODYtMTUxMTczM2ZiYTJh", "triggers": [ "QWxlcnRUcmlnZ2VyOjhiOTg3YmYxLTk4YmMtNDViNy1hZDFkLTEwNWY0YTExZjdlOA==", "QWxlcnRUcmlnZ2VyOjdlOWEzMDA2LTQxODItNDQ0MC1iYzE2LTFiNjNjMzI2NzkwZA==" ], "groups": ["QWxlcnRHcm91cDo5MDQyYmM1ZC1hYTU5LTQ3Y2EtOWE4NC00NWIxNDBmZjYzYmQ"], "users": ["VXNlcjo4MzBjZTZmYS1iNDFlLTQ2MTQtODgzNi04NjA1MTcyY2IyOTc="] } ``` ##### Alerting on connection errors[​](#alerting-on-connection-errors "Direct link to Alerting on connection errors") You can set up an alert monitor to notify you when a connection expires or becomes invalid (for example, if credentials have been revoked or an API is down). To alert on connection errors, create a new alert monitor and select **Connection Threw an Exception** as the trigger. This is especially useful with OAuth 2.0 connections. If refreshing your access key fails, the alert message will direct you straight to the relevant logs. ##### Editing existing alert monitors[​](#editing-existing-alert-monitors "Direct link to Editing existing alert monitors") To modify an existing alert monitor, click **Instances** on the left-hand sidebar and then select an instance. Under the instance's **Monitors** tab, select a monitor. This brings you to the same screen you saw when creating the monitor, where you can modify notification settings under the **Details** tab. ##### Deleting an alert monitor[​](#deleting-an-alert-monitor "Direct link to Deleting an alert monitor") * Web App * CLI * API Click **Customers** from the left-hand sidebar and select a customer. Under the customer's **Instances** tab, select an instance and then click **Monitors**. Click into an alert monitor and open the **Details** tab. Scroll to the bottom of the page. Click **Delete Monitor** and confirm deletion by clicking **Remove monitor** Find the ID of the alert monitor you would like to delete using ```bash prism alerts:monitors:list --extended ``` and then delete the monitor with ```bash prism alerts:monitors:delete ${ALERT_MONITOR_ID} ``` Delete an alert monitor with the [deleteAlertMonitor](https://prismatic.io/docs/api/schema/mutations.md#deletealertmonitor)) mutation: ```graphql mutation { deleteAlertMonitor( input: { id: "QWxlcnRNb25pdG9yOjQ4ZjVkZjkzLWU3MTAtNGFmNi1iZmRmLWU5ZWM4MDAzYTAyOA==" } ) { alertMonitor { id } } } ``` --- ### Alert Webhooks #### Alert webhooks[​](#alert-webhooks "Direct link to Alert webhooks") Beyond email notifications, you can configure alert monitors to send HTTP requests to webhook endpoints with customizable payloads. You can use alert webhooks to integrate with incident management systems like PagerDuty or OpsGenie, custom DevOps alert endpoints, or any HTTP-based alerting service. ##### Creating alert webhooks[​](#creating-alert-webhooks "Direct link to Creating alert webhooks") * Web App * CLI * API To create or modify a webhook endpoint, navigate to the **Settings** page and select the **Alert Webhooks** tab. Click the **+ Add alert webhook** button to configure the webhook name, URL, and payload template. You can reuse alert webhooks across multiple alert monitors through configurable payload templates. In the **Payload Template** section, you can use predefined variables that are dynamically replaced when an alert monitor triggers: * `$SUBJECT` - The static string "Prismatic.io Alert" * `$NAME` - The name of the [alert monitor](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md) that triggered * `$INSTANCE` - The name of the instance associated with the triggered alert monitor * `$INSTANCE_ID` - The global identifier of the instance (the `SW5z....` portion of the instance URL) * `$EXECUTION_ID` - The global identifier of the execution * `$CUSTOMER` - The name of the customer to whom the instance is deployed * `$CUSTOMER_EXTERNAL_ID` - The [external ID](https://prismatic.io/docs/customers/managing-customers.md#customer-external-ids) of the customer to whom the instance is deployed * `$FLOW` - The name of the flow that was executing when the alert monitor triggered * `$TRIGGER` - The name of the [alert trigger](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md#alert-triggers) (e.g., "Execution Failed") * `$STEP` - The name of the step within the integration that triggered the alert monitor * `$URL` - A direct link to the triggered alert monitor ![Configure alert webhook in Prismatic app](/docs/assets/images/alert-webhook-7fe7e7747347a0ad0eef40bfbbe01315.png) After creating the alert webhook, you can update the name, URL, or payload template, and optionally configure HTTP headers. Headers are commonly used for authentication, such as passing an API token to the webhook endpoint. ![Configure HTTP headers for alert webhook in Prismatic app](/docs/assets/images/alert-webhook-headers-559343c0f27c119aa84bdc8157592edd.png) To create an alert webhook, use the `alerts:webhooks:create` subcommand: ```bash prism alerts:webhooks:create \ --name 'Devops Webhook' \ --headers '{"Authorization": "Bearer abc123"}' \ --payloadTemplate 'The instance "$INSTANCE" was triggered by "$TRIGGER" on monitor "$NAME". It appears that step "$STEP" generated an error. Respond by visiting $URL.' \ --url https://devops.progix.io/alerts/webhook ``` To create an alert webhook, use the [createAlertWebhook](https://prismatic.io/docs/api/schema/mutations.md#createalertwebhook)) mutation: ```graphql mutation { createAlertWebhook( input: { name: "Devops Webhook" url: "https://devops.progix.io/alerts/webhook" headers: "{\"Authorization\": \"Bearer abc123\"}" payloadTemplate: "The instance \"$INSTANCE\" was triggered by \"$TRIGGER\" on monitor \"$NAME\". It appears that step \"$STEP\" generated an error. Respond by visiting $URL." } ) { alertWebhook { id } } } ``` ##### Editing existing alert webhooks[​](#editing-existing-alert-webhooks "Direct link to Editing existing alert webhooks") To modify an existing alert webhook, navigate to **Settings** in the left-hand sidebar and select the **Alert Webhooks** tab. Select the webhook to edit. You can update the webhook name by clicking the name at the top of the page. In the **Details** tab, you can modify the webhook template, payload template, or URL, and configure optional HTTP headers for authentication or other requirements. ![Edit alert webhook configuration in Prismatic app](/docs/assets/images/edit-webhook-09cd7505e7427790e12da4954edfd734.png) ##### Deleting alert webhooks[​](#deleting-alert-webhooks "Direct link to Deleting alert webhooks") * Web App * CLI * API To delete an alert webhook, navigate to the **Settings** page from the left-hand sidebar. Select the **Alert Webhooks** tab and choose the webhook to delete. On the webhook's page, click **Delete Alert Webhook**. Confirm the deletion by clicking **Remove alert webhook**. Retrieve the webhook's ID with: ```bash prism alerts:webhooks:list --extended ``` Then delete the webhook using: ```bash prism alerts:webhooks:delete ${WEBHOOK_ID} ``` Delete an alert webhook using the [deleteAlertWebhook](https://prismatic.io/docs/api/schema/mutations.md#deletealertwebhook)) mutation: ```graphql mutation { deleteAlertWebhook( input: { id: "QWxlcnRXZWJob29rOjczOGNiNTM2LWFhMGMtNGUwNS05ZTBmLTQ5ZDMzZDE5ODYwNA==" } ) { alertWebhook { id } } } ``` --- ### Creating Alert Monitors Programmatically While you can create alert monitors manually through the web interface, when you automate monitor creation, you ensure consistent alerting across all your instances. This guide shows you how to use the Prismatic API to programmatically create and configure alert monitors at scale. #### Programmatically creating alert monitors[​](#programmatically-creating-alert-monitors "Direct link to Programmatically creating alert monitors") [Alert monitors](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md) enable automated notifications when specific events occur within an instance's flow execution. Most commonly, alert monitors are used to notify you when an execution fails. This guide demonstrates how to programmatically create alert monitors across all instances. An example script that creates alert monitors for all flows of all instances is available in the [examples repository](https://github.com/prismatic-io/examples/tree/main/api/create-alert-monitors). ##### List instances programmatically[​](#list-instances-programmatically "Direct link to List instances programmatically") First, you'll retrieve all customer instances, including their flows, associated customers, and existing monitors. The following GraphQL query provides this information: ```graphql query myGetInstancesQuery($cursor: String) { instances( enabled: true sortBy: { direction: ASC, field: CREATED_AT } after: $cursor ) { nodes { id name flowConfigs { nodes { id flow { name } monitors { nodes { id name groups { nodes { id } } } } } } customer { id name } } pageInfo { hasNextPage endCursor } } } ``` Note three important details about this query: * `enabled: true` filters for currently active instances * The combination of `sortBy { direction: ASC, field: CREATED_AT }`, `after: $cursor`, and `pageInfo` enables [pagination](https://prismatic.io/docs/api/pagination.md) through results If you'd like to see an example of how to paginate through results, check out the [example script](https://github.com/prismatic-io/examples/blob/main/api/create-alert-monitors/queries/get-instances.ts) which implements the query above. ##### Fetch alert trigger information[​](#fetch-alert-trigger-information "Direct link to Fetch alert trigger information") Next, retrieve information about the desired alert trigger. Alert monitors can be [triggered](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md#alert-triggers) by several events, such as execution failures, successful executions, or execution duration thresholds. Query available alert triggers using: ```graphql { alertTriggers { nodes { id name } } } ``` The returned `id` is required for creating the alert monitor. An example of this query is available in the [example script](https://github.com/prismatic-io/examples/blob/main/api/create-alert-monitors/queries/get-alert-trigger.ts). ##### Fetch notification recipient information[​](#fetch-notification-recipient-information "Direct link to Fetch notification recipient information") To configure user notifications, retrieve the recipient's information. The user must be registered in your Prismatic organization to receive email notifications. Query a user by email address: ```graphql query myGetUsersByEmail($email: String!) { users(email: $email) { nodes { id name email } } } ``` Note that this query may return zero or one users - you'll need to check the length of the `nodes` array to determine if a user was found, like the example script does [here](https://github.com/prismatic-io/examples/blob/main/api/create-alert-monitors/queries/get-user.ts). The user's `id` is required for alert monitor creation. ##### Create the alert monitor[​](#create-the-alert-monitor "Direct link to Create the alert monitor") Finally, you'll iterate through instances and their flows to create alert monitors. For each flow, check for existing monitors before creating a new one using the `createAlertMonitor` mutation: ```graphql mutation myCreateAlertMonitor( $name: String! $instanceId: ID! $flowConfigId: ID! $triggerId: ID! $userId: ID! ) { createAlertMonitor( input: { name: $name instance: $instanceId flowConfig: $flowConfigId triggers: [$triggerId] users: [$userId] } ) { alertMonitor { id } errors { field messages } } } ``` Required mutation parameters: * `name`: Alert monitor identifier. Use a consistent naming pattern (e.g., `[Generated] Alert on Error - FLOW NAME`) to ensure idempotency * `instanceId`: Target instance ID from the instance listing * `flowConfigId`: Target flow ID from the instance listing * `triggerId`: Alert trigger ID from the trigger listing * `userId`: Notification recipient ID from the user query You should handle potential errors by checking the `errors` field in the response. See the [example implementation](https://github.com/prismatic-io/examples/blob/main/api/create-alert-monitors/queries/create-alert-monitor.ts) for error handling. --- ### Responding to Alert Events #### Alert events[​](#alert-events "Direct link to Alert events") An **alert event** is generated when an alert monitor is triggered. Upon event creation, you and team members in the monitor's associated alert groups receive email notifications containing a direct link to the event. You and your team members can acknowledge and indicate active resolution of the issue by **clearing** the alert event. ##### Viewing alert events[​](#viewing-alert-events "Direct link to Viewing alert events") * Web App * CLI * API You can access an alert event most directly through the link provided in the notification email. Alternatively, navigate to the **Instances** section via the left-hand sidebar to view all instances. You'll see an indicator in the lower-right corner of each instance when alert monitors are triggered but not yet cleared. ![Alert monitor status indicator on instance in Prismatic app](/docs/assets/images/triggered-alert-monitor-8d2f758c4d64bd2331d7bba5060c2d8d.png) Select an instance with triggered monitors and navigate to the **Monitors** tab to view active alerts. ![Instance monitors with active alerts highlighted in Prismatic app](/docs/assets/images/list-instance-monitors-8a5045d09a6704ac7b2924d19234f94c.png) Select a triggered monitor to access its **Details** tab, then navigate to the **Events** tab for specific event information. When you select an individual alert event, Prismatic displays relevant logs from the time period surrounding the event at the bottom of the page. ![Contextual logs for alert event in Prismatic app](/docs/assets/images/alert-event-logs-94bb5f518bc4808d1a50d092bf789d71.png) To view alert events, use the `alerts:events:list` subcommand with the target alert monitor's ID: ```bash prism alerts:monitors:list --extended Id Name Triggered ──────────────────────────────────────────────────────────────────── ────────────────────────────────── ───────── QWxlcnRNb25pdG9yOmQyM2NlOGZlLTZiMzktNGFkNy1hMGM1LTFlMTRjMjY4MTI1Mg== Alert Project Managers on Enabling true QWxlcnRNb25pdG9yOmQ0MTM3N2M5LWE1NTItNDJjNi04ZWYwLWNiY2ZkM2E2ODMxYg== Alert Devops false prism alerts:events:list QWxlcnRNb25pdG9yOmQ0MTM3N2M5LWE1NTItNDJjNi04ZWYwLWNiY2ZkM2E2ODMxYg== ``` Query [alertEvents](https://prismatic.io/docs/api/schema/queries.md#alertevents)) to list alert events for a specific alert monitor. **For More Information:** [Log Retention](https://prismatic.io/docs/monitor-instances/logging.md#log-retention) #### Clearing a triggered alert monitor[​](#clearing-a-triggered-alert-monitor "Direct link to Clearing a triggered alert monitor") [How to Respond to an Alert Message](https://player.vimeo.com/video/500203509) When multiple team members receive alert notifications, it's crucial to track the event's resolution status. When you clear an alert monitor, you indicate acknowledgment of the event and active resolution efforts. Navigate to the **Monitors** section via the left-hand sidebar. Select one or more triggered monitors. Click the icon to clear the selected events. ![Clear selected alert events in Prismatic app](/docs/assets/images/clear-events-22f85c2b3706dd711c0dad98fb350ba9.png) --- ### Sending Alerts to PagerDuty PagerDuty is a leading incident response platform that helps teams manage and track production issues effectively. You can send Prismatic's alert webhooks to PagerDuty using [PagerDuty's Events API](https://developer.pagerduty.com/docs/events-api-v2/trigger-events/) to automatically create and manage incidents. To configure PagerDuty alerts: 1. Create a new alert webhook in Prismatic 2. Set the webhook URL to: `https://events.pagerduty.com/v2/enqueue` 3. Configure the payload template with the following JSON: PagerDuty webhook payload template ```json { "routing_key": "YOUR-PAGERDUTY-KEY", "event_action": "trigger", "links": [{ "href": "$URL", "text": "Link to Prismatic alert monitor" }], "payload": { "summary": "$NAME triggered - $INSTANCE failed to run.", "severity": "error", "source": "$SUBJECT" } } ``` You can add additional fields to the payload template as documented in the [PagerDuty API documentation](https://developer.pagerduty.com/docs/events-api-v2/trigger-events/). You don't need additional headers since the PagerDuty integration key is included in the payload. Each alert monitor trigger creates a corresponding incident in PagerDuty. ![Sample alert details in PagerDuty app](/docs/assets/images/pagerduty-f262aadff6c18a8f3d3f7eb5102f29d4.png) --- ### Sending Alerts to Slack [How to Send Alerts to Slack](https://player.vimeo.com/video/500205967) Many operations teams use Slack to notify themselves of production issues. You can configure Prismatic alert webhooks to send messages to a Slack channel. #### Generate a Slack webhook[​](#generate-a-slack-webhook "Direct link to Generate a Slack webhook") First, create an incoming webhook in your Slack workspace: 1. Visit 2. Click **Create New App** and select your workspace 3. Under **Add features and functionality**, select **Incoming Webhooks** 4. Toggle **Activate Incoming Webhooks** to enable the feature 5. Click **Add New Webhook to Workspace** and select your target channel 6. Copy the generated Webhook URL (format: `https://hooks.slack.com/services/foo/bar/baz`) #### Configure Prismatic alert webhook[​](#configure-prismatic-alert-webhook "Direct link to Configure Prismatic alert webhook") Now, create a new alert webhook in Prismatic to send notifications to Slack: 1. Create a new alert webhook in Prismatic 2. Enter the Slack webhook URL from step 6 above 3. Configure the payload template with the following JSON: Slack webhook payload template ```json { "text": "$NAME triggered - $INSTANCE failed to run. See $URL" } ``` You don't need additional headers for Slack webhooks. Once configured, any alert monitor using this webhook will automatically send notifications to your specified Slack channel. ![Slack channel showing Prismatic alert notification with instance name, error trigger, and link to execution details](/docs/assets/images/slack-af0449f5315a06392d5fa549f3c208d1.png) #### Next steps[​](#next-steps "Direct link to Next steps") Now that you've configured Slack alerts, you can: * [Create alert monitors](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md) for your instances * [Configure alert groups](https://prismatic.io/docs/monitor-instances/alerting/alert-groups.md) to notify different teams * [Set up alert triggers](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md#alert-triggers) for specific events --- ### Instance Executions When you trigger a flow, it creates an execution. An execution represents a single run of a flow. Executions can be [triggered](https://prismatic.io/docs/integrations/triggers.md) by multiple event types: 1. Scheduled runs via [schedule triggers](https://prismatic.io/docs/integrations/triggers/schedule.md) 2. Webhook invocations ([webhook triggers](https://prismatic.io/docs/integrations/triggers/webhook.md)) 3. Instance deployment or removal events ([deployment triggers](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger)) You can trigger a flow within an instance concurrently, resulting in multiple simultaneous executions. Flows can also invoke other flows within the same instance by calling sibling flows' webhook URLs. Each invocation is a distinct execution. If an execution fails, or if you need to re-run an execution, you can [replay](https://prismatic.io/docs/monitor-instances/retry-and-replay.md) previous executions. #### Viewing execution step results[​](#viewing-execution-step-results "Direct link to Viewing execution step results") For debugging and analysis, you can review the results of instance executions. Navigate to the **Executions** tab on an instance's page to view logs and step outputs for each execution. Alternatively, view executions for all instances by selecting **Executions** from the left-hand sidebar. ![Instance execution results in Prismatic app](/docs/assets/images/execution-results-274849506a7323391947f59b5ac96e2f.png) If an instance fails to complete successfully, you can inspect the input data provided at invocation to assist with debugging. You can access execution results for all instances and customers via the **Executions** link in the sidebar. For a specific customer, navigate to their **Executions** tab. #### Fetching step results from the API[​](#fetching-step-results-from-the-api "Direct link to Fetching step results from the API") You can access step results via the Prismatic GraphQL API using the `executionResult` query. Prismatic serializes results with [MessagePack](https://msgpack.org/index.html), which you can deserialize using the MessagePack library for your preferred language. For more information: [Fetching and Unpacking Step Results](https://prismatic.io/docs/api/common-queries/fetching-step-results.md) #### Viewing execution logs[​](#viewing-execution-logs "Direct link to Viewing execution logs") You can access instance logs from the **Logs** tab on the instance's page. You can also view logs for all instances via the **Logs** link in the sidebar, or for a specific customer by selecting their **Logs** tab. Use the **Search Logs** bar at the top of the page to search log messages. Filter logs by severity or date range using the **Filter** link to the right of the search bar. ![Filter instance logs in Prismatic app](/docs/assets/images/instance-logs-16c45b46accb7aefde3bce27e7c17eca.png) For more information, see [Logging](https://prismatic.io/docs/monitor-instances/logging.md). --- ### Logging You need comprehensive log access when building, deploying, and supporting integrations. When an [alert monitor](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md) notifies your team of unexpected instance behavior, detailed logs provide insight into execution timing, step status, and error details. You can access logs for all instance invocations and test runs in Prismatic. You can also [stream logs](https://prismatic.io/docs/monitor-instances/logging/streaming-logs-externally.md) to an external logging system for centralized analysis, or [fetch logs programmatically](https://prismatic.io/docs/api/common-queries/fetching-logs.md) using Prismatic's GraphQL API. #### Log retention[​](#log-retention "Direct link to Log retention") Prismatic retains logs and step results for 14 days before automatically deleting them. ##### Disabling logs and step results[​](#disabling-logs-and-step-results "Direct link to Disabling logs and step results") Your organization may need to disable log and step result storage for compliance reasons. To discuss retention policy adjustments, contact [support](mailto:support@prismatic.io). When you disable storage, Prismatic doesn't persist log and step result data in its database, and the data isn't available in the web app. If your organization has custom retention policies, a toggle will appear in the instance configuration wizard to disable storage for specific instances. ![Disable logs and step results in Prismatic app](/docs/assets/images/disable-logs-and-step-results-a8e9b25b11371d836ec6da7773a08a5b.png) #### Viewing logs for all customers[​](#viewing-logs-for-all-customers "Direct link to Viewing logs for all customers") To view logs for all instances across all customers, select **Logs** from the left-hand sidebar. You'll see columns for log **messages**, **timestamps** (in your local time), **instance** name, **integration** name, and **customer** name. #### Viewing logs for a specific customer[​](#viewing-logs-for-a-specific-customer "Direct link to Viewing logs for a specific customer") To view logs for a specific customer, select **Customers** in the sidebar, choose a customer, and click the **Logs** tab. You'll see columns for log **messages**, **timestamps** (in your local time), **instance** name, and **integration** name. For more information, see [Customers](https://prismatic.io/docs/customers.md). #### Viewing logs for a specific instance[​](#viewing-logs-for-a-specific-instance "Direct link to Viewing logs for a specific instance") To view logs for a specific instance: 1. Click **Instances** in the sidebar and select an instance, or 2. Click **Customers**, select a customer, and choose an instance under the **Instances** tab. Once viewing an instance, select the **Logs** tab. You'll see columns for log **messages**, **timestamps** (in your local time), **integration** name, and **customer** name. For more information, see [Instances](https://prismatic.io/docs/instances.md). #### Searching and filtering logs[​](#searching-and-filtering-logs "Direct link to Searching and filtering logs") Search log messages using the **Search Logs** bar at the top of any log page. For detailed information about a specific log entry, click the log line to display an information panel at the bottom of the screen. ![Customer log details in Prismatic app](/docs/assets/images/log-line-more-info-d92c0e9a057a65f580a85f8d97c82a74.png) Filter logs using the **Filter** dropdown to the right of the search bar. Filter by: * **Log Type** (execution, connection, data source, or trigger logs) * **Time range** * **Log Severity** (Error, Warn, Info, Debug) * **Flow** ![Filter customer logs in Prismatic app](/docs/assets/images/filter-customer-logs-50fe105cb0f7a84dc93780b2135b8e4c.png) #### Viewing connection logs[​](#viewing-connection-logs "Direct link to Viewing connection logs") Connections generate logs during testing in the integration designer and when used in deployed instances. If a connection encounters an error (e.g., expired credentials), it is recorded in the connection's logs. To view a connection's logs, click the log icon next to the connection. ![Connection logs in Prismatic app](/docs/assets/images/connection-log-button-72adaf2d3cf5ff6b807e5d732afce081.png) Click any log line in the resulting popover to view more details. #### Viewing data source config variable logs[​](#viewing-data-source-config-variable-logs "Direct link to Viewing data source config variable logs") [Data sources](https://prismatic.io/docs/integrations/data-sources.md) fetch data from third-party APIs and present it in the [config wizard](https://prismatic.io/docs/integrations/config-wizard.md). Data source logs are not tied to specific executions. Organization users can view data source logs by clicking the log icon near the data source config variable. **Note**: This icon is not available to customer users configuring integrations in your [embedded marketplace](https://prismatic.io/docs/embed/marketplace.md). ![Data source logs in Prismatic app](/docs/assets/images/data-source-logs-4723d46f20c3560a37ac58486fb40597.png) Data source logs are also available with their associated config variables in the **Test Configuration** drawer under **Logs**. ![Test configuration drawer logs in Prismatic app](/docs/assets/images/test-configuration-logs-drawer-a610f28aba734bc46e74b44413c3df52.png) #### Viewing trigger lifecycle logs[​](#viewing-trigger-lifecycle-logs "Direct link to Viewing trigger lifecycle logs") Most trigger functions run as part of an execution - receiving a webhook request and returning a value, or running on a schedule. Some [trigger lifecycle functions](https://prismatic.io/docs/integrations/lifecycle.md) (such as `onInstanceDeploy` and `onInstanceDelete`) execute when instances are created or deleted. These function logs are available in the **Test Configuration** drawer under **Logs**. ![Test configuration drawer logs in Prismatic app](/docs/assets/images/test-configuration-logs-drawer-a610f28aba734bc46e74b44413c3df52.png) #### What gets logged?[​](#what-gets-logged "Direct link to What gets logged?") When a component calls `context.logger.{debug,info,warn,error}()`, the log entry is saved in Prismatic's logging system. In addition to component-generated logs, the following standard log types are recorded: | Type | Example | Purpose | Log Level | | -------------- | ----------------------------------- | -------------------------------------------------------- | --------- | | Instance Start | Starting Instance 'Sample Instance' | Marks the beginning of an instance run | info | | Instance End | Ending Instance 'Sample Instance' | Indicates successful instance completion | info | | Step Started | Fetch file from Dropbox | Shows the name of the step being executed | info | | Step Failed | `{{ ERROR MESSAGE }}` | Indicates step failure with the associated error message | error | **For More Information**: [`context.logger`](https://prismatic.io/docs/custom-connectors/actions.md#logger-object) #### Log levels[​](#log-levels "Direct link to Log levels") Prismatic uses four log levels: `debug`, `info`, `warn`, and `error`. Each level is visually distinguished: * `debug`: green icons * `info`: gray icons * `warn`: yellow icons * `error`: red icons ![Log levels illustrated and explained](/docs/assets/images/levels-logs-a8fe618c3c340e8da813d75a9459e9b0.png) --- ### Streaming Logs Externally [Streaming Prismatic Logs to DataDog](https://player.vimeo.com/video/894997344) #### External log streaming[​](#external-log-streaming "Direct link to External log streaming") Feature Availability The external log streaming feature is available to customers on some pricing plans. Refer to your contract, or contact Prismatic support for details. You can stream logs from Prismatic to external logging services (such as [Datadog](https://www.datadoghq.com/), [New Relic](https://newrelic.com/)), or to your own logging infrastructure. Most logging services accept HTTP POST requests with JSON payloads containing log data. To configure log streaming in Prismatic, you'll need to specify the destination URL, payload format, and any required headers (e.g., authorization or API keys). To set up external log streaming, open **Settings** in the sidebar and select the **Log Streams** tab. Create a new log stream by clicking **+ Log stream**. ![Add log stream in Prismatic app](/docs/assets/images/add-log-stream-7479206a49052bc7eeb8f1fcac395093.png) Enter the destination URL and add any required headers (such as API keys or authorization tokens). ![Configure log streaming to external service in Prismatic app](/docs/assets/images/url-and-headers-a8028f3ed62252493a7017ee6e8a8912.png) Next, you'll define a log message template. This template determines the structure of the log message Prismatic sends to your logging service. You can include placeholders for log content and metadata about the instance, customer, flow, and step. ![Create log message template in Prismatic app](/docs/assets/images/sample-template-2e2199905faaf4dd5062406dd5a76193.png) The following placeholders are available and will be replaced with actual values when a log message is sent: | Placeholder | Description | Example | | ------------------------------ | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | | `{{ timestamp }}` | Timestamp in milliseconds since epoch | 1637087683123 | | `{{ timestamp_s }}` | Timestamp in seconds since epoch | 1637087683 | | `{{ timestamp_ns }}` | Timestamp in nanoseconds since epoch | 1637087683123000000 | | `{{ timestamp_iso }}` | Timestamp in ISO format | "2021-11-16T18:34:43.123Z" | | `{{ message }}` | Full log message | "This is a test" | | `{{ severity }}` | Log level (debug, info, warn, error, metric) | "warn" | | `{{ severityNumber }}` | [Syslog severity level](https://en.wikipedia.org/wiki/Syslog#Severity_level) | 4 | | `{{ instanceId }}` | Global ID of the instance | "SW5zdEXAMPLE" | | `{{ instanceName }}` | Name of the instance | "Update Inventory" | | `{{ instanceLabels }}` | Labels assigned to the instance | \["label1", "label2"] | | `{{ flowConfigId }}` | Global ID of the instance's configured flow | "SW5zdEXAMPLE" | | `{{ integrationId }}` | Global ID of the deployed integration version | "SW5zdEXAMPLE" | | `{{ integrationName }}` | Name of the integration | "Update Inventory" | | `{{ logType }}` | Log type: "DATA\_SOURCE", "CONNECTION", "EXECUTION", or "MANAGEMENT" | "EXECUTION" | | `{{ flowId }}` | Global ID of the deployed integration flow | "SW5zdEXAMPLE" | | `{{ flowName }}` | Name of the integration flow | "Remove inventory after order fulfillment" | | `{{ stepName }}` | Name of the step, if available | "Loop over order items" | | `{{ isTestExecution }}` | Indicates if the log is from a test in the integration designer | true | | `{{ executionId }}` | Global ID of the execution | "SW5zdEXAMPLE" | | `{{ customerExternalId }}` | [External ID](https://prismatic.io/docs/customers/managing-customers.md#customer-external-ids) of the customer | "abc-123" | | `{{ customerName }}` | Name of the customer | "Acme Corp" | | `{{ executionErrorStepName }}` | Name of the step that resulted in an execution error | "Loop over order items" | | `{{ durationMS }}` | Duration in milliseconds of the execution | "1000" | | `{{ succeeded }}` | Whether the step or execution succeeded | "true" | | `{{ errorMessage }}` | Error message for the step or execution | "This is an error" | | `{{ retryAttemptNumber }}` | Number of retry attempts for the step or execution | "0" | | `{{ retryForExecutionId }}` | Global ID of the original execution in case of retry | "SW5zdEXAMPLE" | This template works with most logging platforms, but you may need to adjust it for your specific requirements. ###### Default message template[​](#default-message-template "Direct link to Default message template") ```js { "message": {{ message }}, "timestamp": {{ timestamp }}, "severity": {{ severity }}, "service": "Prismatic", "instance": {{ instanceName }}, "customer": {{ customerExternalId }}, "integration": {{ integrationName }}, "logType": {{ logType }}, "isTestExecution": {{ isTestExecution }}, "flow": {{ flowName }}, "step": {{ stepName }}, "executionid": {{ executionId }}, "instanceId": {{ instanceId }}, "flowConfigId": {{ flowConfigId }}, "integrationId": {{ integrationId }}, "flowId": {{ flowId }}, "executionErrorStepName": {{ executionErrorStepName }}, "duration": {{ durationMS }}, "succeeded": {{ succeeded }}, "errorMessage": {{ errorMessage }}, "retryAttempt": {{ retryAttemptNumber }}, "retryForExecutionId": {{ retryForExecutionId }} } ``` ##### Testing log streaming[​](#testing-log-streaming "Direct link to Testing log streaming") After saving your configuration, you can test your external logging setup by clicking the **Test payload** button at the top right of the log stream screen. Prismatic sends a test log message to your external logging system, substituting test values (e.g., "Test message", "Test integration") into your template. **Note**: If your logging provider enforces CORS and blocks logs sent directly from the browser, the **Test payload** button may not work. In this case, save your configuration and run a test integration; logs from the execution will be sent to your external provider. ##### Logging metrics to an external service[​](#logging-metrics-to-an-external-service "Direct link to Logging metrics to an external service") In addition to log lines, you can use [`context.logger`](https://prismatic.io/docs/custom-connectors/actions.md#logger-object) to emit objects containing metrics for external streaming. For example, a code component can include: ```js logger.metric({ inventoryItem: { id: "123", price: 10.55, quantity: 3 } }); ``` Your external streaming configuration can extract attributes from the object passed to `metric()`. For example: ```js { "message": {{ message }}, "timestamp": {{ timestamp }}, "severity": {{ severity }}, "itemId": {{ inventoryItem.id }}, "itemPrice": {{ inventoryItem.price }}, "itemQuantity": {{ inventoryItem.quantity }} } ``` When a metric log contains `inventoryItem.id`, those attributes are included in the payload sent to the logging system. Messages without these fields simply omit them. info When `logger.metric()` is called, `{{ message }}` is the [stringified](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) JSON version of the object, and `{{ level }}` is set to `99`. --- ### Streaming logs to Datadog #### Setting up Datadog log streaming[​](#setting-up-datadog-log-streaming "Direct link to Setting up Datadog log streaming") You can stream Prismatic logs to [Datadog](https://www.datadoghq.com/), an application monitoring and logging platform. To stream logs to Datadog, first [generate an API key](https://docs.datadoghq.com/account_management/api-app-keys/#add-an-api-key-or-client-token). Be sure to generate an API key (not an application key). Set the endpoint as specified in the below table, and add a header named `DD-API-KEY` and provide your API key. #### Datadog log intake endpoints by region[​](#datadog-log-intake-endpoints-by-region "Direct link to Datadog log intake endpoints by region") | Region | Endpoint URL | | ------------ | -------------------------------------------------------- | | US (default) | `https://http-intake.logs.datadoghq.com/api/v2/logs` | | US3 | `https://http-intake.logs.us3.datadoghq.com/api/v2/logs` | | US5 | `https://http-intake.logs.us5.datadoghq.com/api/v2/logs` | | EU | `https://http-intake.logs.datadoghq.eu/api/v2/logs` | | AP1 | `https://http-intake.logs.ap1.datadoghq.com/api/v2/logs` | | US1-FED | `https://http-intake.logs.ddog-gov.com/api/v2/logs` | See the [Datadog documentation](https://docs.datadoghq.com/api/latest/logs/#endpoints) for the latest endpoints. ![Configure Datadog log streaming in Prismatic app](/docs/assets/images/datadog-filled-in-4637b7e0963f3b1a9cb46afba9c8a7bf.png) The [default message template](https://prismatic.io/docs/monitor-instances/logging/streaming-logs-externally.md#default-message-template) works with Datadog, but you can customize it to match your attribute naming conventions. Once you configure the stream, Prismatic sends logs from all enabled customer instances and all test runs in the integration designer to Datadog: ![List of logs in Datadog app](/docs/assets/images/datadog-d8ac53bed4d33e4bb22043d57c9feead.png) Testing payloads with Datadog For security reasons, Datadog prohibits sending logs directly from your web browser when using an API key. As a result, the **TEST PAYLOAD** button doesn't work with Datadog. Rest assured, you should see instance logs in Datadog as long as your API key is valid. --- ### Streaming Logs to Google Cloud #### Setting up Google Cloud log streaming[​](#setting-up-google-cloud-log-streaming "Direct link to Setting up Google Cloud log streaming") To stream logs to Google Cloud Logging, you'll need a service account and a way to map Prismatic's severity levels to Google's (`WARNING` vs `warn`). We recommend sending logs to a Google Cloud Function, which then writes to Cloud Logging. 1. In [Google Cloud IAM](https://console.cloud.google.com/iam-admin/serviceaccounts), create a new service account. Grant it the **Logging Admin** role via the [IAM dashboard](https://console.cloud.google.com/iam-admin/iam). 2. Create a [Google Cloud Function](https://console.cloud.google.com/functions/) and assign the service account. Add [@google-cloud/logging](https://www.npmjs.com/package/@google-cloud/logging) as a dependency in `package.json`: ```json { "dependencies": { "@google-cloud/functions-framework": "^3.0.0", "@google-cloud/logging": "11.2.0" } } ``` Use code similar to this for your `index.js` (replace `PROJECT_ID` with your actual project ID): ```javascript const functions = require("@google-cloud/functions-framework"); const { Logging } = require("@google-cloud/logging"); const PROJECT_ID = "INSERT YOUR PROJECT ID HERE"; const LOG_NAME = "prismatic"; const SEVERITY_MAP = { debug: "DEBUG", info: "INFO", warn: "WARNING", error: "ERROR", }; functions.http("helloHttp", async (req, res) => { // Creates a client const logging = new Logging({ projectId: PROJECT_ID }); // Selects the log to write to const log = logging.log(LOG_NAME); const { timestamp, severity, message, ...rest } = req.body; // Labels must be strings const labels = Object.entries(rest).reduce( (acc, [key, value]) => ({ [key]: value === null ? "" : `${value}`, ...acc, }), {}, ); // The metadata associated with the entry const metadata = { resource: { type: "global" }, severity: SEVERITY_MAP[severity], timestamp, labels, }; // Prepares a log entry const entry = log.entry(metadata, message); await log.write(entry); res.send({ success: true }); }); ``` 3. Deploy the function and note its URL (e.g., `https://us-central1-your-project-12345.cloudfunctions.net/your-function`). 4. In Prismatic, configure an external log stream to point to your function's URL. Make sure you send the timestamp in ISO format. Use the following payload template: ```text { "message": {{ message }}, "timestamp": {{ timestamp_iso }}, "severity": {{ severity }}, "instance": {{ instanceName }}, "customer": {{ customerExternalId }}, "integration": {{ integrationName }}, "isTestExecution": {{ isTestExecution }}, "flow": {{ flowName }}, "step": {{ stepName }}, "executionId": {{ executionId }}, "instanceId": {{ instanceId }}, "flowConfigId": {{ flowConfigId }}, "integrationId": {{ integrationId }}, "flowId": {{ flowId }}, "executionErrorStepName": {{ executionErrorStepName }}, "duration": {{ durationMS }}, "succeeded": {{ succeeded }}, "errorMessage": {{ errorMessage }}, "retryAttempt": {{ retryAttemptNumber }}, "retryForExecutionId": {{ retryForExecutionId }} } ``` ![Screenshot of logs streamed to Google](/docs/assets/images/google-logging-56472c751ba27f77f82c8777289eb0f2.png) --- ### Streaming Logs to New Relic [New Relic](https://newrelic.com/) is an application monitoring platform and logging service. #### Setting up New Relic log streaming[​](#setting-up-new-relic-log-streaming "Direct link to Setting up New Relic log streaming") To stream logs to New Relic, first [generate an API key](https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/#ingest-license-key). Make sure you create an **INGEST - LICENSE** key. Next, create a new external log stream in Prismatic. * For US-hosted data, use the endpoint: `https://log-api.newrelic.com/log/v1` * For EU-hosted data, use: `https://log-api.eu.newrelic.com/log/v1` Add a header named `X-License-Key` and provide the license key you generated. **Note**: When copying your key, select "Copy key" (not "key ID"). ![Configure New Relic log streaming in Prismatic app](/docs/assets/images/new-relic-filled-in-31f45d563c8363337995bfa2a172cfbb.png) The default message template ([see here](https://prismatic.io/docs/monitor-instances/logging/streaming-logs-externally.md#default-message-template)) works with New Relic, but you can customize it to match your attribute naming conventions. Once you configure the stream, Prismatic sends logs from all enabled customer instances and all test runs in the integration designer to New Relic: ![List of logs in New Relic app](/docs/assets/images/newrelic-2f7433d33e44bd21294bf83978037dc7.png) --- ### Execution Retry and Replay Because external systems aren't always available, you should [configure automatic retry](https://prismatic.io/docs/monitor-instances/retry-and-replay/automatic-retry.md) in your integrations where appropriate. Retry automatically resends payloads to destination systems at regular intervals or using exponential backoff (e.g., retrying after 2 minutes, then 4, then 8, etc.). Implementing robust retry logic helps ensure your integration succeeds even if the destination system experiences a temporary outage. By leveraging retry, you can avoid involving your development team in integration issues until you have confirmed the problem is more serious than a transient outage. This prevents many ephemeral issues from reaching your support team. However, if errors persist (for example, if the destination system appears operational but the integration continues to return internal server errors), you may need to escalate [from retry to replay functionality](https://prismatic.io/docs/monitor-instances/retry-and-replay/replaying-failed-executions.md). Unlike retry, replay is a manual operation. You initiate it by clicking the **Replay** button in the instance UI. Replay reruns the entire integration with the original payload, allowing you to debug by monitoring each step's execution and pinpoint where the process succeeds or fails. --- ### Automatic Execution Retry Integration executions can fail due to temporary issues like network timeouts or brief third-party service outages. Automatic retry allows your integrations to attempt execution again after a failure, improving reliability without manual intervention. #### Integration retry configuration[​](#integration-retry-configuration "Direct link to Integration retry configuration") You can configure your [asynchronously](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md)-invoked instances to retry if they fail to run to completion. This is especially useful if your integration relies on an unreliable third-party API that may experience brief outages. By enabling retry, your integration can attempt execution again after a short delay, reducing unnecessary alerting and manual intervention. To enable automatic retry, select your trigger and then choose **Flow retry**. ![Set integration to retry in Prismatic app](/docs/assets/images/configure-retry-d7dd528afd396b383d242a53ffe11e44.png) **Retry Attempts** specifies the maximum number of times (up to 10) Prismatic will attempt to run the same instance invocation after a failure. If the number of failures exceeds **Retry Attempts**, the execution is marked as failed and any configured [alert monitors](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md) will fire. **Minutes Between Attempts** sets the interval (in minutes) between retry attempts. For example, if set to **4** minutes and the first attempt fails at 10:24, subsequent attempts will occur at 10:28, 10:32, 10:36, 10:40, and 10:44 if failures persist. *Note:* Retry intervals are precise to the minute (not the second). Thus, a retry scheduled for 4 minutes after a failure at 10:24 may occur at 10:28 or 10:29. If **Exponential Backoff** is enabled, the interval between retries increases exponentially (factor of 2). For example, with **Minutes Between Attempts** set to 3 and **Exponential Backoff** enabled, retries will occur after 3, 6, 12, 24, and 48 minutes. *Note*: The maximum delay before a retry is **24 hours**. If exponential backoff would result in a longer delay, the retry will occur after 24 hours instead. **Retry Cancellation** allows you to cancel pending retries if a more recent invocation occurs. For example, if your integration processes payloads with unique IDs, you may want to cancel retries for older invocations when new data arrives, preventing outdated data from overwriting newer updates. To configure retry cancellation, select a unique request ID from the trigger payload. For example, you might pass in a header, `x-my-unique-id: abc123` as part of your trigger payload. If another invocation with that header comes in that updates resource `abc123`, you might want to cancel currently queued retries. To do that, select your trigger's `results.headers.results.headers.x-my-unique-id` reference as your **Unique Cancellation ID**. Cancellation IDs do not need to be headers. Instead, you can select a key from the payload body. For example, if your instance invocation looks like this: ```bash curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --location \ --header "Content-Type: application/json" \ --data '{"productId":"abc123","price":"250","description":"A box of widgets"}' ``` You can key your unique cancellation ID off of `results.body.data.productId`. For more information, see [Instance retry and replay](https://prismatic.io/docs/monitor-instances/retry-and-replay.md). --- ### Replay Failed Executions When an execution fails due to temporary issues or unexpected data formats, you can replay it with the same input data after resolving the problem. This guide shows you how to programmatically replay failed executions using the Prismatic API. #### Replaying failed executions programmatically[​](#replaying-failed-executions-programmatically "Direct link to Replaying failed executions programmatically") Integrations can fail for several reasons: * A third-party API may be unavailable when your instance attempts to access it. While retry can address brief outages, it doesn't handle scenarios where the API is down for extended periods. * A third party may begin sending data in an unexpected format. * Your instance may encounter other edge cases that aren't handled gracefully. Regardless of the error's cause, you can re-run your instance with the exact same input data after the third-party API is restored or after you've updated your integration to handle new data formats. Replay allows you to re-execute a previous run's data through your instance. You can perform this easily via the [Prismatic API](https://prismatic.io/docs/api.md). ##### Querying for failed executions[​](#querying-for-failed-executions "Direct link to Querying for failed executions") First, query an instance for failed executions. You can find an instance's ID in your browser's URL bar or via the API. Filter for executions where `error_Isnull: false` (i.e., executions that encountered an error). To exclude replays, include `replayForExecution_Isnull: true`. To fetch replays that have since succeeded, use `replays(error_Isnull: true)`: Query for Failed Executions ```graphql query getFailedExecutions($instanceId: ID!, $startCursor: String) { executionResults( instance: $instanceId error_Isnull: false replayForExecution_Isnull: true after: $startCursor ) { nodes { id startedAt replays(error_Isnull: true) { nodes { id startedAt } } error } pageInfo { hasNextPage endCursor } } } ``` Query Variables ```json { "instanceId": "SW5zdGFuY2U6ZGVkZDQ3ZjQtNmQ4OC00NjJmLWE5YmYtNWM1OGNiMTg0MDAy", "startCursor": "" } ``` [Try It Out ❯](https://prismatic.io/docs/explorer?query=query+getFailedExecutions%28%24instanceId%3A+ID%21%2C+%24startCursor%3A+String%29+%7B%0A++executionResults%28%0A++++instance%3A+%24instanceId%0A++++error_Isnull%3A+false%0A++++replayForExecution_Isnull%3A+true%0A++++after%3A+%24startCursor%0A++%29+%7B%0A++++nodes+%7B%0A++++++id%0A++++++startedAt%0A++++++replays%28error_Isnull%3A+true%29+%7B%0A++++++++nodes+%7B%0A++++++++++id%0A++++++++++startedAt%0A++++++++%7D%0A++++++%7D%0A++++++error%0A++++%7D%0A++++pageInfo+%7B%0A++++++hasNextPage%0A++++++endCursor%0A++++%7D%0A++%7D%0A%7D\&query_variables=%7B%0A++%22instanceId%22%3A+%22SW5zdGFuY2U6ZGVkZDQ3ZjQtNmQ4OC00NjJmLWE5YmYtNWM1OGNiMTg0MDAy%22%2C%0A++%22startCursor%22%3A+%22%22%0A%7D) If there are multiple pages of executions (more than 100), use the `endCursor` as the `startCursor` to paginate results. The GraphQL API will return failed executions for the instance, along with any successful replays: ```json { "data": { "instance": { "executionResults": { "nodes": [ { "id": "SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6NjBkZDliOWMtOGIyOS00NDQyLWFkNDctMjZkZTg5Y2NlNWM5", "startedAt": "2023-07-26T17:18:15.886806+00:00", "replays": { "nodes": [] }, "error": "Unable to connect to API" }, { "id": "SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6MmYxNTcxZTktNDVmOS00Mzc2LTg2OGUtMTJkNjZkNDhiNzRl", "startedAt": "2023-07-26T17:18:13.800335+00:00", "replays": { "nodes": [ { "id": "SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6N2U1MDBkZTgtY2ZmYS00NWY5LWI0OGYtNGU1YjU2YWMzMzFh", "startedAt": "2023-07-26T17:28:10.003443+00:00" } ] }, "error": "Unable to connect to API" } ] } } } } ``` ##### Issuing replay mutations[​](#issuing-replay-mutations "Direct link to Issuing replay mutations") With the IDs of failed executions, issue a [replayExecution](https://prismatic.io/docs/api/schema/mutations.md#replayexecution)) mutation for each one that doesn't have a successful replay: Replay a failed execution ```graphql mutation myReplayExecution($executionId: ID!) { replayExecution(input: {id: $executionId}) { instanceExecutionResult { id } errors { field messages } } } ``` Query Variables ```json { "executionId": "SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6NjBkZDliOWMtOGIyOS00NDQyLWFkNDctMjZkZTg5Y2NlNWM5" } ``` [Try It Out ❯](https://prismatic.io/docs/explorer?query=mutation+myReplayExecution%28%24executionId%3A+ID%21%29+%7B%0A++replayExecution%28input%3A+%7Bid%3A+%24executionId%7D%29+%7B%0A++++instanceExecutionResult+%7B%0A++++++id%0A++++%7D%0A++++errors+%7B%0A++++++field%0A++++++messages%0A++++%7D%0A++%7D%0A%7D\&query_variables=%7B%0A++%22executionId%22%3A+%22SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6NjBkZDliOWMtOGIyOS00NDQyLWFkNDctMjZkZTg5Y2NlNWM5%22%0A%7D) The mutation returns the ID of the new execution. You can then query the API for that execution to verify success or perform further debugging if needed. For a script that automates these GraphQL calls, see our [examples GitHub repo](https://github.com/prismatic-io/examples/blob/main/api/replay-failed-executions/queries.ts). For more information, see the [API documentation](https://prismatic.io/docs/api.md). --- ### Build vs Buy > "Why do I need an iPaaS? I can build this all myself." > > \- Every engineer ever The challenge isn't building integrations - it's building the infrastructure that makes them reliable, secure, and maintainable at scale. This is deceptively difficult engineering that looks simple until you encounter the edge cases. Your customers don't care if you built webhook or OAuth 2.0 infrastructure yourself, or leaned on an iPaaS. They care that their data syncs reliably and that your product solves their problems. ![hero image](/docs/assets/images/hero-b80729c468b6c669ffcded4eb8c339ed.png) #### The easy part[​](#the-easy-part "Direct link to The easy part") When you first think about building integrations, you focus on getting information from point A to point B. That part of the problem is generally straight-forward. Figure out how authentication works with a third-party app, examine their API docs, and then make the calls you need to fetch or push data. Your team can knock out the basic data flow in a few days. The problem is that getting a basic integration working is only the beginning. The hard part is handling everything that happens after "it works on my machine." Once you have some code that pulls data from one API, transforms it, and sends it to another API, you start the "long tail" of the integration development process. #### The hard part[​](#the-hard-part "Direct link to The hard part") **Multi-tenant isolation at scale** Your integration platform needs to run code from different customers in isolation while efficiently sharing resources. Getting this wrong means one customer's runaway integration affects others, or worse, customer A can access customer B's data. Proper isolation requires deep expertise in container orchestration, resource limits, and security boundaries. This isn't just about spinning up containers - it's about preventing resource exhaustion, quota management, and ensuring security boundaries hold under load. **OAuth that actually works** OAuth 2.0 seems simple until you implement it. Different providers interpret the spec differently. Some use JWT tokens, others use opaque tokens. Refresh token rotation, PKCE flows, state parameter validation, and handling clock skew all have subtle failure modes. Miss any of these details and you'll leak credentials or fail intermittently in ways that are difficult to debug. You'll discover these edge cases only after customers report mysterious auth failures. **Idempotency and retry logic** What happens when an API is down? Or rate limited? You need queue management, exponential backoff, replay logic, and a way to notify customers when something fails. Multiply this across dozens of third-party APIs. **Monitoring and observability** You need logging, alerting, and dashboards to diagnose issues before customers report them. Integration problems are rarely obvious - was it bad data, an API change, a network issue, or a bug in your code? **Webhook reliability** Receiving webhooks reliably requires handling duplicate deliveries, validating signatures (each vendor does this differently), managing replay attacks, queue overflow, and poison messages. What happens when your webhook handler has a bug? You need to replay millions of events without duplicates or missing data. This is a surprisingly deep distributed systems problem that looks simple until you need to handle it at scale. **Credential encryption at rest and in transit** Your integration platform stores API keys and OAuth tokens for every customer. A breach here exposes all your customers' third-party systems. Proper credential management requires envelope encryption, key rotation, audit logging, and defense-in-depth security. Half-measures create vulnerabilities that you won't discover until it's too late. This requires dedicated security expertise and constant vigilance. **Customer self-service** You want your customers to configure integrations for themselves. That means coordination between your frontend and backend teams, as your frontend team crafts a pleasant experience for your users and the backend team spins up new databases that can safely house the configuration. #### The real question[​](#the-real-question "Direct link to The real question") Can you build an integration platform? Yes. Can you build one that handles edge cases correctly, scales reliably, maintains security, and doesn't become a maintenance nightmare? That's substantially harder. The challenge isn't writing integration code - it's building infrastructure that your team can confidently deploy and maintain without becoming experts in distributed systems, security, OAuth minutiae, and the quirks of dozens of third-party APIs. Integration infrastructure is complex, specialized work. The question isn't whether you have the capability to build it, but whether building this infrastructure is where your engineering expertise will have the most impact. --- ### Deployment Options Integrations built on Prismatic can be deployed in a variety of ways, depending on your customers' needs. Below are some of the most common deployment options for integrations built on Prismatic. * [Productized integrations](https://prismatic.io/docs/platform/deployment-options.md#productized-integration-marketplace) are built by your team and presented to your customers in a marketplace. * The [embedded workflow builder](https://prismatic.io/docs/platform/deployment-options.md#embedded-workflow-builder) allows your customers to build their own workflows using a low-code interface embedded in your app. * [Agentic flows](https://prismatic.io/docs/platform/deployment-options.md#ai-tools-and-agentic-flows) allow you to connect an LLM in your app to data in third-party apps and services. * [Custom integrations](https://prismatic.io/docs/platform/deployment-options.md#custom-integrations) are built by your team for specific customers with unique use-cases. #### Productized integration marketplace[​](#productized-integration-marketplace "Direct link to Productized integration marketplace") You're a B2B SaaS and you want to build integrations between your app and the other apps your customers use. You would like your customers to be able to enable these integrations for themselves through an integration marketplace you've embedded in your app. By writing TypeScript using our [code-native SDK](https://prismatic.io/docs/integrations/code-native.md), or through the [low-code designer](https://prismatic.io/docs/integrations/low-code-integration-designer.md), your team builds integrations that sync data between your app and a third-party, and you design a configuration / deployment experience for your customers using our config wizard. Your customers enable these productized integrations for themselves through your customizable [embedded integration marketplace](https://prismatic.io/docs/embed/marketplace.md). You designate an area in your app to embed the marketplace, and Prismatic provides the infrastructure to back your marketplace. ![Example marketplace](/docs/assets/images/acme-saas-example-f3c74aa7fa39bbe6367853d290ad7418.png) #### Embedded workflow builder[​](#embedded-workflow-builder "Direct link to Embedded workflow builder") Your customers would like to build workflows for themselves that sync data between your app and the other apps and services they use. They have niche use-cases - possibly one-off integrations with internal systems they use. You would like to provide your customers with a white-labeled low-code workflow builder so they can easily build these flows. By embedding Prismatic's [workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) in your app, your customers have access to hundreds of [built-in connectors](https://prismatic.io/docs/components.md), as well as any [custom connectors](https://prismatic.io/docs/custom-connectors.md) that your team has built, and can build, test, and enable workflows - all from within your app without putting strain on your engineering resources. You can provide pre-built workflow templates to your customers as a jumping-off point, and your customers can leverage the Prismatic's [AI Co-Pilot](https://prismatic.io/blog/introducing-ai-copilot-for-embedded-workflow-builder/) to accelerate the build experience. #### AI tools and agentic flows[​](#ai-tools-and-agentic-flows "Direct link to AI tools and agentic flows") Your app has incorporated a chat bot or uses an LLM to enhance your customers' experience. You'd like the LLM to have access to data from the other apps and services your customers use. You build [agentic workflows](https://prismatic.io/docs/ai/model-context-protocol.md) as part of your integrations with third-party apps. The agentic workflows present themselves as "tools" via the [MCP protocol](https://prismatic.io/docs/ai/model-context-protocol.md). Now, when your customer asks your LLM to cross-reference data stored in your app with data in a third-party, the LLM is able to pull data from both sources through your agentic flows. ![Sample LLM chat](/docs/assets/images/sample-llm-chat-a8468227c6d67dfa4e187ce30a28d9a0.png) Other ways Prismatic supports AI In addition to connecting an LLM in your app to agentic flows, you can use AI to [accelerate the build process](https://prismatic.io/docs/dev-tools/prism-mcp.md), and can reach out to [popular LLMs](https://prismatic.io/docs/components/openai.md) from within your flows. #### Custom integrations[​](#custom-integrations "Direct link to Custom integrations") Some of your customers have unique use-cases that require custom-built integrations. Your team builds custom integrations for these customers using our code-native SDK or low-code designer (similar to [productized integrations](https://prismatic.io/docs/platform/deployment-options.md#productized-integration-marketplace)). Then, you can either deploy these custom integrations to your customers yourself, or assign the integration to the customer and allow them to deploy it from their end. --- ### Prismatic & AI Prismatic embraces AI in a variety of ways - from helping you and your customers build better integrations to enriching data within your flows and giving additional context to chat bots in your app. Prismatic's adoption of AI focuses on four main areas: 1. [Code-native build tools](https://prismatic.io/docs/platform/prismatic-and-ai.md#code-native-build-tools) to help AI coding agents build better integrations faster. 2. [Embedded workflow builder copilot](https://prismatic.io/docs/platform/prismatic-and-ai.md#embedded-workflow-builder-copilot) to assist your customers as they build workflows in your app. 3. [Agentic flows as MCP tools](https://prismatic.io/docs/platform/prismatic-and-ai.md#agentic-flows-as-mcp-tools) to expose your integrations as MCP tools for an AI agent in your app. 4. [AI-enhanced data in flows](https://prismatic.io/docs/platform/prismatic-and-ai.md#ai-enhanced-data-in-flows) to enrich data streaming through your flows. #### Code-native build tools[​](#code-native-build-tools "Direct link to Code-native build tools") AI coding agents like Claude, GitHub Copilot or Codex are enhanced by Prismatic's [MCP dev server](https://prismatic.io/docs/dev-tools/prism-mcp.md) and [Prismatic Skills](https://github.com/prismatic-io/prismatic-skills/). The **MCP dev server** adds tools to AI coding agents that allow them to connect to Prismatic's API and do things like "add the Salesforce connector to my integration" or "publish my integration and run a test of this flow". ![MCP dev server tools](/docs/assets/images/prism-mcp-b073fe22b32d501193aa1de8e54af27e.png) **Prismatic Skills** are Claude skills (agent skills) give your AI assistant context and examples of what "good" looks like for custom connectors and code-native integrations. So, when you ask your AI assistant to "write a trigger that polls new lead records from Acme", or "build a JSON Form field mapper for Salesforce", your agent has context for how to do that in a way that works well with Prismatic's platform and follows best practices. ![Prismatic Skills example](/docs/assets/images/prismatic-skills-dc34a7264705f6bfe8ccfc9438b021a1.png) #### Embedded workflow builder copilot[​](#embedded-workflow-builder-copilot "Direct link to Embedded workflow builder copilot") The [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) includes a [copilot](https://prismatic.io/blog/introducing-ai-copilot-for-embedded-workflow-builder/) that gives your customers AI assistance as they build workflows in your app. The copilot can help your customers build workflows faster using natural language prompts. ![Workflow builder copilot](/docs/assets/images/workflow-builder-copilot-4d8810d8718a37c2eefb40e2a5af1762.png) #### Agentic flows as MCP tools[​](#agentic-flows-as-mcp-tools "Direct link to Agentic flows as MCP tools") You can mark some of your integrations' flows as "agentic" - meaning they can be called by an AI agent to perform a task. For example, you could have an "Update CRM" flow that your customers can call from a chat bot in your app to update a record in their CRM - all within the context of a conversation in your app. This enhances the capabilities of your app's chat bots - they'll have access to integrations that your customers have enabled and can perform tasks that interact with the other apps and services they use. ![Agentic flow example](/docs/assets/images/sample-llm-chat-a8468227c6d67dfa4e187ce30a28d9a0.png) #### AI-enhanced data in flows[​](#ai-enhanced-data-in-flows "Direct link to AI-enhanced data in flows") You can reach out to popular LLM providers like [OpenAI](https://prismatic.io/docs/components/openai.md) or [Anthropic](https://prismatic.io/docs/components/anthropic.md) from your code-native or low-code flows. Your flows can gather information from multiple sources using our library of [built-in connectors](https://prismatic.io/docs/components.md), then call out to an LLM to summarize, analyze or enrich that data before taking an action. Several examples are available - see [Data Enrichment with AI](https://prismatic.io/docs/ai/data-enrichment.md). ![AI enrichment flow](/docs/assets/images/ai-enrichment-789a7b37abf299941b20583bd8b1cc8a.png) --- ### Spectral 10.22 Upgrade Guide `@prismatic-io/spectral@10.22` and `@prismatic-io/prism@10.0` introduce a new recommended toolchain for code-native integrations and custom connectors. **Why this change?** The legacy toolchain we've recommended in the past (`webpack`, `eslint`, etc) was cutting-edge when we first introduced custom connectors and code-native integrations, but the JavaScript ecosystem has changed a lot since then. The new toolchain is more modern, faster, and easier to use. **Do I need to change my toolchain?** No. While we have a recommended toolchain, you can use any Node.js tool that can import `@prismatic-io/spectral` and produce a a CJS bundle. Some of our customers have successfully used `esbuild`, `rollup`, `bun`, and other tools. **Can I create a new code-native integration or custom connector with the legacy toolchain?** Yes. Run `prism integrations:init --toolchain legacy` or `prism components:init --toolchain legacy` to create a new code-native integration or custom connector with the legacy toolchain. #### New recommended toolchain[​](#new-recommended-toolchain "Direct link to New recommended toolchain") | | Legacy Tool | New Recommended Tool | | ------------------ | --------------------- | ------------------------------- | | Build | `webpack` | [`tsdown`](https://tsdown.dev/) | | Formatting/Linting | `eslint` / `prettier` | [`biome`](https://biomejs.dev/) | | Unit Testing | `jest` | [`vitest`](https://vitest.dev/) | | TypeScript | `typescript@5.x` | `typescript@6.x` | #### Update existing projects[​](#update-existing-projects "Direct link to Update existing projects") If you'd like to update an existing code-native integration or custom connector to use the new recommended toolchain, take the following steps: Update your dependencies Remove legacy toolchain: ```bash npm uninstall --save-dev @prismatic-io/eslint-config-spectral @types/jest copy-webpack-plugin eslint jest ts-jest ts-loader webpack webpack-cli ``` Install new recommended tools: ```bash npm install @prismatic-io/spectral@10.22.0 npm install --save-dev typescript@6.0.3 npm install --save-dev @biomejs/biome@2.5.1 tsdown@0.22.3 vitest@4.1.9 ``` Update your package.json Remove the unnecessary `eslintConfig` block from your `package.json` file and update `build`, `test`, and `lint` scripts to use the new recommended tools. package.json ```diff diff --git a/package.json b/package.json index 67fee06..a7b1f27 100644 --- a/package.json +++ b/package.json @@ -4,20 +4,14 @@ "main": "index.js", "private": true, "scripts": { - "build": "webpack", + "build": "tsdown", "publish": "npm run build && prism components:publish", "generate:manifest": "npm run build && npx @prismatic-io/spectral component-manifest", "generate:manifest:dev": "npm run build && npx @prismatic-io/spectral component-manifest --skip-signature-verify", - "test": "jest", - "lint": "eslint --ext .ts .", + "test": "vitest run", + "lint": "biome lint .", "typecheck": "tsc --noEmit" }, - "eslintConfig": { - "root": true, - "extends": [ - "@prismatic-io/eslint-config-spectral" - ] - }, "dependencies": { "@prismatic-io/spectral": "10.21.0" }, ``` Remove legacy toolchain configuration files ```bash rm jest.config.js webpack.config.js ``` Add new toolchain configuration files Create the following files: biome.json ```json { "$schema": "https://biomejs.dev/schemas/2.5.1/schema.json", "files": { "includes": ["**", "!**/dist", "!**/node_modules"] }, "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2, "lineWidth": 100 }, "assist": { "actions": { "source": { "organizeImports": "on" } } }, "linter": { "enabled": true, "rules": { "preset": "recommended", "suspicious": { "noExplicitAny": "off", "noTsIgnore": "off" } } } } ``` tsdown.config.mts ```js import { defineConfig } from "tsdown"; export default defineConfig({ entry: { index: "src/index.ts" }, format: "cjs", outDir: "dist", platform: "node", clean: true, dts: false, deps: { alwaysBundle: () => true, onlyBundle: false, }, outExtensions: () => ({ js: ".js" }), outputOptions: { exports: "named" }, loader: { ".md": "text", }, copy: [{ from: "assets/*", to: "dist" }], }); ``` vitest.config.ts ```ts import { defineConfig } from "vitest/config"; export default defineConfig({ test: { globals: true, environment: "node", }, }); ``` Update your tsconfig.json configuration We've moved from TypeScript 5 to 6. Replace `tsconfig.json` with the following: tsconfig.json ```json { "compilerOptions": { "target": "es2022", "lib": ["es2022"], "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "module": "esnext", "moduleResolution": "bundler", "types": ["vitest/globals"], "skipLibCheck": true }, "include": ["src", ".spectral/*"] } ``` --- ### Spectral 10.6 Upgrade Guide Spectral 10.6 introduces a new way for you to reference existing component actions, data sources and connections in a code-native integration. You can now use new action, data source, and connection reference functions to build code-native integrations more easily, with better type safety and autocompletion in your IDE. Existing reference syntax will continue to work, but we recommend updating your code-native integrations to use the new reference functions. #### Generating new component manifests[​](#generating-new-component-manifests "Direct link to Generating new component manifests") Previously, you had to install all component type manifests as npm dependencies. Now, you can install component manifests into your `src/manifests/` directory. This lets you skip the step of generating manifests for your custom components and publishing them to npm - the manifest is generated from information in the Prismatic API. You can now generate new manifests for both public and private components, which include new type wrappers. To install a component manifest, run the following command: ```bash # Public component npx cni-component-manifest slack # Private component npx cni-component-manifest slack --private ``` Next, remove your component's npm dependency from `package.json` and run `npm install` or `yarn install`. Finally, update your import statements to import from the manifest file instead of the npm package. componentRegistry.ts ```diff @@ -1,5 +1,5 @@ import { componentManifests } from "@prismatic-io/spectral"; -import slack from "@component-manifests/slack"; +import slack from "./manifests/slack"; export const componentRegistry = componentManifests({ slack, ``` #### New component action functions[​](#new-component-action-functions "Direct link to New component action functions") Actions can now be imported from a component manifest and invoked directly. yourFlow.ts ```diff @@ -8,6 +8,7 @@ import { flow, util } from "@prismatic-io/spectral"; import axios from "axios"; +import slackActions from "../manifests/slack/actions"; interface TodoItem { id: number; @@ -34,7 +35,7 @@ export const todoAlertsFlow = flow({ } else { logger.info(`Sending message for item ${item.id}`); try { - await context.components.slack.postMessage({ + await slackActions.postMessage.perform({ channelName: util.types.toString( configVars["Select Slack Channel"] ``` #### New component trigger functions[​](#new-component-trigger-functions "Direct link to New component trigger functions") Similar to actions, triggers can be imported from a component manifest and invoked directly. ```ts import { flow } from "@prismatic-io/spectral"; import { salesforceFlowOutboundMessageTrigger } from "./manifests/salesforce/triggers/flowOutboundMessageTrigger"; export const salesforceAccountNotifications = flow({ name: "Listen for Salesforce Account Notifications", stableKey: "salesforce-account-notifications", description: "This flow uses an existing component trigger to listen for Account notifications from Salesforce.", onTrigger: salesforceFlowOutboundMessageTrigger({ connection: { configVar: "Salesforce Connection" }, prefix: { value: "acme" }, triggerObject: { value: "Account" }, fields: { value: ["Id", "Name"] }, }), onExecution: async (context, params) => { // ... }, }); ``` #### New component connection functions[​](#new-component-connection-functions "Direct link to New component connection functions") Connections can be imported from a component manifest, and are named ``. The first parameter of the connection function is the stable key of the connection config var. configPages.ts ```diff @@ -1,9 +1,9 @@ import { configPage, configVar, - connectionConfigVar, dataSourceConfigVar, } from "@prismatic-io/spectral"; +import { slackOauth2 } from "./manifests/slack/connections/oauth2"; import { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, @@ -14,34 +14,26 @@ export const configPages = { Connections: configPage({ tagline: "Authenticate with Slack", elements: { - "Slack OAuth Connection": connectionConfigVar({ - stableKey: "slack-oauth-connection", - dataType: "connection", - connection: { - component: "slack", - key: "oauth2", - values: { - clientId: { - value: SLACK_CLIENT_ID, - permissionAndVisibilityType: "organization", - visibleToOrgDeployer: false, - }, - clientSecret: { - value: SLACK_CLIENT_SECRET, - permissionAndVisibilityType: "organization", - visibleToOrgDeployer: false, - }, - signingSecret: { - value: SLACK_SIGNING_SECRET, - permissionAndVisibilityType: "organization", - visibleToOrgDeployer: false, - }, - scopes: { - value: "chat:write chat:write.public channels:read", - permissionAndVisibilityType: "organization", - visibleToOrgDeployer: false, - }, - }, + "Slack OAuth Connection": slackOauth2("slack-oauth-connection", { + clientId: { + value: SLACK_CLIENT_ID, + permissionAndVisibilityType: "organization", + visibleToOrgDeployer: false, + }, + clientSecret: { + value: SLACK_CLIENT_SECRET, + permissionAndVisibilityType: "organization", + visibleToOrgDeployer: false, + }, + signingSecret: { + value: SLACK_SIGNING_SECRET, + permissionAndVisibilityType: "organization", + visibleToOrgDeployer: false, + }, + scopes: { + value: "chat:write chat:write.public channels:read", + permissionAndVisibilityType: "organization", + visibleToOrgDeployer: false, }, }), }, ``` #### New component data source functions[​](#new-component-data-source-functions "Direct link to New component data source functions") Similar to connections, data sources can be imported from a component manifest, and are named ``. The first parameter of the data source function is the stable key of the data source config var. configPages.ts ```diff @@ -1,9 +1,6 @@ import { configPage, configVar, - dataSourceConfigVar, } from "@prismatic-io/spectral"; import { slackOauth2 } from "./manifests/slack/connections/oauth2"; +import { slackSelectChannels } from "./manifests/slack/dataSources/selectChannels"; import { SLACK_CLIENT_ID, SLACK_CLIENT_SECRET, @@ -61,16 +58,9 @@ export const configPages = { "Slack Config": configPage({ tagline: "Select a Slack channel from a dropdown menu", elements: { - "Select Slack Channel": dataSourceConfigVar({ - stableKey: "select-slack-channel", - dataSource: { - component: "slack", - key: "selectChannels", - values: { - connection: { configVar: "Slack OAuth Connection" }, - includePublicChannels: { value: true }, - }, - }, + "Select Slack Channel": slackSelectChannels("select-slack-channel", { + connection: { configVar: "Slack OAuth Connection" }, + includePublicChannels: { value: true }, }), }, }), ``` --- ### Spectral 10.x Upgrade Guide In Spectral 10.x, you can add custom icons to connections on a per-connection basis. This is useful when you're building a code-native integration that interacts with multiple third-party APIs. You can have one connection with a Pied Piper icon, while another has a Hooli icon. Prior to Spectral 10.x, an OAuth 2.0 connection in a custom component or code-native integration was defined like this: Connection in Spectral 9.x ```ts export const myConnection = oauth2Connection({ key: "myConnection", label: "This is my label", comments: "This is my description", // Optional in-app description iconPath: "connect.png", // Optional OAuth connect button override inputs: { /* ... */ }, }); ``` Now, `label`, `comments`, and `iconPath` are nested within a `display` object, where: * `display.label` represents the connection's "type" in the integration builder * `display.description` was previously called `comments` and represents an optional in-app description * `display.icons.avatarPath` is a new option and represents the icon displayed next to the connection when a customer deploys your integration * `display.icons.oauth2ConnectionIconPath` was previously called `iconPath` and, when present, is displayed instead of the default OAuth 2.0 **Connect** button Connection in Spectral 10.x ```ts export const myConnection = oauth2Connection({ key: "myConnection", display: { label: "This is my label", description: "This is my description", // Previously called "comments" icons: { avatarPath: "hooli.png", oauth2ConnectionIconPath: "connect.png", // Previously called "iconPath" }, }, inputs: { /* ... */ }, }); ``` --- ### Spectral 2.x Upgrade Guide Spectral 2.x introduces several syntactical changes to improve your developer experience and catch common errors at compile time rather than at runtime. Let's walk through the changes and how to upgrade from Spectral 1.x to 2.x. To see an example of upgrading a component from Spectral 1.x to 2.x, [this commit](https://github.com/prismatic-io/examples/commit/411598bac4163c8a25b492a9275590b3fd7855a9) upgrades the "Format Name" example component from the [writing custom components](https://prismatic.io/docs/custom-connectors.md) article. To start, update `@prismatic-io/spectral` in your `package.json` file and then run `npm install` or `yarn install`: ```json { "dependencies": { "@prismatic-io/spectral": "^2.0.0" } } ``` #### Input keys have moved[​](#input-keys-have-moved "Direct link to Input keys have moved") **Motivation**: In Spectral 1.x, each `input` had a `key` attribute to uniquely identify it. This caused problems, however, as the value of the `input`'s `key` had to exactly match the `action`'s perform function's `inputs.keyName` to reference inputs correctly. Mismatched input keys and parameter keys would yield `unknown` values for inputs. TypeScript generics were used in 2.x to ensure that your editor and compiler catch mismatched keys prior to deployment. First, remove the `key:` property from each input: ```typescript const myFirstInput = input({ /* key: "first", */ // Removed in spectral 2.x label: "My Input Field", placeholder: "Some example input", type: "string", required: true, }); const mySecondInput = input({ /* key: "second", */ label: "My Input Field", placeholder: "Some example input", type: "string", required: true, }); ``` Inputs on actions are now an **object** (key-value pairs) instead of an **array**. Adjust your actions accordingly: ```typescript /* Spectral 1.x */ const myAction = action({ perform: async (context, { first, second }) => { /*...*/ }, inputs: [myFirstInput, mySecondInput], }); /* Spectral 2.x */ const myAction = action({ perform: async (context, { first, second }) => { /*...*/ }, inputs: { first: myFirstInput, second: mySecondInput, }, }); ``` Ensure that your destructured second positional `inputs` parameter (in this case, `{ first, second }`) has keys that match the keys you provide in the `inputs` object. If they do not match, your compiler will throw an error. #### Action keys have moved[​](#action-keys-have-moved "Direct link to Action keys have moved") Similar to inputs, action keys have also moved. This addresses an issue in Spectral 1.x where if an action's `key` property did not match the name of the variable representing the `action`, a component would compile but the component's action code would not be found at runtime. Action's unique keys are now declared as part of the `component` declaration, and the `key` property has been removed from the `action` declaration: ```typescript /* Spectral 1.x */ const myAction = action({ key: "myAction", // Remove this /*...*/ }); export default component({ /*...*/ actions: { // Remove the spread operators ...myAction, ...myOtherAction, }, }); /* Spectral 2.x */ const myAction = action({ /*...*/ }); export default component({ /*...*/ actions: { myAction: myAction, myOtherAction: myOtherAction, }, }); ``` Note: you can use JavaScript [shorthand property names](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) to write `actions: { myAction, myOtherAction }` instead. #### Component version is deprecated[​](#component-version-is-deprecated "Direct link to Component version is deprecated") [Component versioning](https://prismatic.io/docs/custom-connectors/publishing.md#component-versioning) is handled by the platform, so there's no need to pass in a `version` property in your `component` declaration. You can remove it. ```typescript export default component({ key: "my-component", /* version: "123", */ }); ``` #### Input types are more strict[​](#input-types-are-more-strict "Direct link to Input types are more strict") In Spectral 1.x, all inputs had an `any` type. So, if a helper function expected `(string, number)` inputs, this type of invocation would be acceptable by TypeScript with Spectral 1.x: ```typescript const helper = (foo: string, bar: number) => `Hi, ${foo}, adding 3 gives you ${bar + 3}`; const myAction = action({ perform: async (context, { myInput1, myInput2 }) => { helper(myInput1, myInput2); }, }); ``` This caused runtime issues. What if a component's user entered the string `"2"` for `myInput2`? That would cause problems, since `"2" + 3` equals `"23"` in JavaScript. That's probably not the desired result. In Spectral 2.x, we changed all inputs to have an `unknown` type, so you need to explicitly convert inputs into the type expected by your helper functions and third-party libraries. We created a series of utility functions to help convert inputs to types your helper functions and third-party libraries require. The above code could be written using `util.types` functions: ```typescript const helper = (foo: string, bar: number) => `Hi, ${foo}, adding 3 gives you ${bar + 3}`; const myAction = action({ perform: async (context, { myInput1, myInput2 }) => { helper(util.types.toString(myInput1), util.types.toNumber(myInput2)); }, }); ``` If a string `"2"` is input into `myInput2` using Spectral 2.x now, that string is converted to a number and then passed into `helper`, and the result would be `5` instead of `"23"`. #### Unit testing[​](#unit-testing "Direct link to Unit testing") In Spectral 2.x, it is assumed that you want a `PerformDataReturn` object when you run an `invoke` function in a unit test. Passing the return object type to the invoke function is no longer required. ```typescript /* Spectral 1.x */ await invoke(myAction, { someInput }); /* Spectral 2.x */ await invoke(myAction, { someInput }); ``` --- ### Spectral 3.x Upgrade Guide With Spectral 3.x, you can now specify authorization methods (Basic Auth, OAuth2, etc.) at the action level rather than at the component level. Let's explore this change and how to upgrade your component from Spectral 2.x to Spectral 3.x. To start, update `@prismatic-io/spectral` to `^3.0.0` in your `package.json` file and then run `npm install` or `yarn install`: ```json { "dependencies": { "@prismatic-io/spectral": "^3.0.0" } } ``` Next, **remove** the `authorization` block from your `component` definition: ```typescript export default component({ key: "sampleComponent", public: false, display: { label: "Sample Component", description: "sampleComponent", iconPath: "icon.png", }, actions: { myAction }, authorization: { required: true, methods: ["basic", "api_key"], }, }); ``` Finally, **add** an `authorization` block to any `action` definition that requires credentials: ```typescript export const myAction = action({ display: { label: "My Action", description: "This is my action", }, perform: async ({ credential }, { myInput }) => { // Do something with the credential ... }, inputs: { myInput: myInputField }, authorization: { required: true, methods: ["basic", "api_key"], }, }); ``` --- ### Spectral 4.x Upgrade Guide Spectral 4.x introduces the ability to write [component-specific triggers](https://prismatic.io/docs/custom-connectors/triggers.md). This version is purely additive, so very few changes to your code are required. To start, update `@prismatic-io/spectral` to `^4.0.6` in your `package.json` file and then run `npm install` or `yarn install`: ```json { "dependencies": { "@prismatic-io/spectral": "^4.0.6" } } ``` Next, optionally write some [triggers](https://prismatic.io/docs/custom-connectors/triggers.md) and add those triggers to your `component` definition: ```typescript export default component({ key: "sampleComponent", public: false, display: { label: "Sample Component", description: "sampleComponent", iconPath: "icon.png", }, actions: { myAction1, myAction2, myAction3 }, triggers: { myTrigger1, myTrigger2 }, }); ``` --- ### Spectral 5.x Upgrade Guide The major change in Spectral 5.x is the introduction of `connections`, which allow you to package all required information (endpoints, credentials, etc.) into single inputs. Connections work similar to credentials, except that you can create connections with any number of custom fields required. It's easiest to illustrate the upgrade through an example. Suppose you have a component that interacts with "Acme Inventory". The component has a single action, "Get Item", which fetches information about a specific item in inventory. To connect to an Acme Inventory server, you need to know the server's hostname. To authenticate with Acme, you need the user's `username`, `token`, and `tenantid`. In Spectral 4.x, this component might have looked like this: Sample Component using Spectral 4.x ```typescript import { action, input, component } from "@prismatic-io/spectral"; import { acmeClient } from "@acme-inventory/client"; const itemIdInput = input({ label: "Item ID", type: "string" }); const acmeEndpointInput = input({ label: "Acme Endpoint", type: "string" }); const tenantIdInput = input({ label: "Tenant ID", type: "string" }); const getItem = action({ display: { label: "Get Item", description: "Get an item from inventory", }, inputs: { item: itemIdInput, endpoint: acmeEndpointInput, tenant: tenantIdInput, }, perform: async ({ credential }, { item, endpoint, tenant }) => { const { username, password: token } = credential.fields; const client = new acmeClient({ endpoint, username, token, tenant }); const itemData = await client.get(item); return { data: itemData }; }, authorization: { required: true, methods: ["basic"] }, }); export default component({ key: "acme-inventory", public: false, display: { label: "Acme Inventory", description: "Manage items in Acme inventory", iconPath: "icon.png", }, actions: { getItem }, }); ``` Note that secret information, such as `username` and `token`, were slotted into basic auth (username/password) out of convenience (each has two fields). The action declared with its `authorization` block that it supports basic auth. Other information about how to connect to Acme Inventory (`endpoint` and `tenant`) were passed in as inputs. Since username, token, endpoint, and tenant are all used to connect to Acme, it makes more sense to package them together into one cohesive **connection** input. Here's the same component and action in Spectral 5.x, using connections: Sample Component using Spectral 5.x ```typescript import { action, input, component, connection } from "@prismatic-io/spectral"; import { acmeClient } from "@acme-inventory/client"; const itemIdInput = input({ label: "Item ID", type: "string" }); const acmeConnection = connection({ key: "acmeTokenAuth", label: "Acme Token Authentication", inputs: { endpoint: { label: "Acme Endpoint", type: "string" }, tenant: { label: "Tenant ID", type: "string", example: "606B0F820C0C" }, token: { label: "Token", type: "string", example: "30A5979A91F0" }, username: { label: "Username", type: "string" }, }, }); const getItem = action({ display: { label: "Get Item", description: "Get an item from inventory", }, inputs: { connection: acmeConnection, item: itemIdInput }, perform: async (context, { connection, item }) => { const { endpoint, tenant, token, username } = connection.fields; const client = new acmeClient({ endpoint, username, token, tenant }); const itemData = await client.get(item); return { data: itemData }; }, }); export default component({ key: "acme-inventory", public: false, display: { label: "Acme Inventory", description: "Manage items in Acme inventory", iconPath: "icon.png", }, actions: { getItem }, connections: [acmeConnection], }); ``` Endpoint, tenant ID, token, and username are now presented as a cohesive **connection** config variable to integration builders, and that config variable is passed to an action as an input with multiple `fields`. Finally, the component has a `connections:` block which declares the types of custom connections that this component supports. --- ### Spectral 6.x Upgrade Guide Spectral 6.x is completely backwards-compatible with Spectral 5.x. You can safely upgrade your version of Spectral from 5.x to 6.x without changes to your custom component code. The focus of Spectral 6.x was developer experience enhancements - helping developers follow [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) principles, ensuring type safety, improving error handling, and building a more robust component testing suite. #### New - cleaning and typing input values[​](#new---cleaning-and-typing-input-values "Direct link to New - cleaning and typing input values") An input of an action can be anything - a number, string, boolean, JavaScript Buffer, a complex object with numerous properties, etc. Prior to Spectral 6.x, this meant that inputs were passed to `perform` functions with TypeScript type `unknown`. ![Screenshot of code editor showing unknown type](/docs/assets/images/unknown-type-a73efe8a8fa81aa5287304988f0b5783.png) To ensure that the input was properly typed, each step that used that input had to use a utility function, like `util.types.toNumber()`, to ensure that the value was cast to the proper type. If multiple actions shared the same input, this resulted in repetitive code. You can now `clean` an input prior to it being presented to the `perform` function. Ensure an input is cast to a string with a clean function ```typescript const lastName = input({ label: "Last Name", placeholder: "Last name of a person", type: "string", required: true, clean: (value) => util.types.toString(value), }); ``` With a typed clean function (like `util.types.toString`, which always returns a `string`), your perform function will be cognizant of the input's type. ![Screenshot of code editor showing string type](/docs/assets/images/string-type-356fa5a4b614e36c470064dac6928355.png) The `clean` function always takes one parameter - the input from the integration runner - and should return a typed value. Your clean functions can be as simple or complex as required, and you can incorporate data validation within the clean function to catch incorrectly formatted input. For example, you can ensure that the input you received is an array, and that the array's values are cast to numbers (in case they happen to come in as strings): Ensure input is an array of numbers ```typescript const prices = input({ label: "Prices", placeholder: "A list of prices", type: "string", required: true, clean: (value) => { if (!Array.isArray(value)) { throw new Error("Provided list is not an array."); } return value.map(util.types.toNumber); }, }); ``` An example of a more complex `clean` function that returns an object with multiple fields is available in our [examples repository](https://github.com/prismatic-io/examples/blob/9ff35d3174c6d55b25381a9b0c33997aea5625f1/components/data-example/src/index.ts#L10-L27). #### New - global error handlers[​](#new---global-error-handlers "Direct link to New - global error handlers") This is another improvement that helps keep your component code DRY. The actions in your component might all wrap API endpoints using an HTTP client, and that client might throw specific errors. You could handle those errors within each action, but you would end up writing the same error handlers repeatedly. You can now specify an error handler function to run whenever any of your actions throws an error. To specify an error handler, add a `handlers` block to your `component({})` function definition: ```typescript components({ // ... handlers: { error: (error) => doSomething(error), }, }); ``` For example, the popular HTTP client [axios](https://www.npmjs.com/package/axios) throws an error whenever it receives a status code that's [*not* between 200-299](https://github.com/axios/axios/blob/1f13dd7e26124a27c373c83eff0a8614acc1a04f/lib/defaults/index.js#L127-L129). If your HTTP client receives a status code in the 4xx or 5xx range, an error is thrown with a minimal message. If you require additional information, such as the status code or full response to the HTTP request, you can inspect the error being thrown and return a more detailed error message, as illustrated in Spectral's [HTTP error handling implementation](https://github.com/prismatic-io/spectral/blob/v6.5.0/packages/spectral/src/clients/http/index.ts#L53-L62). #### New - spectral testing harness[​](#new---spectral-testing-harness "Direct link to New - spectral testing harness") New error handlers are defined at the *component* level, so testing your component's behavior holistically is important. Prior to Spectral 6.x, you would write a Jest unit test for an action using an `invoke` function like this: Example of unit testing in Spectral 5.x ```typescript import { myAction } from "."; import { invoke } from "@prismatic-io/spectral/dist/testing"; describe("test my action", () => { test("verify the return value of my action", async () => { const sampleInputData = { productName: "Widget", price: 1.25, quantity: 75, }; const expectedOutput = "This is an invoice for 75 Widgets at price $1.25. Total price: $93.75"; const { result } = await invoke(myAction, { pointOfSale: sampleInputData, }); expect(result.data).toBe(expectedOutput); }); }); ``` The same test can be performed by creating a testing harness with `new ComponentTestHarness(component)`. The advantage of this method is that your component-level global error handling hooks and input `clean` functions will be included in your test. The same test in Spectral 6.x ```typescript import component from "."; import { ComponentTestHarness } from "@prismatic-io/spectral/dist/testing"; const harness = new ComponentTestHarness(component); describe("test my action", () => { test("verify the return value of my action", async () => { const sampleInputData = { productName: "Widget", price: 1.25, quantity: 75, }; const expectedOutput = "This is an invoice for 75 Widgets at price $1.25. Total price: $93.75"; const result = await harness.action("myAction", { pointOfSale: sampleInputData, }); expect(result.data).toBe(expectedOutput); }); }); ``` --- ### Spectral 7.x Upgrade Guide Spectral 7.x is completely backwards-compatible with Spectral 6.x, so you can safely upgrade your version from 6.x to 7.x without changing your custom component code. In Spectral 7.x, we focused on improving instance deployment. You can now create **Data Sources**. Like triggers or actions, data sources use connections to call third-party APIs and gather data. Data sources gather data that you can dynamically insert into config variables in an instance configuration wizard. Read more about the instance configuration wizard [here](https://prismatic.io/docs/integrations/data-sources.md), and about writing your own data source [here](https://prismatic.io/docs/custom-connectors/data-sources.md). --- ### Spectral 8.x Upgrade Guide Spectral 8.x is completely backwards-compatible with Spectral 7.x. You can safely upgrade your version of Spectral from 7.x to 8.x without changes to your custom component code. Spectral 8.x introduced [code-native integrations](https://prismatic.io/docs/integrations/code-native.md), which allow you to build integrations entirely in code using the Spectral SDK. --- ### Spectral 9.x Upgrade Guide Spectral 9.x upgrades to TypeScript 5, which introduces several breaking changes. While the Spectral component APIs remain backwards-compatible with Spectral 8.x,you'll need to update your custom component's TypeScript to version 5 or later. For [code-native integrations](https://prismatic.io/docs/integrations/code-native.md), Spectral 9.x lets you reference existing components' actions within a code-native flow. To update your code-native integration to Spectral 9.x, we recommend re-initializing a new project and copying your flows and configuration page code into it. Alternatively, you can initialize a new project for reference and follow these steps: * Copy `.spectral/index.ts` from the new project into your current project * Copy `.npmrc` from the new project into your current project * Update `@prismatic-io/spectral` in your `package.json` file to the latest version Several syntax changes were made between 8.x and 9.x: * Flows no longer require `configPage` generics to infer the shape of your configuration variables. `flow()` can be changed to `flow()`. * References to existing components' data sources and connections are now done by installing a component's manifest package into your project. See [Using existing components in code-native integrations](https://prismatic.io/docs/integrations/code-native/existing-components.md). * References to existing components' triggers remain syntactically unchanged. Visibility of inputs on connections can now be explicitly set. For example, a reference to an existing Slack OAuth connection can now read: ```ts export const configPages = { Connections: configPage({ tagline: "Authenticate with Slack", elements: { "Slack OAuth Connection": connectionConfigVar({ stableKey: "slack-oauth-connection", dataType: "connection", connection: { component: "slack", key: "oauth2", values: { clientId: { value: SLACK_CLIENT_ID, permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, clientSecret: { value: SLACK_CLIENT_SECRET, permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, signingSecret: { value: SLACK_SIGNING_SECRET, permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, scopes: { value: "chat:write chat:write.public channels:read", permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, }, }, }), }, }), }; ``` --- ### Prismatic Event Webhooks A webhook is a way for an application to provide other applications with real-time information. This article covers **outbound event webhooks** in Prismatic - notifications you receive when events occur in your Prismatic account. This article covers *outbound* webhooks Here, we're talking about Prismatic-specific events that occur (e.g. an integration in Prismatic was published or a customer in Prismatic was updated), and you want an external app to know about that change. If you're interested in *incoming webhooks* for your integrations (e.g. A Salesforce contact was updated and you want your integration's trigger to be notified), see [What is a webhook?](https://prismatic.io/docs/integrations/triggers/webhook.md). #### Why use Prismatic event webhooks?[​](#why-use-prismatic-event-webhooks "Direct link to Why use Prismatic event webhooks?") Prismatic event webhooks allow you to stay informed about important changes and activities within your Prismatic platform. Here are some common use cases: * **Audit Trail**: Keep a record of all platform changes for compliance and tracking purposes * **Monitoring & Alerting**: Get notified when critical events occur, such as integrations being published or instances being deployed * **Integration with External Systems**: Send Prismatic events to your monitoring tools, logging systems, or custom applications * **Real-time Updates**: Receive immediate notifications instead of manually checking the web app or querying the Prismatic API #### How event webhooks work[​](#how-event-webhooks-work "Direct link to How event webhooks work") When an event occurs in your Prismatic organization, Prismatic sends a POST request to your configured webhook URL with a JSON payload containing details about the event. Your application receives the webhook, validates it (optionally using HMAC signatures), and processes the event according to your business logic. To get started with event webhooks, see [Configuring event webhooks](https://prismatic.io/docs/webhooks/configuring-event-webhooks.md). #### Webhook payload structure[​](#webhook-payload-structure "Direct link to Webhook payload structure") When an event occurs, Prismatic sends a POST request to your webhook URL with a JSON payload. Here's an example of what the payload looks like: ```json { "integration": { "id": "SW50ZWdyYXRpb246ZWM5YzViM2EtZjNhNy00MDliLTllM2QtODA3MDAxNDVlNWU0", "name": "Slack Integration", "description": "Get alerts in Slack when new contacts are created", "category": "Communication", "has_unpublished_changes": false, "version_number": 6, "created_at": "2025-08-21T20:22:54.828609+00:00", "updated_at": "2025-08-21T20:22:54.828609+00:00" }, "customer": null, "parent_integration": null, "user": { "id": "VXNlcjoyMzZkMDA3ZS0zZGIxLTQ4MWItOTMyNS0zMjhhYTE0OTY5MDA=", "email": "user@example.com", "name": "John Doe" }, "event_type": "integration.published", "timestamp": "2025-08-21T20:22:57.855591+00:00", "organization_id": "T3JnYW5pemF0aW9uOmJjYjE0NjEzLTNjZTItNGQ0MC04OTZmLTIyNTZiNjcyYTllYw==", "webhook_id": "c1e226a7-8d13-4d46-98d9-a99a2d6bdb37" } ``` ##### Payload fields[​](#payload-fields "Direct link to Payload fields") | Field | Description | | ----------------- | ------------------------------------------------------------------ | | `event_type` | The type of event that occurred (e.g., `integration.published`) | | `timestamp` | When the event occurred (ISO 8601 format) | | `webhook_id` | Unique identifier for the webhook that sent this notification | | `organization_id` | Your Prismatic organization ID | | `user` | Information about the user who triggered the event (if applicable) | | `integration` | Integration details (for integration-related events) | | `customer` | Customer details (for customer-related events) | | `instance` | Instance details (for instance-related events) | | `workflow` | Workflow details (for workflow-related events) | | `component` | Component details (for component-related events) | | `connection` | Connection details (for connection-related events) | | `alert_monitor` | Alert monitor details (for alert-related events) | | `alert_group` | Alert group details (for alert group events) | | `log_stream` | Log stream details (for log stream events) | #### Available event types[​](#available-event-types "Direct link to Available event types") Prismatic provides comprehensive coverage of platform events. Here's a complete list of available event types: ##### Instance events[​](#instance-events "Direct link to Instance events") | Event | ID | Emitted when | | ----------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Instance Created | `instance.created` | A new instance is created via `createInstance`. | | Instance Updated | `instance.updated` | An instance's stored configuration is modified via `updateInstance` or `updateInstanceConfigVariables`. Does **not** fire when only toggling `enabled` - see `instance.enabled` / `instance.disabled`. | | Instance Deleted | `instance.deleted` | An instance is removed via `deleteInstance`. | | Instance Deployed | `instance.deployed` | An instance is successfully deployed via `deployInstance`. Configuration changes do **not** auto-deploy - this event is the signal that the running instance reflects the new config. | | Instance Enabled | `instance.enabled` | An instance is activated (for example, `updateInstance` with `enabled: true`). | | Instance Disabled | `instance.disabled` | An instance is deactivated (for example, `updateInstance` with `enabled: false`). | ##### Customer events[​](#customer-events "Direct link to Customer events") | Event | ID | Description | | ---------------- | ------------------ | ------------------------------------------------------- | | Customer Created | `customer.created` | Triggered when a new customer is added to your platform | | Customer Updated | `customer.updated` | Triggered when customer information is modified | | Customer Deleted | `customer.deleted` | Triggered when a customer is removed from the platform | ##### User events[​](#user-events "Direct link to User events") | Event | ID | Description | | ------------ | -------------- | --------------------------------------------------- | | User Created | `user.created` | Triggered when a new user account is created | | User Updated | `user.updated` | Triggered when user profile information is modified | | User Deleted | `user.deleted` | Triggered when a user account is removed | ##### Integration events[​](#integration-events "Direct link to Integration events") | Event | ID | Description | | --------------------- | ----------------------- | ------------------------------------------------------------- | | Integration Created | `integration.created` | Triggered when a new integration is created | | Integration Updated | `integration.updated` | Triggered when integration configuration is modified | | Integration Deleted | `integration.deleted` | Triggered when an integration is removed | | Integration Published | `integration.published` | Triggered when an integration is published to the marketplace | ##### Workflow events[​](#workflow-events "Direct link to Workflow events") | Event | ID | Description | | ------------------ | -------------------- | -------------------------------------------------------------- | | Workflow Created | `workflow.created` | Triggered when a new workflow is created within an integration | | Workflow Updated | `workflow.updated` | Triggered when workflow configuration is modified | | Workflow Deleted | `workflow.deleted` | Triggered when a workflow is removed | | Workflow Published | `workflow.published` | Triggered when a workflow is published | | Workflow Enabled | `workflow.enabled` | Triggered when a workflow is activated | | Workflow Disabled | `workflow.disabled` | Triggered when a workflow is deactivated | ##### Component events[​](#component-events "Direct link to Component events") | Event | ID | Description | | ------------------- | --------------------- | ---------------------------------------------------------- | | Component Deleted | `component.deleted` | Triggered when a custom component is removed | | Component Published | `component.published` | Triggered when a component is published to the marketplace | ##### Connection events[​](#connection-events "Direct link to Connection events") | Event | ID | Description | | ------------------ | -------------------- | --------------------------------------------------- | | Connection Updated | `connection.updated` | Triggered when connection configuration is modified | | Connection Deleted | `connection.deleted` | Triggered when a connection is removed | ##### Alert & monitoring events[​](#alert--monitoring-events "Direct link to Alert & monitoring events") | Event | ID | Description | | --------------------- | ----------------------- | ---------------------------------------------------- | | Alert Monitor Created | `alert_monitor.created` | Triggered when a new alert monitor is configured | | Alert Monitor Updated | `alert_monitor.updated` | Triggered when alert monitor settings are modified | | Alert Monitor Deleted | `alert_monitor.deleted` | Triggered when an alert monitor is removed | | Alert Group Created | `alert_group.created` | Triggered when a new alert group is created | | Alert Group Updated | `alert_group.updated` | Triggered when alert group configuration is modified | | Alert Group Deleted | `alert_group.deleted` | Triggered when an alert group is removed | ##### Log stream events[​](#log-stream-events "Direct link to Log stream events") | Event | ID | Description | | ------------------ | -------------------- | ----------------------------------------------- | | Log Stream Created | `log_stream.created` | Triggered when a new log stream is configured | | Log Stream Updated | `log_stream.updated` | Triggered when log stream settings are modified | | Log Stream Deleted | `log_stream.deleted` | Triggered when a log stream is removed | ##### OAuth2 events[​](#oauth2-events "Direct link to OAuth2 events") | Event | ID | Description | | ------------------------------ | -------------------------------- | ------------------------------------------------------------- | | OAuth2 Authorization Completed | `oauth2.authorization_completed` | Triggered when OAuth2 authorization is successfully completed | | OAuth2 Authorization Failed | `oauth2.authorization_failed` | Triggered when OAuth2 authorization fails | | OAuth2 Token Refreshed | `oauth2.token_refreshed` | Triggered when an OAuth2 token is successfully refreshed | | OAuth2 Token Refresh Failed | `oauth2.token_refresh_failed` | Triggered when OAuth2 token refresh fails | ##### System events[​](#system-events "Direct link to System events") | Event | ID | Description | | ----- | -------------- | ---------------------------------------------- | | Test | `webhook.test` | Triggered when testing a webhook configuration | #### Event relationships[​](#event-relationships "Direct link to Event relationships") Several event types relate to the same underlying object but represent different state changes. Understanding how they relate helps you choose the right subset to subscribe to. ##### `instance.updated` vs `instance.deployed`[​](#instanceupdated-vs-instancedeployed "Direct link to instanceupdated-vs-instancedeployed") These are distinct signals, and most consumers want both: * `instance.updated` fires when an instance's stored configuration changes (via `updateInstance` or `updateInstanceConfigVariables`). * `instance.deployed` fires when you run the `deployInstance` mutation and the running instance begins using the new configuration. **Configuration changes do not automatically deploy.** After updating config variables via the API, you must call `deployInstance` for the runtime to pick them up. If you're using webhooks to know when to bust a cache of deployed config, listen to `instance.deployed`. ##### `connection.updated` vs OAuth2 events[​](#connectionupdated-vs-oauth2-events "Direct link to connectionupdated-vs-oauth2-events") `connection.updated` fires only when a connection's stored configuration is modified via the `updateOAuth2Connection` mutation. It does **not** fire for OAuth2 token refreshes or user-initiated authorization flows: * OAuth2 token refreshes emit `oauth2.token_refreshed` or `oauth2.token_refresh_failed`. * User-completed OAuth2 authorization emits `oauth2.authorization_completed` or `oauth2.authorization_failed`. Credential state and connection-configuration state change through separate mechanisms and surface as separate events. Subscribing only to `instance.updated` or `connection.updated` will not notify you of credential changes. ##### `instance.updated` is not a superset of all instance changes[​](#instanceupdated-is-not-a-superset-of-all-instance-changes "Direct link to instanceupdated-is-not-a-superset-of-all-instance-changes") A common misconception is that `instance.updated` fires for every event that affects an instance. It does not. For complete coverage of instance-related state, subscribe to: * `instance.created`, `instance.updated`, `instance.deleted` for configuration lifecycle * `instance.deployed` for runtime state * `instance.enabled` / `instance.disabled` for activation state * OAuth2 events and `connection.updated` for credential and connection state on the connections an instance uses ##### Payload contents[​](#payload-contents "Direct link to Payload contents") Instance webhook payloads identify the instance (ID, name, timestamps, deployed version, enabled state) but do **not** include the config variables themselves or a diff of what changed. If you need the new config values after an `instance.updated` event, query the instance by ID through the GraphQL API. #### Next steps[​](#next-steps "Direct link to Next steps") * [Configure event webhooks](https://prismatic.io/docs/webhooks/configuring-event-webhooks.md) to start receiving notifications * [Prismatic Insider: Subscribing to Events with Prismatic's API and Event Webhooks](https://prismatic.io/docs/insider.md#subscribing-to-events-with-prismatics-api-and-event-webhooks-2025-11-18) * [Example Node.js Express Webhook Receiver](https://github.com/prismatic-io/examples/tree/main/api/handle-event-webhooks) --- ### Configuring Event Webhooks This guide shows you how to set up, test, and secure event webhooks in Prismatic. For an overview of what event webhooks are and why they're useful, see [Prismatic event webhooks](https://prismatic.io/docs/webhooks.md). #### Setting up event webhooks[​](#setting-up-event-webhooks "Direct link to Setting up event webhooks") To set up or manage your event webhooks, first navigate to the **Event Webhooks** tab in your Prismatic organization settings. Click **+Event Webhook** to configure a new webhook, or click an existing webhook to modify it. ![Screenshot of configuring an event webhook](/docs/assets/images/configure-dd0757df2f67fbcc74a1cea529e52d2b.png) Fill in the required information: * **Name**: A descriptive name for your webhook (e.g., "Production Monitoring") * **URL**: The endpoint where webhooks will be sent * **Secret**: (Optional) An HMAC secret key for [verifying webhook signatures](https://prismatic.io/docs/webhooks/configuring-event-webhooks.md#securing-webhooks-with-hmac-signatures) * **Description**: Additional context about the webhook's purpose * **Is Enabled**: Toggle to enable or disable the webhook Next, select the [events](https://prismatic.io/docs/webhooks.md#available-event-types) that should trigger this webhook: * You can select individual events or use category checkboxes to select all events of a certain type * Common selections include: * Instance lifecycle events - `instance.created`, `instance.updated`, `instance.deployed`, and `instance.deleted`. Add `instance.enabled` / `instance.disabled` if you care about activation state separately. See [event relationships](https://prismatic.io/docs/webhooks.md#event-relationships) for how these interact. * Integration changes (published, updated) * Customer management events * Alert monitors After configuring your webhook, use the **Test Webhook** button to verify your endpoint is working and check that you receive the test payload at your endpoint. Finally, click **Save** to create the webhook and ensure the **Is Enabled** toggle is turned on. #### Testing event webhooks[​](#testing-event-webhooks "Direct link to Testing event webhooks") Use the **Test Webhook** button in the Prismatic UI to: * Verify your endpoint is accessible * Confirm the payload format is correct * Test your webhook processing logic * Validate authentication and security measures When you click **Test Webhook**, your endpoint will receive a `webhook.test` event with a payload that looks like: ```json { "message": "This is a test webhook event from Prismatic.", "webhook_endpoint": { "id": "V2ViaG9va0VuZHBvaW50OmJiNGVjYTIzLWI5NzgtNDU1Mi05MDljLTI5YmRlMzZjZTYxMQ==", "name": "Notify Acme of Changes" }, "user": { "id": "VXNlcjoyMzZkMDA3ZS0zZGIxLTQ4MWItOTMyNS0zMjhhYTE0OTY5MDA=", "email": "john.doe@example.io", "name": "John Doe" }, "event_type": "webhook.test", "timestamp": "2025-08-21T20:21:40.405396+00:00", "organization_id": "T3JnYW5pemF0aW9uOmJjYjE0NjEzLTNjZTItNGQ0MC04OTZmLTIyNTZiNjcyYTllYw==", "webhook_id": "bdea273d-2500-42a2-854b-5189c3f66cfa" } ``` #### Securing webhooks with HMAC signatures[​](#securing-webhooks-with-hmac-signatures "Direct link to Securing webhooks with HMAC signatures") If you set a **Secret** when configuring your webhook, Prismatic will use it to generate an HMAC SHA-256 signature for each webhook request. The signature will be sent as a header, `x-webhook-signature` in the form `sha256=`. This allows you to ensure that the webhook request is coming from Prismatic and has not been tampered with. For example, suppose you receive the following webhook event payload: Example webhook event payload ```text {"message":"This is a test webhook event from Prismatic.","webhook_endpoint":{"id":"V2ViaG9va0VuZHBvaW50OjEwNTI1MjE3LTE4NDMtNGRiNC04YjYyLTgwZTdmOTc5OGEzZA==","name":"Testing"},"user":{"id":"VXNlcjozNDkwNjA3MC0wMjRmLTQxNzMtYjYxMy1mN2I0MWFmYmEwNDM=","email":"john.doe@example.com","name":"John Doe"},"event_type":"webhook.test","timestamp":"2025-10-08T14:13:05.914923+00:00","organization_id":"T3JnYW5pemF0aW9uOjQ0ZjkyMTlkLWU0ZGEtNGEwZi04ZmNhLWJkZmJlNTdiMzBjNA==","webhook_id":"4c79940b-b166-41cb-8b45-944e25b480f3"} ``` If your secret is set to `my-secret-key-abc-123`, the `x-webhook-signature` header would contain: Example x-webhook-signature header ```text sha256=88563276df8a665d1e57bf8a05c2c2432ff80b583297082b768fb06f173e0b59 ``` ##### Verifying signatures with Express[​](#verifying-signatures-with-express "Direct link to Verifying signatures with Express") In this example [Express](https://expressjs.com/) app, we verify the request signature using Node.js's `crypto` module before processing the webhook event: Verifying webhook signatures in Node.js with Express ```ts import express from "express"; import { createHmac } from "node:crypto"; const PORT = 3000; const PRISMATIC_SIGNING_SECRET = "my-secret-key-abc-123"; const app = express(); app.post("/my-webhook-endpoint", express.raw({ type: "*/*" }), (req, res) => { // Get HMAC signature from header and compare it to the one we generate const signatureHeader = req.headers["x-webhook-signature"]; const signature = createHmac("sha256", PRISMATIC_SIGNING_SECRET) .update(req.body) .digest("hex"); // If the signatures don't match, return a 401 if (signatureHeader !== `sha256=${signature}`) { console.warn("Rejecting request with invalid HMAC signature"); return res.status(401).send({ error: "Invalid signature" }); } // Parse the event request and handle the event const payload = JSON.parse(req.body.toString()); switch (payload.event_type) { case "webhook.test": console.log("Got a test webhook"); break; case "instance.created": { console.log( `Instance (${payload.instance.id}) created from integration (${payload.integration.name}) for customer ${payload.customer.external_id}`, ); break; } default: console.warn(`Unhandled event type: ${req.body.toString()}`); } res.status(200).send({ received: true }); }); app.listen(PORT, () => { console.log(`Example webhook receiver listening on port ${PORT}`); }); ``` Similar strategies can be used in other programming languages and frameworks. For a complete working example, see [Example Node.js Express Webhook Receiver](https://github.com/prismatic-io/examples/tree/main/api/handle-event-webhooks). #### Understanding webhook retry behavior[​](#understanding-webhook-retry-behavior "Direct link to Understanding webhook retry behavior") If a Prismatic event webhook request fails (e.g., due to a network error or a non-200 HTTP response), Prismatic will automatically retry the request up to three times after 100, 200, and 400ms. If more than 25 webhook requests have failed within one minute, the webhook will be disabled for 5 minutes. All events that accumulate during that duration will be sent once the webhook is re-enabled after 5 minutes. #### Troubleshooting[​](#troubleshooting "Direct link to Troubleshooting") **Webhook not receiving events** * Verify the webhook is enabled * Check that the correct event types are selected * Ensure your endpoint is accessible and responding with 2xx status codes **Missing events** * Confirm the event types you've selected * Check if the events actually occurred in Prismatic * Verify your webhook configuration is saved **Authentication errors** * Ensure your endpoint accepts POST requests * Check that you're not requiring authentication that Prismatic can't provide * Verify your endpoint can handle the webhook payload format --- ### AI Integrations #### Code-native agentic flows Learn by building Check out [this tutorial](https://prismatic.io/docs/get-started/agentic-flows/code-native-agentic-flows.md) to learn how to add agentic flows to your code-native integrations. #### Add an invocation schema to a flow[​](#add-an-invocation-schema-to-a-flow "Direct link to Add an invocation schema to a flow") To add a flow invocation schema to a code-native flow, add a `schemas.invoke` property to your flow definition. You must also set `isAgentFlow: true` on the flow definition. In this example, we define a flow that searches for people in Acme CRM by first and last name. The invocation schema expects two optional string properties, `first` and `last`. Example code-native flow with invocation schema ```ts import { flow } from "@prismatic-io/spectral"; import axios from "axios"; export const searchPeople = flow({ name: "Search People", stableKey: "search-people", description: "Search for People in Acme CRM", isAgentFlow: true, schemas: { invoke: { $schema: "https://json-schema.org/draft/2020-12/schema", $comment: "Given a first and last name of a person, search for matching people in Acme CRM", properties: { first: { description: "A person's first name", type: "string", }, last: { description: "A person's last name", type: "string", }, }, title: "search-people-in-acme", type: "object", }, }, isSynchronous: true, onExecution: async (context, params) => { const { first: firstNameSearch, last: lastNameSearch } = params.onTrigger .results.body.data as { first: string; last: string }; const { data: people } = await axios.get<{ name: string }[]>( "https://jsonplaceholder.typicode.com/users", ); const matchingPeople = people.filter((person) => { const [firstName, lastName] = person.name.split(" "); if (firstNameSearch) { if (!firstName.toLowerCase().includes(firstNameSearch.toLowerCase())) { return false; } } if (lastNameSearch) { if (!lastName.toLowerCase().includes(lastNameSearch.toLowerCase())) { return false; } } return true; }); return { data: matchingPeople }; }, }); export default [searchPeople]; ``` Setting `isAgentFlow: true` automatically marks the flow as tool-enabled. Once deployed, you can [query for the flow's invocation schema](https://prismatic.io/docs/ai/flow-invocation-schema.md#querying-for-invocation-schemas) and [connect it to an AI agent](https://prismatic.io/docs/ai/connect-ai-agent.md). --- #### Low-Code Agentic Flows Learn by building Check out [this tutorial](https://prismatic.io/docs/get-started/agentic-flows/low-code-agentic-flows.md) to learn how to add agentic flows to your integrations in the low-code designer. #### Add an invocation schema to a flow[​](#add-an-invocation-schema-to-a-flow "Direct link to Add an invocation schema to a flow") To add an invocation schema to a low-code flow, click on the flow's trigger and select the **Schemas** tab. ![Schemas tab in the flow designer](/docs/assets/images/edit-invocation-schema-518536b4dca684994c0edaec20592b25.png) Note that `invoke` schemas are required but `result` schemas are optional. When an AI client like Claude invokes this flow, it translates a human language prompt like: > Search people in Acme CRM whose first name includes "Clem" Into an HTTP request to the agentic flow's webhook URL with a payload of `{ "first": "Clem" }`. ![Claude using an agentic flow to fetch data](/docs/assets/images/claude-fetch-person-61d2335f36bcdcf669535af57d7b3109.png) #### Mark a flow as tool-enabled[​](#mark-a-flow-as-tool-enabled "Direct link to Mark a flow as tool-enabled") After adding an invocation schema to your flow, you must also mark the flow as **tool-enabled**. This is done in the integration designer by clicking the MCP icon on the left side of the flow designer, and toggling the **Tool-enabled** switch for specific flows. ![Marking a flow as tool-enabled](/docs/assets/images/integration-designer-mcp-menu-ec4090407211036aed1359e84f728594.png) Once your flow is tool-enabled, you can [query for its invocation schema](https://prismatic.io/docs/ai/flow-invocation-schema.md#querying-for-invocation-schemas) and [connect it to an AI agent](https://prismatic.io/docs/ai/connect-ai-agent.md). --- #### Connect Your App to Agentic Flows The [Prismatic MCP flow server](https://prismatic.io/docs/ai/model-context-protocol.md) allows you to connect your app's AI agent or chatbot to your [agentic flows](https://prismatic.io/docs/ai/flow-invocation-schema.md). ![Example in-app chat using Prismatic MCP ](/docs/assets/images/sample-llm-chat-a8468227c6d67dfa4e187ce30a28d9a0.png) #### Authentication for embedded customer users[​](#authentication-for-embedded-customer-users "Direct link to Authentication for embedded customer users") If your AI client is embedded in your application (for example, you've built a chat bot with the [AI SDK](https://www.npmjs.com/package/ai)), you will need to [create an embedded JWT](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md) for your customer user and provide that JWT as a bearer token to Prismatic's MCP flow server. #### Node AI SDK[​](#node-ai-sdk "Direct link to Node AI SDK") If you're building your own AI client using the [AI SDK](https://www.npmjs.com/package/ai), you can use the `createMCPClient` function to register Prismatic's MCP flow server with the SDK. You'll need to provide a `StreamableHTTPClientTransport` that points to Prismatic's MCP flow server, and also provide an [embedded JWT](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md) for the customer user (it can be the same JWT you use for embedding Prismatic). When invoking your LLM with `streamText`, provide the tools fetched from Prismatic's MCP flow server to the `tools` option. Connecting to Prismatic's MCP flow server with the AI SDK ```ts import { openai } from "@ai-sdk/openai"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; import { streamText, experimental_createMCPClient as createMCPClient, } from "ai"; // Allow streaming responses up to 30 seconds export const maxDuration = 30; // Fetch all tools available from Prismatic's MCP server given an embedded customer user's access token const getTools = async (prismaticAccessToken: string) => { const transport = new StreamableHTTPClientTransport( new URL("https://mcp.prismatic.io/mcp"), { requestInit: { headers: { Authorization: `Bearer ${prismaticAccessToken}`, }, }, }, ); const mcpClient = await createMCPClient({ transport: transport, onUncaughtError(error) { console.error("Error in MCP client:", error); throw error; }, }); // Remove the "get-me" tool if it exists const { "get-me": getMe, ...tools } = await mcpClient.tools(); return tools; }; // Handle chat completion requests from a chat bot export async function POST(req: Request) { const { messages } = await req.json(); const mcpTools = await getTools( (req.headers.get("Authorization") ?? "").replace("Bearer ", ""), ); const result = streamText({ model: openai("gpt-4o"), messages, tools: { ...mcpTools }, maxSteps: 20, onError: (error) => { console.error("Error in AI response:", error); throw error; }, }); return result.toDataStreamResponse(); } ``` A full example Next.js app is available on [GitHub](https://github.com/prismatic-io/examples/tree/main/ai/nextjs-chatbot). * [util/tools.ts](https://github.com/prismatic-io/examples/blob/main/ai/nextjs-chatbot/src/util/tools.ts) is a helper to fetch tools from Prismatic's MCP flow server. * [app/api/chat/route.ts](https://github.com/prismatic-io/examples/blob/main/ai/nextjs-chatbot/src/app/api/chat/route.ts) is the API route that handles chat completions. * [app/ai-chat/page.tsx](https://github.com/prismatic-io/examples/blob/main/ai/nextjs-chatbot/src/app/ai-chat/page.tsx) is the front-end chat UI. --- #### Building a Custom MCP Flow Server Use Prismatic's MCP server when possible Prismatic offers a [built-in MCP flow server](https://prismatic.io/docs/ai/model-context-protocol.md) that connects to all of your agentic flows with no additional code. For most use cases, the built-in server is the right choice. You may want to build your own MCP server if you need fine-grained control over things like custom authentication, filtering which flows are exposed, or embedding MCP tooling within a larger application. #### Building your own MCP flow server using Prismatic flows[​](#building-your-own-mcp-flow-server-using-prismatic-flows "Direct link to Building your own MCP flow server using Prismatic flows") If you would like to build your own MCP flow server and instruct it to interact with **agentic flows** in Prismatic, your MCP flow server must be told which flows are available to be queried. This example code instructs an MCP flow server to fetch agentic flows from Prismatic's API, and creates a [resource](https://modelcontextprotocol.io/specification/2026-07-28/server/resources) for each: ```tsx import { type JSONSchema, JSONSchemaToZod, } from "@dmitryrechkin/json-schema-to-zod"; import { McpServer, type ToolCallback, } from "@modelcontextprotocol/sdk/server/mcp.js"; import { gql, request } from "graphql-request"; const STACK_BASE_URL = "https://app.prismatic.io"; // Change this to your own region const PRISMATIC_API_KEY = ""; // TODO: Set your Prismatic API key here interface AgentFlowResponse { ai: { agentFlows: { nodes: { id: string; name: string; description: string; webhookUrl: string; apiKeys: string[]; invokeSchema: string; resultSchema: string; }[]; }; }; } // Query for agentFlows const result = await request( new URL("/api", STACK_BASE_URL).toString(), gql` query agentFlows { ai { agentFlows { nodes { id name description webhookUrl apiKeys invokeSchema resultSchema } } } } `, {}, { Authorization: `Bearer ${PRISMATIC_API_KEY}`, }, ); // Customize MCP server to allow custom tool registrations, particularly // for use with JSON Schema (as, by default, only zod schemas are supported). class DynamicMcpServer extends McpServer { jsonSchemaTool( name: string, description: string, schema: JSONSchema, cb: ToolCallback, ): void { const zodSchema = JSONSchemaToZod.convert(schema) as any; super.tool(name, description, zodSchema.shape, cb); } } const server = new DynamicMcpServer({ name: "person-getter", version: "1.0.0", capabilities: { resources: {}, tools: {}, }, }); // For each flow, create a tool that can be invoked by the MCP client for (const flow of result.ai.agentFlows.nodes) { server.jsonSchemaTool( flow.name, flow.description, JSON.parse(flow.invokeSchema), async (args) => { console.log("flow invoke", flow.name, args); const result = await fetch(flow.webhookUrl, { method: "POST", body: JSON.stringify(args), headers: { "Content-Type": "application/json", "prismatic-synchronous": "true", }, }); return { content: [{ type: "text", text: await result.text() }] }; }, ); } ``` --- #### Data Enrichment with AI This article provides practical examples and patterns for using AI to enrich data flowing through your integration. #### CRM Lead Enrichment[​](#crm-lead-enrichment "Direct link to CRM Lead Enrichment") Automatically research incoming leads and enrich them with company information, scoring, and insights before adding to your CRM. This example demonstrates researching a lead's company, analyzing fit criteria, and creating an enriched record in Salesforce. [See the source →](https://github.com/prismatic-io/examples/tree/main/ai/salesforce-lead-enricher-and-routing) * Code-Native * Low-Code ```tsx /** * Enrich Incoming Lead Flow * * Researches incoming leads using web search, enriches them with company data, * and creates them in Salesforce. */ export const enrichIncomingLead = flow({ name: "Enrich Incoming Lead", description: "Research and enrich leads before creating them in Salesforce", onExecution: async (context, params) => { const { configVars } = context; const triggerPayload = params.onTrigger.results.body as TriggerPayload; // Step 1: Set up web search tool const setupWebResearchTool = await context.components.openai.createWebSearchTool({ name: "Web Search", searchContextSize: "high", }); // Step 2: Create lead research agent const createLeadResearchAgent = await context.components.openai.createAgent({ instructions: `When provided a new lead, attempt to research them based on their domain. Look for their industry, employee count, and the problem the company solves`, modelName: "gpt-4o-mini", name: "Lead Researcher", outputSchema: { type: "object", properties: { company: { type: "string", }, employeeCount: { type: "number", }, vertical: { type: "string", }, companyDescription: { type: "string", }, }, required: ["employeeCount", "vertical", "companyDescription"], additionalProperties: false, }, outputSchemaName: "output", outputSchemaStrict: false, tools: [setupWebResearchTool.data], }); // Step 3: Research and enrich the lead const researchAndEnrichLead = await context.components.openai.runAgent({ agentConfig: createLeadResearchAgent.data, maxTurns: "10", openaiConnection: configVars["OpenAI Connection"], userInput: `Research this lead on the web: Company: ${triggerPayload.data.company} Email: ${triggerPayload.data.email} Name: ${triggerPayload.data.firstName} ${triggerPayload.data.lastName}`, }); const enrichedData = researchAndEnrichLead.data.finalOutput; // Step 4: Create lead in Salesforce const createLead = await context.components.salesforce.createLead({ company: triggerPayload.data.company, connection: configVars["Salesforce Connection"], description: enrichedData.companyDescription, email: triggerPayload.data.email, employeeCount: enrichedData.employeeCount.toString(), firstName: triggerPayload.data.firstName, lastName: triggerPayload.data.lastName, leadStatus: "Open", version: "63.0", }); return { data: createLead }; }, }); ``` ![Lead research enrichment workflow](/docs/assets/images/lead-enrichment-salesforce-05938158c7dd2eb9f0318e36b67572e2.png) #### AI Routing and Classification[​](#ai-routing-and-classification "Direct link to AI Routing and Classification") Use AI to analyze incoming data and make intelligent decisions. The AI evaluates content against defined criteria, classifies it, and produces a structured output to enable branching and intelligent routing. ##### Duplicate Record Detection[​](#duplicate-record-detection "Direct link to Duplicate Record Detection") Prevent duplicate records by using AI to analyze and compare incoming data against existing records. This example queries for potential matches and uses AI classification to determine if an account already exists, with confidence thresholds to ensure accuracy. [See the source →](https://github.com/prismatic-io/examples/blob/main/ai/salesforce-lead-enricher-and-routing/cni/src/flows/checkForDuplicates.ts) * Code-Native * Low-Code ```tsx /** * Check for Duplicates Flow * * Analyzes incoming leads against existing Salesforce accounts to identify and prevent * duplicate entries. Uses AI classification to determine similarity with high confidence. */ export const checkForDuplicates = flow({ name: "Check for Duplicates", description: "Prevent duplicate lead creation by checking against existing Salesforce accounts", onExecution: async (context, params) => { const { configVars } = context; const incomingLead = params.onTrigger.results.body.data as IncomingLeadData; // Step 1: Query Salesforce for potential duplicate accounts const findAccounts = await context.components.salesforce.query({ connection: configVars["Salesforce Connection"], queryString: `SELECT Id, Name, Website FROM Account WHERE Name like '${incomingLead.company}%'`, version: "63.0", }); // Step 2: Use AI to classify if the lead is a duplicate const classification = await context.components.openai.classifyAndBranch({ openaiConnection: configVars["OpenAI Connection"], model: "gpt-4o-mini", branches: { Duplicate: "The name, domain, or firmographics suggest it is a duplicate", "Not a Duplicate": "The account appears to be unique based on the provided information.", Else: "You are unable to determine if it is a duplicate.", }, classificationInstructions: `Analyze the account and possible duplicates. Use all available information to determine if this is a duplicate account.`, inputText: `New Account: ${JSON.stringify(incomingLead)} Possible Duplicates: ${JSON.stringify(findAccounts.data.records)}`, }); // Step 3: Route based on classification result if (classification.data.selectedBranch === "Not a Duplicate") { // Create new lead in Salesforce const createLead = await context.components.salesforce.createLead({ connection: configVars["Salesforce Connection"], company: incomingLead.company, email: incomingLead.email, firstName: incomingLead.firstName, lastName: incomingLead.lastName, leadStatus: "Open", version: "63.0", }); return { data: createLead }; } return { data: null }; }, }); ``` ![AI duplicate detection workflow](/docs/assets/images/detect-duplicate-salesforce-789a7b37abf299941b20583bd8b1cc8a.png) #### Smart Data Extraction[​](#smart-data-extraction "Direct link to Smart Data Extraction") Transform unstructured content into structured data by defining JSON schemas that enforce consistent output formats. AI agents use these schemas to parse documents, logs, and other content into predictable, validated structures ##### Error Logs to Jira Issues[​](#error-logs-to-jira-issues "Direct link to Error Logs to Jira Issues") Convert application logs into structured Jira issues by extracting error details, severity, and priority. This example uses a JSON schema to enforce output structure, ensuring the AI returns data in the exact format needed for ticket creation. [See the source →](https://github.com/prismatic-io/examples/tree/main/ai/jira-issues-from-error-logs) * Code-Native * Low-Code ```tsx /** * Create Jira Issue for Error Logs Flow * * This flow automatically analyzes system error logs and creates Jira issues * for significant errors that require attention. It uses AI to intelligently * classify errors, determine their severity, and generate appropriate ticket * descriptions. */ export const createJiraIssueForErrorLogs = flow({ name: "Create Jira Issue for Error Logs", description: "Automatically analyze error logs and create Jira issues for critical errors using AI", onExecution: async (context, params) => { const { configVars } = context; const logData = params.onTrigger.results.body.data as TriggerPayload; // Step 1: Create AI agent for log analysis const createLogAnalyzer = await context.components.openai.createAgent({ instructions: `You are a log analyzer that creates Jira issues from system errors. ## Your Task 1. Identify the main error in the logs 2. Extract the details necessary to create a Jira issue ## Priority Rules - CRITICAL logs or customer-facing errors → High - ERROR logs → Medium - WARN logs → Low ## Severity Scale 1. **Minimal** - Cosmetic issue, no functional impact 2. **Minor** - Small feature affected, easy workaround exists 3. **Moderate** - Feature degraded, some users impacted 4. **Major** - Feature broken, many users affected 5. **Critical** - System down, data loss, or security issue ## Confidence Score Rate 0.0 to 1.0 based on: - Clear error with stack trace → 0.8-1.0 - Timeout or connection issue → 0.6-0.8 - Warning that might be transient → 0.3-0.5 - Unclear if action needed → 0.0-0.3 ## Important Guidelines - Keep the title clear and actionable`, mcpServers: [], modelName: "gpt-4o-mini", name: "Log Analysis Expert", outputSchema: JSON.stringify(JIRA_ISSUE_SCHEMA), outputSchemaName: "jira_issue_output", outputSchemaStrict: false, tools: [], }); // Step 2: Analyze logs and extract Jira issue data const extractJiraIssueInputs = await context.components.openai.runAgent({ agentConfig: createLogAnalyzer.data, maxTurns: "10", openaiConnection: configVars["OpenAI Connection"], previousResponseId: "", userInput: `Analyze the following logs and attempt to extract the necessary fields to create a Jira issue:\n\n${JSON.stringify( logData, )}`, }); const issueData = extractJiraIssueInputs.data.finalOutput; // Step 3: Check confidence threshold before creating issue if (issueData.confidence < 0.3) { return { data: { message: "No significant errors requiring Jira ticket", confidence: issueData.confidence, analysis: issueData, success: true, }, }; } // Step 4: Create Jira issue const createIssue = await context.components.atlassianJira.createIssue( { issueTypeId: configVars["Issue Type"], projectId: configVars["Project"], summary: issueData.title, description: issueData.description, jiraConnection: configVars["Jira Connection"], }, ); // Step 5: Return comprehensive result return { data: { jiraIssue: createIssue.data, analysis: { title: issueData.title, priority: issueData.priority, severity: issueData.severity, confidence: issueData.confidence, }, success: true, }, }; }, }); /** * Jira issue output schema for structured data extraction */ const JIRA_ISSUE_SCHEMA = { $schema: "http://json-schema.org/draft-07/schema#", title: "JiraIssueOutput", type: "object", required: [ "title", "description", "priority", "issue_type", "severity", "confidence", ], properties: { title: { type: "string", maxLength: 100, description: "Brief description of the error", }, description: { type: "string", description: "Detailed description including what happened, when, and error details", }, priority: { type: "string", enum: ["High", "Medium", "Low"], description: "Issue priority level", }, issue_type: { type: "string", enum: ["Bug"], description: "Type of Jira issue. Always capitalized", }, severity: { type: "integer", minimum: 1, maximum: 5, description: "Impact severity (1=minimal, 5=critical)", }, confidence: { type: "number", minimum: 0.0, maximum: 1.0, description: "Confidence score that this needs a Jira ticket", }, }, additionalProperties: false, }; ``` ![Structured output Jira workflow](/docs/assets/images/structured-output-jira-9181abf55a3d7823427f76b2eab2b60d.png) ##### Extract Invoices from PDFs in Dropbox[​](#extract-invoices-from-pdfs-in-dropbox "Direct link to Extract Invoices from PDFs in Dropbox") Automatically process PDF invoices from a Dropbox folder by extracting structured data and creating records in your accounting system. This example shows how to combine file monitoring, PDF parsing, and AI extraction with schema validation. [See the source →](https://github.com/prismatic-io/examples/tree/main/ai/dropbox-extract-receipt-from-pdf) * Code-Native * Low-Code ```tsx /* Import Receipts from PDFs Flow This flow automatically processes PDF files from a Dropbox folder, identifies receipts/invoices using AI classification, and extracts structured data from valid documents. The flow runs every 5 minutes and performs the following steps: 1. Lists all files in the configured Dropbox import folder 2. Downloads each PDF file 3. Uploads files to OpenAI for processing 4. Classifies documents to identify receipts/invoices 5. Extracts structured data from valid receipts @returns Extracted receipt data or empty object if no valid receipts found */ export const importReceiptsFromPdFs = flow({ name: "Import Receipts from PDFs", description: "Automatically process PDF receipts from Dropbox, classify documents, and extract structured receipt data using AI", onExecution: async (context) => { const { configVars } = context; const processedReceipts: ExtractedReceipt[] = []; const listImportFolder = await context.components.dropbox.listFolder({ dropboxConnection: configVars["Dropbox Connection"], path: configVars["Import Folder"], }); // Step 1: Process each file for (const file of listImportFolder.data.result.entries) { // Step 2: Download the file from Dropbox const downloadFile = await context.components.dropbox.downloadFile({ dropboxConnection: configVars["Dropbox Connection"], path: file.path_lower, }); // Step 3: Upload file to OpenAI for processing const uploadFile = await context.components.openai.uploadFile({ connection: configVars["OpenAI Connection"], file: downloadFile.data as any, filename: file.name, purpose: "assistants", timeout: 10000, }); // Step 4: Classify the document to determine if it's a receipt/invoice const agentClassifyAndBranch = await context.components.openai.classifyAndBranch( { agentMcpServers: [], agentTools: [], branches: { "Needs Processing": "The analyzed file is an invoice or receipt that contains transaction data.", }, classificationInstructions: `Analyze the provided file carefully. Determine if it is an invoice or receipt that should be processed. A document should be classified as "Needs Processing" if it contains: - Transaction details (items, prices, totals) - Vendor/store information - Date of transaction - Receipt or invoice number If the document doesn't contain these elements or you cannot determine its type, return the "Else" branch. Always return the required output schema with confidence and reasoning.`, fileIds: [uploadFile.data.id], inputText: `Analyze this PDF file and determine if it's a receipt or invoice that contains extractable transaction data.`, model: "gpt-4o", openaiConnection: configVars["OpenAI Connection"], }, ); // Step 5: Extract structured data from the receipt/invoice if (agentClassifyAndBranch.branch === "Needs Processing") { // Create pdf extraction ai agent const pdfExtractionAgent = await context.components.openai.createAgent({ instructions: `You are an expert at analyzing PDFs and extracting receipt and invoice information. Your task is to: 1. Carefully read and analyze the entire document 2. Extract all transaction details including items, prices, and totals 3. Identify store/vendor information 4. Extract dates in ISO format 5. Ensure all numeric values are accurate 6. If a receipt ID is not visible, generate one based on the store name and date Be thorough and accurate in your extraction.`, mcpServers: [], modelName: "gpt-4o-mini", name: "PDF Receipt Data Extractor", outputSchema: JSON.stringify(RECEIPT_SCHEMA), outputSchemaName: "receipt_data", outputSchemaStrict: false, tools: [], }); // Run the extraction agent against the uploaded pdf const extractedReceipt = await context.components.openai.runAgent<{ data: { finalOutput: ExtractedReceipt }; }>({ agentConfig: pdfExtractionAgent.data, maxTurns: "10", openaiConnection: configVars["OpenAI Connection"], fileIds: [uploadFile.data.id], userInput: `Please analyze this PDF document and extract all receipt/invoice information according to the provided schema. Be thorough in identifying all line items, calculating totals, and extracting vendor information.`, }); processedReceipts.push(extractedReceipt.data.finalOutput); } } // Return summary of processed receipts return { data: { processedReceipts, summary: { totalProcessed: processedReceipts.length, totalFiles: listImportFolder.data.result.entries.length, success: true, }, }, }; }, }); ``` ![Receipt PDF extraction workflow](/docs/assets/images/structured-output-dropbox-96d7abfe332fe75f8172a33ab53a0ecf.png) #### AI-Enhanced Field Mapping[​](#ai-enhanced-field-mapping "Direct link to AI-Enhanced Field Mapping") Use AI to automatically generate intelligent field mappings between different systems. This is particularly useful when integrating with CRMs like Salesforce, where field names may vary between organizations or when mapping to your own application's fields. ##### Salesforce Field Mapper with AI[​](#salesforce-field-mapper-with-ai "Direct link to Salesforce Field Mapper with AI") Automatically generate field mappings between Salesforce Account fields and your destination system using OpenAI. The AI analyzes field names and suggests semantic matches, pre-populating the mapping form so users can verify and adjust before deploying. [See the source →](https://github.com/prismatic-io/examples) * Code-Native * Low-Code ````ts // This example field mapper uses OpenAI to generate intelligent field mappings // between Salesforce Account fields and destination system fields. const getAiDefaultMapping = async (sourceFields, destinationFields, apiKey) => { try { const prompt = ` You are a field mapping expert. Given two lists of fields from different systems, suggest intelligent mappings based on field name similarities and common naming conventions. Source fields: ${JSON.stringify( sourceFields.map((f) => f.title || f.const || f), )} Destination fields: ${JSON.stringify( destinationFields.map((f) => f.title || f.const || f), )} Return a JSON array of mappings in the format: [ {"source": "SourceFieldName", "destination": "DestinationFieldName"}, ... ] Only map fields that have clear semantic matches. Return only valid JSON without any markdown formatting or explanations. `; const response = await fetch("https://api.openai.com/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ model: "gpt-4", messages: [ { role: "system", content: "You are a helpful assistant that creates field mappings between different data systems. Respond only with valid JSON.", }, { role: "user", content: prompt, }, ], temperature: 0.3, max_tokens: 2000, }), }); if (!response.ok) { throw new Error( `OpenAI API error: ${response.status} ${response.statusText}`, ); } const data = await response.json(); const mappingText = data.choices[0].message.content.trim(); // Parse the JSON response, removing any markdown code blocks if present const cleanedText = mappingText .replace(/```json\n?/g, "") .replace(/```\n?/g, ""); const mappings = JSON.parse(cleanedText); // Create lookup maps to convert titles back to const values const sourceTitleToConst = new Map( sourceFields.map((f) => [f.title || f.const || f, f.const || f]), ); const destTitleToConst = new Map( destinationFields.map((f) => [f.title || f.const || f, f.const || f]), ); // Convert AI's title-based mappings to const-based mappings const constMappings = mappings .map((mapping) => ({ source: sourceTitleToConst.get(mapping.source) || mapping.source, destination: destTitleToConst.get(mapping.destination) || mapping.destination, })) .filter((mapping) => mapping.source && mapping.destination); return constMappings; } catch (error) { console.error("Error getting AI mapping:", error); return []; } }; module.exports = async (context, { connection, contextValue }) => { const { token: { access_token, instance_url }, } = connection; const openAiApiKey = context.configVars["OpenAI Connection"].fields.apiKey; // Fetch Salesforce Account fields const response = await fetch( `${instance_url}/services/data/v60.0/sobjects/Account/describe`, { headers: { Authorization: `Bearer ${access_token}` } }, ); const { fields } = await response.json(); const salesforceAccountFields = fields .map((field) => ({ title: field.label, const: field.name })) .sort((a, b) => (a.title < b.title ? -1 : 1)); // Destination fields from your target system const acmeAccountFields = [ { title: "Account Name", const: "accountName" }, { title: "Account Identifier", const: "accountID" }, { title: "Email Address", const: "email" }, { title: "Phone Number", const: "phone" }, { title: "Street Address", const: "street" }, { title: "City", const: "city" }, { title: "State/Province", const: "state" }, { title: "Postal Code", const: "zipCode" }, { title: "Country", const: "country" }, { title: "Company Website", const: "website" }, { title: "Industry Type", const: "industry" }, { title: "Annual Revenue", const: "revenue" }, { title: "Employee Count", const: "employees" }, { title: "Created Date", const: "createdAt" }, { title: "Last Modified", const: "updatedAt" }, ]; const schema = { type: "array", items: { type: "object", properties: { source: { type: "string", oneOf: salesforceAccountFields, }, destination: { type: "string", oneOf: acmeAccountFields, }, }, required: ["source", "destination"], }, }; const uiSchema = { type: "Control", label: "Salesforce / Acme Account Field Mapper", scope: "#", options: { detail: { type: "HorizontalLayout", elements: [ { type: "Control", scope: "#/properties/source", options: { autocomplete: true }, label: "Salesforce Field", }, { type: "Control", scope: "#/properties/destination", options: { autocomplete: true }, label: "Acme Field", }, ], }, }, }; // Get AI-generated default mapping const data = await getAiDefaultMapping( salesforceAccountFields, acmeAccountFields, openAiApiKey, ); context.logger.info(`AI-generated default mapping: ${JSON.stringify(data)}`); return { schema, uiSchema, data, }; }; ```` ![AI-enhanced Salesforce field mapper](/docs/assets/images/salesforce-field-mapper-00a658ec0e83d6d84b39bd20e0835f64.png) ##### Prerequisites[​](#prerequisites "Direct link to Prerequisites") 1. **OpenAI Connection**: Must be named exactly `"OpenAI Connection"` and set to `organization` visibility (not customer-visible) 2. **Salesforce Connection**: Already configured in your integration (created automatically when you add a Salesforce step) [Learn more about field mapping →](https://prismatic.io/docs/integrations/data-sources/field-mapping/salesforce-field-mapper.md) #### Conversational Interfaces[​](#conversational-interfaces "Direct link to Conversational Interfaces") Build AI-powered chat interfaces that maintain conversation context across multiple interactions. Agents store and retrieve chat history to understand context, process natural language, and execute workflows based on user intent. ##### Slack Assistant[​](#slack-assistant "Direct link to Slack Assistant") Create an AI assistant that responds to user messages in Slack threads, providing application intelligence directly in the customer's Slack. [See the source →](https://github.com/prismatic-io/examples/tree/main/ai/slack-chatbot-agent) * Code-Native * Low-Code ```tsx export const eventHandler = flow({ name: "Slack Message Handler", description: "Handles Slack Events and generates responses with OpenAI Assistant SDK", onExecution: async (context, params) => { const { configVars, customer, integration, instanceState } = context; const connection = configVars["Slack Connection"]; const openaiKey = util.types.toString( configVars.OPENAI_API_KEY.fields.apiKey, ); const prismaticRefreshToken = util.types.toString( configVars.PRISMATIC_REFRESH_TOKEN, ); // Set OpenAI API key globally setDefaultOpenAIKey(openaiKey); // Build agent tools const tools = await buildTools( customer.externalId !== "testCustomerExternalId" ? customer : undefined, prismaticRefreshToken, integration.id, ); const agent = new Agent({ name: "Slack Assistant", instructions: configVars.SYSTEM_PROMPT, tools, }); const executionId = params.onTrigger.results.executionId; // Create slack assistant const assistant = new Assistant({ userMessage: async (args) => { const { client, message, logger, setStatus } = args; if ( !("text" in message) || !("thread_ts" in message) || !message.text || !message.thread_ts ) { return; } setStatus("is typing..."); const conversationId = message.thread_ts; const userInput = message.text; try { // Get stored state for this conversation const convState = instanceState[conversationId]; const lastResponseId = convState.lastResponseId; // Run the agent with the message const result = await run(agent, [user(userInput)], { previousResponseId: lastResponseId, }); // Handle interruptions if (result.interruptions && result.interruptions.length > 0) { const firstInterruption = result.interruptions[0]; // Store state in instanceState instanceState[conversationId] = { state: result.state.toString(), lastResponseId: result.lastResponseId, pendingInterruption: { functionId: firstInterruption.rawItem.id!, name: firstInterruption.rawItem.name, arguments: firstInterruption.rawItem.arguments, }, }; // Post approval block await client.chat.postMessage({ channel: message.channel, thread_ts: message.thread_ts, blocks: createApprovalBlocks( firstInterruption.rawItem.name, firstInterruption.rawItem.arguments, executionId, ), text: `Approval required for tool: ${firstInterruption.rawItem.name}`, metadata: { event_type: "tool_approval", event_payload: { conversationId }, }, }); } else { // Store lastResponseId for next message instanceState[conversationId] = { lastResponseId: result.lastResponseId, }; // Post response await client.chat.postMessage({ channel: message.channel, thread_ts: message.thread_ts, text: result.finalOutput || "I couldn't generate a response.", metadata: { event_type: "execution_id", event_payload: { execution_id: executionId }, }, }); } } catch (e) { await args.say({ text: "I encountered an error processing your request. Please try again.", }); } }, threadStarted: async (args) => { await args.say("Hi! I'm your AI assistant. How can I help you today?"); await args.saveThreadContext(); }, }); const actionHandlers: ActionHandlers = { onToolApproval: async ({ approved, previousExecutionId, userId, conversationId, channelId, client, updateMessage, }) => { // Get stored state for this conversation const convState = instanceState[conversationId] as ConversationState; // Deserialize and apply users decision let agentState = await RunState.fromString(agent, convState.state); const interrupts = agentState.getInterruptions(); const interrupt = interrupts[0]; if (approved) { agentState.approve(interrupt); } else { agentState.reject(interrupt); } // Update message to show decision await updateMessage( approved ? `✅ Tool execution approved by <@${userId}>` : `❌ Tool execution denied by <@${userId}>`, ); // Continue execution const result = await run(agent, agentState); instanceState[conversationId] = { lastResponseId: result.lastResponseId, } as ConversationState; // Post final response await client.chat.postMessage({ channel: channelId, thread_ts: conversationId, text: result.finalOutput || "Task completed.", metadata: { event_type: "execution_id", event_payload: { execution_id: executionId, }, }, }); }, }; const app = App(connection, { assistant, actionHandlers }); const handler = await app.start(); await handler(params.onTrigger.results); return { data: { result: "Event processed successfully", }, }; }, }); ``` ![Slack chat interface](/docs/assets/images/chat-interface-slack-c664b55933955fb1c203be1da0a1e217.png) #### Human-in-the-Loop Approval Flows[​](#human-in-the-loop-approval-flows "Direct link to Human-in-the-Loop Approval Flows") Combine AI automation with human oversight by restricting tool access and implementing approval gates for sensitive operations. Define permission levels (read vs. write), create approval workflows that pause execution for human review, and maintain audit logs. ##### API Operation Tools Requiring Approval[​](#api-operation-tools-requiring-approval "Direct link to API Operation Tools Requiring Approval") Gate sensitive API operations behind human approval workflows. This example shows how to differentiate between read-only operations (no approval needed) and write operations (approval required), with a mechanism to pause execution and resume after human review. [See the source →](https://github.com/prismatic-io/examples/blob/main/ai/openai-agent/src/flows/agentWithApprovals.ts) * Code-Native * Low-Code ```tsx export const approvalFlow = flow({ name: "Approval Flow", description: "Demonstrates wrapping REST APIs as AI tools for interaction", onExecution: async ({ configVars }, params) => { const openaiKey = util.types.toString( configVars.OPENAI_API_KEY.fields.apiKey, ); // Set the OpenAI API key setDefaultOpenAIKey(openaiKey); // Create agent with API tools const agent = new Agent({ name: "API Assistant", instructions: `You are an API assistant that helps users interact with their data. Use the available tools to fulfill user requests.`, tools: [ // Read-only tools apiTools.getCurrentUserInfo, apiTools.getPosts, apiTools.getPost, apiTools.getPostComments, // Write tools apiTools.createPost, //needsApproval: true apiTools.updatePost, //needsApproval: true ], }); // Get the message from the payload const { message, conversationId, lastResponseId, state, interruptions: userResponses, } = params.onTrigger.results.body.data as ChatRequest; if (userResponses && state) { let agentState = await RunState.fromString(agent, state); agentState = updateStateWithUserResponse( agentState, agentState.getInterruptions(), userResponses, ); const result = await run(agent, agentState); const interruptions: Interruption[] = handleInterrupt( result.interruptions, ); return { data: { response: interruptions.length > 0 ? undefined : result.finalOutput, interruptions, lastResponseId: result.lastResponseId, conversationId, state: result.state.toString(), }, }; } else { if (!message) { throw new Error("Message is required to run the agent"); } // Run the agent with the message const result = await run(agent, [user(message)], { previousResponseId: lastResponseId, }); const interruptions: Interruption[] = handleInterrupt( result.interruptions, ); return { data: { response: interruptions.length > 0 ? undefined : result.finalOutput, interruptions, lastResponseId: result.lastResponseId, conversationId, state: result.state.toString(), }, }; } }, }); function updateStateWithUserResponse( state: RunState>, interrupts: RunToolApprovalItem[], userResponses: Interruption[], ) { for (const userResponse of userResponses) { const interrupt = interrupts.find( (i) => i.rawItem.id === userResponse.functionId, ); if (interrupt) { if (userResponse.approved) { state.approve(interrupt); } else { state.reject(interrupt); } } } return state; } function handleInterrupt(interrupts: RunToolApprovalItem[]): Interruption[] { if (interrupts.length === 0) { return []; } const userApprovalItems = interrupts.map((intr) => ({ functionId: intr.rawItem.id!, name: intr.rawItem.name, approved: false, arguments: intr.rawItem.arguments, })); return userApprovalItems; } ``` ![Approval flow workflow](/docs/assets/images/hitl-openai-agent-df68f3d1d013436f04ef73b3c692c45a.png) ##### Incident Monitoring Slackbot[​](#incident-monitoring-slackbot "Direct link to Incident Monitoring Slackbot") Monitor backend services for errors and automatically alert your team via Slack with AI-generated summaries. Enable operators to trigger deeper investigation or create incidents directly from Slack, combining automated detection with human decision-making. ![Incident Monitoring](/docs/assets/images/hitl-slack-2-36ae0aa1f3dcc4ffd736c7c60b03e205.png) [See the source →](https://github.com/prismatic-io/examples/tree/main/ai/slack-acme-incident-monitoring) * Code-Native * Low-Code ```tsx /* Flow for processing new incident alerts with AI agent assistance. This flow: 1. Creates tools for the AI agent (get on-call staff, create incident) 2. Configures an AI agent with incident response capabilities 3. Runs the agent to process the alert 4. If approval is needed, posts an interactive message to Slack 5. Stores agent state for resumption when approval is received */ export const newIncidentAlert = flow({ name: "New Incident Alert", description: "Create a new incident from an incoming alert", onExecution: async (context, params) => { const { configVars } = context; // Setup tools for the agent const agentCreateIncidentTool = await context.components.openai.createFlowTool({ flowName: "Create Incident", requiresApproval: true, strictMode: false, toolDescription: "Create a new incident using the provided description", }); const agentGetOnCallStaffTool = await context.components.openai.createFlowTool({ flowName: "Get On Call Staff", requiresApproval: false, strictMode: false, toolDescription: "Get On Call Staff", }); // Create the AI agent with our configuration const agentCreateAssistantAgent = await context.components.openai.createAgent({ instructions: AGENT_INSTRUCTIONS, mcpServers: [], modelName: "gpt-4o", name: "Acme SaaS Assistant", outputSchema: JSON.stringify(INCIDENT_RESPONSE_SCHEMA), outputSchemaName: "output", outputSchemaStrict: false, tools: [agentCreateIncidentTool.data, agentGetOnCallStaffTool.data], }); // Prepare the alert input for the agent const setupAlertInputPrompt = `You must create a new incident from the provided alert for the on-call user. First, use a tool to get the on call staff, second create an incident using the create incident tool from the following alert. \nAlert Detected: ${JSON.stringify( params.onTrigger.results.body.data, )}`; // Run the agent to process the alert const runAgentCreateIncident = await context.components.openai.runAgent({ agentConfig: agentCreateAssistantAgent.data, fileIds: [], handoffs: [], history: "", maxTurns: "10", openaiConnection: configVars["OpenAI Connection"], previousResponseId: "", userInput: setupAlertInputPrompt, }); // Handle approval interruptions if (runAgentCreateIncident.data.hasInterruptions) { const approvalRequest = runAgentCreateIncident.data.pendingApprovals?.[0].approvalRequest; const approvalArgs = { ...JSON.parse( runAgentCreateIncident.data.pendingApprovals[0].arguments, ), approvalRequest, }; // Build approval message blocks const createApprovalBlocks = buildApprovalMessage(approvalArgs); // Post approval request to Slack await context.components.slack.postBlockMessage({ channelName: configVars["Alert Channel"], connection: configVars["slackConnection"], blocks: createApprovalBlocks as any, message: "An approval is required to create a new incident", }); // Store agent state for resumption after approval const crossFlowState = context.crossFlowState; crossFlowState[approvalArgs.anomaly_id] = { ...runAgentCreateIncident.data, agentConfig: agentCreateAssistantAgent.data, }; return { data: { interrupted: true }, crossFlowState, }; } }, }); ``` ![Create approval request and send to human](/docs/assets/images/approval-flow-slack-fde79079ffc7d3c90f458ebac84fde5d.png) [See the source →](https://github.com/prismatic-io/examples/tree/main/ai/slack-acme-incident-monitoring) * Code-Native * Low-Code ```tsx /* Handles Slack events and interactions for the incident management system. This flow processes approval actions from Slack buttons when users decide whether to create an incident from an anomaly alert. Integration flow: 1. newIncidentAlert flow detects anomaly and requests approval 2. User clicks approve/investigate/ignore button in Slack 3. This flow processes the interaction and resumes the AI agent 4. Agent completes the incident creation or rejection 5. Result is posted back to Slack */ export const handleSlackEventsAndInteractions = flow({ name: "Handle Slack Events and Interactions", onExecution: async (context, params) => { const triggerResults = params.onTrigger.results.body; // Decode the URL-encoded payload from Slack const rawBody = util.types.toString(triggerResults.data); const formData = new URLSearchParams(rawBody); const payloadString = formData.get("payload"); if (!payloadString) { console.log("No payload found in request body"); return { data: { error: "No payload found" } }; } // Parse the JSON payload const interactionPayload = JSON.parse(payloadString) as any; // Build response data const responseData = { type: interactionPayload.type, }; // Add common fields if (interactionPayload.trigger_id) { responseData.trigger_id = interactionPayload.trigger_id; } if (interactionPayload.user) { responseData.user = interactionPayload.user; } // Process different interaction types switch (interactionPayload.type) { case "block_actions": { const blockAction = interactionPayload as BlockAction; responseData.actions = blockAction.actions; responseData.response_url = blockAction.response_url; responseData.container = blockAction.container; // Process approval action const action = blockAction.actions[0]; // Parse the action value const approvalAction = parseApprovalAction(action); const { anomalyId, functionId, approved } = approvalAction; try { const storedState = retrieveStoredAgentState(context, anomalyId); const pendingApprovals = storedState.pendingApprovals || []; const matchingApproval = findMatchingApproval( pendingApprovals, functionId, ); // Create approval response const approvalResponses = createApprovalResponse( functionId, approved, action.action_id, ); // Resume the agent with approval response const resumeResult = await resumeAgent( context, storedState, approvalResponses, ); // Handle the agent's final output const finalOutput = resumeResult.data.finalOutput; await postIncidentResult(context, finalOutput); // Update the original approval message await updateApprovalMessage( context, blockAction, approved, action.action_id, ); // Clean up stored state await cleanupStoredState(context, anomalyId); responseData.handled = true; responseData.anomalyId = anomalyId; responseData.finalOutput = finalOutput; } catch (error) { console.error("Error handling approval:", error); responseData.error = error instanceof Error ? error.message : String(error); } break; } default: console.log("Unsupported interaction type:", interactionPayload); responseData.raw = interactionPayload; } return { data: responseData }; }, }); ``` ![Handle human approval or rejection](/docs/assets/images/approval-flow-slack-fde79079ffc7d3c90f458ebac84fde5d.png) --- #### Flow Invocation Schemas Similar to other remote web services and APIs, Prismatic flows can be invoked by AI agents when they are expressed as tools. To make a flow compatible with LLM calls, you must do two things: 1. Give the flow an **invocation schema** 2. Mark the flow as **tool-enabled** See the [low-code](https://prismatic.io/docs/ai/agentic-flows/low-code.md) and [code-native](https://prismatic.io/docs/ai/agentic-flows/code-native.md) guides for instructions specific to your integration type. #### Invocation schema[​](#invocation-schema "Direct link to Invocation schema") The LLM must understand the structure of requests that the flow expects. For example, if you have a flow that fetches a person given their first and last name, it may expect a request body like this: ```json { "first": "string", "last": "string" } ``` The shape of the request can be expressed with JSON Schema, which is a standard way to describe the shape of JSON data: Example invocation schema in JSON ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "search-people", "$comment": "Given a first and last name of a person, search for matching people in Acme CRM", "type": "object", "properties": { "first": { "description": "A person's first name", "type": "string" }, "last": { "description": "A person's last name", "type": "string" } } } ``` **Optional**: The response that your flow returns can also be described using JSON Schema. Here, we describe a response that returns an array of people records, each with an `id`, `name`, and `address`. Example result schema in JSON ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "search-people-result", "$comment": "Returns any people records that match the search query", "type": "object", "properties": { "people": { "description": "An array of people who match the query", "items": { "properties": { "address": { "description": "Address of person in Acme CRM", "type": "string" }, "id": { "description": "ID of a person in Acme CRM", "type": "number" }, "name": { "description": "Full name of a person in Acme CRM", "type": "string" } }, "type": "object" }, "type": "array" } } } ``` Your agentic flows must be synchronous Your flow must respond [synchronously](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md#synchronous-invocations-and-redirects) to return a pertinent response to an AI agent. A synchronous flow returns the results of the last step to the caller and must complete its work within 30 seconds. #### Querying for invocation schemas[​](#querying-for-invocation-schemas "Direct link to Querying for invocation schemas") The flows you build that have invocation schemas are considered **agentic flows**. As a Prismatic organization user, you can programmatically query for agentic flows that are associated with test instances (the instances used when testing an integration in the low-code designer). Customer users, on the other hand, can only query for agentic flows that are associated with instances deployed to the customer they are logged in as. Information about these flows, including webhook URL and invokeSchema / resultSchema can be fetched from the Prismatic API using the `ai { agentFlows }` query: Query for deployed agentic flows ```graphql query agentFlows { ai { agentFlows { nodes { id name description webhookUrl apiKeys invokeSchema resultSchema } } } } ``` The above query, when run by an organization team member, will return information about all agentic flows for your test instances. If you are querying as a customer user using an embedded JWT, on the other hand, you will see only agentic flows deployed to the customer you're logged in as. Agentic flows response ```json { "data": { "ai": { "agentFlows": { "nodes": [ { "id": "SW5zdGFuY2VGbG93Q29uZmlnOmI2N2EzMjU2LWMzOTgtNDRkMy04Mzg0LWExZDkyMjZkN2JhYg==", "name": "search-people", "description": "Given a first and last name of a person, search for matching people in Acme CRM", "webhookUrl": "https://hooks.dev.prismatic-dev.io/trigger/SW5zdGFuY2VGbG93Q29uZmlnOmI2N2EzMjU2LWMzOTgtNDRkMy04Mzg0LWExZDkyMjZkN2JhYg==", "apiKeys": [], "invokeSchema": "{\"type\": \"object\", \"title\": \"search-people\", \"$comment\": \"Given a first and last name of a person, search for matching people in Acme CRM\", \"properties\": {\"last\": {\"type\": \"string\", \"description\": \"A person's last name\"}, \"first\": {\"type\": \"string\", \"description\": \"A person's first name\"}}}", "resultSchema": "{\"type\": \"object\", \"title\": \"search-people-result\", \"$comment\": \"Returns any people records that match the search query\", \"properties\": {\"people\": {\"type\": \"array\", \"items\": {\"type\": \"object\", \"properties\": {\"id\": {\"type\": \"number\", \"description\": \"ID of a person in Acme CRM\"}, \"name\": {\"type\": \"string\", \"description\": \"Full name of a person in Acme CRM\"}}}, \"description\": \"An array of people who match the query\"}}}" } ] } } } } ``` Once you have the `invokeSchema` and `webhookUrl`, you can [create tools](https://prismatic.io/docs/ai/connect-ai-agent.md) for AI agents (like OpenAI or Claude) to consume. --- #### MCP Endpoints Prismatic offers a hosted MCP flow server that you can use to connect your AI agent to your agentic flows. This article describes the available MCP endpoint scopes and URLs. #### Global MCP endpoints[​](#global-mcp-endpoints "Direct link to Global MCP endpoints") The global MCP endpoints give your AI agent access to all agentic flows across all integrations. Below are the global endpoints for Prismatic's public regions. If your organization uses a private stack, contact support for your MCP endpoint. | Region | MCP Endpoint | | ----------------------- | ------------------------------------- | | US Commercial (default) | `mcp.prismatic.io/mcp` | | US GovCloud | `mcp.us-gov-west-1.prismatic.io/mcp` | | Europe (Ireland) | `mcp.eu-west-1.prismatic.io/mcp` | | Europe (London) | `mcp.eu-west-2.prismatic.io/mcp` | | Canada (Central) | `mcp.ca-central-1.prismatic.io/mcp` | | Australia (Sydney) | `mcp.ap-southeast-2.prismatic.io/mcp` | | Africa (Cape Town) | `mcp.af-south-1.prismatic.io/mcp` | | Private Stack | `mcp./mcp` | #### Integration-scoped MCP endpoints[​](#integration-scoped-mcp-endpoints "Direct link to Integration-scoped MCP endpoints") If you have a Salesforce integration among dozens of other integrations and want to connect only to its agentic flows, open the MCP tab of your Salesforce integration and take note of the custom MCP endpoint. It will look like `https://mcp.prismatic.io/SW5...../mcp` (depending on your region). ![Custom MCP endpoint for a specific integration](/docs/assets/images/integration-designer-mcp-menu-ec4090407211036aed1359e84f728594.png) If your customers have instances of this integration deployed to them, they can also connect to the agentic flows associated with their instance by using the same MCP endpoint. #### Instance-scoped MCP endpoints[​](#instance-scoped-mcp-endpoints "Direct link to Instance-scoped MCP endpoints") If a customer has multiple instances of the same integration deployed, each instance exposes agentic flows with identical tool names. This causes a "duplicate tool name" error when querying the global or integration-scoped MCP URL, since LLMs require unique tool names. To target a specific instance's agentic flows, use the instance-scoped URL: ```text https://mcp.prismatic.io/instances//mcp ``` This lets you connect to a single instance's agentic flows when multiple instances of the same integration exist for a customer. --- #### Prismatic's MCP Flow Server #### What is MCP?[​](#what-is-mcp "Direct link to What is MCP?") [MCP](https://modelcontextprotocol.io/docs/getting-started/intro) is an open protocol, introduced by Anthropic, that standardizes how LLMs request context from applications. When an LLM connects to an MCP server, it requests a list of **tools** that the MCP server offers. These tools can perform actions such as "Find people with a specified name in HubSpot" or "Add an event to my Google Calendar." An MCP server offers a standardized schema so LLMs know to send a first and last name to the "find people" tool or an event name and date to the "add event" tool. #### Why use Prismatic MCP?[​](#why-use-prismatic-mcp "Direct link to Why use Prismatic MCP?") Out-of-the-box AI agents have limited capabilities - they can only access data provided to them and lack the authentication credentials or API endpoints needed to interact with the other apps and services your customers use. Without structured tooling, agents can also behave non-deterministically, calling wrong endpoints or mishandling errors. Prismatic MCP solves this by exposing your existing integration flows as MCP tools. Your AI agent can interact with your customers' third-party apps in a deterministic, authenticated way - without custom API endpoints or additional code. ![Example in-app chat using Prismatic MCP ](/docs/assets/images/sample-llm-chat-a8468227c6d67dfa4e187ce30a28d9a0.png) #### How does Prismatic's MCP Flow Server work?[​](#how-does-prismatics-mcp-flow-server-work "Direct link to How does Prismatic's MCP Flow Server work?") You can designate some of your integrations' flows to be **agentic flows** (i.e. MCP tools) that your AI agent can invoke. Using either the low-code designer or code-native SDK, you can assign an [invocation schema](https://prismatic.io/docs/ai/flow-invocation-schema.md) to these agentic flows, which tells the AI agent how to call them and what parameters to send. When your AI agent queries the Prismatic MCP Flow Server, it gets a list of available tools - agentic flows you've built - along with their invocation schema. If your customer wants to know `How many contacts do I have in HubSpot named 'Bill'?`, the AI agent can determine that it needs to call the `Get contact count` flow that you've built, extract the necessary parameters from the customer's query, and invoke that flow via the Prismatic MCP Flow Server. Organization team members have access to agentic flows of test instances When querying for agentic flows as an organization team member, you will receive a list of agentic flows within your test instances (the instances you interact with in the integration designer). You will not see your customers' instances' agentic flows. Customer users, on the other hand, will see agentic flows of production instances deployed to their customer. #### How do I get started?[​](#how-do-i-get-started "Direct link to How do I get started?") 1. Build an agentic flow by following the [Getting Started](https://prismatic.io/docs/get-started/agentic-flows/first-agentic-flow.md) guide. 2. [Test your agentic flow using an MCP client](https://prismatic.io/docs/ai/test-mcp-clients.md), like Postman, ChatGPT or Claude. 3. [Connect your app's AI agent(s)](https://prismatic.io/docs/ai/connect-ai-agent.md) to Prismatic's MCP server to give your customers access to your agentic flows. --- #### Documentation for LLMs Prismatic provides documentation optimized for large language models (LLMs). * [llms.txt](https://prismatic.io/docs/llms.txt) is a directory of all documentation pages optimized for LLMs. * [llms-full.txt](https://prismatic.io/docs/llms-full.txt) is a single file containing all documentation content optimized for LLMs. Additionally, an LLM-optimized version of any article can be accessed by replacing the trailing slash of its URL with `.md`. For example, * This article's LLM-optimized version can be found at `/docs/ai/prismatic-docs.md`. * `/docs/custom-connectors/data-sources-in-embedded-workflow-builder/` becomes `/docs/custom-connectors/data-sources-in-embedded-workflow-builder.md` --- #### Test Using Common MCP Clients It's not likely that your customers will interact with your agentic flows directly through tools like Claude or OpenAI, but you can use these MCP clients to test your agentic flows as you're building them. This article covers how to connect standalone clients like Claude Desktop, Claude Code, and Cursor to Prismatic's MCP flow server. For endpoint URLs scoped by region, integration, or instance, see [MCP endpoints](https://prismatic.io/docs/ai/mcp-endpoints.md). #### Authentication[​](#authentication "Direct link to Authentication") Prismatic's MCP flow server uses OAuth 2.0 (similar to how you authenticate with the [Prism CLI](https://prismatic.io/docs/cli.md)). When connecting an MCP client to Prismatic's MCP flow server, your client will likely direct you to Prismatic's OAuth 2.0 consent screen in order to connect. Alternatively, you can run `prism me:token` to generate a temporary access token that you can use to authenticate your client. Provide the token as a bearer token to Prismatic's MCP flow server. #### Client requirements[​](#client-requirements "Direct link to Client requirements") Prismatic's MCP flow server is designed to be used with any client that supports the current draft of the [Model Context Protocol](https://modelcontextprotocol.io/specification/2026-07-28) (MCP) specification. Specifically, your client must support the following: * [MCP OAuth](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization) as the means to manage authorization between your application and Prismatic, so your client implementation must provide this support. * Streamable HTTP transport, not stdio, nor the legacy HTTP SSE. Stand-alone clients can leverage the [mcp-remote](https://www.npmjs.com/package/mcp-remote) package to connect to Prismatic's MCP servers. `mcp-remote` handles OAuth connection and stores your Prismatic token for you in `$HOME/.mcp-auth/`. #### Claude Desktop[​](#claude-desktop "Direct link to Claude Desktop") Here's a sample `claude_desktop_config.json` config file that will connect to Prismatic's MCP flow server. You may need to adjust the URL to match your region. claude\_desktop\_config.json ```json { "mcpServers": { "prismatic": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.prismatic.io/mcp"] } } } ``` This instructs Claude to use the `mcp-remote` shim to connect to Prismatic's MCP server. If successful, after editing your configuration file and *restarting* Claude, you should automatically be directed to an OAuth 2.0 consent screen to grant Claude access to your Prismatic environment. After authenticating, ask Claude "Who am I?". Claude will use Prismatic's `get-me` tool (which is a default tool you'll have access to from Prismatic's MCP server). ![Connecting Claude to Prismatic](/docs/assets/images/claude-510056b7fbd2a3007d0d0b3d3a134bc9.png) #### Claude Code[​](#claude-code "Direct link to Claude Code") Similar to Claude Desktop, Claude Code uses the `mcp-remote` package to connect to Prismatic's MCP flow server. Run this command to add Prismatic's MCP flow server to Claude Code: ```bash claude mcp add-json prismatic '{"type":"stdio","command":"npx","args":["-y","mcp-remote","https://mcp.prismatic.io/mcp"]}' ``` You may need to run `claude mcp list` once in order for authentication to be successful. ![Connecting Claude Code to Prismatic](/docs/assets/images/claude-code-1beebf59179f3d4e78df4e1c6b674788.png) #### Cursor[​](#cursor "Direct link to Cursor") To add Prismatic's MCP flow server to Cursor, add this JSON to your `mcp.json` configuration file: mcp.json ```json { "mcpServers": { "prismatic": { "type": "stdio", "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.prismatic.io/mcp"], "env": {} } } } ``` Alternatively, [add Prismatic MCP to Cursor](cursor://anysphere.cursor-deeplink/mcp/install?name=prismatic\&config=ewogICJ0eXBlIjogInN0ZGlvIiwKICAiY29tbWFuZCI6ICJucHgiLAogICJhcmdzIjogWyIteSIsICJtY3AtcmVtb3RlIiwgImh0dHBzOi8vbWNwLnByaXNtYXRpYy5pby9tY3AiXQp9) directly. ![Connecting Cursor to Prismatic](/docs/assets/images/cursor-81f8df8805887a500cffc45da5a647c5.png) --- ### Build Integrations #### What are Productized Integrations? As a B2B software company, your customers expect seamless data synchronization between your application and the other systems they use. A **productized integration** is built as a core feature of your product, designed for self-service deployment by multiple customers, rather than as a one-off solution for a specific customer. #### What is an integration?[​](#what-is-an-integration "Direct link to What is an integration?") An **integration** is a collection of logical flows and steps that move data between your app and another app that your customers use. When you build an integration, you can build it in the [low-code designer](https://prismatic.io/docs/integrations/low-code-integration-designer.md) or as a TypeScript project in your preferred IDE. An integration built with code is called a [code-native integration](https://prismatic.io/docs/integrations/code-native.md) (or CNI). ![Low-code or code-native](/docs/assets/images/low-code-or-code-native-3ad05c2f6b5ba1cda9e301d8baa76945.png) An integration starts when its [trigger](https://prismatic.io/docs/integrations/triggers.md) fires. Triggers can either follow a [schedule](https://prismatic.io/docs/integrations/triggers/schedule.md) or can be invoked via a [webhook URL](https://prismatic.io/docs/integrations/triggers/webhook.md). You can use a combination of actions from common [built-in components](https://prismatic.io/docs/components.md) and your own [custom components](https://prismatic.io/docs/custom-connectors.md) to build an integration. #### What does "productized" mean?[​](#what-does-productized-mean "Direct link to What does \"productized\" mean?") When you **productize** an integration, you build it as a repeatable, configurable product feature rather than a custom solution for one customer. This means: * **Configuration-driven design**: Integrations use [config variables](https://prismatic.io/docs/integrations/config-wizard.md) so they can be deployed to multiple customers with different settings * **Self-service deployment**: Customers deploy and configure integrations themselves through an [embedded marketplace](https://prismatic.io/docs/integrations/embedded-marketplace.md) * **Repeatable experience**: Every customer gets a consistent, polished integration experience Some integrations have a single [flow](https://prismatic.io/docs/integrations/low-code-integration-designer/flows.md) (one trigger and one set of steps), while others group multiple related flows together into a single deployable integration. For example, if you have a third-party service that sends data via various webhooks, it makes sense to have a single integration with several flows, each with its own trigger but sharing config variables. #### Why productize integrations?[​](#why-productize-integrations "Direct link to Why productize integrations?") Productized integrations offer several key advantages: * **Customer acquisition**: Prospective customers are more likely to choose your application if it integrates with the other systems they use * **Faster time-to-value**: Integrations reduce onboarding time as customers quickly see their data reflected in your application * **Customer stickiness and retention**: When your app becomes part of customers' workflows through integrations, they're less likely to churn. Self-service deployment makes integrations accessible to all customers, not just those who can afford custom work * **Upsell opportunities**: Depending on your commercial model, you can leverage integrations to increase contract value #### How do customers deploy integrations?[​](#how-do-customers-deploy-integrations "Direct link to How do customers deploy integrations?") When an integration is completed and published, [customers](https://prismatic.io/docs/customers.md) can enable it through the [embedded integration marketplace](https://prismatic.io/docs/integrations/embedded-marketplace.md), or your team members can deploy an [instance](https://prismatic.io/docs/instances.md) of the integration on the customer's behalf. Most commonly, you embed the integration marketplace in your application using Prismatic's embedded SDK. The marketplace requires minimal code and appears native to your app, providing customers with a self-service experience. When customers deploy an integration, they're guided through a custom [configuration wizard](https://prismatic.io/docs/integrations/config-wizard.md) that you design. The configuration wizard collects customer-specific settings (API keys, endpoints, preferences) needed for that integration instance. #### Can I also build custom integrations?[​](#can-i-also-build-custom-integrations "Direct link to Can I also build custom integrations?") Yes! In addition to productized integrations, you can build custom integrations for specific customers with unique use-cases. Custom integrations are built similarly to productized integrations, but are not designed for self-service deployment. Instead, your team deploys custom integrations directly to the customer, or assigns the integration to the customer and allows them to deploy it from their end. #### How do I build integrations?[​](#how-do-i-build-integrations "Direct link to How do I build integrations?") Prismatic offers three ways to build integrations: 1. **[Low-code designer](https://prismatic.io/docs/integrations/low-code-integration-designer.md)**: An intuitive drag-and-drop interface for both developers and technical non-developers. Use pre-built connectors to create workflows that sync data between apps. 2. **[Code-native integrations](https://prismatic.io/docs/integrations/code-native.md)**: Write integrations in TypeScript using Prismatic's SDK, leveraging your preferred IDE and development tools. 3. **[Embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md)**: Embed the low-code designer in your application to allow customers to create their own custom integrations. Not sure which approach is right for your team? See [Low-code vs code-native integrations](https://prismatic.io/docs/integrations/low-code-vs-code-native.md) for a detailed comparison. #### Next steps[​](#next-steps "Direct link to Next steps") We recommend that you follow our low-code [Getting Started](https://prismatic.io/docs/get-started/build-integrations/first-integration-low-code.md) tutorial to familiarize yourself with integration development and deployment. Once you understand the basics, explore these related topics: * [What is the embedded marketplace?](https://prismatic.io/docs/integrations/embedded-marketplace.md) - Learn how customers self-serve integrations * [Low-code vs code-native integrations](https://prismatic.io/docs/integrations/low-code-vs-code-native.md) - Choose the right build method for your team * [Config wizard](https://prismatic.io/docs/integrations/config-wizard.md) - Design the configuration experience for your customers * [Connections](https://prismatic.io/docs/integrations/connections.md) - Manage authentication and credentials --- #### Connections Overview **Connections** contain the information necessary for the steps in your integration to connect to third-party apps and services. A connection is made up of fields for things like usernames, passwords, API keys, OAuth 2.0 secrets, host endpoints, API versions, and more - whatever a component needs to know to connect to an outside service. For example, an [Asana](https://prismatic.io/docs/components/asana.md#apikey) personal access token requires a single API key that a user would fill in. The [Slack](https://prismatic.io/docs/components/slack.md#oauth2) OAuth 2.0 connection includes an authorize URL, token URL, client ID, and client secret, but those inputs are generally hidden from end users - they see a single "Connect" button instead. Connections are often presented to customer users on the first page of your integration's configuration wizard, but you can also set up the connection on your customers' behalf if you know the values the connection requires. ![Acme and Slack connection in Prismatic config wizard](/docs/assets/images/asana-and-slack-c70de4daa1bb44fbf76b047a6620e8e8.png) #### Choosing a connection type[​](#choosing-a-connection-type "Direct link to Choosing a connection type") The type of connection you select depends on who knows the connection information and if your connection will be shared by multiple customers. Generally speaking, you should select a [Customer](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md), [Organization (Customer)](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md), or [Organization (Global)](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-global.md) connection type due to the benefits of centralized management (even if you don't plan to reuse the connection across multiple integrations). [Integration-scoped](https://prismatic.io/docs/integrations/connections/integration-specific.md) connections are required for connections that leverage an [on-prem agent](https://prismatic.io/docs/integrations/connections/on-prem-agent.md) or [user-level config](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md), or when you want to set connections [programmatically](https://prismatic.io/docs/embed/marketplace.md#dynamically-setting-config-variables-in-marketplace) via the embedded SDK when a user configures an instance. [Customer Connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md) [Your customers will enter their own credentials. You might manage an OAuth 2.0 app that powers the connection, but customers will authenticate with their own credentials.](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md) [**Example:** Your customer enters their own API key or authenticates via OAuth 2.0.](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md) [Organization (Customer) Connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md) [You as an organization manage the connection on behalf of your customers, but each customer has distinct credentials.](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md) [**Example:** You assign an API key to each of your customers for connecting to your app, so they don't have to.](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md) [Organization (Global) Connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-global.md) [All of your customers share the same set of credentials.](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-global.md) [**Example:** You have a single API key for Twilio for sending text messages that all of your customers use.](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-global.md) [Integration-Scoped Connections](https://prismatic.io/docs/integrations/connections/integration-specific.md) [Your connection needs a feature the other connection types don't support (like the on-prem agent).](https://prismatic.io/docs/integrations/connections/integration-specific.md) [**Example:** Your integration connects to an on-prem PostgreSQL database.](https://prismatic.io/docs/integrations/connections/integration-specific.md) #### Build-only connections[​](#build-only-connections "Direct link to Build-only connections") Some components offer **build-only connections**, which are used to connect to third-party services during development and testing. Build-only connections are OAuth 2.0 apps that Prismatic creates and manages to allow you to begin developing your integration without needing to set up your own OAuth 2.0 app. Build-only connections are only intended for development and testing, and they can't be used in production or shared with customers. When you're ready to deploy your integration, you'll need to create your own OAuth 2.0 app and connection (or have your customers create their own connections) to replace the build-only connection. Configure build-only connections from the **Components** tab in the left sidebar of the Prismatic UI. --- #### Customer Connections A **Customer Connection** is used when each of your customers have unique credentials to a third-party app, and they need to enter the credentials (or go through an OAuth 2.0 auth code flow) themselves. Your customer will enter usernames/passwords/API keys/etc for the third-party app when they deploy an instance of an integration that uses them. The customer connection is defined once by you, the integration author, and then can be used in one or more integrations. When your customer activates an integration that uses a customer connection, they will be prompted to enter their unique credentials to the third-party app. If they have previously saved credentials for that connection type, they can select from existing saved connections, saving them time and effort. If you would like to provide your customers with a central page where they can manage all of their reusable connections, you can embed a [connections page](https://prismatic.io/docs/embed/additional-screens/show-connections.md) using the embedded SDK. #### Creating a new customer connection[​](#creating-a-new-customer-connection "Direct link to Creating a new customer connection") To create a new customer connection, see [Managing Connections](https://prismatic.io/docs/integrations/connections/managing.md). When prompted to select the connection type, select **Customer's own credentials** and then **My customer will authenticate themselves**. you should create OAuth 2.0 apps Despite this being a "customer connection", you should still create an OAuth 2.0 app in the third-party app for your customers to authenticate with whenever possible. That way, your customers only need to click a **Connect** button and walk through an OAuth 2.0 consent screen to authenticate, rather than needing to copy/paste API keys or client IDs themselves. #### FAQ[​](#faq "Direct link to FAQ") ##### What will customer users see?[​](#what-will-customer-users-see "Direct link to What will customer users see?") When a customer activates a marketplace integration that requires a customer connection: * If they have never saved credentials for that customer connection type, they will be prompted to enter them, as they would for any standard connection * If they have previously saved credentials for that connection type, they can select from existing saved connections or opt to enter a new set of credentials ![Customer view of a customer connection](/docs/assets/images/config-wizard-customer-view-dcac91378cb3cbcd203bc4fa319be246.png) * Within the embedded workflow builder, customers will see a list of their saved connections when they add a step that uses a component with an associated customer connection. ![Customer view of a customer connection in the embedded workflow builder](/docs/assets/images/workflow-builder-customer-view-d7639819a587c12b9739dad2400d845d.png) ##### Can customer connections be used if I have multiple tenants?[​](#can-customer-connections-be-used-if-i-have-multiple-tenants "Direct link to Can customer connections be used if I have multiple tenants?") Yes. If your organization has multiple tenants (for example, a US and EU tenant, or a dev and prod tenant), be sure to assign your customer connections the same **Stable Key** in each tenant. Then, when you [sync integrations](https://prismatic.io/docs/configure-prismatic/integrations-multiple-regions.md) between tenants, your integrations will automatically use the correct customer connection in the new tenant. --- #### Organization (Customer) Connections An **Organization (Customer) Connection** is used when your customers have unique credentials to your app (or another third-party app), but you (the organization) will be the one to enter those credentials on their behalf. For example, suppose you are Acme SaaS. Several of your integrations interact with your API, and each of your customers have their own Acme SaaS API key. *You* know their Acme SaaS API keys. It would feel strange for a customer user who is logged in to your Acme SaaS web app to enter their own Acme SaaS API key into an embedded config wizard. So, you can set each of your customers' Acme SaaS API keys on their behalf once, and the instances that your customer deploys that rely on that key will reference the customer-specific connection. #### Creating a new organization (customer) connection[​](#creating-a-new-organization-customer-connection "Direct link to Creating a new organization (customer) connection") To create a new organization (customer) connection, see [Managing Connections](https://prismatic.io/docs/integrations/connections/managing.md). When prompted to select the connection type, select **Customer's own credentials** and then **I will authenticate on my customer's behalf**. #### Configuring a customer's connection[​](#configuring-a-customers-connection "Direct link to Configuring a customer's connection") Once an organization (customer) connection is created, you can assign connection values for each of your customers. You can do this through the UI or [programmatically](#programmatically-creating-a-customers-connection). Through the UI, open **Customers** and select a customer. Under the customer's **Connections** tab, select **+ Add connection** and then select the organization (customer) connection you just created. ![Create a new customer](/docs/assets/images/create-customer-oac-52393fff0be7f8e738c52a3e3e02ff5e.png) Fill in the fields that your connection requires (API key, username, password, endpoint URL, etc.) with customer-specific values. ![Fill in a new customer](/docs/assets/images/fill-in-customer-oac-6b56f804b9ba84c3c816f1f305a31922.png) The values you fill in here will be used whenever this customer deploys an instance of an integration that relies on your connection. #### Programmatically creating a customer's connection[​](#programmatically-creating-a-customers-connection "Direct link to Programmatically creating a customer's connection") Programmatically setting a customer's connection values is a three-query process using the [Prismatic GraphQL API](https://prismatic.io/docs/api.md): 1. Fetch the organization (customer) connection's ID You can do this by running a `scopedConfigVariables` query: ```graphql query { scopedConfigVariables(stableKey: "acme-api-key") { nodes { id key stableKey connection { inputs { nodes { id key } } } } } } ``` This will yield your organization (customer) connection's ID along with its inputs and their keys. Take note of the ID you get back - it should start with `U2Nvc...`. 2. Fetch your customer's ID. You can do that through a `customers` query. ```graphql query { customers(externalId: "my-external-id") { nodes { id name } } } ``` Take note of your customer's ID (note: not their external ID) - it should start with `Q3Vzd...`. 3. Create a connection for your customer with some values you know. You can do that with a `createCustomerConfigVariable` mutation using the connection and customer ID you noted, along with an array of `inputs`. Inputs should have the shape ```js { value: string, name: string, type: "value" } ``` Each input's `name` is the key of the connection input (e.g. "username", "apiKey", "host", etc.). `value` is the customer's value for that input. ```graphql mutation { createCustomerConfigVariable( input: { scopedConfigVariable: "U2NvcGVkQ29uZmlnVmFyaWFibGU6NjIzZjM2NWItMzc3Ny00MWRkLWEyODAtNTBjNjliMjQyMGQ4" customer: "Q3VzdG9tZXI6NzFlY2NiYzQtYjc5OC00YzQzLWIzZDAtZjdmYzE5OTEyYzlj" inputs: [{ value: "Testing", name: "apiKey", type: "value" }] } ) { customerConfigVariable { id } errors { field messages } } } ``` #### FAQ[​](#faq "Direct link to FAQ") ##### What will customer users see?[​](#what-will-customer-users-see "Direct link to What will customer users see?") Nothing. You as an organization will create the connection for your customer. When a customer configures an instance that relies on that connection, no UI elements will appear in their config wizard. ##### Can organization (customer) connections be used if I have multiple tenants?[​](#can-organization-customer-connections-be-used-if-i-have-multiple-tenants "Direct link to Can organization (customer) connections be used if I have multiple tenants?") Yes. If your organization has multiple tenants (for example, a US and EU tenant, or a dev and prod tenant), be sure to assign your organization (customer) connections the same **Stable Key** in each tenant. Then, when you [sync integrations](https://prismatic.io/docs/configure-prismatic/integrations-multiple-regions.md) between tenants, your integrations will automatically use the correct organization (customer) connection in the new tenant. --- #### Organization (Global) Connections **Organization (Global) connections** are used when your customers' instances reference a third-party account *you own*. Your customers won't see organization global connections when configuring an instance. For example, your organization may have a [Twilio](https://prismatic.io/docs/components/twilio.md) API key that all of your customers' instances will use for SMS. You want your customers to be able to send SMS messages through your organization's Twilio account, but you don't want each customer to have to enter your Twilio API key when configuring their instance. #### Creating a new organization global connection[​](#creating-a-new-organization-global-connection "Direct link to Creating a new organization global connection") To create a new organization global connection, see [Managing Connections](https://prismatic.io/docs/integrations/connections/managing.md). When prompted to select the connection type, select **My organization's credentials**. #### FAQ[​](#faq "Direct link to FAQ") ##### What will customer users see?[​](#what-will-customer-users-see "Direct link to What will customer users see?") Nothing. You as an organization will create the global connection. When a customer configures an instance that relies on that connection, no UI elements will appear in their config wizard. ##### Can organization (global) connections be used if I have multiple tenants?[​](#can-organization-global-connections-be-used-if-i-have-multiple-tenants "Direct link to Can organization (global) connections be used if I have multiple tenants?") Yes. If your organization has multiple tenants (for example, a US and EU tenant, or a dev and prod tenant), be sure to assign your organization (global) connections the same **Stable Key** in each tenant. Then, when you [sync integrations](https://prismatic.io/docs/configure-prismatic/integrations-multiple-regions.md) between tenants, your integrations will automatically use the correct organization (global) connection in the new tenant. --- #### Integration-Scoped Connections **Integration-scoped connections** are the "classic" way of adding connections to an integration. They were first introduced [when we developed connections](https://prismatic.io/blog/simpler-more-flexible-authentication/) but have largely been replaced by other connection types that are centrally managed and shared across integrations. Now, integration-scoped connections are primarily used in three situations: 1. You want to connect to an [on-prem resource](https://prismatic.io/docs/integrations/connections/on-prem-agent.md) 2. The connection must be scoped to a user using [user-level configuration](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md) 3. You want to [programmatically set connection information](https://prismatic.io/docs/embed/marketplace.md#dynamically-setting-config-variables-in-marketplace) through the embedded SDK at instance deploy time #### Integration-scoped connections in low-code integrations[​](#integration-scoped-connections-in-low-code-integrations "Direct link to Integration-scoped connections in low-code integrations") When you add a step to your low-code integration that requires a connection, you'll have the option to add an **Integration-Scoped Connection**. After selecting this connection type, you'll be prompted to select which component you want to create a connection for, along with any input values you want to pre-fill for that connection (like an OAuth client ID, etc.). ![Add an integration-scoped connection in the low-code designer](/docs/assets/images/add-new-connection-9238009ffcbafeb09a057b3805709425.png) #### Integration-scoped connections in code-native integrations[​](#integration-scoped-connections-in-code-native-integrations "Direct link to Integration-scoped connections in code-native integrations") Connections are defined in the `configPages.ts` of your code-native project. See: * [Connections in code-native integrations](https://prismatic.io/docs/integrations/code-native/config-wizard.md#connections-in-code-native-integrations) for inline-defined connections * [Referencing existing connections in code-native](https://prismatic.io/docs/integrations/code-native/existing-components.md#using-existing-connections-in-code-native) for referencing existing components' connections #### Connection templates[​](#connection-templates "Direct link to Connection templates") A **Connection Template** allows you to pre-fill a connection's input fields with default values. A template can be referenced by an integration you build or by an integration your customers build using [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md). Connection templates are useful for a few reasons: 1. If several of your integrations require the same connection input fields (for example, an OAuth 2.0 client ID and secret), you can create a template that contains those fields and reference it in each integration. 2. If you want to provide a template for your customers to use in their own integrations, you can create a template and share it with them. They can then reference the template in their own integrations but will not be able to view or change the template's input fields (so they can't access your client secret). 3. If you store your integrations' [YAML definitions](https://prismatic.io/docs/configure-prismatic/integrations-multiple-regions.md#exporting-an-integrations-yaml-definition) in source control, your YAML will be cleaner if you reference a template instead of including the connection's input fields directly in the YAML. By referencing a template that contains a client ID and client secret, you can avoid committing sensitive information to source control. connection templates vs customer connections A [customer connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md) provides the same benefits as a connection template, but with the added benefit of being reusable across integrations. ##### Creating a connection template[​](#creating-a-connection-template "Direct link to Creating a connection template") To create a connection template, select **Components** and then search for your desired component. Then, open the **Connections** tab and select **+ Add Connection**. Select **Create a Connection Template**. ![Add connection template in Prismatic app](/docs/assets/images/add-connection-template-84a370e8f0eae5b7fdfed499de574c44.png) Select the connector to add a connection template for, and then select **Connection Template** for the **Connection Type**. Give your template a name, and then add input fields that you would like to pre-fill to the template. Fields that you omit will be configurable by an integration builder (either an organization member or a customer using embedded designer). ![Add connection template inputs in Prismatic app](/docs/assets/images/connection-template-inputs-b89a3b50285da00249b0ca4371448563.png) Updating a connection template's input values A connection template's input values can be updated until a version of an integration that uses the template has been published. If an integration that uses the template has been published, the template's input values cannot be updated. To update a connection template's input values, create a new connection template with your updated values and update your integration to reference the new connection template. Your deployed instances (perhaps on `v10` of your integration) will continue to reference the old connection template, but new instances (perhaps on `v11` of your integration) will reference the new connection template and its values. ##### Referencing a connection template in a low-code integration[​](#referencing-a-connection-template-in-a-low-code-integration "Direct link to Referencing a connection template in a low-code integration") To reference a connection template in an integration, add a new connection to the integration. Then, select the template you created from the **Connection Template** dropdown menu. You will now only see fields that were not included in the template. ![Reference connection template in Prismatic app](/docs/assets/images/connection-template-reference-8157b84af50099e003b02fa888b1cea3.png) ##### Referencing a connection template in a code-native integration[​](#referencing-a-connection-template-in-a-code-native-integration "Direct link to Referencing a connection template in a code-native integration") Connection templates can be used if you [reference an existing component's connection](https://prismatic.io/docs/integrations/code-native/existing-components.md#using-existing-connections-in-code-native) in your code-native integration. You can do this by adding a `template` property to your connection reference. Reference connection template in code-native connection ```ts connectionConfigVar({ stableKey: "my-salesforce-connection", dataType: "connection", connection: { component: "salesforce", key: "oauth2", values: {}, template: "My Salesforce Connection", }, }); ``` --- #### Managing Connections Whether you're creating a [customer connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md), [organization (customer) connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md), or [organization (global) connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-global.md), you can manage your connections in one of two places: 1. From a component's connections page. Click **Components** on the left sidebar and then search for the component whose connections you want to manage. You can alter an existing connection or create a new one by clicking **+ Add Connection**. 2. From the low-code integration designer when you add new steps to your integration. #### Adding a new connection for a component[​](#adding-a-new-connection-for-a-component "Direct link to Adding a new connection for a component") After clicking **Components** on the left sidebar, search for the component whose connections you want to manage and click on it. From that component's **Connections** tab, select **+ Add Connection** to create a new connection for that component. You will be asked to select the connection type based on **whose credentials** the connection uses - your customer's, or your organization's? ![Whose credentials ](/docs/assets/images/whose-credentials-32d491adba925d47e25b005b50028cad.png) If you select **Customer-Specific Credentials**, and the connection is not an OAuth 2.0 connection, you will next be asked who supplies those credentials. Your customer might supply them themselves (for example, by clicking an OAuth 2.0 button or pasting in their own API key), or you might supply them on the customer's behalf (for example, by generating an API key for your customer in your own app for seamless authentication). ![Who completes authentication ](/docs/assets/images/who-completes-auth-3cfce9fcb9ce31d2ceb38bb59a443e8d.png) On the other hand, if you select **Your Organization's Credentials**, you're creating an [organization (global) connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-global.md) that can be used across all of your customers. You'll be prompted for your organization's connection information. Regardless of [connection type](https://prismatic.io/docs/integrations/connections.md#choosing-a-connection-type), you will be prompted to select the type of connection you want to create (if the component has multiple), and any global values you would like all of your customers to inherit. Typically, OAuth 2.0 connections contain mostly global values for authorize URL, client ID, scopes, etc., while API key connections contain mostly customer-specific values for the API key itself. ![Create connection ](/docs/assets/images/create-connection-8ba4aa4103df58f82aca805d467e22c7.png) ##### Syncing connections between tenants[​](#syncing-connections-between-tenants "Direct link to Syncing connections between tenants") If your organization has multiple tenants (for example, a US and EU tenant, or a dev and prod tenant), be sure to assign your connections the same **Stable Key** in each tenant. Then, when you [sync integrations](https://prismatic.io/docs/configure-prismatic/integrations-multiple-regions.md) between tenants, your integrations will automatically use the correct connections in the new tenant. #### Adding a new connection from the low-code integration designer[​](#adding-a-new-connection-from-the-low-code-integration-designer "Direct link to Adding a new connection from the low-code integration designer") When you add a step to your integration that requires a connection, if one for that component already exists, you'll be prompted to select from existing connections. ![Select existing connection ](/docs/assets/images/use-existing-in-designer-6a20fae01b3f407f17152cb2e469293d.png) If no connections already exist, you will be prompted to create a new connection of a type of your choosing, similar to adding a new connection on the component's **Connections** tab. ![Create new connection in designer ](/docs/assets/images/create-new-in-designer-17420d71656c41ac061193898c82294f.png) If you would like to switch which connection you're using for a particular step, you can click the connection in the step editor and select a different connection, or create a new one. ![Switch connection in designer ](/docs/assets/images/switch-connection-in-designer-172dce847e5dc2503f05dc7e4e19dc44.png) If you would like to switch **all steps** using the same connection to a different connection, open the config wizard designer (settings icon in the toolbar at the top of the designer) and edit your integration's connection to use a different one. #### Configuring a test connection for building integrations[​](#configuring-a-test-connection-for-building-integrations "Direct link to Configuring a test connection for building integrations") After creating a new connection, you'll be prompted to supply test credentials for that connection. These test credentials will be used in the integration designer as you're building integrations that rely on the connection. If you'd like to change your test credentials at any time, you can return to the **Connections** tab of a component, select your connection, and edit your credentials under **Test Runner Connection**. Your test instances that run as you test your integration in the integration designer will immediately begin using the new test credentials that you supply. #### Enabling a connection for the embedded workflow builder[​](#enabling-a-connection-for-the-embedded-workflow-builder "Direct link to Enabling a connection for the embedded workflow builder") If you embed the [workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) in your app for your customers to build their own workflows, you can choose which connections to expose to them. Open the connection you want to make available, and toggle on **Use for embedded workflows**. ![Use for embedded workflows ](/docs/assets/images/use-for-embedded-workflows-fb04b18b03682ce226ace3b755529bac.png) --- #### What is OAuth 2.0? You've probably come across OAuth 2.0 at some point - any time you click "Log in with my Google Account" or "Connect my Dropbox" on a website, that website leverages OAuth 2.0 to fetch information (your email address, files, etc.) on your behalf. You don't enter your Google or Dropbox credentials into the website. Instead, you enter your credentials on a Google, Dropbox, etc. page, and the OAuth provider generates a unique code that grants the website a set of your permissions. [OAuth 2.0](https://oauth.net/2/) is a special type of connection that is ubiquitous in integration development. OAuth 2.0 allows your customers to authorize your integration to perform certain functions on their behalf without needing to give you their username or password. For example, customers can authorize your integration to fetch their Salesforce leads, create Slack channels, or generate Quickbooks invoices for them. With Prismatic, you can offer your customers a single "Connect to Acme" button in your integrations' [config wizard](https://prismatic.io/docs/integrations/config-wizard.md), and your customers can seamlessly grant you permission to their accounts in other platforms. Prismatic's OAuth 2.0 service takes care of generating authentication URLs, handling token exchange and token refresh, and ensuring that up-to-date keys are available to your instances when they run. ![Configure OAuth 2.0 connection via Prismatic app](/docs/assets/images/connect-oauth-app-0126f9c77ef544aa437087d16c0e68da.webp) #### Why use OAuth 2.0?[​](#why-use-oauth-20 "Direct link to Why use OAuth 2.0?") OAuth 2.0 has some advantages over other authentication mechanisms (like basic auth): 1. OAuth 2.0 provides your users with a seamless authentication experience. They only need to click a "connect" button and then select "I approve" on a permissions consent screen, and the OAuth service takes care of the rest. 2. Permissions are granular. Your customers can grant you permission to do specific tasks in their account, like "read Salesforce leads" or "write Slack messages". This gives customers peace of mind. 3. Customers don't need to hand you their credentials. They don't enter a username and password for a third-party in your app. Instead, they authenticate with the third-party app, and your app is handed an access token with granular permission to do specific tasks. It's fine if they change their third-party password; they don't need to log in to your app and change their integration configuration, too. 4. Your customers can generally revoke tokens at any time. Most apps have a screen that displays what apps have access to their account, where they can see things like *Acme has read access to your Dropbox files*. Those screens generally have a "revoke access" button. #### OAuth 2.0 grant types[​](#oauth-20-grant-types "Direct link to OAuth 2.0 grant types") The [OAuth 2.0 framework](https://oauth.net/2/) supports several **grant types**, three of which are common for B2B integrations: 1. Most common is the [Authorization Code grant type](https://prismatic.io/docs/integrations/connections/oauth2/authorization-code-grant-type.md). When one of your customers configures an integration, they click a "Connect to Dropbox" or "Connect my Salesforce" button. After logging in to the external app and consenting to give you permissions to their account, the user returns to a Prismatic callback URL, and the **authorization code** they brought back is exchanged for an access token that you can use to access their data. 2. The [Client Credentials grant type](https://prismatic.io/docs/integrations/connections/oauth2/client-credentials-grant-type.md) is also common in integrations. Sometimes called the **machine to machine** (M2M) grant type, this process is a little more involved for your customer. They log in to their third-party app, generate a **Client ID** / **Client Secret** key pair, and enter their key into your integration's config wizard. That key pair is exchanged for an access token for the third-party app. 3. While officially [deprecated](https://oauth.net/2/grant-types/password/), the [Password grant type](https://prismatic.io/docs/integrations/connections/oauth2/password-grant-type.md) prompts a user for their username and password for a third-party app. That username and password are exchanged for an access token. --- #### OAuth 2.0 Authorization Code Grant Type #### Authorization code grant type overview[​](#authorization-code-grant-type-overview "Direct link to Authorization code grant type overview") The OAuth 2.0 **Authorization Code** grant type is something you've probably used before. Any time you've clicked "Log in with Google" or "Connect my Outlook Calendar", the application asking for your Google account information or for access to your Outlook calendar uses the authorization code flow to fetch an API key that they can use to interact with Google or Microsoft on your behalf. **Additional resources**: #### How does the authorization code grant type work?[​](#how-does-the-authorization-code-grant-type-work "Direct link to How does the authorization code grant type work?") At a high level, the OAuth 2.0 authorization code flow works like this: 1. You as a software vendor register with the third-party application. You tell them your application's name, a description, and a callback URL. They give you a **client ID** and **client secret** that are unique to you. 2. You send customers to the third-party application's OAuth 2.0 **authorize endpoint**. You include your **client ID**, a **redirect URL**, and an optional list of [scopes](https://prismatic.io/docs/integrations/connections/oauth2/authorization-code-grant-type.md#authorization-code-grant-type-scopes) (permissions) that you want to request from your user as a set of search parameters. For example, you might ask for `files.content.read` from Dropbox, so you can read your customer's Dropbox files. 3. The customer authenticates with the third-party application and then interacts with a **consent screen**. This is the page you've likely seen before that says something like *Acme corp would like to read files in your Dropbox folder. Are you okay with that?* Once they consent, the user is directed back to your application with a unique authentication `code`. 4. Your application exchanges the `code` using the third-party application's **token endpoint**, along with your **client ID** and **client secret**. The third-party application verifies that the code is valid, and if so, responds with an **access token** and an optional **refresh token**. 5. You periodically exchange the refresh token for a new access token and use the access token to make API calls on behalf of the customer. ![Infographic describing the OAuth 2.0 auth code flow](/docs/assets/images/how-oauth2-works-7b65ba6d27a4ce6b7826b45412d19dca.png) #### Creating an OAuth 2.0 "app" in a third-party service[​](#creating-an-oauth-20-app-in-a-third-party-service "Direct link to Creating an OAuth 2.0 \"app\" in a third-party service") To use the authorization code grant type in your integration, you will need to work with the third-party service to create an "OAuth 2.0 Application". Most common SaaS platforms have documentation on how to create an OAuth application, and we link to that documentation on our component documentation pages. It's usually found in an "API Access" section of a settings page or on a page similarly named. registering with a third-party can take time Most third-party services allow you to create an unverified OAuth application for testing purposes but require you to go through a verification process before you can use the application in production. The verification process can take days or weeks, so it's best to start the process early. The third-party may require you to provide a privacy policy, terms of service, and other information about your use case and may require a partnership agreement. ##### Authorization code callback URL[​](#authorization-code-callback-url "Direct link to Authorization code callback URL") When you configure your OAuth application, you'll likely need to set up an authorized **callback URL**. That's the URL to which users return with a special `code` after granting you permission to their account. Your callback URL depends on the region where your tenant resides and whether or not you use a [custom domain](https://prismatic.io/docs/configure-prismatic/custom-domains.md): | Region | Callback URL | | ----------------------- | ----------------------------------------------------- | | US Commercial (default) | `https://oauth2.prismatic.io/callback` | | US GovCloud | `https://oauth2.us-gov-west-1.prismatic.io/callback` | | Europe (Ireland) | `https://oauth2.eu-west-1.prismatic.io/callback` | | Europe (London) | `https://oauth2.eu-west-2.prismatic.io/callback` | | Canada (Central) | `https://oauth2.ca-central-1.prismatic.io/callback` | | Australia (Sydney) | `https://oauth2.ap-southeast-2.prismatic.io/callback` | | Africa (Cape Town) | `https://oauth2.af-south-1.prismatic.io/callback` | | Custom Domain | `https://oauth2./callback` | ![Set up callback URL for OAuth in Dropbox app console](/docs/assets/images/dropbox-configure-app-53987fd42271d84c7e5ae53f48f61fbd.png) ##### Authorization code client ID and secret[​](#authorization-code-client-id-and-secret "Direct link to Authorization code client ID and secret") The third-party application will supply you with a **client ID** and **client secret**. These are sometimes called "App ID", "App Key", or something similar. Take note of these - you'll use them to configure your Prismatic integration's connection. ##### Configuring an authorization code consent screen[​](#configuring-an-authorization-code-consent-screen "Direct link to Configuring an authorization code consent screen") Depending on what application you're integrating with, they likely let you specify your application's name, your company's icon, a link to a privacy policy, and more. Be sure to enter *your* application's name - not "Prismatic". This is the page that users will see after clicking the "connect" button in your integration's config wizard. The page will say something like > Acme corp would like access to view and create leads in your Salesforce account. Are you okay with that? ##### Authorization code grant type scopes[​](#authorization-code-grant-type-scopes "Direct link to Authorization code grant type scopes") A **scope** is a specific permission that you would like to request from your customer. For example, you might request `file.contents.write` permission for your customer's Dropbox account, so you can write files to their Dropbox, or you might request the `channels:read` permission from your customer's Slack account so you can get a list of public channels they have access to. Some applications, like Dropbox and Salesforce, have you identify which permissions you need when you create your application. Others have you specify scopes as a URL search parameter when you send your customers to their **authorization URL**. ###### The offline\_access scope[​](#the-offline_access-scope "Direct link to The offline_access scope") Many applications offer a scope called `offline_access`. Granting this permission signifies that you want long-term access and will need a refresh token so you can continually refresh the access token you have. #### Adding an OAuth 2.0 authorization code connection to an integration[​](#adding-an-oauth-20-authorization-code-connection-to-an-integration "Direct link to Adding an OAuth 2.0 authorization code connection to an integration") Once you have an OAuth 2.0 Application configured in a third-party service, create a reusable [customer connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md) for the component you want to use. Copy the client ID, client secret, scopes, and other relevant information from the third-party application and paste it into your new customer connection. #### Configuring an authorization code connection[​](#configuring-an-authorization-code-connection "Direct link to Configuring an authorization code connection") If you've created an integration with an OAuth 2.0 connection, customers will see a **Connect** button when they enable the integration. When they click the **Connect** button, they will be brought to the third-party application's OAuth service and will be prompted to verify that they want to grant permissions to your integration. Once they are done, they'll see an "Authorization completed successfully" page, which they can close to return to the instance configuration screen. ![Configure OAuth 2.0 connection via Prismatic app](/docs/assets/images/connect-oauth-app-0126f9c77ef544aa437087d16c0e68da.webp) To change an OAuth connection (for example, if you logged in as the incorrect person when you clicked **Connect**), you can click **Disconnect** and then **Connect** again to reauthenticate against the OAuth provider. If any problems occur during the OAuth flow (incorrect Auth or Token URL, incorrect scopes, etc.), you can view related connection logs by clicking the button to the right of the connection config variable. The connection will be marked with a green if the connection has been used successfully in an execution of the instance, yellow if it has been configured (but not used), and red if the component using the connection threw a connection-related error. #### Disconnecting an authorization code connection[​](#disconnecting-an-authorization-code-connection "Direct link to Disconnecting an authorization code connection") When you **disconnect** an OAuth 2.0 connection, two things (and one optional thing) happen: 1. Prismatic stops periodically refreshing the access token 2. Prismatic deletes the access and refresh tokens from its database, so steps can't reference it 3. \[Optional] Some OAuth 2.0 providers allow you to *revoke* a token. [Quickbooks](https://prismatic.io/docs/components/quickbooks.md) is a prominent example of an API that supports revocation. If a *revocation endpoint* is present in a component's connection, Prismatic reaches out to that endpoint to revoke the token with the third-party's API. To disconnect an active OAuth 2.0 connection, click the **Disconnect** button under the config variable name: ![Disconnect active OAuth 2.0 connection in Prismatic app](/docs/assets/images/disconnect-8d67a9fc97a09e6c669b1963802fb9e2.png) #### Authorization code connections in custom components[​](#authorization-code-connections-in-custom-components "Direct link to Authorization code connections in custom components") If you would like to build a custom component that implements an OAuth 2.0 auth code connection, see the example code on the custom connectors [connections](https://prismatic.io/docs/custom-connectors/connections.md#writing-oauth-20-connections) article. --- #### OAuth 2.0 Client Credentials Grant Type The client credentials grant type, also known as machine-to-machine (M2M) authentication, enables server-to-server communication without user interaction. This flow is commonly used when your integration needs to access a third-party API on behalf of your application rather than an individual user. #### Client credentials grant type overview[​](#client-credentials-grant-type-overview "Direct link to Client credentials grant type overview") The OAuth 2.0 **Client Credentials** grant type is sometimes called the Machine-to-Machine (M2M) grant type and allows your application to communicate with a third-party directly. The **Client Credentials** flow is different from the [Authorization Code](https://prismatic.io/docs/integrations/connections/oauth2/authorization-code-grant-type.md) flow in a couple of key ways: 1. Customers do not work through a consent screen. Rather, customers generate their own client ID / secret key pair and explicitly grant permissions to the key pair they generate. 2. Key pairs are generally not associated with a specific user. Instead, the key pairs have permissions to access certain resources in their account. 3. This flow generally does not require an approval process from the third-party app, since you don't create an OAuth 2.0 app. Instead, your customer logs in to their account to create the key pair that you will use. **Additional resources**: #### How does the client credentials grant type work?[​](#how-does-the-client-credentials-grant-type-work "Direct link to How does the client credentials grant type work?") At a high level, the OAuth 2.0 client credentials flow works like this: 1. You ask your customer to log in to their third-party app account and generate a **Client ID** / **Client Secret** key pair. You ask them to grant that key pair a certain set of permissions. 2. Your customers enter their key pair in your integration's config wizard. 3. Your app exchanges the key pair using the third-party app's **token URL** for an access token that you can use to interact with your customer's third-party account. The Prismatic OAuth service takes care of the token exchange for you. #### Configuring a client credentials connection[​](#configuring-a-client-credentials-connection "Direct link to Configuring a client credentials connection") When your customer walks through your configuration wizard, they will be prompted to enter their **Client ID** and **Client Secret**. Clicking **Connect** will cause Prismatic's OAuth 2.0 service to exchange their key pair with the third-party API for an access token that your integration will then begin to use. ![Client credentials input visibility](/docs/assets/images/client-credentials-config-wizard-452320d340b6300b85c89647e92188c8.png) After clicking **Connect**, the user will either see an "Authorization Complete" or an "Authorization Failed" screen, depending on whether their connection was successful or not. If you'd like this screen to close immediately, see [these documentation files](https://prismatic.io/docs/integrations/connections/oauth2/custom-redirects.md#closing-oauth-20-success-pages-immediately). --- #### Custom OAuth 2.0 Redirects By default, users are redirected to a generic authorization completion page after completing an OAuth 2.0 flow. You can customize these redirect destinations to send users to specific pages in your application after successful or failed authorization attempts. #### Configuring custom OAuth 2.0 redirects[​](#configuring-custom-oauth-20-redirects "Direct link to Configuring custom OAuth 2.0 redirects") Normally, a customer user who completes an OAuth 2.0 flow finds themselves on an "Authorization Complete" screen - . If you would like to customize where a customer is redirected after a successful or failed OAuth 2.0 flow, toggle the **Custom OAuth Redirects** option on the connection and enter URLs for **OAuth Success Redirect URI** and **OAuth Failure Redirect URI**. ![Custom oauth redirect configuration](/docs/assets/images/custom-oauth-redirect-config-1444562ce8f9cc5007d53beffe35a96f.png) Your user will be redirected to those URLs with URL search parameters representing: * The instance's `instanceId` and `instanceName` * The integration's `integrationId` and `integrationName` * The required config variable's `requiredConfigVariableId` and `requiredConfigVariableKey` * The connection's `id`. For instance-level connections, this will be the instance's config variable ID. For [user-level](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md) connections, this will be the user-level config variable ID. ![Custom oauth redirect result](/docs/assets/images/custom-oauth-redirect-result-1e7bae980057a166d5ed8184cccc1590.png) #### Closing OAuth 2.0 success pages immediately[​](#closing-oauth-20-success-pages-immediately "Direct link to Closing OAuth 2.0 success pages immediately") If you'd like to omit the "connection successful" page altogether, create a publicly-available HTML page that immediately runs a JavaScript `parent.close()` function, like this: ```html

Success!

You have successfully authorized the application to access your account.

You can now close this window

``` After arriving at Prismatic's OAuth 2.0 callback URL, the user will be redirected to your HTML page that immediately closes their tab. That should leave them on your integration's config wizard, ready to complete the rest of the integration configuration. --- #### OAuth 2.0 Password Grant Type The OAuth 2.0 password grant type is a legacy authentication method that exchanges a username and password directly for an access token. While less common than authorization code flows, some legacy systems still require this approach. #### Password grant type overview[​](#password-grant-type-overview "Direct link to Password grant type overview") The OAuth 2.0 **Password** grant type is a legacy way to exchange a user's username and password for an access token. This grant type is generally not recommended, since it requires a user to enter their credentials to a third-party app within your app. **Additional resources**: #### How does the password grant type work?[​](#how-does-the-password-grant-type-work "Direct link to How does the password grant type work?") At a high level, the OAuth 2.0 password flow works like this: 1. As a software vendor, you ask your users for their username and password for a third-party app. 2. You exchange their credentials for an access token using the third-party's **token URL**. 3. You use the access token to access third-party resources the user has access to. #### Implementing OAuth 2.0 password grant type in custom components[​](#implementing-oauth-20-password-grant-type-in-custom-components "Direct link to Implementing OAuth 2.0 password grant type in custom components") The password grant type is [deprecated](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-29#section-2.4), and Prismatic's OAuth 2.0 service does not automatically exchange usernames and passwords for access tokens. That logic will need to be implemented within your custom component. Whenever an action that calls the third-party app is run, it will need to exchange the username and password for an access token and then initialize an HTTP client that has that access token. This example HTTP client code handles the password exchange and returns an authenticated HTTP client: ```typescript import { Connection, util } from "@prismatic-io/spectral"; import { createClient } from "@prismatic-io/spectral/dist/clients/http"; export const createAcmeClient = async (connection: Connection) => { // Extract necessary fields from connection definition const { tokenUrl, username, password, client_id, client_secret } = connection.fields; // Password grant often requires a client ID / secret base64-encoded as an authorization header const authHeader = Buffer.from(`${client_id}:${client_secret}`).toString( "base64", ); // Create an HTTP client to make a token exchange request const authClient = createClient({ baseUrl: "https://auth.acme.com", headers: { Authorization: `Basic ${authHeader}`, }, }); // Exchange username/password for an access token const { data: authResponseData } = await authClient.post("/oauth/token", { grant_type: "password", username, password, }); const { access_token } = authResponseData; // Return an authenticated HTTP client return createClient({ baseUrl: "https://api.acme.com", headers: { Authorization: `Bearer ${access_token}`, }, }); }; ``` --- #### Troubleshooting OAuth 2.0 Connections This page focuses on troubleshooting OAuth 2.0 [authorization code](https://prismatic.io/docs/integrations/connections/oauth2/authorization-code-grant-type.md) connections, but similar debugging concepts can be applied to [client credentials](https://prismatic.io/docs/integrations/connections/oauth2/client-credentials-grant-type.md) connections. Note that every application implements OAuth 2.0 slightly differently, but this provides general recommendations for debugging OAuth 2.0 connections. #### Troubleshooting authorization endpoints[​](#troubleshooting-authorization-endpoints "Direct link to Troubleshooting authorization endpoints") When a customer user clicks **Connect**, they are brought to a third-party app's **Authorization URL**. Your client ID, the config variable's ID, permission scopes and the redirect URI is appended as search parameters to the Authorize URL. For example, if the external application's authorize URL is `https://auth.example.com/authorize`, and your client ID is `abc-123`, your user will be directed to ```text https://auth.example.com/authorize?client_id=abc-123&redirect_uri=https%3A%2F%2Foauth2.prismatic.io%2Fcallback&scope=widget%3Aread+widget%3Awrite&state=SW5example ``` Here, `state` represents the config variable's ID in Prismatic and is used when the user returns to our callback URL to determine which config variable to update. ##### Invalid client\_id errors[​](#invalid-client_id-errors "Direct link to Invalid client_id errors") If your users arrive at an authorization page that says "Invalid client\_id parameter" (or a similar error), you should double-check your client ID. Verify that there are no leading or trailing whitespace characters and that the client ID matches the client ID that you saw when you configured your application in the third-party system. ##### Incorrect redirect\_uri errors[​](#incorrect-redirect_uri-errors "Direct link to Incorrect redirect_uri errors") If your customers see an authorization page that says "redirect\_uri mismatch" (or a similar error), you should verify that the callback URL you configured in the third-party system is correct. For the US region, the callback URL is `https://oauth2.prismatic.io/callback`. For other public regions, private cloud hosted options, or white-labeled callback URLs, see [Authorization code callback URL](https://prismatic.io/docs/integrations/connections/oauth2/authorization-code-grant-type.md#authorization-code-callback-url). #### Troubleshooting code token exchange[​](#troubleshooting-code-token-exchange "Direct link to Troubleshooting code token exchange") After authenticating with a third-party app and walking through the app's consent screen, a user will return to Prismatic's OAuth 2.0 callback URL with their **authorization code** in hand. Depending on whether you white-label the callback URL or if you're hosted in a different region, they'll end up on a URL that looks similar to: ```text https://oauth2.prismatic.io/callback?code=some-unique-auth-code&state=SW5example ``` The Prismatic OAuth 2.0 service then loads the config variable from the `state` parameter to match a config variable's ID and attempts to exchange the `code` for an `access_token` using the third-party app's **token URL**. ##### Flavors of auth code token exchange[​](#flavors-of-auth-code-token-exchange "Direct link to Flavors of auth code token exchange") Different apps implement auth code exchange in different ways. Some apps expect you to pass your client ID and secret as a base64-encoded auth header. Others expect that they're passed in a body. Some apps expect that your body is formdata-encoded, while others expect JSON. The Prismatic OAuth 2.0 service attempts each of the six common "flavors" of auth code token exchange, in order of popularity, and succeeds once one has succeeded, and fails if none succeed. Suppose your client ID is `my-client-id`, your client secret is `my-client-secret`, and the `code` your customer returned with is `some-unique-auth-code`. Prismatic would attempt these token exchanges: 1. Client ID / secret are URL-encoded and then base64-encoded and sent as an auth header. Body is formdata-encoded ```text curl -X POST \ --header 'Authorization: Basic bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ=' \ --header 'Content-Type: application/x-www-form-urlencoded' \ https://app.example.com/oauth2/token \ --data 'grant_type=authorization_code&scope=widgets%3Aread%20widgets%3Awrite%20offline_access&redirect_uri=https%3A%2F%2Foauth2.prismatic.io%2Fcallback&code=some-unique-auth-code' ``` 2. Client ID / secret are URL-encoded and then base64-encoded and sent as an auth header. Body is JSON-encoded ```text curl -X POST \ --header 'Authorization: Basic bXktY2xpZW50LWlkOm15LWNsaWVudC1zZWNyZXQ=' \ --header 'Content-Type: application/json' \ https://app.example.com/oauth2/token \ --data '{"code":"some-unique-auth-code","grant_type":"authorization_code","redirect_uri":"https://oauth2.prismatic.io/callback","scope":"widgets:read widgets:write offline_access"}' ``` 3. Client ID / secret are sent within the body. Body is formdata-encoded ```text curl -X POST \ --header 'Content-Type: application/x-www-form-urlencoded' \ https://app.example.com/oauth2/token \ --data 'grant_type=authorization_code&scope=widgets%3Aread%20widgets%3Awrite%20offline_access&redirect_uri=https%3A%2F%2Foauth2.prismatic.io%2Fcallback&code=some-unique-auth-code&client_id=my-client-id&client_secret=my-client-secret' ``` 4. Client ID / secret are sent within the body. Body is JSON-encoded ```text curl -X POST \ --header 'Content-Type: application/json' \ https://app.example.com/oauth2/token \ --data '{"client_id":"my-client-id","client_secret":"my-client-secret","code":"some-unique-auth-code","grant_type":"authorization_code","redirect_uri":"https://oauth2.prismatic.io/callback","scope":"widgets:read widgets:write offline_access"}' ``` 5. Client ID / secret are just base64-encoded and sent as an auth header. Body is formdata-encoded. This will be the same as flavor #1, but characters (like whitespace) are not URL-encoded before base64-encoding. 6. Client ID / secret are just base64-encoded and sent as an auth header. Body is JSON-encoded. This will be the same as flavor #2, but characters (like whitespace) are not URL-encoded before base64-encoding. ##### Mocking a token exchange endpoint[​](#mocking-a-token-exchange-endpoint "Direct link to Mocking a token exchange endpoint") If you'd like to see exactly what the Prismatic OAuth 2.0 service attempts to send a token endpoint, you can spin up a Docker container locally to simulate a token endpoint. Run a [Smocker](https://github.com/smocker-dev/smocker) container: ```bash docker run -d --restart=always \ -p 8080:8080 \ -p 8081:8081 \ --name smocker \ thiht/smocker ``` Then, declare a token "smock" endpoint: ```text curl -XPOST \ localhost:8081/mocks \ --header "Content-Type: application/x-yaml" \ --data \ ' - request: method: POST path: /oauth2/token response: status: 200 headers: Content-Type: application/json body: > { "access_token": "my-access-token", "token_type": "bearer", "expires_in": 60, "example_parameter": "example_value", "refresh_token": "my-refresh-token" } ' ``` This endpoint will accept any request and return a fake access token response. Next, expose your Docker container with [ngrok](https://ngrok.com/): ```bash ngrok http 8080 ``` From there, you can take note of your `ngrok` endpoint and set your **Token URL** in your connection to something like `https://31ce-123-123-123-123.ngrok-free.app/oauth/token`. By visiting `http://localhost:8081`, you'll be able to view each request Prismatic's OAuth 2.0 service made. If you change the `status` in the smock to `status: 500`, you'll see all six token exchange requests that Prismatic's OAuth 2.0 token exchange service made. ![Mocking OAuth 2.0 token endpoint with Smocker](/docs/assets/images/mocking-token-endpoint-smocker-5d1ef3cd519c917b032a72c729b11e9d.png) ##### Verify token exchange works with Postman[​](#verify-token-exchange-works-with-postman "Direct link to Verify token exchange works with Postman") Once you've captured a token request, try to send that equivalent request to your third-party app either with `curl` or [Postman](https://postman.com/). If you get a 404, you may have the wrong token URL configured. You may also get a more descriptive response that can help you identify changes you need to make to scopes, etc. #### Token refresh errors[​](#token-refresh-errors "Direct link to Token refresh errors") If a token exchange works initially, but later the token fails to refresh, it's possible that you omitted an [offline\_access](https://prismatic.io/docs/integrations/connections/oauth2/authorization-code-grant-type.md#the-offline_access-scope) scope, which many apps use to indicate that you need long-term access. --- #### On-Prem Agent The **On-Prem Agent** lets you connect your instances to resources that are not accessible from the public internet. This is useful when you or your customers have databases, file storage systems, or other services that reside on a private network behind a firewall. Feature Availability The on-prem feature is available to customers on specific pricing plans. Refer to your pricing plan or contract, or contact the Prismatic support team to learn more. #### How the on-prem agent works[​](#how-the-on-prem-agent-works "Direct link to How the on-prem agent works") The on-prem agent is a lightweight [Docker container](https://hub.docker.com/r/prismaticio/on-prem-agent) that you or your customer can install on your own infrastructure. When the Docker container is started, it establishes a secure [mutual TLS](https://en.wikipedia.org/wiki/Mutual_authentication) (mTLS) connection to an on-prem service running within the Prismatic platform and thereafter maintains a persistent connection with Prismatic. When an instance of your integration is deployed, your customer can select the OPA as the connection method. When an on-prem connection is used in the instance, the instance communicates with the OPA on the private network using the established connection, which in turn communicates with your resource on the private network. Data sent from the instance to the OPA through the on-prem service is encrypted using mTLS, and data is transmitted on [OSI Layer 4](https://www.cloudflare.com/learning/ddos/glossary/open-systems-interconnection-model-osi/) (transport layer). This allows you to send both HTTP and non-HTTP traffic through the OPA. No inbound ports need to be opened Note that the on-prem agent initiates the connection to the Prismatic platform, so you do not need to open any inbound ports on your firewall. The on-prem agent only needs to be able to make *outbound* connections to the Prismatic platform on ports 22 and 443: * The agent will connect on **port 22** to `onprem.prismatic.io` (or `onprem.` for other regions or white-label domains) to create a persistent connection. For example, `onprem.eu-west-1.prismatic.io` for the Europe (Ireland) region, or `onprem.integrations.example.com` for a white-labeled domain. * The agent will also connect on **port 443** to `app.prismatic.io` (or your region or white-labeled domain) for authentication and configuration data. #### Connectors with on-prem support[​](#connectors-with-on-prem-support "Direct link to Connectors with on-prem support") The following built-in connectors support on-prem connections: * [FTP](https://prismatic.io/docs/components/ftp.md) * [HTTP](https://prismatic.io/docs/components/http.md) * [IMAP](https://prismatic.io/docs/components/imap.md) * [Active Directory](https://prismatic.io/docs/components/ldap.md) * [Microsoft SQL Server](https://prismatic.io/docs/components/ms-sql-server.md) * [MySQL](https://prismatic.io/docs/components/mysql.md) * [Oracle Database](https://prismatic.io/docs/components/oracledb.md) * [PostgreSQL](https://prismatic.io/docs/components/postgres.md) * [SAP Business One](https://prismatic.io/docs/components/sap-business-one.md) * [SAP ECC](https://prismatic.io/docs/components/sap-ecc.md) * [SFTP](https://prismatic.io/docs/components/sftp.md) * [SMTP](https://prismatic.io/docs/components/smtp.md) #### Next steps[​](#next-steps "Direct link to Next steps") * [Set up the on-prem agent](https://prismatic.io/docs/integrations/connections/on-prem-agent/setup.md) - Configure and deploy the on-prem agent for your customers * [Configure custom connectors](https://prismatic.io/docs/integrations/connections/on-prem-agent/custom-connectors.md) - Add on-prem support to your custom connectors --- #### Support On-Prem Connections in Custom Connectors Prismatic provides several built-in connectors that connect to systems that are often on-prem (like PostgreSQL, MySQL, and MS SQL databases, SFTP file systems, SMTP and IMAP email servers, etc.). Those built-in connectors support on-prem connections out of the box. If you've built a connector that connects to a system that is often hosted on-prem, you can add support for on-prem connections as well. #### Add on-prem support to a connection[​](#add-on-prem-support-to-a-connection "Direct link to Add on-prem support to a connection") Within your custom connector, change your `connection()` invocation to an `onPremConnection` invocation. The `onPremConnection` function takes the same arguments as `connection` but requires that your connection include inputs named `host` and `port`. Additionally, update your `host` and `port` to have the property `onPremControlled: true`. For example, here is a basic auth on-prem connection for a custom connector: ```typescript import { onPremConnection } from "@prismatic-io/spectral"; export const basicAuth = onPremConnection({ key: "basicAuth", display: { label: "Username, password and endpoint", description: "Basic auth username and password and endpoint", }, inputs: { username: { label: "Username", placeholder: "Username", type: "string", example: "john.doe", required: false, shown: true, }, password: { label: "Password", placeholder: "Password", type: "password", example: "p@s$W0Rd", required: false, shown: true, }, host: { label: "Host", placeholder: "Name of the host", type: "string", required: true, comments: "The address of the Acme server. This should be an IP address or hostname.", example: "server.example.io", onPremControlled: true, }, port: { label: "Port", placeholder: "Port of the host", default: "1234", required: true, comments: "The port of the Acme server.", type: "string", onPremControlled: true, }, }, }); ``` For a full example of a connector that supports on-prem connections, see our SFTP connector source code in GitHub. `src/connections.ts` contains the connection definitions for the connector. [SFTP component source code](https://github.com/prismatic-io/examples/tree/main/components/sftp) When an execution is run, the instance will provide the connection with the host and port of the on-prem agent to connect to. Support connections that don't have host and port inputs What if your custom connector doesn't have `host` and `port` inputs? You might have an input called `endpoint` for example that represents your customer's (generally publicly available) app endpoint that is a URL like `https://my-customer-id.example.com`. Add `host` and `port` inputs but set them to `required: false, shown: false`. Then, in your connector's code, you can check if `yourConnection.fields.host` has a value. If it does, construct the endpoint from `https://${yourConnection.fields.host}:${yourConnection.fields.port}`. #### Handle servers that use host-based routing[​](#handle-servers-that-use-host-based-routing "Direct link to Handle servers that use host-based routing") Some HTTP servers use host-based routing to determine which site to serve. For example, a server with IP `10.1.2.3` might serve both your app and a different app on port 80, and determine which app to serve based on the `Host` header in the HTTP request. When using the on-prem agent, the `Host` header in your HTTP request will default to the IP address of the on-prem service. You can override the `Host` header in your HTTP client to match the hostname of the service you want to connect to. For example, you could specify the `Host` header as the `endpoint` input of your connection: ```typescript const response = client.get( `https://${yourConnection.fields.host}:${yourConnection.fields.port}`, { headers: { Host: yourConnection.fields.endpoint } }, ); ``` A full example connector that supports on-prem connections with host-based routing can be found in the GitHub examples repo. [Example on-prem-compatible component](https://github.com/prismatic-io/examples/tree/main/components/on-prem-example) #### Handle HTTPS-based connections[​](#handle-https-based-connections "Direct link to Handle HTTPS-based connections") If the service that you are connecting to uses HTTPS on the private network, you will need to make sure that your HTTP client in your connector is configured to trust (or ignore) the SSL certificate of the service. The HTTPS client that the custom connector SDK provides is an instance of Axios, which uses the `https` module from Node.js. You can ignore SSL certificate errors by setting the `rejectUnauthorized` option to `false` in the `https` module's global agent: ```typescript const https = require("https"); const agent = new https.Agent({ rejectUnauthorized: false, }); const response = client.get( `https://${yourConnection.fields.host}:${yourConnection.fields.port}`, { headers: { Host: yourConnection.fields.endpoint, }, httpsAgent: agent, }, ); ``` #### Related topics[​](#related-topics "Direct link to Related topics") * [On-Prem Agent overview](https://prismatic.io/docs/integrations/connections/on-prem-agent.md) - Learn how the on-prem agent works * [Set up the on-prem agent](https://prismatic.io/docs/integrations/connections/on-prem-agent/setup.md) - Configure and deploy the on-prem agent for your customers --- #### On-Prem Databases Requiring Client Libraries or Drivers Prismatic's integration runner executes NodeJS code. Node.js is great for connecting to HTTP-based APIs (like REST, SOAP, or GraphQL APIs) or when connecting to databases when pure JavaScript clients are available. For example, connectors like [MySQL](https://prismatic.io/docs/components/mysql.md), [PostgreSQL](https://prismatic.io/docs/components/postgres.md), or [Redis](https://prismatic.io/docs/components/redis.md) can rely on pure JavaScript libraries like [mysql2](https://www.npmjs.com/package/mysql2), [pg-promise](https://www.npmjs.com/package/pg-promise), and [redis](https://www.npmjs.com/package/redis) respectively. However, some on-prem databases like [IBM DB2](https://www.ibm.com/db2) do not have pure JavaScript libraries. They may offer NodeJS packages (like [ibm\_db](https://www.npmjs.com/package/ibm_db)), but those packages are wrappers around ODBC drivers or non-JavaScript libraries that require additional compilation or installation. #### Connecting to databases like IBM DB2[​](#connecting-to-databases-like-ibm-db2 "Direct link to Connecting to databases like IBM DB2") When connecting to on-prem databases that lack pure JavaScript client libraries, install the required database libraries, drivers, or binaries on a Docker image that lives alongside the [on-prem agent](https://prismatic.io/docs/integrations/connections/on-prem-agent.md) container. This database client container can translate HTTP requests from your integration into requests that the database understands and can return HTTP-based responses to your integration. ##### Creating an IBM DB2 client container[​](#creating-an-ibm-db2-client-container "Direct link to Creating an IBM DB2 client container") IBM offers a [set of libraries](https://www.ibm.com/docs/en/db2-warehouse?topic=python-sqlalchemy-django-framework) that wrap their database client. In this example, we'll create a database client using Python and [Flask](https://github.com/pallets/flask) (though you could do the same with NodeJS and Express, PHP, etc.). Dockerfile for an IBM DB2 client in Python ```docker FROM --platform=linux/x86_64 python:3 WORKDIR /app # Download and decompress database driver ADD https://public.dhe.ibm.com/ibmdl/export/pub/software/data/db2/drivers/odbc_cli/linuxx64_odbc_cli.tar.gz /tmp/linuxx64_odbc_cli.tar.gz RUN mkdir /app/db2_driver RUN tar -xzvf /tmp/linuxx64_odbc_cli.tar.gz -C /app/db2_driver ENV IBM_DB_HOME=/app/db2_driver ENV LD_LIBRARY_PATH="/app/db2_driver/lib:$LD_LIBRARY_PATH" # Install Python Dependencies RUN pip install --upgrade setuptools pip RUN pip install flask RUN pip install ibm_db # Copy our app code and run a server on port 4000 COPY app.py . EXPOSE 4000 CMD ["flask", "run", "--host=0.0.0.0", "--port=4000"] ``` In this `Dockerfile`, we generate a container from the `python:3` image. We download the DB2 driver from IBM and extract the driver, setting two required environment variables. Then, we install two Python dependencies: `flask`, which is a webserver, and `ibm_db`, which relies on the IBM driver we downloaded. Finally, we run our web server on port 4000. The webserver is defined in `app.py`. It's a short script that declares a single `POST` endpoint that takes a `username`, `password`, `database`, and SQL `query` from the `POST` request and issues that query against that database. It returns the resulting records in an HTTP response as JSON. app.py to translate HTTP to IBM DB2 ```python import ibm_db import os from flask import Flask, request, jsonify, make_response app = Flask(__name__) # Handle SELECT statements and return results @app.route("/select", methods=['POST']) def query(): data = request.get_json() username = data["username"] password = data["password"] database = data["database"] query = data["query"] hostname = os.environ["DB2_HOST"] port = os.environ["DB2_PORT"] conn = ibm_db.connect(f"DATABASE={database};HOSTNAME={hostname};PORT={port};PROTOCOL=TCPIP;UID={username};PWD={password};", "", "") stmt = ibm_db.exec_immediate(conn, query) response_data = [] result = True while(result): result = ibm_db.fetch_assoc(stmt) if (result): response_data.append(result) return make_response(jsonify(response_data)) ``` This short script only handles `SELECT` queries but could easily be extended to respond appropriately to `CREATE`, `UPDATE`, or `DELETE` SQL statements. ##### Running a client container alongside an on-prem agent[​](#running-a-client-container-alongside-an-on-prem-agent "Direct link to Running a client container alongside an on-prem agent") It is important that the database client container runs alongside the on-prem agent container, and you can do that by declaring the database client service in the same `docker-compose.yml` file as the on-prem agent service. Here, we run three services: * `db2` is an IBM DB2 database that we run locally to simulate a database on our network. Your customer likely runs a full IBM DB2 database. * `db_client` runs our Python code above, translating HTTP requests to DB2 queries * `on-prem-agent` is the Prismatic [on-prem agent](https://prismatic.io/docs/integrations/connections/on-prem-agent.md) which establishes a connection between your integration in Prismatic's cloud and the `db_client` container on your customer's network. docker-compose.yml ```yaml version: "3.1" volumes: dbdata: name: db2-data services: # This container runs an IBM DB2 database and simulates an on-prem database # After starting this container, wait up to 15 minutes for the database to # be initialized and ready for connections (it takes a while!). db2: platform: linux/x86_64 image: ibmcom/db2 privileged: true environment: LICENSE: accept DBNAME: testdb DB2INST1_PASSWORD: my-pass volumes: - dbdata:/database # This container takes HTTP requests and translates them into DB2 queries db_client: platform: linux/x86_64 build: ./db-client environment: DB2_HOST: db2 DB2_PORT: 50000 # Prismatic on-prem agent which will proxy requests to the "client" container on-prem-agent: image: prismaticio/on-prem-agent:latest environment: PRISMATIC_URL: https://app.prismatic.io APP_PORT: 4000 APP_HOST: db_client NAME: DB2 Client REGISTRATION_JWT: eyJ0e... restart: always # Use "always" to start this service when the Docker engine starts ``` ##### Invoking the database client from an integration[​](#invoking-the-database-client-from-an-integration "Direct link to Invoking the database client from an integration") Now that an HTTP server is running alongside the on-prem agent, you can either use the built-in [HTTP](https://prismatic.io/docs/components/http.md) connector or build a custom connector to send requests to the database client. Since our database client runs a web server on port 4000 and we're proxying requests through the on-prem agent to the database client, we can point an [HTTP POST](https://prismatic.io/docs/components/http.md#httppost) request to `http://localhost:4000/select` and send a query that we would like to run. ![Send a POST request to our on-prem database client](/docs/assets/images/post-request-a65d9e2112dcc7a205d6a352eb23ee41.png) --- #### On-Prem Agent on Windows The on-prem agent is a Linux-based Docker container, which naturally lends itself well to a Linux host. For ease of installation, we recommend running the on-prem agent on a Linux host. However, if for compliance or other reasons you are required to run the on-prem agent on a Windows host, you can. This document outlines considerations for installing the on-prem agent on a Windows host. #### Installing Docker on a Windows host[​](#installing-docker-on-a-windows-host "Direct link to Installing Docker on a Windows host") You have several options for installing Docker on a Windows host. 1. You can run a Linux host in Windows Subsystem for Linux (WSL2) and run `docker` within WSL2. To install WSL2 on your Windows host, run ```text wsl --install ``` from PowerShell and follow the prompts. Once WSL2 is installed, assume the root user with `sudo su` and run ```text apt update && apt upgrade -y ``` Then, follow the [steps on Docker's website](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository) to add Docker's aptitude repository and install the latest Docker packages. Additionally, install `docker-compose` with ```text apt install docker-compose ``` You can start the docker service as the root user with ```text service docker start ``` 2. You can download and install [Docker Desktop](https://www.docker.com/) for Windows. Note that depending on your company's size and other factors, you may need to license Docker Desktop. Please consult Docker's licensing information. 3. You can purchase and install the [Mirantis Container Runtime](https://www.mirantis.com/software/mirantis-container-runtime/) Docker engine. #### Ensuring containers run on boot on a Windows host[​](#ensuring-containers-run-on-boot-on-a-windows-host "Direct link to Ensuring containers run on boot on a Windows host") By default, WSL2 and Docker Desktop are not launched until a user has logged in to the Windows host. This makes maintenance difficult - a system that reboots does not automatically launch WSL2 or Docker Desktop without a manual login. However, you can use Windows scheduled tasks to ensure that WSL2 or Docker Desktop start automatically. note Regardless of whether you use WSL2, Docker Desktop, or another Docker service on your Windows host, please remember to mark your on-prem agent as `restart: always` and run docker compose in "detached" mode (i.e. `docker-compose up --detach`) For example, docker-compose.yml ```yaml services: on-prem-agent: image: prismaticio/on-prem-agent:latest environment: PRISMATIC_URL: https://app.prismatic.io APP_PORT: 1433 APP_HOST: 10.0.0.123 NAME: Acme MS SQL REGISTRATION_JWT: eyJ0eXAiOiJK... restart: always # Use "always" to start this service when the Docker engine starts ``` To create a new scheduled task, search for "Task Scheduler" from your Windows search bar. ![Open the task scheduler from the windows search bar](/docs/assets/images/task-scheduler-2a9fe077e3c230fa57012602f2bc969d.png) Then, select **Action** > **Create Task** ![Create a new scheduled task](/docs/assets/images/create-task-5f1316ebc936ae5f32584d80ac38b882.png) Within the scheduled task, ensure that **Run whether user is logged on or not** and **Run with highest privileges** are selected. Give the task an identifiable name and select an appropriate version of Windows for your installation. ![Configure a task to run on boot](/docs/assets/images/run-as-user-06e486014152fcd4dc5e019b2b51708d.png) ##### Configuring Docker Desktop to run on boot[​](#configuring-docker-desktop-to-run-on-boot "Direct link to Configuring Docker Desktop to run on boot") Within a new scheduled task's **Triggers** tab, create a new trigger to begin the task **At startup**. Set the **Delay task for** to **1 minute** and ensure **Enabled** is selected. ![Setting up triggers for Docker Desktop](/docs/assets/images/triggers-docker-desktop-cd36c6544850658947ca9f0d31257029.png) Under the **Actions** tab, create a new action and start the program `"C:\Program Files\Docker\Docker\Docker Desktop.exe"` ![Setting up actions for Docker Desktop](/docs/assets/images/actions-docker-desktop-e09fa6a8e5ce289492346e654b60a87b.png) After rebooting your Windows host, you should see Docker Desktop and any containers marked `restart: always` automatically start without user login within a few minutes. ##### Ensuring WSL2 runs on boot[​](#ensuring-wsl2-runs-on-boot "Direct link to Ensuring WSL2 runs on boot") Within a new scheduled task's **Triggers** tab, create a new trigger to begin the task **At startup**. Set the **Delay task for** to **1 minute** and ensure **Enabled** is selected. Additionally, set **Repeat task every** to every couple of minutes for the first 15 minutes. HyperV may not be ready to start the Docker service one minute after boot, and the additional task repeats help to ensure that the task completes eventually. ![Setting up triggers for WSL2](/docs/assets/images/triggers-wsl-939fee8540c59a17b12ef6d96213a378.png) Under the **Actions** tab, create a new action and start the program `"C:\Program Files\WSL\wsl.exe"`. Under **Add arguments (optional)**, include the arguments: ```text -u root -e sh -c "service docker status || service docker start" ``` ![Setting up actions for WSL2](/docs/assets/images/actions-wsl-c2c35c50518847ecbaab419a12347817.png) This instructs your machine to run `wsl.exe` on boot, and as the root user it will start the `docker` service if it is not already running. After rebooting your Windows host, you should see WSL, its Docker service, and any containers marked `restart: always` automatically start without user login within a few minutes. --- #### Set Up the On-Prem Agent This guide walks you through setting up the on-prem agent, configuring instances to use it, and managing registration tokens. #### Prerequisites[​](#prerequisites "Direct link to Prerequisites") You need a system on your private network that is capable of running a [Docker](https://www.docker.com/) container. This can be the same server that serves the database, filesystem, or other resource you want to connect to, or a separate server on the same network that can access the resource. The on-prem container itself is very lightweight, generally consuming less than 100MB of memory and a small amount of CPU. While we recommend using a Linux Docker host for the on-prem container, you can run the on-prem agent on Windows as well. Please see the [On-Prem Agent on Windows](https://prismatic.io/docs/integrations/connections/on-prem-agent/on-prem-agent-windows.md) article. #### Configure the on-prem Docker container[​](#configure-the-on-prem-docker-container "Direct link to Configure the on-prem Docker container") An on-prem resource is configured for a specific customer. As an organization team member, you can view all on-prem resources by running `prism on-prem-resources:list`: ```bash prism on-prem-resources:list Name Status Customer ─────────────── ─────────── ──────── Acme PostgreSQL AVAILABLE Acme Corp Hooli SFTP UNAVAILABLE Hooli ``` To create a new on-prem resource, first look up the ID of the customer whom the resource is for: ```bash prism customers:list --columns "Id,Name" Id Name ──────────────────────────────────────────────────────────── ───────────────── Q3VzdG9tZXI6YjBmZDAyZTItYmE1OC00NzE0LWJhYzgtMDMwNWM5N2JiY2Vj Acme Corp Q3VzdG9tZXI6MTE0ODdlYmItNDdlMC00MGFjLWI1NjYtYzBiZWVjNjlkZTMz Initech Q3VzdG9tZXI6M2RkMjAwYjAtMjlmYy00MzZjLTk2OWYtMmNkMjUzYWNkYzY1 Stark Enterprises Q3VzdG9tZXI6NzFlY2NiYzQtYjc5OC00YzQzLWIzZDAtZjdmYzE5OTEyYzlj Hooli ``` Next, generate a registration JSON web token (JWT) for your customer: ```bash prism on-prem-resources:registration-jwt \ --customerId Q3VzdG9tZXI6YjBmZDAyZTItYmE1OC00NzE0LWJhYzgtMDMwNWM5N2JiY2Vj eyJ0eXAiO.... ``` create org-only resources for testing To test the on-prem agent in the integration designer, you can create an on-prem resource that is only visible to your organization (and not attached to a particular customer). To do that, run `prism on-prem-resources:registration-jwt --orgOnly` Now, with a registration JWT in hand, you can start the on-prem agent Docker. The container takes a set of environment variables to configure the connection to the Prismatic platform: * `PRISMATIC_URL` is the URL of the Prismatic platform. For the US commercial region, that's `https://app.prismatic.io`. For [other regions](https://prismatic.io/docs/configure-prismatic/deployment-regions.md), use the appropriate URL. * `APP_HOST` is the hostname of the service running on the private network. For example, if you're connecting to a database that runs on a host with IP address `10.1.2.3`, enter that as the `APP_HOST`. Connect to the docker host If you run the on-prem agent on the same host as the service you're connecting to, you can use the special hostname `host.docker.internal` to connect to the host. `host.docker.internal` resolves to the internal IP address of the host running the Docker container. Note that `localhost` or `127.0.0.1` does not work in this context, as it refers to the container itself. * `APP_PORT` is the port on which the service is running (`5432` for PostgreSQL, `3306` for MySQL, `22` for SFTP, etc.). * `NAME` is the name of the on-prem resource that you will see when you run `prism on-prem-resources:list`. * `REGISTRATION_JWT` is the JWT you generated for the customer. Start the on-prem agent Docker container ```bash export REGISTRATION_JWT=$(prism on-prem-resources:registration-jwt --customerId Q3VzdG9tZXI6YjBmZDAyZTItYmE1OC00NzE0LWJhYzgtMDMwNWM5N2JiY2Vj) docker run \ --env PRISMATIC_URL=https://app.prismatic.io \ --env APP_PORT=1433 \ --env APP_HOST=host.docker.internal \ --env "NAME=Acme MS SQL" \ --env REGISTRATION_JWT \ -t prismaticio/on-prem-agent:latest ``` #### Run the on-prem agent using Docker Compose[​](#run-the-on-prem-agent-using-docker-compose "Direct link to Run the on-prem agent using Docker Compose") [Docker Compose](https://docs.docker.com/compose/) allows you to define and run multi-container Docker applications and has some useful features like automatic restart of containers on system reboot. Here's an example `docker-compose.yml` file that starts the on-prem agent: On-Prem docker-compose.yml ```yaml services: on-prem-agent: image: prismaticio/on-prem-agent:latest environment: PRISMATIC_URL: https://app.prismatic.io APP_PORT: 1433 APP_HOST: host.docker.internal # Or specify the IP of the service NAME: Acme MS SQL REGISTRATION_JWT: ${REGISTRATION_JWT} # Source from host's environment variable restart: always # Use "always" to start this service when the Docker engine starts ``` After creating a `docker-compose.yml` file, you can run `docker-compose up` from the command line to start the on-prem agent, or `docker-compose up -d` to start it in the background. #### Configure an instance to use the on-prem agent[​](#configure-an-instance-to-use-the-on-prem-agent "Direct link to Configure an instance to use the on-prem agent") Once an on-prem agent is running and has connected to the Prismatic platform, you can configure an instance to use the on-prem agent. First, you need to update connections on your integration to support an on-prem connection. Open a connection in your config wizard designer and select **Allow On-Prem Connections**. ![Screenshot of setting up on-prem agent](/docs/assets/images/allow-on-prem-connections-f695311c256ae67549ffd8ba3417393e.png) When your customer configures an instance of your integration, they can select an existing on-prem agent to use for the connection by toggling **Use On-Prem Connection** and selecting a connection to use: ![Screenshot of using on-prem agent ](/docs/assets/images/use-on-prem-connection-0ad4a6d5a835ed4bc7c03294acf888f3.png) Note that when an on-prem connection is selected, the connection's "Host" and "Port" inputs disappear. That is because the on-prem service is responsible for connecting to the private network service, and the instance communicates with the on-prem service. The on-prem service will provide the instance with a local host and port to connect to when an execution is run. #### Regenerate or revoke the registration JWT[​](#regenerate-or-revoke-the-registration-jwt "Direct link to Regenerate or revoke the registration JWT") If you lose the registration JWT for an on-prem resource, you can regenerate it using the `prism on-prem-resources:registration-jwt` command. You will need to provide the command with a `--customerId` and `--resourceId` of the on-prem resource you want to regenerate the JWT for. Those values can be found by running `prism on-prem-resources:list --extended --output json`. If you need to revoke an on-prem resource registration JWT, you revoke all old JWTs and generate a new one by running `prism on-prem-resources:registration-jwt --customerId {ID} --resourceId {ID} --rotate`. #### White-label the on-prem agent[​](#white-label-the-on-prem-agent "Direct link to White-label the on-prem agent") If you would like to white-label the on-prem agent, so your customers install and run a Docker container from your organization, follow these steps: 1. Create a `Dockerfile` that reads: ```docker FROM prismaticio/on-prem-agent:latest ENV PRISMATIC_URL=https://app.prismatic.io ``` 2. Build and publish the image with a white-labeled name to Docker Hub: ```bash docker build . -t acme-corp/on-prem-agent:latest docker push acme-corp/on-prem-agent:latest ``` 3. Your customers can then start a Docker container using your white-label name, and can omit the `PRISMATIC_URL` parameter, since that's hard-coded in your `Dockerfile` above: ```bash docker run \ --env APP_PORT=1433 \ --env APP_HOST=host.docker.internal \ --env "NAME=Acme MS SQL" \ --env REGISTRATION_JWT \ -t acme-corp/on-prem-agent:latest ``` #### Related topics[​](#related-topics "Direct link to Related topics") * [On-Prem Agent overview](https://prismatic.io/docs/integrations/connections/on-prem-agent.md) - Learn how the on-prem agent works * [Configure custom connectors](https://prismatic.io/docs/integrations/connections/on-prem-agent/custom-connectors.md) - Add on-prem support to your custom connectors --- #### Troubleshooting On-Prem Connections When an integration makes a request to an on-prem resource, it takes several steps to get there: 1. The integration sends a request to the cloud-based on-prem service. 2. The on-prem service forwards that request to an on-prem agent on your customer's network. 3. The on-prem agent forwards the request to your customer's private resource (database, file server, etc.). When a step that relies on an on-prem connection throws an error, it's important to be able to determine where in the chain the connection failed. This article offers basic troubleshooting tips you can use to verify connectivity. #### Verifying integration connectivity to the on-prem service[​](#verifying-integration-connectivity-to-the-on-prem-service "Direct link to Verifying integration connectivity to the on-prem service") There's not much to debug when it comes to this initial network hop. When an instance configured with an on-prem connection runs, the connection contains two system-generated inputs: `host` and `port`. These values refer to a host and port of Prismatic's **on-prem service** running alongside your integration. If you're using a [built-in connector that supports on-prem](https://prismatic.io/docs/integrations/connections/on-prem-agent.md#connectors-with-on-prem-support), they will use these host and port values automatically. If you want to build a custom connector that supports on-prem connections, see [Configure custom connectors](https://prismatic.io/docs/integrations/connections/on-prem-agent/custom-connectors.md). Verify that you are not hard-coding host or port values or caching these values. Some database libraries persist database connections behind the scenes and can cause issues if the `port` value changes between executions. Ensure that you close connections when a custom action finishes. #### Verifying the on-prem agent is connected[​](#verifying-the-on-prem-agent-is-connected "Direct link to Verifying the on-prem agent is connected") Next, you should verify that the on-prem agent on your customer's network is connected to Prismatic's on-prem service. Running `prism on-prem-resources:list` will show you a list of on-prem resources and their statuses: ```text $ prism on-prem-resources:list Name Status Customer ───────────────────── ─────────── ───────────── Test MSSQL Server AVAILABLE Acme MSSQL Server UNAVAILABLE Acme Corp IBM DB2 Test Server AVAILABLE Hooli IBM DB2 Server AVAILABLE Hooli ``` Resources without a **Customer** listed are those that you've marked `--orgOnly` and are used for testing on-prem connections in the integration designer. Verify that your customer's on-prem resource appears on this list and is marked `AVAILABLE`. Additionally, check the on-prem agent container's logs. You should see lines that look like this: ```text 2024-12-18 11:48:38 Name: IBM DB2 Test Server 2024-12-18 11:48:39 Registered successfully. ``` If the `Registered successfully` log line is missing, your customer may not allow network egress from the Docker network and will need to open outbound connections to Prismatic on ports 22 and 443. Ensure your Docker container can reach `onprem.prismatic.io` (or `onprem.` for other regions or white-label domains) on **port 22**, and `app.prismatic.io` (or your region or white-label domain) on **port 443**. From the Docker network, your customer can verify connectivity to Prismatic's on-prem service by running `telnet `, replacing `` with the appropriate on-prem service endpoint for their region or white-label domain, and `` with 22 or 443. They should see a connection message like this: ```bash > telnet onprem.eu-west-1.prismatic.io 22 Trying 99.80.255.255... Connected to onprem.eu-west-1.prismatic.io. Escape character is '^]'. SSH-2.0-OpenSSH_9.9 ``` #### Verifying connectivity to the customer's resource[​](#verifying-connectivity-to-the-customers-resource "Direct link to Verifying connectivity to the customer's resource") The easiest way to verify that the on-prem agent can access the customer's resource is to attempt to `telnet` from the on-prem container to the resource. That can be done by opening `bash` in the on-prem agent, installing telnet, and checking for connectivity: ```bash # Identify the ID of your on-prem container (something like 0ff1cec0ffee) $ docker container ls # Open a bash shell using the container's ID within your on-prem container $ docker exec -it 0ff1cec0ffee bash # Make sure APP_HOST and APP_PORT environment variables look correct in the on-prem container root@0ff1cec0ffee:/home/envoy$ printenv # Install telnet in the on-prem container root@0ff1cec0ffee:/home/envoy$ apt update && apt install telnet # Verify connectivity from on-prem container to private resource root@0ff1cec0ffee:/home/envoy$ telnet ${APP_HOST} ${APP_PORT} ``` If the `telnet` command throws an error, the on-prem agent container is unable to access the private resource. The private resource may have a firewall in place, or the customer's network might prevent connectivity between the Docker network and the private resource's network. Ask your customer to enable network traffic between the Docker container and private resource. Additionally, check the on-prem container's logs. If it has received requests from your integration but cannot forward those requests to the customer's private resource, you'll see a log that looks like this: ```text Received data but failed to forward it to the final destination service (172.21.0.2:4000) ``` --- #### Data Sources **Data Sources** allow you to dynamically populate config variables with data from third-party apps. This is particularly useful because customers' configurations in third-party applications vary. Customers have different Facebook business names, Google Analytics accounts, Salesforce resource fields, and more. You can retrieve this information and allow customers to choose specific options, such as Slack channels, Google Drive folders, or Salesforce field mappings. To populate a config variable dynamically from a data source, first ensure that a **connection** config variable for the third-party application exists on your first config page. Prismatic automatically creates these connections when you add a step from that third-party to your integration. On subsequent pages, add your desired config variables. Under **Data Source**, select the component and data source you want to use (for example, the [Slack Select Channel](https://prismatic.io/docs/components/slack.md#selectchannels) data source). When customers configure the integration, they'll authenticate with the third-party application on the first config page. Then, they'll see their data dynamically loaded from the third-party service populate the config variables. Many [built-in connectors](https://prismatic.io/docs/components.md) include data sources for common configuration tasks (like selecting records from dropdown menus). To build your own data sources, either using an existing built-in connector's connection or adding a data source to your custom connector, see [config Wizard Data Sources](https://prismatic.io/docs/custom-connectors/data-sources.md). --- #### Code Component Data Sources You can use the [code](https://prismatic.io/docs/components/code.md) component within your config wizard in order to fetch data from a third-party API and present it to your customers as they configure their instance. In the video above, we create a couple of datasources that fetch data from third-party apps and present them as dropdown options in the config wizard. The code for both data sources is below. #### When should I use a code component data source?[​](#when-should-i-use-a-code-component-data-source "Direct link to When should I use a code component data source?") You'll want to use a code component data source if: * Your data source is a one-off, unique to your integration * Your data source is simple and wouldn't benefit from unit testing * You want to fetch data from a third-party API that doesn't have a built-in component or the component doesn't have a relevant data source already built out You should build a data source into a component if: * Your data source is reusable across multiple integrations * Your data source is complex and would benefit from unit testing * You use the [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) and want to use the data source to feed [dynamic action inputs](https://prismatic.io/docs/custom-connectors/data-sources-in-embedded-workflow-builder.md) #### Simple picklist code datasource[​](#simple-picklist-code-datasource "Direct link to Simple picklist code datasource") This example picklist code data source fetches a list of users from a public API and maps their names to the `label` and their emails to the `key` so that a customer can select a user from a dropdown. Simple picklist code datasource ```js module.exports = async (context, { connection, contextValue }) => { const response = await fetch("https://jsonplaceholder.typicode.com/users"); const users = await response.json(); const options = users.map((user) => { return { label: user.name, key: user.email, }; }); return { result: options, }; }; ``` ![Example of a picklist code datasource in the config wizard ](/docs/assets/images/people-picklist-6a0611c6aad9cecf6f8792361ffb90b2.png) #### Slack channel picklist as code datasource[​](#slack-channel-picklist-as-code-datasource "Direct link to Slack channel picklist as code datasource") This example code datasource fetches a list of channels from Slack and maps the channel name to the `label` and the channel ID to the `key` so that a customer can select a channel from a dropdown when configuring their instance. Slack channel picklist as code datasource ```js module.exports = async (context, { connection, contextValue }) => { const accessToken = connection.token.access_token; const response = await fetch("https://slack.com/api/conversations.list", { headers: { Authorization: `Bearer ${accessToken}`, }, }); const data = await response.json(); const options = data.channels.map((channel) => ({ label: channel.name, key: channel.id, })); return { result: options, }; }; ``` --- #### Debugging JSON Forms Locally In this video, we debug a JSON Form config variable that is throwing an error by running the data source locally in our IDE. Invoking the data source within our editor as we edit and test our code provides a tighter feedback loop and allows us to iterate quickly. The debugging in this video uses the [`prism components:dev:run`](https://prismatic.io/docs/cli/prism.md#componentsdevrun) command to fetch an active Slack OAuth 2.0 connection from a Prismatic test instance. You can read more about the custom connector unit testing harness that was used in [Unit Testing for Custom Connectors](https://prismatic.io/docs/custom-connectors/unit-testing.md). --- #### Salesforce Field Mapper Every customer of Salesforce (SFDC) configures their account differently. Some Salesforce customers add unique fields to existing resources. Others use existing fields in unique ways. If you integrate with a CRM like SFDC, you may want your users to map fields from the CRM to your product. In this tutorial, you'll learn how to use an existing Salesforce connection to fetch your customer's SFDC fields and present them to a user as a field map during the instance configuration process using [JSON Forms](https://prismatic.io/docs/custom-connectors/data-sources.md#json-forms-data-sources). ![Screenshot of a data mapper in the configuration wizard](/docs/assets/images/salesforce-field-mapper-00a658ec0e83d6d84b39bd20e0835f64.png) The final product is available in our [examples repo](https://github.com/prismatic-io/examples/blob/main/components/salesforce-field-mapping-example/src/index.ts). [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=data-mapper-accordion) #### Initializing the data source component[​](#initializing-the-data-source-component "Direct link to Initializing the data source component") You can initialize the data mapper data source project as you would [any other custom component](https://prismatic.io/docs/custom-connectors/initializing.md). After initializing the component, remove all templated files in the `src/` directory. Then, install Salesforce's NPM package, [jsforce](https://jsforce.github.io/): Install JSforce and its TypeScript definitions ```bash npm install jsforce npm install --save-dev @types/jsforce ``` If you are integrating with a different CRM, you can use their respective NPM package or a [generic HTTP client](https://prismatic.io/docs/custom-connectors/connections.md#using-the-built-in-createclient-http-client). #### Reusing HTTP connections[​](#reusing-http-connections "Direct link to Reusing HTTP connections") It would be a poor user experience to require a user to authenticate with Salesforce twice. Instead, add a Salesforce step to your integration (that will automatically create a Salesforce connection config variable). You can re-use that existing Salesforce connection for your data source! You do not need to define any `connections` for your component. Simply add a connection input to your data source: Reuse the existing Salesforce connection ```typescript const salesforceFieldMappingExample = dataSource({ dataSourceType: "jsonForm", display: { label: "Salesforce field mapping example", description: "Map fields from a Salesforce 'Lead' to an acme 'Sale'", }, inputs: { sfConnection: input({ label: "Salesforce Connection", type: "connection", required: true, }), }, // ... }); ``` The Salesforce connection uses OAuth, so the access token that you'll need will be available via `sfConnection.token?.access_token`. #### Fetch fields from SFDC[​](#fetch-fields-from-sfdc "Direct link to Fetch fields from SFDC") Next, you'll use the existing connection to fetch fields from SFDC. Fetch custom fields on the Lead resource from Salesforce ```typescript { // ... perform: async (context, inputs) => { // Reference an existing SFDC OAuth access token const salesforceClient = new jsforce.Connection({ instanceUrl: util.types.toString(inputs.sfConnection.token?.instance_url), version: "63.0", accessToken: util.types.toString(inputs.sfConnection.token?.access_token), }); // Fetch all fields on a Lead using https://jsforce.github.io/document/#describe const { fields } = await salesforceClient.sobject("Lead").describe(); // Filter out non-required fields const salesforceRequiredLeadFields = fields.filter( ({ nillable }) => !nillable, ); }; } ``` For illustration purposes, this example fetches fields on the "Lead" resource and filters them down to only fields that are required (i.e. not `nillable`). You can fetch fields on any resource and can choose to filter those fields or not. #### Generate a JSON Forms schema[​](#generate-a-json-forms-schema "Direct link to Generate a JSON Forms schema") JSON Forms allows you to define a **schema** where you declare how the UI that your customer uses should look. Here, this example hard-codes some fields from "Acme" and creates an array where every element of the array has one Salesforce Lead field and one Acme field: Create the JSON Form Schema ```typescript // Hard-code Acme fields - these can be fetched from an external source, too const acmeSaleFields: { name: string; id: number }[] = [ { id: 123, name: "First Field" }, { id: 456, name: "Second Field" }, { id: 789, name: "Third Field" }, ]; // Schema defines the shape of the object to be returned to the integration, // along with options for dropdown menus const schema = { type: "object", properties: { mymappings: { // Arrays allow users to make one or more mappings type: "array", items: { // Each object in the array should contain a salesforceField and an acmeField type: "object", properties: { salesforceLeadField: { type: "string", // Have users select "one of" a dropdown of items oneOf: salesforceRequiredLeadFields.map((field) => ({ // Display the pretty "label" like "My First Name" to the user title: field.label, // Feed programmatic "name" like "My_First_Name__c" to the integration const: field.name, })), }, acmeSaleField: { type: "string", oneOf: acmeSaleFields.map((field) => ({ title: field.name, const: util.types.toString(field.id), // JSON Forms requires string values })), }, }, }, }, }, }; ``` JSON Forms also require UI schema, which determines how the above UI elements should be placed (vertically, horizontally, etc): Define UI Schema ```typescript // UI Schema defines how the schema should be displayed in the configuration wizard const uiSchema = { type: "VerticalLayout", elements: [ { type: "Control", scope: "#/properties/mymappings", label: "Salesforce Lead <> Acme Sale Field Mapper", }, ], }; ``` #### Add an optional default mapping[​](#add-an-optional-default-mapping "Direct link to Add an optional default mapping") If you have an idea of what SFDC fields should map to your fields, you can provide a default mapping. Here, for illustration purposes, this example simply maps the first three SFDC fields to the three Acme fields: Add an optional default mapping ```typescript const defaultValues = { mymappings: [ { salesforceLeadField: util.types.toString( salesforceRequiredLeadFields[0].name, ), acmeSaleField: util.types.toString(acmeSaleFields[0].id), }, { salesforceLeadField: util.types.toString( salesforceRequiredLeadFields[1].name, ), acmeSaleField: util.types.toString(acmeSaleFields[1].id), }, { salesforceLeadField: util.types.toString( salesforceRequiredLeadFields[2].name, ), acmeSaleField: util.types.toString(acmeSaleFields[2].id), }, ], }; return { result: { schema, uiSchema, data: defaultValues }, }; ``` #### AI-enhanced field mapping[​](#ai-enhanced-field-mapping "Direct link to AI-enhanced field mapping") You can enhance your field mapper with AI to automatically generate intelligent field mappings. This uses OpenAI to analyze field names and suggest semantic matches between Salesforce fields and your destination fields, pre-populating the mapping form for faster configuration. [Learn how to implement AI-enhanced field mapping →](https://prismatic.io/docs/ai/data-enrichment.md#ai-enhanced-field-mapping) #### The completed code[​](#the-completed-code "Direct link to The completed code") The full source code of this field mapper can be found in our [examples repo](https://github.com/prismatic-io/examples/blob/main/components/salesforce-field-mapping-example/src/index.ts). It serves as a good jumping-off point for your own field mapping, and you can modify it however you like. For example you could: * Fetch your app's fields dynamically rather than hard-coding them * Fetch fields for other SFDC resources, like opportunities or accounts * Fetch fields from another CRM #### Using the field mapper data in an integration[​](#using-the-field-mapper-data-in-an-integration "Direct link to Using the field mapper data in an integration") The next step is to use the results of that field mapper to map data in a flow. In the video above, you create a field mapper that maps Salesforce fields to "Acme" fields. The result of the field mapper config variable is a JavaScript object that looks like this: ```json [ { "source": "Id", "destination": "external_id", }, { "source": "Name", "destination": "acct_name", }, { "source": "AnnualRevenue", "destination": "revenue", }, ]; ``` Salesforce yields data that looks like this, with keys in [Pascal Case](https://en.wikipedia.org/wiki/Camel_case): ```json { "Id": "0018c0000321QvpAAE", "Name": "Example Account", "AnnualRevenue": 1000000 } ``` To map fields from the Salesforce payload to an "Acme" payload, you can apply a JavaScript [reduce](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce) function onto the map configuration object: ```javascript module.exports = async ({ logger, configVars }, stepResults) => { const sfdcAccount = stepResults.loopOverAccounts.currentItem; const mapping = configVars["Salesforce Account Field Mapping"]; const mappedFields = mapping.reduce( (acc, { source, destination }) => ({ [destination]: sfdcAccount[source], ...acc, }), {}, ); return { data: mappedFields }; }; ``` That will yield an object that our "Acme" API can consume: ```json { "external_id": "0018c0000321QvpAAE", "acct_name": "Taylor test account 5", "revenue": 10000 } ``` --- #### What are JSON Forms? JSON Forms help you customize the deployment experience for your customers. They allow you to add one or many custom fields to the [configuration wizard](https://prismatic.io/docs/integrations/config-wizard.md) by defining a JSON schema and UI schema. With JSON Forms, you can create rich form experiences that go beyond simple text inputs. You can build complex field mappers, multi-step forms with tabs, dynamic dropdowns, and conditional fields that show or hide based on user input. For hands-on examples, check out the [JSON Forms Playground](https://prismatic.io/docs/jsonforms/playground). Additional examples are available in the JSON Forms project [documentation](https://jsonforms.io/). #### Why use JSON Forms?[​](#why-use-json-forms "Direct link to Why use JSON Forms?") JSON Forms are particularly useful when you need to: * **Collect structured data** from your customers during deployment * **Build field mappers** between your app and third-party systems * **Create dynamic forms** with conditional fields that adapt to user selections * **Provide dropdown menus** populated from third-party APIs * **Validate user input** with custom rules and formats Instead of using multiple separate config variables, JSON Forms let you group related fields together and create sophisticated form experiences within a single config variable. #### How JSON Forms work[​](#how-json-forms-work "Direct link to How JSON Forms work") A JSON Form is defined by two main components: * **Schema**: The data model that describes the shape of data you expect to collect * **UI schema**: The presentation layer that describes how input fields should be rendered ##### Schema: defining your data model[​](#schema-defining-your-data-model "Direct link to Schema: defining your data model") The schema uses [JSON Schema](https://json-schema.org/) syntax to define the structure and validation rules for your form data. Here's a simple schema example: Example JSON Schema ```json { "type": "object", "properties": { "companyName": { "type": "string" }, "companyDescription": { "type": "string", "description": "You can enter multiple lines here" }, "numEmployees": { "type": "integer", "description": "Include employees in all offices" }, "continent": { "type": "string", "enum": [ "North America", "South America", "Europe", "Asia", "Africa", "Australia" ] }, "biDirectionalSync": { "type": "boolean" } }, "required": ["companyName"] } ``` In this schema: * The form will return an **object** with five properties * `companyName` is **required**, while other fields are optional * `companyDescription` is a string field with a helpful description * `numEmployees` accepts only integer values * `continent` uses an `enum` to restrict values to a predefined list * `biDirectionalSync` is a boolean (true/false) field When a customer completes this form, your integration receives data like this: Example JSON Form data ```json { "companyName": "Acme Corp", "companyDescription": "We make everything", "numEmployees": 100, "continent": "North America", "biDirectionalSync": true } ``` ##### UI schema: defining the presentation[​](#ui-schema-defining-the-presentation "Direct link to UI schema: defining the presentation") The UI schema controls how Prismatic displays and organizes your form fields. It determines the layout, labels, and rendering options for each field. Here's a UI schema for the form above: Simple UI Schema ```json { "type": "VerticalLayout", "elements": [ { "type": "Control", "scope": "#/properties/companyName" }, { "type": "Control", "scope": "#/properties/companyDescription", "options": { "multi": true } }, { "type": "Control", "label": "Employee Count", "scope": "#/properties/numEmployees" }, { "type": "Control", "scope": "#/properties/continent" }, { "type": "Control", "label": "Sync Data Bi-Directionally?", "scope": "#/properties/biDirectionalSync" } ] } ``` Key UI schema concepts: * **Layout types**: Organize fields vertically, horizontally, or in groups * **Control elements**: Reference specific properties from the schema using `scope` * **Labels**: Override default labels derived from property names * **Options**: Customize rendering behavior (like `multi: true` for multi-line text) This combination of schema and UI schema produces a form like this: ![Screenshot of a basic JSON form](/docs/assets/images/basic-form-8ccc4ce937307dff826daab208dbccba.png) [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=basic) #### Field types in JSON Forms[​](#field-types-in-json-forms "Direct link to Field types in JSON Forms") JSON Forms support several field types out of the box: * **String fields**: Plain text inputs * **Number and integer fields**: Numeric inputs with optional min/max constraints * **Boolean fields**: Checkboxes or toggle switches * **Date and time fields**: Date pickers, time pickers, and datetime pickers * **Dropdown menus**: Using `enum` or `oneOf` for predefined options * **Arrays**: Repeating sets of fields for collecting multiple items Time and datetime fields are defined as `string` type with a `format` property: Date and time field examples ```json { "date": { "type": "string", "format": "date" }, "time": { "type": "string", "format": "time" }, "dateTime": { "type": "string", "format": "date-time" } } ``` #### Static vs dynamic JSON Forms[​](#static-vs-dynamic-json-forms "Direct link to Static vs dynamic JSON Forms") You can implement JSON Forms in two ways: ##### Static JSON Forms[​](#static-json-forms "Direct link to Static JSON Forms") For forms with predefined fields that don't change based on third-party data, you can use the [JSONForms component](https://prismatic.io/docs/components/jsonforms.md) directly. You provide fixed `schema` and `uiSchema` values, and Prismatic renders the form. ##### Dynamic JSON Forms[​](#dynamic-json-forms "Direct link to Dynamic JSON Forms") For forms that need to fetch data from third-party apps (like populating dropdown menus with Salesforce objects or Slack channels), you can build JSON Forms in a custom component. See the [custom component data sources documentation](https://prismatic.io/docs/custom-connectors/data-sources.md#json-forms-data-sources) for details. #### Next steps[​](#next-steps "Direct link to Next steps") Now that you understand what JSON Forms are and how they work, you can: * **Learn practical techniques**: See [Using JSON Forms](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md) for how-to guides on layouts, arrays, validation, and more * **Look up schema and UI schema details**: See the [JSON Forms Reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference.md) for full coverage of the schema, UI schema, renderers, rules, and options Prismatic supports * **Build a field mapper**: Follow the [Building a Field Mapper Data Source](https://prismatic.io/docs/integrations/data-sources/field-mapping/salesforce-field-mapper.md) tutorial * **Add custom validation**: Learn about [JSON Form Validation](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md) * **Explore examples**: Try the [JSON Forms Playground](https://prismatic.io/docs/jsonforms/playground) --- #### JSON Forms Data Validation [JSON Forms Validation](https://player.vimeo.com/video/1053980092) Specific fields of a JSON Forms config variable can have [validation rules](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md#validating-user-input) applied. For example, you can check that an input matches a [regular expression](https://en.wikipedia.org/wiki/Regular_expression), ensuring that it is an email address or phone number, or you can ensure a date is within a certain range. But, not all data validation can be done with regex and min/max rules. Sometimes it's useful to examine the form in its entirety for correctness. For example, you may want to ensure that a [Salesforce field mapper](https://prismatic.io/docs/integrations/data-sources/field-mapping/salesforce-field-mapper.md) contains a one-to-one field mapping (so a customer can't map "Phone" to both "Cell Phone" and "Work Phone" fields, etc). One straightforward way to tackle JSON Forms form validation is to feed the results of a JSON Form config variable into a subsequent JSON Form data source that validates the data with JavaScript rules you write. This "validator" form can show helpful, human-readable error messages and prevent a user from completing a config wizard until they've corrected their mistakes. In this example, we'll validate a simple JSON Form. #### Our example JSON Form[​](#our-example-json-form "Direct link to Our example JSON Form") For this example, we have a simple form that contains: 1. A field mapper mapping "source" fields to "destination" fields. For example, you can map `Source Option 2` to `Destination Option 5`, etc. It's important that these mappings are unique. So, you can't map both `Source Option 2` and `Source Option 3` to `Destination Option 5`. 2. A number input representing a person's age. While you could validate a number like this with `minimum` and `maximum` [input validators](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md#validating-user-input), we'll show how to validate it with JavaScript here. Example JSON Form data source ```ts const myJsonForm = dataSource({ dataSourceType: "jsonForm", display: { label: "My JSON Form", description: "A JSON form for testing", }, inputs: {}, perform: async () => { const schema = { type: "object", properties: { mappings: { type: "array", items: { type: "object", properties: { source: { type: "string", enum: [ "Source Option 1", "Source Option 2", "Source Option 3", "Source Option 4", "Source Option 5", ], }, destination: { type: "string", enum: [ "Destination Option 1", "Destination Option 2", "Destination Option 3", "Destination Option 4", "Destination Option 5", ], }, }, }, required: ["source", "destination"], }, age: { type: "integer", }, }, }; const uiSchema = { type: "VerticalLayout", elements: [ { type: "Control", scope: "#/properties/mappings", }, { type: "Control", scope: "#/properties/age", }, ], }; return Promise.resolve({ result: { schema, uiSchema } }); }, }); ``` ![Example JSON Form with field mapping](/docs/assets/images/example-form-1e8fc2479ec00f981a0dfd27045176c4.png) Our JSON Forms config variable will yield an object that looks like this: ```json { "mappings": [ { "source": "Source Option 1", "destination": "Destination Option 1" }, { "source": "Source Option 2", "destination": "Destination Option 5" }, { "source": "Source Option 2", "destination": "Destination Option 3" }, { "source": "Source Option 4", "destination": "Destination Option 3" } ], "age": -5 } ``` #### Our example validator form[​](#our-example-validator-form "Direct link to Our example validator form") Within our form validator we want to: 1. Verify that each `source` field was selected at most once. 2. Verify that each `destination` field was selected at most once. 3. Verify that `age` was a positive number no greater than 130. If any of these checks fail, we want to display an error and disallow a user from continuing (note the disabled "Finish" button). ![Failed JSON Forms validation](/docs/assets/images/validation-failed-a9259f39bb7ee5d4055f4ce635fe3b5b.png) If all checks pass, we want to display confirmation that their data looks correct and allow the user to continue. ![Passed JSON Forms validation](/docs/assets/images/validation-passed-ca0ff4597184791012bd7a1be1490de5.png) In our validator code below, we pass our previous JSON Form's results to our "validator" data source. We initialize an array, `errors`, to `[]`. Then, if we detect invalid data in the form that is being processed, we push error messages onto our `errors` array. If `errors` is empty at the end of the function, we display a JSON Form with a label that says `✅ No errors found`, and the user is able to click the "finish" button in the config wizard. If `errors` contains error messages, those messages like `❌ Age must be a positive number` are displayed in the config wizard. The form then requires an invisible field, `isInvalid`, which cannot be set because it is invisible. This prevents a user from clicking "Finish" when errors are present. Validation JSON Form ```ts interface JsonFormData { mappings: { source: string; destination: string }[]; age: number; } const myValidator = dataSource({ dataSourceType: "jsonForm", display: { label: "Validator", description: "Validates previous JSON form", }, inputs: { data: input({ label: "Data", type: "data", required: true, clean: (value) => value as JsonFormData, }), }, perform: async (context, { data }) => { const { mappings, age } = data; // Initialize with an empty set of errors const errors: string[] = []; if ((mappings || []).length === 0) { // If the user submitted no mappings, add an error errors.push("❌ At least one mapping is required"); } else { mappings.forEach((mapping, index) => { // If multiple source fields are mapped to a single destination, add an error if (mappings.findIndex((m) => m.source === mapping.source) !== index) { errors.push( `❌ Duplicate source of "${mapping.source}" selected. Only use each source once.`, ); } // If multiple destination fields were mapped to a single source, add an error if ( mappings.findIndex((m) => m.destination === mapping.destination) !== index ) { errors.push( `❌ Duplicate destination of "${mapping.destination}" selected. Only use each destination once.`, ); } }); } // Add an error if there is no age, or the age is too high or low if (age === undefined) { errors.push("❌ You must specify an age"); } else { if (age < 0) { errors.push("❌ Age must be a positive number"); } if (age > 130) { errors.push("❌ Nobody is that old."); } } // If any errors were added to the errors array, return a series of labels displaying the errors if (errors.length) { return Promise.resolve({ result: { schema: { type: "object", properties: { isInvalid: { type: "string", }, }, // Add an invisible, but required, input to prevent the "Finish" button from being clickable required: ["isInvalid"], }, uiSchema: { type: "VerticalLayout", elements: errors.map((error) => ({ type: "Label", text: `Error: ${error}`, })), }, }, }); } else { // If no errors were present, display a single affirmative label and allow a user to continue return Promise.resolve({ result: { schema: { type: "object", properties: {}, }, uiSchema: { type: "VerticalLayout", elements: [{ type: "Label", text: "✅ No errors found" }], }, }, }); } }, }); ``` --- #### JSON Forms Reference This reference covers everything Prismatic's configuration wizard supports for [JSON Forms](https://jsonforms.io/). Use it when you already know what JSON Forms is and you need exhaustive details on schema features, UI schema elements, renderers, or the options you can pass. If you're new to JSON Forms, start with [What are JSON Forms?](https://prismatic.io/docs/integrations/data-sources/json-forms.md) and work through [Using JSON Forms](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md) before diving in here. Prismatic adds a set of custom renderers, layouts, and options on top of what JSON Forms ships by default. Everything in this reference reflects what's available in the Prismatic configuration wizard, which may differ from stock JSON Forms. #### How to read this reference[​](#how-to-read-this-reference "Direct link to How to read this reference") JSON Forms drives a form from two JSON documents you author: * **[Schema](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md)** - a JSON Schema that describes the *data* your form collects. * **[UI schema](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md)** - a JSON document that describes how the schema is *rendered* - layouts, labels, visibility, and per-control behavior. Three reference pages break the UI schema down further: * **[Renderers](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md)** - how each schema type maps to an input control, and which controls Prismatic customizes. * **[Rules](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/rules.md)** - the `rule` block you attach to any UI schema element to show, hide, enable, or disable it based on form data. * **[Options](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md)** - an alphabetized catalog of every `options` key you can set on a UI schema element. #### JSON Schema in Prismatic[​](#json-schema-in-prismatic "Direct link to JSON Schema in Prismatic") Prismatic uses the standard [JSON Schema](https://json-schema.org/) vocabulary, and the [schema reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md) walks through the keywords you're most likely to reach for: * [Supported types](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#supported-types) and [nested objects](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#nested-objects) - the core building blocks of a form. * [Formats](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#formats) - `date`, `time`, `date-time`, `password`, `email`, `uri`, and `ipv4`. * [Validation keywords](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#validation-keywords) - `required`, `minLength`, `pattern`, `minimum`, and the rest. * [Default values and pre-filling the form](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#default-values-and-pre-filling-the-form) - how to open a form with values already populated. * [Dropdowns with `enum` and `oneOf`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#dropdowns-with-enum-and-oneof) - static and third-party populated lists. * [Arrays](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#arrays) and [`$ref`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#references-with-ref) - repeating items and shared subschemas. * [Composing schemas with `allOf`, `anyOf`, `oneOf`, and `not`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#composing-schemas-with-allof-anyof-oneof-and-not) - pulling properties in through composition keywords. * [Conditional validation with `if` / `then` / `else`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#conditional-validation-with-if--then--else) - varying validation rules based on form data. A handful of keywords behave differently in Prismatic than they do in the JSON Schema specification - see [Keywords Prismatic handles differently](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#keywords-prismatic-handles-differently). #### Related pages[​](#related-pages "Direct link to Related pages") * [What are JSON Forms?](https://prismatic.io/docs/integrations/data-sources/json-forms.md) * [Using JSON Forms](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md) * [JSON Form Validation](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md) * [JSON Forms Playground](https://prismatic.io/docs/jsonforms/playground) * [Custom component JSON Forms data sources](https://prismatic.io/docs/custom-connectors/data-sources.md#json-forms-data-sources) * [JSON Forms component](https://prismatic.io/docs/components/jsonforms.md) --- #### Options Reference Every UI schema element accepts an `options` object. The keys it recognizes depend on the element type and the selected renderer. This page lists every option Prismatic's configuration wizard respects, alphabetized. For how options fit into the rest of the UI schema, see the [UI schema reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md). For which options a specific renderer respects, see the [renderers reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md). #### `autocomplete`[​](#autocomplete "Direct link to autocomplete") **Applies to:** `Control` whose schema property uses `oneOf` **Type:** `boolean` **Default:** `false` Renders a `oneOf` dropdown as a type-ahead autocomplete field. Typing in the input filters the list. ```json { "type": "Control", "scope": "#/properties/company", "options": { "autocomplete": true } } ``` #### `detail`[​](#detail "Direct link to detail") **Applies to:** `Control` scoped to an array, and `ListWithDetail` **Type:** `object` (a UI schema element) or `"DEFAULT"` | `"GENERATED"` | `"REGISTERED"` Overrides the default per-item detail form. Provide an inlined UI schema to control exactly how each array item is laid out. Custom detail layout inside an Accordion ```json { "type": "Control", "scope": "#", "options": { "layout": "Accordion", "detail": { "type": "HorizontalLayout", "elements": [ { "type": "Control", "scope": "#/properties/source" }, { "type": "Control", "scope": "#/properties/destination" } ] } } } ``` See [Customizing field mapper layouts](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md#customizing-field-mapper-layouts) for a worked example. #### `elementLabelProp`[​](#elementlabelprop "Direct link to elementlabelprop") **Applies to:** `Control` with `options.layout: "Accordion"`, `ListWithDetail` **Type:** `string` **Default:** The first primitive property of the item schema Which property to use as each accordion panel or list entry label. ```json { "type": "Control", "scope": "#/properties/mappings", "options": { "layout": "Accordion", "elementLabelProp": "destination" } } ``` #### `focus`[​](#focus "Direct link to focus") **Applies to:** Any `Control` **Type:** `boolean` **Default:** `false` Autofocuses the control when the form first mounts. Only one control per form should set `focus: true`. #### `format`[​](#format "Direct link to format") **Applies to:** `Control` on a string property **Type:** `string` Overrides the renderer choice for a string control. In practice, use the schema's `format` keyword instead - it drives the same selection and validates input. Set `options.format: "password"` in rare cases where you need a masked input without declaring `"format": "password"` in the schema (for example, when the schema comes from an external system you don't control). #### `hideRequiredAsterisk`[​](#hiderequiredasterisk "Direct link to hiderequiredasterisk") **Applies to:** Any `Control` or layout **Type:** `boolean` **Default:** `false` Hides the `*` marker next to required field labels within this element's subtree. #### `labelPlacement`[​](#labelplacement "Direct link to labelplacement") **Applies to:** `Control` rendered as a [toggle switch](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#toggle) **Type:** `"top"` | `"left"` **Default:** `"top"` Where the label sits relative to the toggle switch. ```json { "type": "Control", "scope": "#/properties/enabled", "options": { "toggle": true, "labelPlacement": "left" } } ``` #### `layout`[​](#layout "Direct link to layout") **Applies to:** `Control` scoped to an array of objects **Type:** `"Accordion"` | `"Table"` **Default:** `"Table"` Selects the [array renderer](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#arrays). `"Accordion"` renders each item as a collapsible panel; `"Table"` renders them as rows in a table. ```json { "type": "Control", "scope": "#/properties/mappings", "options": { "layout": "Accordion" } } ``` #### `multi`[​](#multi "Direct link to multi") **Applies to:** `Control` on a string property **Type:** `boolean` **Default:** `false` Renders the string as a multi-line textarea instead of a single-line input. Useful for descriptions, notes, or any field where line breaks matter. ```json { "type": "Control", "scope": "#/properties/description", "options": { "multi": true } } ``` #### `readonly`[​](#readonly "Direct link to readonly") **Applies to:** Any `Control` or layout **Type:** `boolean` **Default:** `false` Disables editing of the element and its subtree. Customers see the current value but can't change it. #### `restrict`[​](#restrict "Direct link to restrict") **Applies to:** `Control` on a string property with `maxLength` **Type:** `boolean` **Default:** `false` Enforces the schema's `maxLength` at the browser level by setting the input's HTML `maxlength` attribute. Without `restrict`, customers can type past the limit and see a validation error instead. #### `showNavButtons`[​](#shownavbuttons "Direct link to shownavbuttons") **Applies to:** `Categorization` with `options.variant: "stepper"` **Type:** `boolean` **Default:** `false` Displays **Next** and **Previous** buttons for navigating between steps. ```json { "type": "Categorization", "elements": [ /* ... */ ], "options": { "variant": "stepper", "showNavButtons": true } } ``` #### `showSortButtons`[​](#showsortbuttons "Direct link to showsortbuttons") **Applies to:** `Control` scoped to an array **Type:** `boolean` **Default:** `false` Adds up and down reorder buttons to each array item. Works with both the table and accordion renderers. #### `showUnfocusedDescription`[​](#showunfocuseddescription "Direct link to showunfocuseddescription") **Applies to:** Any `Control` **Type:** `boolean` **Default:** `true` Keeps the schema's `description` visible beneath the input at all times. When `false`, descriptions only appear while the field is focused. #### `slider`[​](#slider "Direct link to slider") **Applies to:** `Control` on a numeric property with `minimum`, `maximum`, and `default` **Type:** `boolean` **Default:** `false` Renders the numeric input as a [slider](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#slider). The schema must include `minimum`, `maximum`, and `default` values for the slider to render. The schema's `multipleOf` sets the slider's step increment. #### `toggle`[​](#toggle "Direct link to toggle") **Applies to:** `Control` on a boolean property **Type:** `boolean` **Default:** `false` Renders the boolean as a switch instead of a [checkbox](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#checkbox). ```json { "type": "Control", "scope": "#/properties/enabled", "options": { "toggle": true } } ``` #### `trim`[​](#trim "Direct link to trim") **Applies to:** `Control` on a string property with `maxLength` **Type:** `boolean` **Default:** `false` Sizes the input to fit the schema's `maxLength`, producing a narrower field. Pair with `restrict` to enforce the limit as the customer types. #### `variant`[​](#variant "Direct link to variant") **Applies to:** `Categorization` **Type:** `"stepper"` **Default:** Tabs Switches a `Categorization` from its default tabbed layout to a stepper. Combine with `showNavButtons` to add **Next** and **Previous** buttons. ```json { "type": "Categorization", "elements": [ /* ... */ ], "options": { "variant": "stepper", "showNavButtons": true } } ``` #### Inheriting options[​](#inheriting-options "Direct link to Inheriting options") Options set on a layout pass down to child elements through JSON Forms' shared config. You can set cross-cutting options like `showUnfocusedDescription: false` once at the root rather than repeating them on every control. #### Related pages[​](#related-pages "Direct link to Related pages") * [UI schema reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md) * [Renderers reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md) * [Rules reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/rules.md) * [JSON Forms controls docs](https://jsonforms.io/docs/uischema/controls/) --- #### Renderers Reference A **renderer** is what the configuration wizard uses to draw a given UI schema element. Prismatic selects a renderer automatically based on the schema type, format, and `options` at each UI schema element. This page documents which renderer the wizard uses for each schema shape and which [`options`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md) each renderer respects. #### Controls by schema type[​](#controls-by-schema-type "Direct link to Controls by schema type") ##### `string`[​](#string "Direct link to string") | Schema shape | Renderer | Notes | | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------- | | `"type": "string"` | [Text input](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#text-input) | Default | | `"type": "string"`, `"format": "password"` | Password input | Masks the value | | `"type": "string"`, `"format": "date"` | [Date picker](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#date-picker) | | | `"type": "string"`, `"format": "time"` | [Time picker](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#time-picker) | | | `"type": "string"`, `"format": "date-time"` | [Date-time picker](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#date-time-picker) | | | `"type": "string"`, `"enum": [...]` | [Enum dropdown](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#enum-dropdown) | | | `"type": "string"`, `"oneOf": [...]` | [OneOf dropdown](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#oneof-dropdown) | Supports `autocomplete` | ##### `integer` and `number`[​](#integer-and-number "Direct link to integer-and-number") | Schema shape | Renderer | | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | | `"type": "integer"` or `"type": "number"` | Number input | | `"type": "integer"` or `"type": "number"` with `minimum`, `maximum`, `default`, and `options.slider: true` | [Slider](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#slider) | ##### `boolean`[​](#boolean "Direct link to boolean") | Schema shape | Renderer | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `"type": "boolean"` | [Checkbox](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#checkbox) | | `"type": "boolean"` with `options.toggle: true` | [Toggle switch](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#toggle) | ##### `object`[​](#object "Direct link to object") Objects render as grouped controls inside the parent layout. Use a [`Group`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md#group) to add a visible label and container around a nested object's inputs. ##### `array`[​](#array "Direct link to array") Array rendering has several variants - see the [arrays](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#arrays) section below. #### Text input[​](#text-input "Direct link to Text input") The default renderer for `"type": "string"`. **Respected options:** | Option | Effect | | ---------- | ------------------------------------------------------------ | | `multi` | Renders a multi-line textarea instead of a single-line input | | `trim` | Sizes the input to match the schema's `maxLength` | | `restrict` | Prevents input beyond the schema's `maxLength` | | `focus` | Autofocuses this field when the form first mounts | Multi-line text ```json { "type": "Control", "scope": "#/properties/companyDescription", "options": { "multi": true } } ``` The text input also handles password rendering when the schema property uses `"format": "password"`. #### Date picker[​](#date-picker "Direct link to Date picker") Used for `"type": "string"` with `"format": "date"`. The picker stores the selected date in ISO 8601 format (`YYYY-MM-DD`). Validate against a specific range with `formatMinimum` and `formatMaximum` on the schema: Date input with a valid range ```json { "type": "object", "properties": { "startDate": { "type": "string", "format": "date", "formatMinimum": "2026-01-01", "formatMaximum": "2026-12-31" } } } ``` #### Time picker[​](#time-picker "Direct link to Time picker") Used for `"type": "string"` with `"format": "time"`. Stores the selected time in ISO 8601 format (`HH:mm:ss`). #### Date-time picker[​](#date-time-picker "Direct link to Date-time picker") Used for `"type": "string"` with `"format": "date-time"`. Stores a full ISO 8601 timestamp including timezone. #### Enum dropdown[​](#enum-dropdown "Direct link to Enum dropdown") Used when a string property has an `enum` array. The customer sees - and the config variable stores - each value as written in the schema. Enum ```json { "continent": { "type": "string", "enum": ["North America", "Europe", "Asia"] } } ``` #### OneOf dropdown[​](#oneof-dropdown "Direct link to OneOf dropdown") Used when a string property has a `oneOf` array of `{ "title", "const" }` entries. The customer sees the `title`; the config variable stores the `const`. oneOf ```json { "continent": { "type": "string", "oneOf": [ { "title": "North America", "const": "NA" }, { "title": "Europe", "const": "EU" } ] } } ``` **Respected options:** | Option | Effect | | -------------- | -------------------------------------------------- | | `autocomplete` | Renders the dropdown as a type-ahead input instead | | `focus` | Autofocuses this field when the form first mounts | `autocomplete` is especially valuable when the list comes from a third-party API and might contain dozens or hundreds of entries. #### Checkbox[​](#checkbox "Direct link to Checkbox") The default renderer for `"type": "boolean"`. Renders as a checkbox with the schema's `description` as helper text. #### Toggle[​](#toggle "Direct link to Toggle") Set `options.toggle: true` on a boolean control to render a switch instead of a checkbox. **Respected options:** | Option | Effect | | ---------------- | ----------------------------------------------------------------------------------- | | `toggle` | Renders as a toggle switch (required for this renderer) | | `labelPlacement` | `"top"` (default) places the label above the switch; `"left"` places it to the left | | `focus` | Autofocuses this field when the form first mounts | Toggle with label on the left ```json { "type": "Control", "scope": "#/properties/enabled", "options": { "toggle": true, "labelPlacement": "left" } } ``` #### Slider[​](#slider "Direct link to Slider") Used for `integer` or `number` properties that declare `minimum`, `maximum`, and `default`, and set [`options.slider: true`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#slider). Uses the schema's `multipleOf` as the step increment. Slider schema ```json { "type": "object", "properties": { "volume": { "type": "integer", "minimum": 0, "maximum": 11, "default": 5, "multipleOf": 1 } } } ``` Slider UI schema ```json { "type": "Control", "scope": "#/properties/volume", "options": { "slider": true } } ``` #### Arrays[​](#arrays "Direct link to Arrays") Prismatic provides several renderers for array properties. Choose one with [`options.layout`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#layout) on the `Control`, or let Prismatic pick the default. ##### Table[​](#table "Direct link to Table") The default renderer for `array` of `object`. Each array item becomes a row; each object property becomes a column. Array as a table ```json { "type": "Control", "scope": "#/properties/contacts", "options": { "showSortButtons": true } } ``` **Respected options:** | Option | Effect | | ----------------- | -------------------------------------------- | | `showSortButtons` | Adds up and down reorder buttons to each row | Tables suit arrays where each item has a handful of primitive fields and the customer wants to scan them side by side. ##### Accordion[​](#accordion "Direct link to Accordion") Set `options.layout: "Accordion"` on a `Control` scoped to an array to render each item as a collapsible panel. Array as an accordion ```json { "type": "Control", "scope": "#/properties/mappings", "options": { "layout": "Accordion", "elementLabelProp": "source", "showSortButtons": true } } ``` **Respected options:** | Option | Effect | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `layout` | Set to `"Accordion"` to use this renderer | | `elementLabelProp` | Name of the property to use as each accordion header (defaults to the first primitive property) | | `showSortButtons` | Adds up and down reorder buttons to each panel | | `detail` | Inline UI schema for the item's detail view - see [custom field mapper layouts](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md#customizing-field-mapper-layouts) | Use an accordion when each array item has several fields and the customer wants to focus on one item at a time. ##### List with detail[​](#list-with-detail "Direct link to List with detail") Use the [`ListWithDetail`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md#listwithdetail) UI type for a master-list / detail-form split. The master list shows one entry per item using the first primitive property (or `options.elementLabelProp`); the detail form shows the currently selected item's full shape. ##### Array of primitives[​](#array-of-primitives "Direct link to Array of primitives") For arrays of strings, numbers, or booleans (no nested object), Prismatic renders an inline list with an add button. #### Related pages[​](#related-pages "Direct link to Related pages") * [Schema reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md) * [UI schema reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md) * [Options reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md) * [JSON Forms controls docs](https://jsonforms.io/docs/uischema/controls/) * [JSON Forms layouts docs](https://jsonforms.io/docs/uischema/layouts/) --- #### Rules Reference A **rule** attaches to any UI schema element and conditionally changes how Prismatic displays that element based on other values in the form. Rules are how you build dynamic, responsive forms - hiding fields that don't apply, disabling inputs until a prerequisite is set, or stepping a wizard forward as the customer makes choices. #### Rule shape[​](#rule-shape "Direct link to Rule shape") Add a `rule` property to any UI schema element: Rule shape ```json { "rule": { "effect": "HIDE" | "SHOW" | "ENABLE" | "DISABLE", "condition": { "scope": "", "schema": { /* JSON Schema */ } } } } ``` The rule evaluates the `condition` against the current form data. If the condition passes, the `effect` applies to the element. #### Effects[​](#effects "Direct link to Effects") | Effect | Applied when condition passes | | --------- | --------------------------------------- | | `SHOW` | The element is displayed | | `HIDE` | The element is hidden | | `ENABLE` | The element is interactive | | `DISABLE` | The element is visible but not editable | `SHOW` and `HIDE` are logical opposites. Use whichever reads most clearly - typically `SHOW` for the inclusion case ("show when a checkbox is checked") and `HIDE` for the exclusion case ("hide when a country is selected"). #### Condition[​](#condition "Direct link to Condition") A condition consists of a `scope` - a JSON Pointer into the form's data - and a `schema` that the referenced data must match for the condition to pass. ##### `scope`[​](#scope "Direct link to scope") Use JSON Pointer syntax to reference another property in the form: ```json "scope": "#/properties/enableBidirectionalSync" ``` Use `scope: "#"` to reference the entire form data object. This is useful when your condition needs to check multiple properties at once with `allOf`, `anyOf`, or `required`. ##### `schema`[​](#schema "Direct link to schema") Any JSON Schema the referenced value must satisfy. Every JSON Schema keyword you'd use for field validation works here too. Common patterns: **Exact value match:** condition.schema with const ```json { "scope": "#/properties/country", "schema": { "const": "United States" } } ``` **One of several values:** condition.schema with enum ```json { "scope": "#/properties/region", "schema": { "enum": ["EU", "EMEA"] } } ``` **Negation:** condition.schema with not ```json { "scope": "#/properties/country", "schema": { "not": { "const": "United States" } } } ``` **Numeric range:** condition.schema with minimum ```json { "scope": "#/properties/quantity", "schema": { "minimum": 10 } } ``` **Required property at the form root:** condition.schema with required ```json { "scope": "#", "schema": { "required": ["apiKey"] } } ``` #### `failWhenUndefined`[​](#failwhenundefined "Direct link to failwhenundefined") By default, if the `scope` resolves to `undefined` (the customer hasn't filled that field in), the condition passes. Set `failWhenUndefined: true` on the condition to invert that behavior - the condition fails when the referenced value is missing: failWhenUndefined ```json { "rule": { "effect": "SHOW", "condition": { "scope": "#/properties/country", "schema": { "const": "Canada" }, "failWhenUndefined": true } } } ``` Use `failWhenUndefined: true` when a field should stay hidden until the customer has actively made a selection. #### Rules on layout elements[​](#rules-on-layout-elements "Direct link to Rules on layout elements") Rules work on any UI schema element, not just controls. Attaching a rule to a [`Group`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md#group), [`Category`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md#category), or other layout hides or disables the entire subtree: Hiding a whole group ```json { "type": "Group", "label": "Currency Configuration", "elements": [ { "type": "Control", "scope": "#/properties/currency" }, { "type": "Control", "scope": "#/properties/exchangeRate" } ], "rule": { "effect": "HIDE", "condition": { "scope": "#/properties/country", "schema": { "const": "United States" } } } } ``` #### Complete example[​](#complete-example "Direct link to Complete example") Here's the pattern from [Using JSON Forms](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md#showing-and-hiding-fields-conditionally) broken out. The `convertToUSD` toggle appears only when the `country` is *not* "United States": Conditional toggle ```json { "type": "Control", "scope": "#/properties/convertToUSD", "options": { "toggle": true }, "rule": { "effect": "SHOW", "condition": { "scope": "#/properties/country", "schema": { "not": { "const": "United States" } } } } } ``` #### Patterns and tips[​](#patterns-and-tips "Direct link to Patterns and tips") ##### Show when a checkbox is checked[​](#show-when-a-checkbox-is-checked "Direct link to Show when a checkbox is checked") ```json { "rule": { "effect": "SHOW", "condition": { "scope": "#/properties/enableAdvanced", "schema": { "const": true } } } } ``` ##### Disable until a required field has a value[​](#disable-until-a-required-field-has-a-value "Direct link to Disable until a required field has a value") ```json { "rule": { "effect": "DISABLE", "condition": { "scope": "#/properties/apiKey", "schema": { "type": "string", "minLength": 1 }, "failWhenUndefined": true } } } ``` Because the effect is `DISABLE`, the field stays *enabled* while the condition fails (missing `apiKey`) and becomes disabled once the condition passes. Invert the effect if you want the opposite: ```json { "rule": { "effect": "ENABLE", "condition": { "scope": "#/properties/apiKey", "schema": { "type": "string", "minLength": 1 }, "failWhenUndefined": true } } } ``` ##### Chain multiple conditions with `allOf`[​](#chain-multiple-conditions-with-allof "Direct link to chain-multiple-conditions-with-allof") `condition.schema` is itself a JSON Schema, so you can combine checks with `allOf`, `anyOf`, `not`, and the [validation keywords](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#validation-keywords): ```json { "rule": { "effect": "SHOW", "condition": { "scope": "#", "schema": { "allOf": [ { "properties": { "country": { "const": "Canada" } } }, { "properties": { "taxExempt": { "const": false } } } ] } } } } ``` #### When rules aren't enough[​](#when-rules-arent-enough "Direct link to When rules aren't enough") Rules handle declarative visibility and enablement well, but they can't run arbitrary logic, make network calls, or produce error messages with custom text. When you need those, use a [validator data source](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md) to inspect the form's output and return a follow-up JSON Forms config variable with the rules (and errors) you compute in code. #### Related pages[​](#related-pages "Direct link to Related pages") * [UI schema reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md) * [JSON Form Validation](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md) * [JSON Forms rules docs](https://jsonforms.io/docs/uischema/rules/) --- #### Schema Reference The **schema** is a [JSON Schema](https://json-schema.org/) document that describes the shape of the data your form collects. JSON Forms uses the schema both to pick an appropriate renderer for each property and to validate what customers enter. This page is a practical tour of JSON Schema as applied to JSON Forms in Prismatic. It covers the types, keywords, and patterns you'll reach for most often, along with notes on how each one influences the generated form. The [JSON Schema specification](https://json-schema.org/) is authoritative for any keyword that isn't called out here. #### Top-level shape[​](#top-level-shape "Direct link to Top-level shape") A JSON Forms schema is almost always an object with a `properties` map: Schema shape ```json { "type": "object", "properties": { "companyName": { "type": "string" }, "numEmployees": { "type": "integer" } }, "required": ["companyName"] } ``` The top-level `type` can also be `"array"` when the entire form collects a list of items. #### Supported types[​](#supported-types "Direct link to Supported types") | `type` | Default renderer | Notes | | --------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `string` | Text input | See [formats](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md#formats) for date, time, password, and so on | | `integer` | Number input | Whole numbers only | | `number` | Number input | Decimal numbers | | `boolean` | Checkbox | Set `options.toggle` on the UI schema element to render as a switch | | `object` | Nested group | Typically used to compose form sections | | `array` | Table layout | Array of objects; Prismatic defaults to a [table layout](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#arrays) | See the [renderers reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md) for a complete type-to-renderer mapping. #### Nested objects[​](#nested-objects "Direct link to Nested objects") Objects nest inside other objects with their own `properties` and `required` arrays. The generated form data preserves the shape exactly - if the schema has a `company` object with `name` and `headcount` properties, the form data will have a `company` object with `name` and `headcount` properties: Nested object ```json { "type": "object", "properties": { "company": { "type": "object", "properties": { "name": { "type": "string" }, "headcount": { "type": "integer" } }, "required": ["name"] } } } ``` Nested objects render inline inside their parent layout by default. Wrap them in a [`Group`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md#group) in the UI schema to add a visible label and container. For shapes reused in more than one place, see [References with `$ref`](#references-with-ref). #### Formats[​](#formats "Direct link to Formats") Set `format` on a string property to pick a specialized renderer, a validator, or both. Prismatic supports these formats: ##### `date`[​](#date "Direct link to date") **Renderer:** Date picker **Accepted values:** ISO 8601 date strings in `YYYY-MM-DD` form, such as `2026-04-21`. Use with `formatMinimum` and `formatMaximum` to constrain the selectable range. ##### `time`[​](#time "Direct link to time") **Renderer:** Time picker **Accepted values:** ISO 8601 time strings in `HH:mm:ss` form, such as `14:30:00`. Fractional seconds (`HH:mm:ss.sss`) and an optional timezone suffix (`Z` or `±HH:mm`) are also accepted. ##### `date-time`[​](#date-time "Direct link to date-time") **Renderer:** Date-and-time picker **Accepted values:** Full ISO 8601 timestamps combining a date and time with a `T` separator and a timezone suffix, such as `2026-04-21T14:30:00Z`. ##### `password`[​](#password "Direct link to password") **Renderer:** Masked text input **Accepted values:** Any string. This format affects presentation only - it does not validate content. ##### `email`[​](#email "Direct link to email") **Renderer:** Text input **Accepted values:** A standard email address in `local-part@domain` form, such as `alex@example.com`. ##### `uri`[​](#uri "Direct link to uri") **Renderer:** Text input **Accepted values:** An absolute URI that includes a scheme, such as `https://example.com/widgets/42`. Relative URIs are rejected. ##### `ipv4`[​](#ipv4 "Direct link to ipv4") **Renderer:** Text input **Accepted values:** Four dot-separated integers from `0` to `255`, such as `192.0.2.1`. ##### Example[​](#example "Direct link to Example") Date and password formats ```json { "type": "object", "properties": { "startDate": { "type": "string", "format": "date" }, "apiKey": { "type": "string", "format": "password" } } } ``` #### Validation keywords[​](#validation-keywords "Direct link to Validation keywords") Prismatic validates user input against the standard JSON Schema validation keywords. The following are the ones you'll use most often: | Keyword | Applies to | Description | | --------------------------------------------------- | ---------------------- | ------------------------------------------------------ | | `required` | `object` | Array of property names that must be present | | `minLength` / `maxLength` | `string` | Minimum and maximum string length | | `pattern` | `string` | Regular expression the input must match | | `minimum` / `maximum` | `integer`, `number` | Numeric bounds | | `exclusiveMinimum` / `exclusiveMaximum` | `integer`, `number` | Exclusive numeric bounds | | `multipleOf` | `integer`, `number` | Value must be a multiple of this number | | `formatMinimum` / `formatMaximum` | `string` with `format` | Inclusive bounds for date, time, and date-time formats | | `formatExclusiveMinimum` / `formatExclusiveMaximum` | `string` with `format` | Exclusive bounds for date, time, and date-time formats | | `minItems` / `maxItems` | `array` | Bounds on the number of items | | `uniqueItems` | `array` | When `true`, all items must be unique | Validation runs continuously as the customer types. You can change when errors display with the **Validation Mode** setting on the config variable - see [Validation mode](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md#validation-mode). For validation that the schema alone can't express, see [JSON Form Validation](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md). #### Default values and pre-filling the form[​](#default-values-and-pre-filling-the-form "Direct link to Default values and pre-filling the form") To pre-fill a form with initial values, return a `data` property alongside `schema` and `uiSchema` from your data source: Data source return value with pre-filled data ```json { "schema": { /* ... */ }, "uiSchema": { /* ... */ }, "data": { "region": "us-east-1", "retryCount": 3, "enableCache": true } } ``` See [Pre-filling forms with default data](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md#pre-filling-forms-with-default-data) for the full pattern. The schema's `default` keyword is an annotation - it describes what a property's default value would be, but doesn't pre-populate the form. Return `data` from your data source whenever you want the form to open with values already filled in. #### Fixed values with `const`[​](#fixed-values-with-const "Direct link to fixed-values-with-const") `const` asserts that a property holds a specific value. Its primary use in JSON Forms is inside a `oneOf` array to build dropdowns where the displayed label and the stored value differ: const inside oneOf ```json { "continent": { "type": "string", "oneOf": [ { "title": "North America", "const": "NA" }, { "title": "Europe", "const": "EU" } ] } } ``` See [Dropdowns with `enum` and `oneOf`](#dropdowns-with-enum-and-oneof) for the full pattern. #### Dropdowns with `enum` and `oneOf`[​](#dropdowns-with-enum-and-oneof "Direct link to dropdowns-with-enum-and-oneof") Two schema patterns produce dropdown menus: * **`enum`** - a flat array of string values. The customer sees the same value that the config variable stores. * **`oneOf`** - an array of `{ "title": ..., "const": ... }` objects. The customer sees the `title`; the config variable stores the `const`. enum ```json { "continent": { "type": "string", "enum": ["North America", "Europe", "Asia"] } } ``` oneOf with custom values ```json { "continent": { "type": "string", "oneOf": [ { "title": "North America", "const": "NA" }, { "title": "Europe", "const": "EU" }, { "title": "Asia", "const": "AS" } ] } } ``` Use `oneOf` whenever the display label and stored value need to differ, or when you're populating the list from a third-party API and want to store a stable ID. Add [`options.autocomplete: true`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#autocomplete) to the UI schema element to render a `oneOf` dropdown as a type-ahead. #### Arrays[​](#arrays "Direct link to Arrays") Arrays let the customer add one or more items with the same structure: Array of objects ```json { "type": "array", "items": { "type": "object", "properties": { "channel": { "type": "string" }, "notification": { "type": "string", "enum": ["Created", "Updated"] } }, "required": ["channel", "notification"] } } ``` By default, arrays render as a [table](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#arrays). Use [`options.layout`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#layout) to switch to an accordion instead, or use the [`ListWithDetail`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md#listwithdetail) UI type for a master-detail layout. `minItems`, `maxItems`, and `uniqueItems` apply to the array itself. Validation keywords on `items` apply to each array entry. #### References with `$ref`[​](#references-with-ref "Direct link to references-with-ref") Prismatic resolves every `$ref` in your schema before rendering the form. This lets you reuse subschemas without worrying about whether a specific renderer resolves references on its own. Schema with a shared subschema ```json { "type": "object", "definitions": { "address": { "type": "object", "properties": { "street": { "type": "string" }, "city": { "type": "string" } }, "required": ["street", "city"] } }, "properties": { "billing": { "$ref": "#/definitions/address" }, "shipping": { "$ref": "#/definitions/address" } } } ``` #### Descriptions and titles[​](#descriptions-and-titles "Direct link to Descriptions and titles") Both `description` and `title` on a property surface in the UI: * **`description`** appears as helper text beneath the input. * **`title`** overrides the auto-generated label (otherwise derived from the property name). The UI schema's `label` takes precedence over the schema's `title` when both are set. Descriptions show even when the input is not focused unless you set [`options.showUnfocusedDescription: false`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#showunfocuseddescription). #### Required fields[​](#required-fields "Direct link to Required fields") Mark required fields in the parent object's `required` array, not on the field itself: required ```json { "type": "object", "properties": { "companyName": { "type": "string" }, "companyDescription": { "type": "string" } }, "required": ["companyName"] } ``` Required fields display an asterisk next to their label. Set [`options.hideRequiredAsterisk: true`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#hiderequiredasterisk) on a control or layout to suppress the asterisk for that element. #### Composing schemas with `allOf`, `anyOf`, `oneOf`, and `not`[​](#composing-schemas-with-allof-anyof-oneof-and-not "Direct link to composing-schemas-with-allof-anyof-oneof-and-not") The composition keywords let you validate against multiple subschemas and pull properties from them into the form. JSON Forms walks into these subschemas when a UI schema control scopes a property defined inside one of them, so a property declared inside an `allOf` branch renders just like a top-level property would: Property defined inside allOf ```json { "type": "object", "allOf": [ { "properties": { "accountId": { "type": "string" } } } ], "properties": { "accountName": { "type": "string" } } } ``` UI schema referencing the allOf-defined property ```json { "type": "VerticalLayout", "elements": [ { "type": "Control", "scope": "#/properties/accountName" }, { "type": "Control", "scope": "#/properties/accountId" } ] } ``` Both controls render, and customer input for `accountId` flows into the form data as expected. | Keyword | Validation | Renders properties from the subschema? | | ------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `allOf` | Yes | Yes - scope controls to `#/properties/` and the property resolves. | | `anyOf` | Yes | Yes - same resolution as `allOf`. | | `oneOf` | Yes | Yes for full subschemas *and* in the [dropdown pattern](#dropdowns-with-enum-and-oneof) (`{ "title", "const" }` entries on a string property). | | `not` | Yes | Not applicable - `not` forbids a shape and doesn't contribute properties. Most useful inside [UI rule conditions](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/rules.md#schema). | Keep in mind: JSON Forms renders every control the UI schema declares regardless of whether the condition's data matches. Composition keywords influence *validation*, not visibility. For show/hide behavior, use [UI rules](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/rules.md). For example, `not` inside a rule condition expresses "show this element when the country is anything other than the US": not in a UI rule ```json { "rule": { "effect": "SHOW", "condition": { "scope": "#/properties/country", "schema": { "not": { "const": "United States" } } } } } ``` #### Conditional validation with `if` / `then` / `else`[​](#conditional-validation-with-if--then--else "Direct link to conditional-validation-with-if--then--else") `if` / `then` / `else` lets you apply different validation rules depending on the form data: Conditional validation ```json { "type": "object", "properties": { "tier": { "type": "string", "enum": ["free", "paid"] }, "promoCode": { "type": "string" } }, "if": { "properties": { "tier": { "const": "paid" } } }, "then": { "required": ["promoCode"] } } ``` Properties declared inside `then` or `else` subschemas resolve the same way `allOf` properties do - scope a control to `#/properties/` and it renders. What `if` / `then` / `else` *doesn't* do is hide controls based on the condition; every control the UI schema declares stays visible. For conditional visibility, use [UI rules](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/rules.md) instead. #### Keywords Prismatic handles differently[​](#keywords-prismatic-handles-differently "Direct link to Keywords Prismatic handles differently") A few JSON Schema keywords are worth calling out because their effect on the form isn't obvious: * **`additionalProperties`** - Prismatic only renders controls for properties you declare in `properties` (or reach through composition keywords), so schema-level `additionalProperties: false` doesn't change the UI. AJV still enforces it during validation when the form data contains keys the schema doesn't cover. * **`readOnly`** - schema-level `readOnly: true` disables the input control, so the customer can see the value but not edit it. You can also set [`options.readonly`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#readonly) on a UI schema element to apply the same effect, including to layouts so an entire group becomes non-editable. * **`writeOnly`** - Prismatic doesn't use `writeOnly` as a rendering hint. If you need a masked input, use [`"format": "password"`](#password) instead. #### Related pages[​](#related-pages "Direct link to Related pages") * [UI schema reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/ui-schema.md) * [Renderers reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md) * [Options reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md) * [JSON Schema specification](https://json-schema.org/) --- #### UI Schema Reference The **UI schema** is a JSON document that describes how to render a [schema](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md). It's a tree of elements - each element has a `type` that identifies what kind of UI it produces. This page documents every UI schema element type Prismatic supports, along with properties that apply to every element. For per-renderer behavior, see the [renderers reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md). For the full catalog of `options` values, see the [options reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md). #### Common properties[​](#common-properties "Direct link to Common properties") These properties can appear on most UI schema elements: | Property | Applies to | Description | | ---------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | All elements | Identifies the element kind (`Control`, `VerticalLayout`, and so on) | | `scope` | `Control` | JSON Pointer into the schema - typically `#/properties/propertyName` | | `label` | `Control`, `Group`, `Category`, `Label` | Overrides the auto-generated label. Set `false` to hide on controls. | | `options` | All elements | Object of renderer-specific options - see [options reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md) | | `rule` | All elements | Conditionally show, hide, enable, or disable - see [rules reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/rules.md) | | `elements` | Layouts and categories | Child elements rendered inside this element | #### `Control`[​](#control "Direct link to control") A `Control` binds one schema property to an input. Control ```json { "type": "Control", "scope": "#/properties/companyName", "label": "Company Name" } ``` | Property | Required | Description | | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `scope` | Yes | JSON Pointer referencing the schema property (e.g. `#/properties/name`) | | `label` | No | Override the derived label. `false` hides the label. | | `options` | No | Renderer-specific options - see [options reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md) | | `rule` | No | Conditional display - see [rules reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/rules.md) | When the schema is a root array, you can reference the whole array with `scope: "#"`: Array control at the root ```json { "type": "Control", "scope": "#", "options": { "showSortButtons": true } } ``` JSON Forms picks the renderer for a `Control` based on the schema type, format, and options at the pointed-to location. See the [renderers reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md) for the full mapping. #### `VerticalLayout`[​](#verticallayout "Direct link to verticallayout") Stacks child elements vertically. This is the most common top-level layout. VerticalLayout ```json { "type": "VerticalLayout", "elements": [ { "type": "Control", "scope": "#/properties/companyName" }, { "type": "Control", "scope": "#/properties/numEmployees" } ] } ``` #### `HorizontalLayout`[​](#horizontallayout "Direct link to horizontallayout") Arranges child elements side by side, splitting horizontal space evenly between them. HorizontalLayout ```json { "type": "HorizontalLayout", "elements": [ { "type": "Control", "scope": "#/properties/firstName" }, { "type": "Control", "scope": "#/properties/lastName" } ] } ``` For *n* children, each occupies 1/*n* of the row. Horizontal layouts work best for short inputs that belong together semantically - like a first name and last name, or a date range. #### `Group`[​](#group "Direct link to group") A vertical layout with a labeled container around it. Use `Group` to visually bundle related inputs. Group ```json { "type": "Group", "label": "Address", "elements": [ { "type": "Control", "scope": "#/properties/street" }, { "type": "Control", "scope": "#/properties/city" } ] } ``` | Property | Required | Description | | ---------- | -------- | ---------------------------------------- | | `label` | Yes | Heading displayed above the group | | `elements` | Yes | Child elements rendered inside the group | #### `Categorization`[​](#categorization "Direct link to categorization") Splits the form into multiple tabbed or stepped pages. A `Categorization` contains only `Category` elements. Categorization with tabs ```json { "type": "Categorization", "elements": [ { "type": "Category", "label": "Contacts", "elements": [{ "type": "Control", "scope": "#/properties/contacts" }] }, { "type": "Category", "label": "Leads", "elements": [{ "type": "Control", "scope": "#/properties/leads" }] } ] } ``` By default, categories render as tabs at the top of the form. Set [`options.variant: "stepper"`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#variant) to render them as a stepper instead, with optional next and previous buttons from [`options.showNavButtons: true`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#shownavbuttons): Categorization as a stepper ```json { "type": "Categorization", "elements": [ { "type": "Category", "label": "Step 1", "elements": [ /* ... */ ] }, { "type": "Category", "label": "Step 2", "elements": [ /* ... */ ] } ], "options": { "variant": "stepper", "showNavButtons": true } } ``` ##### `Category`[​](#category "Direct link to category") Contained by a `Categorization`. Represents one tab or step. | Property | Required | Description | | ---------- | -------- | ------------------------------------------ | | `label` | Yes | Tab or step label | | `elements` | Yes | Child elements rendered in this category | | `rule` | No | Hide or disable the category conditionally | #### `Label`[​](#label "Direct link to label") Renders static text in the form. Useful for inline instructions, separators, or validation messages generated by a downstream data source (see [JSON Form Validation](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md)). Label ```json { "type": "Label", "text": "Enter your primary contact information below." } ``` | Property | Required | Description | | -------- | -------- | ------------------- | | `text` | Yes | Text to display | | `rule` | No | Conditional display | #### `ListWithDetail`[​](#listwithdetail "Direct link to listwithdetail") Renders an array as a master list on the left and a per-item detail form on the right. Use it when the array has many fields per item and you want the customer to focus on one item at a time. ListWithDetail ```json { "type": "ListWithDetail", "scope": "#/properties/contacts" } ``` | Property | Required | Description | | --------- | -------- | ---------------------------------------------- | | `scope` | Yes | JSON Pointer to the array property | | `options` | No | `options.detail` controls the detail UI schema | For lighter-weight array rendering, use a `Control` scoped to the array and set [`options.layout: "Accordion"`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#layout) or [`options.layout: "Table"`](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md#layout) instead. #### Arrays without `ListWithDetail`[​](#arrays-without-listwithdetail "Direct link to arrays-without-listwithdetail") You'll usually render arrays with a plain `Control`, not `ListWithDetail`. Prismatic chooses the default array renderer based on the schema and options: * An `array` of objects with no `options.layout` → [table](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#table) * An `array` with `options.layout: "Accordion"` → [accordion](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#accordion) * An `array` with `options.layout: "Table"` → [table](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#table) (explicit) * An `array` of primitives → inline multi-input list See the [renderers reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md#arrays) for details on each array renderer. #### Validation mode[​](#validation-mode "Direct link to Validation mode") You set **Validation Mode** on the config variable in the integration designer, not in the UI schema itself. It controls when schema-generated validation errors appear to the customer. | Mode | Description | | ----------------- | ------------------------------------------------------------------------ | | `ValidateAndShow` | Validates input and displays errors. This is the default. | | `ValidateAndHide` | Validates input and emits errors to the form, but does not display them. | | `NoValidation` | Skips validation entirely. | Use `ValidateAndHide` when you want to rely on a [custom validator data source](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md) instead of the schema's built-in messages. #### Data Source Reset[​](#data-source-reset "Direct link to Data Source Reset") **Data Source Reset** is a Prismatic setting on the config variable, not a JSON Forms feature. When a JSON Forms data source re-runs (for example, because an upstream connection or parameter changed), the config variable may already contain data the customer entered previously. Data Source Reset controls how Prismatic reconciles the new default data with the existing customer data: | Mode | Behavior | | -------- | ----------------------------------------------------------------------------------------------- | | `never` | Prismatic preserves the customer's existing data and ignores new defaults. This is the default. | | `prompt` | Prismatic asks the customer whether to reset the form to the new defaults. | | `always` | Prismatic overwrites the customer's data with the new defaults automatically. | See [Configuring data source reset behavior](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md#configuring-data-source-reset-behavior) for screenshots and a worked example. #### Related pages[​](#related-pages "Direct link to Related pages") * [Schema reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/schema.md) * [Renderers reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/renderers.md) * [Rules reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/rules.md) * [Options reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference/options.md) * [JSON Forms UI schema docs](https://jsonforms.io/docs/uischema/) --- #### Using JSON Forms This guide shows you how to implement common patterns and advanced features with JSON Forms in your configuration wizard. Before you begin, make sure you understand the basics covered in [What are JSON Forms?](https://prismatic.io/docs/integrations/data-sources/json-forms.md). #### Creating layouts[​](#creating-layouts "Direct link to Creating layouts") UI elements can be laid out horizontally or vertically, and layouts can be nested to create sophisticated form structures. ##### Nested layouts[​](#nested-layouts "Direct link to Nested layouts") In this example, a `HorizontalLayout` contains two `VerticalLayout` elements. The second vertical layout element is a `Group` type layout, which adds a container and `label` around the vertically organized input elements. Nested Layouts ```json { "type": "HorizontalLayout", "elements": [ { "type": "VerticalLayout", "elements": [ { "type": "Control", "scope": "#/properties/companyName" }, { "type": "Control", "scope": "#/properties/companyDescription" } ] }, { "type": "Group", "label": "Additional Data", "elements": [ { "type": "Control", "label": "Employee Count", "scope": "#/properties/numEmployees" }, { "type": "Control", "scope": "#/properties/continent" } ] } ] } ``` ![Screenshot of nested layouts in JSON forms](/docs/assets/images/nested-layouts-2992fbfdda1c61417524bbe7eae421ad.png) [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=nested-layouts) #### Building dropdown menus[​](#building-dropdown-menus "Direct link to Building dropdown menus") You can present customers with dropdown menus of prepopulated options using `enum` or `oneOf` properties in your schema. ##### Using enum for dropdowns[​](#using-enum-for-dropdowns "Direct link to Using enum for dropdowns") An `enum` property creates a dropdown menu from a set of string values: Example using enum ```json { "type": "object", "properties": { "continent": { "type": "string", "enum": [ "North America", "South America", "Europe", "Asia", "Africa", "Australia" ] } } } ``` ![Screenshot of dropdown menu in JSON forms](/docs/assets/images/enum-8f07f1ab33c19dee11056a8a2e65b8e5.png) When someone selects "North America", the integration receives `{ continent: "North America" }`. ##### Using oneOf for custom values[​](#using-oneof-for-custom-values "Direct link to Using oneOf for custom values") `oneOf` is useful when you want to show human-readable labels in the UI but return different values to the integration. In this example, customers see full continent names but the integration receives two-letter codes: Example using oneOf ```json { "type": "object", "properties": { "continent": { "type": "string", "oneOf": [ { "title": "North America", "const": "NA" }, { "title": "South America", "const": "SA" }, { "title": "Europe", "const": "EU" }, { "title": "Asia", "const": "AS" }, { "title": "Africa", "const": "AF" }, { "title": "Australia", "const": "AU" } ] } } } ``` When someone selects "North America", the integration receives `{ continent: "NA" }`. #### Working with arrays[​](#working-with-arrays "Direct link to Working with arrays") Arrays let customers specify values for multiple copies of a set of fields. In this example, two fields (`channel` and `notifications`) are properties of an object that can be repeated within a `slackChannels` array: Example of an array ```json { "type": "array", "items": { "type": "object", "properties": { "channel": { "type": "string" }, "notifications": { "type": "string", "enum": [ "Opportunity Created", "Opportunity Updated", "Opportunity Closed/Won", "Opportunity Closed/Lost" ] } } } } ``` ##### Adding sort buttons[​](#adding-sort-buttons "Direct link to Adding sort buttons") You can enable sort buttons in the `uiSchema` to let customers reorder array elements: Example UI Schema ```json { "type": "VerticalLayout", "elements": [ { "type": "Control", "scope": "#", "options": { "showSortButtons": true } } ] } ``` ![Screenshot of a basic array in JSON forms](/docs/assets/images/basic-array-517b7d9f8447f993a606ce4a454cf240.png) The integration receives arrays as a list of objects: ```json [ { "channel": "#sales-opportunities", "notifications": "Opportunity Created" }, { "channel": "#sales-opportunities", "notifications": "Opportunity Updated" }, { "channel": "#sales-opportunities-won", "notifications": "Opportunity Closed/Won" }, { "channel": "#sales-opportunities-lost", "notifications": "Opportunity Closed/Lost" } ] ``` [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=basic-array) ##### Using accordion layout for arrays[​](#using-accordion-layout-for-arrays "Direct link to Using accordion layout for arrays") Arrays can be presented in an `Accordion` layout to save space in the configuration wizard. Specify which field should be used for accordion labels with the `elementLabelProp` option: Accordion layout UI Schema ```json { "type": "VerticalLayout", "elements": [ { "type": "Control", "scope": "#", "options": { "layout": "Accordion", "elementLabelProp": "channel", "showSortButtons": true } } ] } ``` ![Screenshot of a basic array in JSON forms](/docs/assets/images/basic-array-accordion-92513b82a6c7fc5837dbe71243741a84.png) [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=basic-array-accordion) ##### Building field mappers with arrays[​](#building-field-mappers-with-arrays "Direct link to Building field mappers with arrays") Arrays are especially useful when building data mapping UIs. See the [Building a Field Mapper Data Source](https://prismatic.io/docs/integrations/data-sources/field-mapping/salesforce-field-mapper.md) tutorial for a complete example of how to build a field mapper between custom fields in Salesforce and your app. [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=data-mapper-accordion) #### Showing and hiding fields conditionally[​](#showing-and-hiding-fields-conditionally "Direct link to Showing and hiding fields conditionally") You can show or hide fields based on the value of other fields using UI rules. A `rule` contains a `condition` that determines whether an `effect` should be applied. In this example, the `convertToUSD` input field only appears when the `country` field is not `United States`: ```json { "type": "Control", "scope": "#/properties/convertToUSD", "options": { "toggle": true }, "rule": { "effect": "SHOW", "condition": { "scope": "#/properties/country", "schema": { "not": { "const": "United States" } } } } } ``` [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=ui-rules) For more rule examples, see the JSON Forms [rules documentation](https://jsonforms.io/docs/uischema/rules). #### Validating user input[​](#validating-user-input "Direct link to Validating user input") You can validate what customers enter into input fields by adding validation properties to your schema. ##### Common validation properties[​](#common-validation-properties "Direct link to Common validation properties") * `format`: Built-in formats like `email`, `uri`, `ipv4`, `date`, `time`, `date-time` * `minimum` and `maximum`: Numeric range validation * `minLength` and `maxLength`: String length validation * `pattern`: Regular expression pattern matching * `formatMinimum` and `formatMaximum`: Date range validation Examples of input validation ```json { "type": "object", "properties": { "stringMinLength": { "type": "string", "minLength": 5, "description": "Please enter a string with at least 5 characters" }, "stringMaxLength": { "type": "string", "maxLength": 5, "description": "Please enter a string with at most 5 characters" }, "email": { "type": "string", "format": "email", "description": "Please enter a valid email address." }, "uri": { "type": "string", "format": "uri", "description": "Please enter a valid URI." }, "ipv4": { "type": "string", "format": "ipv4", "description": "Please enter a valid IPv4 address." }, "regex": { "type": "string", "pattern": "^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$", "description": "Please enter a valid phone number in the form (123)456-7890." }, "intOneTen": { "type": "integer", "minimum": 1, "maximum": 10, "description": "Please enter an integer between 1 and 10." }, "startDate": { "type": "string", "format": "date", "description": "Please enter a date between the first and last day of the current month.", "formatMinimum": "2023-08-01", "formatMaximum": "2023-08-31" } }, "required": [ "stringMinLength", "stringMaxLength", "email", "uri", "ipv4", "regex", "intOneTen", "startDate" ] } ``` [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=input-validation) For custom validation rules beyond the built-in options, see [JSON Form Validation](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md). #### Adding autocomplete to dropdown inputs[​](#adding-autocomplete-to-dropdown-inputs "Direct link to Adding autocomplete to dropdown inputs") You can provide autocomplete functionality for `oneOf` dropdown menus by enabling the `autocomplete` option in the `uiSchema`: ```json { "options": { "autocomplete": true } } ``` In this example, typing `"Cor"` will match both `"Acme Corp"` and `"Umbrella Corp"`: ![Screenshot of a autocomplete in dropdown menu in JSON forms](/docs/assets/images/autocomplete-23c9858d78673c7abe1140c9b88b5f99.png) [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=autocomplete) #### Customizing field mapper layouts[​](#customizing-field-mapper-layouts "Direct link to Customizing field mapper layouts") The [Building a Field Mapper Data Source](https://prismatic.io/docs/integrations/data-sources/field-mapping/salesforce-field-mapper.md) tutorial demonstrates a basic field mapper. You can control the field mapper layout by using a custom `options.detail` specification. To create a custom layout: 1. Set `options.layout` to `"Accordion"` 2. Define your custom layout structure using `options.detail` Custom layout UI Schema ```json { "type": "Control", "label": "Salesforce Lead <> Acme Sale Field Mapper", "scope": "#", "options": { "layout": "Accordion", "detail": { "type": "HorizontalLayout", "elements": [ { "type": "Control", "scope": "#/properties/source", "options": { "autocomplete": true } }, { "type": "Control", "scope": "#/properties/destination", "options": { "autocomplete": true } } ] } } } ``` ![Screenshot of a custom field mapper in JSON forms](/docs/assets/images/field-mapper-custom-ui-8c8135837888c59acdbd7f50c9677023.png) [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=data-mapper-detail) #### Creating forms with multiple tabs[​](#creating-forms-with-multiple-tabs "Direct link to Creating forms with multiple tabs") JSON Forms can display content across multiple tabs using categorization. Create tabs with a `"type": "Categorization"` layout in your `uiSchema`. Each category becomes a tab with its own `label`: Tabs UI Schema ```json { "type": "Categorization", "elements": [ { "type": "Category", "label": "Contacts", "elements": [ { "type": "Control", "scope": "#/properties/contacts" } ] }, { "type": "Category", "label": "Leads", "elements": [ { "type": "Control", "scope": "#/properties/leads" } ] }, { "type": "Category", "label": "Opportunities", "elements": [ { "type": "Control", "scope": "#/properties/opportunities" } ] } ] } ``` ![Screenshot of a field mapper with tabs in JSON forms](/docs/assets/images/field-mapper-tabs-29ffaaf75a0447b93d101d5e78294663.png) [See example in playground](https://prismatic.io/docs/jsonforms/playground?key=data-mapper-tabs) This is particularly useful for complex field mappers that handle multiple object types. #### Using JSON Forms in custom components[​](#using-json-forms-in-custom-components "Direct link to Using JSON Forms in custom components") If your form is static and doesn't depend on fetching data from third-party apps, you can provide `schema` and `uiSchema` directly to the [JSONForms component](https://prismatic.io/docs/components/jsonforms.md). For forms that need to present dropdown menus or other fields populated from third-party apps, build the form in a custom component using the custom component SDK. See the [JSON Forms data sources documentation](https://prismatic.io/docs/custom-connectors/data-sources.md#json-forms-data-sources) for details. ##### Pre-filling forms with default data[​](#pre-filling-forms-with-default-data "Direct link to Pre-filling forms with default data") Your custom JSON Forms data sources can include a `data` property in addition to `schema` and `uiSchema`. When you include `data`, the form is pre-filled with the values you provide. #### Handling form data updates[​](#handling-form-data-updates "Direct link to Handling form data updates") Consider a config wizard with this flow: * **Page 1**: Connect to Microsoft SharePoint with OAuth 2.0 * **Page 2**: Select a SharePoint site from a dropdown menu * **Page 3**: Display a JSON form tailored to the selected site, with default data from that site This works fine when a customer first completes the wizard. However, if they reopen the config wizard and select a different site on page 2, you need to handle the stale data from their previous selection. ##### Configuring data source reset behavior[​](#configuring-data-source-reset-behavior "Direct link to Configuring data source reset behavior") By default, if a config variable has customer-supplied data, Prismatic ignores the default `data` your config variable generates in favor of the customer's previous selections. You can override this behavior in the config wizard designer by navigating to **Data Source Reset** within your JSON Forms config variable: ![Configure data source resetting in config wizard](/docs/assets/images/data-source-reset-c587653d70d5be7fb1fcfc6c7e1119a7.png) Choose one of these options: * **Never**: Uses the default behavior and displays the customer's previous data * **Prompt**: Notifies the customer that their data may be stale and lets them reset the form to the default values your data source supplies ![Configure data source resetting in config wizard](/docs/assets/images/data-source-reset-prompt-e3f4ee572acaddfda9e24c77bfa4f42f.png) * **Always**: Automatically overrides the customer's previous selection with your data source's default `data` when the data source inputs change #### Next steps[​](#next-steps "Direct link to Next steps") You now know how to implement common JSON Forms patterns. Continue learning with these resources: * **Look up specifics**: Use the [JSON Forms Reference](https://prismatic.io/docs/integrations/data-sources/json-forms/reference.md) for exhaustive documentation of the schema, UI schema, renderers, rules, and options * **Build a complete field mapper**: Follow the [Building a Field Mapper Data Source](https://prismatic.io/docs/integrations/data-sources/field-mapping/salesforce-field-mapper.md) tutorial * **Add custom validation**: Learn about [JSON Form Validation](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md) * **Create dynamic forms**: See [JSON Forms data sources](https://prismatic.io/docs/custom-connectors/data-sources.md#json-forms-data-sources) in custom components * **Explore more examples**: Try the [JSON Forms Playground](https://prismatic.io/docs/jsonforms/playground) --- #### What is the Embedded Marketplace? The **embedded marketplace** is a pre-built integration catalog that you embed directly into your application, enabling customers to browse, configure, and deploy integrations themselves without requiring support from your team. ![Integration marketplace embedded in your app](/docs/assets/images/acme-saas-example-f3c74aa7fa39bbe6367853d290ad7418.png) #### How it works[​](#how-it-works "Direct link to How it works") The embedded marketplace provides a complete self-service experience for your customers: 1. **Browse integrations**: Customers view available integrations in a catalog that matches your app's branding 2. **Deploy instances**: Customers select an integration and are guided through a custom configuration wizard 3. **Manage integrations**: Customers can view, reconfigure, pause, or remove their deployed integration instances This contrasts with the traditional approach where your team members deploy integrations on behalf of customers through the Prismatic web app. #### Benefits of self-service[​](#benefits-of-self-service "Direct link to Benefits of self-service") Enabling customers to deploy integrations themselves offers several advantages: * **Reduced support burden**: Your team doesn't need to manually configure and deploy each integration for every customer * **Faster customer activation**: Customers can enable integrations immediately without waiting for your team * **Better customer experience**: Customers have control over their integrations and can make changes when needed * **Scales with your customer base**: Self-service deployment scales effortlessly as you add more customers and integrations #### Marketplace features[​](#marketplace-features "Direct link to Marketplace features") The embedded marketplace includes powerful capabilities: * **Filtering and search**: Customers can filter integrations by category or label, and search by name * **Multiple instances**: Customers can deploy multiple instances of the same integration (for example, separate instances for different Shopify stores) * **Custom branding**: The marketplace matches your application's look and feel through theming * **Configuration wizard**: Each integration includes a custom configuration wizard you design to collect necessary settings * **Inline or popover display**: Choose whether to embed the marketplace as an iframe or display it in a popover modal You can also create a [custom marketplace UI](https://prismatic.io/docs/embed/custom-marketplace-ui.md) using Prismatic's GraphQL API if you need more control over the user experience. #### Embedding the marketplace[​](#embedding-the-marketplace "Direct link to Embedding the marketplace") Embedding the marketplace in your application requires minimal code using Prismatic's embedded SDK. The SDK provides JavaScript functions to authenticate customer users and display the marketplace interface. Here's a simple example: ```javascript import prismatic from "@prismatic-io/embedded"; // Authenticate the customer user await prismatic.authenticate({ token: customerToken }); // Display the marketplace prismatic.showMarketplace({ selector: "#marketplace-container", usePopover: false, }); ``` The marketplace can be embedded as an iframe within your application or displayed as a popover overlay. #### Next steps[​](#next-steps "Direct link to Next steps") Ready to add an embedded marketplace to your application? * [Embedding the marketplace](https://prismatic.io/docs/embed/marketplace.md) - Technical guide to implementing the marketplace * [Installing the embedded SDK](https://prismatic.io/docs/embed/get-started/install-embedded-sdk.md) - Set up the SDK in your application * [Config wizard](https://prismatic.io/docs/integrations/config-wizard.md) - Design configuration experiences for your integrations * [Theming the marketplace](https://prismatic.io/docs/embed/theming.md) - Match the marketplace to your app's branding * [Custom marketplace UI](https://prismatic.io/docs/embed/custom-marketplace-ui.md) - Build a fully custom marketplace experience --- #### Flows Overview An integration in Prismatic consists of one or more **flows**, each serving a specific purpose. For example, if you're building a bidirectional integration with Salesforce, you might have several flows: 1. A flow that is invoked by your app, and syncs leads from your app to Salesforce. 2. A flow that is invoked by your app, and syncs contacts from your app to Salesforce. 3. A flow that queries Salesforce for new leads, and syncs changes to your app. 4. A flow that queries Salesforce for new contacts, and syncs changes to your app. In the low-code designer, a [flow](https://prismatic.io/docs/integrations/low-code-integration-designer/flows.md) represents a series of steps that run one after another. Each [step](https://prismatic.io/docs/integrations/low-code-integration-designer/steps.md) runs an action (like "Create Salesforce Lead" or "Get Salesforce Contacts"). You can feed the results of steps into subsequent steps, so the results of "Get Salesforce Leads" can be fed into a step that syncs SFDC leads to your app. A [flow in code-native](https://prismatic.io/docs/integrations/code-native/flows.md) represents a TypeScript function that runs when the flow is invoked. Your flow's code can be as simple or complex as required - it can be a simple HTTP call to fetch data from a third-party, or it can be a sophisticated function that fetches, parses, filters and transforms data from one app to another. Regardless of whether you're using low-code or code-native to build your integrations, each flow begins with a [trigger](https://prismatic.io/docs/integrations/triggers.md). A trigger determines when the flow starts - either on a [schedule](https://prismatic.io/docs/integrations/triggers/schedule.md) (for example, every 5 minutes) or in response to an [app event](https://prismatic.io/docs/integrations/triggers/app-events.md) (for example, when a new file in Dropbox is created). When a flow's trigger is invoked, an [execution](https://prismatic.io/docs/monitor-instances/executions.md) starts. By default, multiple executions of a flow run concurrently. You can optionally run executions sequentially using a [FIFO queue](https://prismatic.io/docs/integrations/triggers/fifo-queue.md). --- #### Runner Environment and Limits Integration instances execute in isolated, containerized Node.js environments with specific resource allocations and execution constraints. Understanding these limits helps you design integrations that perform reliably within the platform's boundaries. #### The Prismatic runner environment[​](#the-prismatic-runner-environment "Direct link to The Prismatic runner environment") Instances of integrations (and test runs of integrations) execute in isolated NodeJS containers with distinct filesystems and memory. Two instances of the same integration run in distinct isolated environments. Integrations currently run using NodeJS version 22. #### Runner limitations[​](#runner-limitations "Direct link to Runner limitations") ##### Memory allocation[​](#memory-allocation "Direct link to Memory allocation") The Prismatic integration runner is allocated 1GB of RAM for execution by default. If you find that your instances are running out of memory, please review [Memory Management](https://prismatic.io/docs/integrations/memory-management.md) for strategies on how to stay within memory limits. Usually, optimizing data processing and avoiding large in-memory datasets can help mitigate memory issues. ##### Execution time limitations[​](#execution-time-limitations "Direct link to Execution time limitations") An instance will run for up to 15 minutes. If you have large datasets to process, you can break data into smaller chunks and process chunks [in parallel](https://prismatic.io/docs/integrations/common-patterns/processing-data-in-parallel.md) or in several subsequent executions with [large data syncs](https://prismatic.io/docs/integrations/common-patterns/large-data-sync.md). #### Instance profiles[​](#instance-profiles "Direct link to Instance profiles") Instance profiles allow you to customize the resource allocations and execution constraints for specific integrations and instances. By assigning an instance profile to an integration or instance, you can modify: 1. **Allocated memory**, allowing you to increase memory limits for memory-intensive integrations. By default, 1GB (1024MB) of memory is allocated. You can increase this to a maximum of 10GB (10240MB). 2. **Step result retention**, allowing you to control whether step results are retained. Some compliance requirements may necessitate not retaining step results or logs. 3. **Log retention**, allowing you to control whether logs are saved. 4. **Quickstart**, which keeps instance runner environments "warm" to reduce cold start times. This is important for some synchronous invocations, where low latency is critical, but typically not necessary for asynchronous invocations. Some features need to be enabled Some of these features may require additional costs and must be enabled by your Prismatic account manager. To create a new instance profile, navigate to **Settings** > **Instance Profiles** in the Prismatic web app and select **+ Instance Profile**. There, give your instance profile a name, configure allocated memory, step result and log retention, and quickstart settings as needed. ![Instance profile configuration](/docs/assets/images/create-instance-profile-ae5a1c241ff1fd41452ff98796880615.png) To assign a default instance profile to an integration, open the integration's **Integration Details** and select the desired instance profile from the **Default Instance Profile** dropdown. ![Assign instance profile to integration](/docs/assets/images/assign-instance-profile-to-integration-034be6ae477b38fa9f9dbdb567796515.png) If a single instance of an integration requires different settings from the rest, you can override instance profile settings for that specific instance by opening the instance and clicking the profile's name under **Profile**. ![Override instance profile for specific instance](/docs/assets/images/override-instance-profile-5701f3290b1e1ec8afd3851d4bfb2964.png) #### Webhook limitations[​](#webhook-limitations "Direct link to Webhook limitations") ##### Webhook request size limitations[​](#webhook-request-size-limitations "Direct link to Webhook request size limitations") Webhook payload size is limited to 6MB. 6MB is generally large enough to handle most JSON, XML, or other webhook payloads. If the payload you need to process exceeds 6MB (i.e. you are processing large images, PDFs, etc.), we recommend saving the large file to a file storage system first (Dropbox, Amazon S3, Azure Files, etc.) and sending *metadata* about the file in your webhook request. Your integration can use the metadata to fetch the file for processing. ##### Synchronous webhook response size limitations[​](#synchronous-webhook-response-size-limitations "Direct link to Synchronous webhook response size limitations") When a webhook is invoked synchronously, the response contains the results of the last step of the flow (so if the last step returned a PDF file, the webhook response would be a PDF file). Prismatic writes the response to a file in Amazon S3 and responds with an HTTP 303 (Redirect) to the object in S3. Step results have a maximum size of 500MB. If the results that you generate exceed 500MB, consider writing the file to a file storage system (Dropbox, your own Amazon S3 bucket, etc.) and returning metadata about the file instead. **Read More**: [Synchronous Invocations and Redirects](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md#synchronous-invocations-and-redirects) ##### Synchronous invocation timeouts[​](#synchronous-invocation-timeouts "Direct link to Synchronous invocation timeouts") A webhook request will time out after 30 seconds. Webhook requests to [synchronous triggers](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md) (triggers that wait until the execution finishes running before responding) must complete their work in under 30 seconds. ##### Webhook rate limiting and concurrent executions[​](#webhook-rate-limiting-and-concurrent-executions "Direct link to Webhook rate limiting and concurrent executions") The number of concurrent executions your organization can run is determined by your pricing plan. If your organization is already running that many executions and an additional request is received, the requester will receive a 429 "too many requests" response. When an execution starts, the first log line includes the number of executions that are currently running. ![Concurrent executions in logs](/docs/assets/images/concurrent-executions-f106718f1ed636578e760286db18203d.png) ##### Alerting on concurrency limits[​](#alerting-on-concurrency-limits "Direct link to Alerting on concurrency limits") You can alert your team when you approach your concurrent execution limit by creating a concurrency threshold alert monitor. To do that, open **Monitors** from the left sidebar and then select **+ Add alert monitor**. Give your monitor a name and select **Concurrency Threshold Warning** as the **Trigger**. ![Concurrent execution alert monitor](/docs/assets/images/alert-monitor-9e0a09ad5a8c248dfcd060b554211f1d.png) Within your trigger, select recipients to receive alerts (via email or webhook request). You will be alerted if the number of concurrent executions you're running exceeds 80% of your maximum limit (for example, if your contract allows for 500 concurrent executions, you will receive an alert if you run over 400 executions at one time). ##### Per-customer concurrent execution limits[​](#per-customer-concurrent-execution-limits "Direct link to Per-customer concurrent execution limits") Enterprise feature Per-customer execution concurrency limits are available on Enterprise plans. If a single customer's high-volume integrations are consuming a disproportionate share of your organization's execution capacity, you can set a concurrency limit specific to that customer. When a customer reaches their limit, additional execution attempts return a 429 "too many requests" response. You can set per-customer limits from the customer's **Utilization** tab or from your organization's **Settings** > **Execution Limits** tab in the Prismatic web app. See [per-customer execution concurrency](https://prismatic.io/docs/customers/managing-customers.md#per-customer-execution-concurrency) for setup instructions. **Alerting:** You can create a **Customer Concurrency Threshold Warning** [alert monitor](https://prismatic.io/docs/monitor-instances/alerting/alert-monitors.md) to notify your team when any customer with a set limit reaches 80% of their allocation. To do that, open **Monitors** from the left sidebar, select **+ Add alert monitor**, and choose **Customer Concurrency Threshold Warning** as the trigger. ![Customer concurrency threshold warning alert monitor](/docs/assets/images/customer-concurrency-alert-monitor-336ccd7da68371998ce8a5c5227f18aa.png) --- #### Instance Lifecycle When an instance is created or deleted, lifecycle functions execute to manage resources and perform setup or cleanup tasks. This is useful for configuring third-party resources (like webhooks or cloud storage folders) that are created or deleted alongside the instance. #### An instance's lifecycle[​](#an-instances-lifecycle "Direct link to An instance's lifecycle") Once a user completes a [config wizard](https://prismatic.io/docs/integrations/config-wizard.md) and an instance is deployed, the following lifecycle functions are executed in order: 1. If any flows start with a trigger that contains an `onInstanceDeploy` function, those functions are executed sequentially, synchronously. Once one `onInstanceDeploy` function completes, the next one starts. Order of execution is not guaranteed to be the same as the order in which the flows are defined. 2. After all `onInstanceDeploy` functions have completed, any flows that start with an [Instance Deployed](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger) management trigger are run in parallel, asynchronously. The flows run as normal flow executions and do not wait for each other to complete. 3. After any "Instance Deploy" flows have started (but likely before they complete), if any flows start with a trigger that contains a `webhookLifecycleHandlers.create` function, those functions are executed sequentially, synchronously. Once one webhook `create` function completes, the next one starts. Order of execution is not guaranteed to be the same as the order in which the flows are defined. ##### Example lifecycle execution order[​](#example-lifecycle-execution-order "Direct link to Example lifecycle execution order") For example, suppose our integration has 4 flows: 1. Flow 1's trigger has an `onInstanceDeploy` function and a `webhookLifecycleHandlers.create` function. 2. Flow 2's trigger has only an `onInstanceDeploy` function. 3. Flow 3's trigger is an "Instance Deployed" management trigger. 4. Flow 4's trigger is also an "Instance Deployed" management trigger. In this case, * Flows 1 and 2's `onInstanceDeploy` functions would run first, one after the other. * After those functions complete, flows 3 and 4 would start running in parallel. * Not waiting for flows 3 and 4 to complete, flow 1's `webhookLifecycleHandlers.create` function would run. lifecycle functions run each time an instance is deployed Remember, lifecycle functions run each time an instance is deployed. This includes both the initial deployment of the instance and any subsequent re-deployments (for example, when a user updates the instance configuration). It is important that lifecycle functions are idempotent and can handle being run multiple times for the same instance. #### `onInstanceDeploy` trigger functions[​](#oninstancedeploy-trigger-functions "Direct link to oninstancedeploy-trigger-functions") If a trigger includes an `onInstanceDeploy` function, that function will be executed each time an instance is deployed. This includes both the initial deployment of the instance and any subsequent re-deployments (for example, when a user updates the instance configuration). These functions are useful for setting up resources in third-party applications that your integration interacts with - things like folders in cloud storage services or custom record types in CRMs. An `onInstanceDeploy` function is defined similar to a trigger's `perform` function and can access the same context and input information. ```typescript const acmeFolderWatcher = trigger({ display: { label: "Acme Folder Watcher", description: "This trigger watches for changes in a specific folder", }, scheduleSupport: "valid", synchronousResponseSupport: "invalid", inputs: { connection: input({ label: "Acme Connection", type: "connection", required: true, }), folder: input({ type: "string", label: "Folder", comments: "The folder where the Acme webhook will send events", }), }, /** This function runs each time the flow is invoked (by webhook or schedule) */ perform: async (_context, payload, _inputs) => { return Promise.resolve({ payload }); }, /** This function runs on instance deploy */ onInstanceDeploy: async (context, inputs) => { context.logger.info( `Creating folder ${inputs.folder} in Acme for instance ${context.instance.id}`, ); await createFolder({ connection: inputs.connection, folderName: inputs.folder, }); }, }); ``` Note that these functions run synchronously, one after the other. An instance deployment mutation must complete within 30 seconds, so it's important that `onInstanceDeploy` functions complete quickly. #### `onInstanceDelete` trigger functions[​](#oninstancedelete-trigger-functions "Direct link to oninstancedelete-trigger-functions") `onInstanceDelete` functions work similarly to `onInstanceDeploy` functions, but they are executed when an instance is deleted. This allows you to clean up any resources that were created when the instance was deployed. #### Management triggers[​](#management-triggers "Direct link to Management triggers") Flows that start with an [Instance Deployed](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger) management trigger are executed after all `onInstanceDeploy` functions have completed. These flows run in parallel, asynchronously, and do not block the instance deployment process. They are useful for performing longer-running tasks that don't need to complete before the instance is considered deployed (like initial data syncs). #### `webhookLifecycleHandlers.create` trigger functions[​](#webhooklifecyclehandlerscreate-trigger-functions "Direct link to webhooklifecyclehandlerscreate-trigger-functions") `webhookLifecycleHandlers.create` functions are executed after any "Instance Deployed" management trigger flows have started (but likely before they complete). These functions are useful for creating webhooks in third-party applications that your integration interacts with. Additionally, these functions are run outside of the instance deployment process if you enter [listening mode](https://prismatic.io/docs/integrations/triggers/listening-mode.md) in the integration designer. When entering listening mode, this function is run to temporarily create a webhook for testing purposes. For an example of how to use `webhookLifecycleHandlers.create` and `webhookLifecycleHandlers.delete` functions in a trigger, see the [Custom Triggers](https://prismatic.io/docs/custom-connectors/triggers.md#example-app-event-trigger-using-webhooks). For an additional real-world example, see our implementation of Asana event triggers in [GitHub](https://github.com/prismatic-io/examples/blob/main/components/asana/src/triggers/eventTriggers.ts). #### `webhookLifecycleHandlers.delete` trigger functions[​](#webhooklifecyclehandlersdelete-trigger-functions "Direct link to webhooklifecyclehandlersdelete-trigger-functions") `webhookLifecycleHandlers.delete` functions are executed when an instance is deleted and when you exit listening mode in the integration designer. This allows you to clean up any webhooks that were created when the instance was deployed or when entering listening mode. #### FAQ[​](#faq "Direct link to FAQ") ##### When should I use `onInstanceDeploy` vs. `webhookLifecycleHandlers.create`?[​](#when-should-i-use-oninstancedeploy-vs-webhooklifecyclehandlerscreate "Direct link to when-should-i-use-oninstancedeploy-vs-webhooklifecyclehandlerscreate") These functions serve different purposes: * Use `onInstanceDeploy` functions to set up resources in third-party applications that your integration interacts with (like creating folders or custom record types). * Use `webhookLifecycleHandlers.create` functions specifically for creating webhooks in third-party applications. While both functions run during the instance deployment process, `webhookLifecycleHandlers.create` functions are also run outside of the instance deployment process if you enter [listening mode](https://prismatic.io/docs/integrations/triggers/listening-mode.md) in the integration designer. ##### When should I use a trigger function vs. a management trigger?[​](#when-should-i-use-a-trigger-function-vs-a-management-trigger "Direct link to When should I use a trigger function vs. a management trigger?") An instance deployment must complete within 30 seconds. So, `onInstanceDeploy` and `webhookLifecycleHandlers.create` functions should complete quickly. If you have longer-running tasks that need to occur when an instance is deployed (like initial data syncs), use an [Instance Deployed](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger) management trigger instead. Management triggers generate full executions that can run for up to 15 minutes. --- #### Low-Code vs Code-Native Integrations The Prismatic low-code designer and code-native SDK are both excellent tools for building, testing, and deploying integrations. When using the **low-code designer**, you build integrations by adding triggers, actions, loops, and branches to a canvas. When using the **code-native SDK**, you write TypeScript code to define your triggers and flow logic. Depending on your team structure, technical expertise, and the complexity of the integration you are building, you may choose to use one or the other. ![Low-code or code-native](/docs/assets/images/low-code-or-code-native-3ad05c2f6b5ba1cda9e301d8baa76945.png) #### Quick comparison[​](#quick-comparison "Direct link to Quick comparison") | Topic | Low-Code | Code-Native | | --------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- | | Build method | Integration builders add triggers, flows and steps to an integration using the low-code integration builder. | Integration builders write triggers and integration logic in TypeScript using the code-native SDK. | | Flows | A low-code flow is a sequence of steps that run in a specific order. | A Code-Native flow is a JavaScript function that executes when the flow's trigger is invoked. | | Config Wizard | Config wizards are built within the low-code builder. | The config wizard is defined in TypeScript using the code-native SDK. | | Testing | Integrations are tested from within the integration designer. | Integrations can be tested within Prismatic or locally through unit tests. | | Step results and logs | Step results for each step are collected and stored and can be viewed later. | Code-native integrations are "single-step", and logging can be used for debugging. | | Best fit for | Hybrid teams of developers and non-developers | Highly technical teams who prefer code | #### When to use low-code[​](#when-to-use-low-code "Direct link to When to use low-code") You may want to reach for the low-code designer when: * Your team is looking to save development time and has non-dev resources that are technical enough to build integrations. * It is important for your non-developer team members to have a visual representation of the integration. * You would like your customers to build their own integrations using [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md). #### When to use code-native[​](#when-to-use-code-native "Direct link to When to use code-native") You might want to use code-native when: * You have a highly technical team that is comfortable writing TypeScript. * Your integration requires complex logic that is easier to write in code. * You want to unit test entire integrations rather than individual actions and triggers. * You want to use a version control system to manage your integration code. #### Key differences[​](#key-differences "Direct link to Key differences") ##### Build method[​](#build-method "Direct link to Build method") With the **low-code designer**, you drag and drop components onto a canvas to build your integration. You select actions from pre-built components, configure their inputs, and connect them together to create flows. This visual approach makes it easy to see the structure of your integration at a glance. With **code-native integrations**, you write TypeScript functions that define your integration logic. You import components and call their actions programmatically, handle data transformations in code, and use familiar development tools like your IDE, debugger, and version control. ##### Flows[​](#flows "Direct link to Flows") In the **low-code designer**, a flow is a sequence of steps that execute one after another in a specific order. Each step runs an action from a component, and you can add loops, branches, and error handling to control the flow of execution. The designer shows you a visual representation of this sequence. In **code-native integrations**, a flow is a JavaScript function that executes when triggered. Within that function, you write imperative code to call actions, transform data, make decisions, and handle errors. The function receives a context object with information about the execution and returns a result. ##### Config wizard[​](#config-wizard "Direct link to Config wizard") In the **low-code designer**, you build the configuration wizard directly in the web interface. You add config variables, organize them into pages, and configure their types and validation rules through forms. In **code-native integrations**, you define the config wizard in TypeScript using the SDK. You declare config variables as part of your integration code, specify their types and defaults, and define pages programmatically. ##### Testing[​](#testing "Direct link to Testing") **Low-code integrations** are tested within the Prismatic web app. You can run test executions, view the results of each step, and see logs and data transformations. The designer shows you exactly which step failed and what data was passed between steps. **Code-native integrations** can be tested within Prismatic the same way, but they can also be unit tested locally. You can write unit tests for your flows, mock component actions, and test your integration logic before deploying. Since code-native integrations are TypeScript projects, you can use all the testing tools you're familiar with. ##### Step results and logs[​](#step-results-and-logs "Direct link to Step results and logs") **Low-code integrations** capture detailed results for every step in a flow. You can view the inputs and outputs of each step, see which actions ran, and inspect the data at any point in the flow. This makes debugging straightforward - you can see exactly where data was transformed or where an error occurred. **Code-native integrations** are treated as a single step from Prismatic's perspective. The entire flow function runs as one unit, and Prismatic captures the overall result and any logs you write. For detailed debugging, you can add logging statements throughout your code, or use local unit tests to debug specific functions. #### Next steps[​](#next-steps "Direct link to Next steps") Ready to start building? Choose the approach that fits your team: * [Low-code getting started](https://prismatic.io/docs/get-started/build-integrations/first-integration-low-code.md) - Build your first integration with the visual designer * [Code-native getting started](https://prismatic.io/docs/get-started/build-integrations/first-integration-code-native.md) - Build your first integration with TypeScript * [Low-code designer overview](https://prismatic.io/docs/integrations/low-code-integration-designer.md) - Learn more about the visual builder * [Code-native overview](https://prismatic.io/docs/integrations/code-native.md) - Learn more about the TypeScript SDK * [Converting low-code to code-native](https://prismatic.io/docs/integrations/code-native/get-started/convert-low-code-code-native.md) - Migrate an existing low-code integration to code-native --- #### Memory Management By default, the integration runner is [allocated 1024 MB (1 GB) of memory](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md#memory-allocation). This is sufficient memory to complete most common integration tasks. This article covers common memory issues (and how to avoid them). #### Common out-of-memory scenarios[​](#common-out-of-memory-scenarios "Direct link to Common out-of-memory scenarios") There are a few common situations where you may run out of memory and encounter an error stating `Execution has exceeded the maximum allowed memory`. ##### Loading large files into memory[​](#loading-large-files-into-memory "Direct link to Loading large files into memory") Suppose that your integration needs to pull down a CSV file from an SFTP server, deserialize the file into a JavaScript object, and perform some task on each record. If your file is large (say, 80MB in size) and you use several steps to accomplish your goal, you can end up using well over 1 GB of memory: * The SFTP step will use at least 160MB when downloading the file, as it fetches the file contents as a Buffer and converts that to a string. * The SFTP step will use at least 80MB when serializing and persisting the step result. * The step that deserializes the CSV string to a JavaScript object will (in our experience) use 12 times the size of the file in memory. So, this step will likely consume around 960MB of memory! It's easy to see how you could run out of memory quickly. When you process large files, you may benefit from writing a custom action that processes data in a [Node.js stream](https://nodejs.org/api/stream.html). This way, you load smaller portions of the large file into memory at a time, processing and then discarding each portion. See [Handling Large Files in Custom Components](https://prismatic.io/docs/custom-connectors/handling-large-files-in-custom-components.md) for examples of how to process large files as streams. ##### Excessive logging[​](#excessive-logging "Direct link to Excessive logging") The runner's logger service writes [logs](https://prismatic.io/docs/monitor-instances/logging.md) asynchronously to ensure that your flows are not slowed down by logs. If you log thousands of debug lines in a step, the logger service will use a large amount of memory to serialize and commit each of those lines. Try to avoid placing log lines in a loop that iterates thousands of times. ##### Ending a loop with a large step result[​](#ending-a-loop-with-a-large-step-result "Direct link to Ending a loop with a large step result") If you have a set of steps in a [loop](https://prismatic.io/docs/components/loop.md) and the final step returns large step results, the loop step itself may cause an out-of-memory error. This is because the loop step returns an array containing the result of the last step of the loop from each iteration. For example, suppose you have a loop with three steps, and the final step returns a result that is 50 KB. If the loop iterates 10,000 times, then the loop step will return an array that is 50 KB x 10000 = 500MB. Add a no-op code step that returns `{ data: null }` at the end of your loop to avoid high-memory-usage loop steps. #### Displaying memory usage[​](#displaying-memory-usage "Direct link to Displaying memory usage") If you'd like to see how much memory is used by each step of your flow, you can enable [debug mode](https://prismatic.io/docs/integrations/troubleshooting.md#debug-mode). After each step, you'll see a log line that displays memory usage. `memoryUsage.rss` represents the total memory used by the flow (in *megabytes*). ![Memory usage per step](/docs/assets/images/step-memory-report-039933c25f88a4ebe9287ce503a1dc1a.png) Additionally, when your execution completes, you will see the maximum amount of memory used by your flow in your execution's logs. ![Memory usage per execution](/docs/assets/images/execution-memory-report-c00281438c97dcee20046eee26dff377.png) If you would like to profile a single custom code or custom connector step within your integration to determine which portion of your code consumes memory, you can use the [`debug.memoryUsage`](https://prismatic.io/docs/integrations/troubleshooting.md#measuring-memory-performance-in-a-code-block-or-custom-connector). #### Configuring memory limits[​](#configuring-memory-limits "Direct link to Configuring memory limits") By default, the integration runner runs with 1GB of memory. If you continue to encounter memory constraints and have already implemented the above strategies, please contact your Prismatic account manager. We can work out a proposal to allocate additional memory to some instances using [instance profiles](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md#instance-profiles). --- #### Persisting State between Executions Integrations often need to remember information between executions, such as tracking which records have been processed or maintaining synchronization state. Prismatic provides built-in state management capabilities to persist data across multiple execution runs. #### Persisting state between executions[​](#persisting-state-between-executions "Direct link to Persisting state between executions") Sometimes it's useful to save data from one execution of an instance so it can be used in a subsequent execution. For example, imagine you have an integration that pulls down and processes records from a third-party API. Your integration recently processed a record with ID `123`, and the next time your integration runs, you want to ensure it processes ID `124` and above. Prismatic provides components and programmatic access to persisted state, so you can save data in one execution and use it in the next. You can persist `123` using a [Save Value](https://prismatic.io/docs/components/persist-data.md#saveinstancevalue) action, and then the next time your integration runs, it can use [Get Value](https://prismatic.io/docs/components/persist-data.md#getinstancevalue) to know that `123` was the most recently processed record. #### Levels of persisted state[​](#levels-of-persisted-state "Direct link to Levels of persisted state") There are four levels of persisted data: * **Execution state** stores state for a single execution of a flow. Data stored are ephemeral and not persisted between executions. Execution state is generally used as a temporary variable or as an accumulator. For example, if you are looping over an array of records and fetching data for each one, you could use the [Execution - Append Value to List](https://prismatic.io/docs/components/persist-data.md#appendexecutionvaluetolist) action to append each record to a list, which you could load up after your loop in its entirety. * **Flow state** (programmatically called `instanceState` for historical reasons) stores persisted data for a single flow. A flow can access its own state but not its sibling flows' states. Each flow has its own state, and two different flows can run concurrently without overwriting one another's flow state. Flow state is useful if you have a scheduled process that checks for new records in a third-party app. You can use flow state to persist a cursor, so the next time your flow runs, it can pick up where the previous execution left off. * **Cross-Flow state** is shared between all flows within an instance. A flow can access its sibling flows' cross-flow state. If two flows run concurrently and both change state, the flow that finishes last overwrites the data that the first stored. * **Integration state** stores persisted data for all flows of all instances of an integration. All instances of an integration deployed to different customers share state. Integration state can be useful if you're building an integration with an app that only allows you to specify a single inbound webhook URL for all of your customers. In that situation, you could generate a key-value store, matching customers' third-party external IDs to their instance's webhook URLs, allowing you to route requests that arrive at a shared endpoint to the proper instances. #### How persisted state works in Prismatic[​](#how-persisted-state-works-in-prismatic "Direct link to How persisted state works in Prismatic") The persist data lifecycle is straightforward: 1. When an execution begins, flow state, cross-flow state, and integration state are downloaded and parsed from JSON files. Execution state is initialized to an empty object `{}`. 2. Throughout your execution, you may create, update, or delete key-value pairs in one or more of the states. You can either use the [Persist Data](https://prismatic.io/docs/components/persist-data.md) component or programmatically do something like `context.crossFlowState["Last Product ID"] = "abc-123";`. 3. When the execution completes successfully, execution state disappears. Flow state, cross-flow state, and integration state are compared to their initial values. If their values changed, they are serialized to JSON and written to storage to be loaded in the next execution. Levels of state are evaluated independently Flow, cross-flow, and integration state are evaluated independently. If you change cross-flow state but not flow or integration state, only cross-flow state will be persisted at the end of the execution. #### Limitations of persisted state[​](#limitations-of-persisted-state "Direct link to Limitations of persisted state") It's important to know what persisted state is, and more importantly, what it is not. Persisted state is a useful tool to cache small key/value pairs between executions. It is not a database (and certainly not an [ACID](https://en.wikipedia.org/wiki/ACID) database). Generally, either your app or the app you're integrating with should be considered the source of truth. ##### Concurrent execution limitations[​](#concurrent-execution-limitations "Direct link to Concurrent execution limitations") Persisted state is loaded at the start of an execution and written at the end of a successful execution. State is written out in its entirety when it is changed. Let's look at a few scenarios where you may run concurrent executions: 1. Suppose you want to keep track of a list of records to process. You have two flows that use cross-flow state (one flow adds items to cross-flow state, and one flow reads and removes items from state). Suppose that both flows are invoked at the same time with an initial cross-flow state of `["a", "b"]`. The first flow adds `"c"` to the list and finishes first. It writes out `["a", "b", "c"]` to persisted state. The second flow reads and removes `"a"` and `"b"` from state and writes `[]` to persisted state. In this case, the second flow would overwrite the first flow's state (`[]` would overwrite `["a", "b", "c"]`), and item `"c"` would never be processed. Depending on which flow completes first, you may miss items or double-process items. When processing items, if order is important, consider leveraging a [FIFO queue](https://prismatic.io/docs/integrations/triggers/fifo-queue.md) to ensure that each item is processed exactly once. If order is not important, consider omitting persisted state and process records in the same flow that you receive them. 2. Suppose you have a flow that is invoked via webhook and tracks orders that are processed as key-value pairs. Flow state might look like this: ```json { "id-abc-123": { "item": "Widgets", "qty": 5 }, "id-def-456": { "item": "Gadgets", "qty": 10 } } ``` If two invocations of the same flow occur at the same time, and each attempts to add a key-value pair to flow state, each flow will write out state with different key-value pairs. The flow that finishes last will overwrite (effectively removing) the key that the first flow wrote. State is written in its entirety Note that state is written in its entirety (rather than key by key). That means that for two concurrently running flows, if one flow writes a value for `instanceState["foo"]`, and then another writes a value for `instanceState["bar"]`, the change to `"foo"` will be overwritten. Generally, Prismatic should be used as the mechanism to move data between systems. The systems (your app and the app you're integrating with) should be the sources of truth where records are stored. 3. Suppose you have two flows, and one calls another via [cross-flow trigger](https://prismatic.io/docs/integrations/triggers/cross-flow.md). The first flow writes state for the second flow to read. This scenario doesn't work, since the second flow starts before the first completes. The second would load state that doesn't contain the first flow's persisted values. When invoking sibling flows, consider sending the data to the sibling flow via POST request. The [cross-flow trigger](https://prismatic.io/docs/integrations/triggers/cross-flow.md) lets you specify data to send to the sibling flow. 4. Suppose you have a flow that processes records that are stored in a list. When your flow runs, it loads 50 records from persisted state. After processing 20 records and removing them from the persisted list, the API you're working with throws an error. This causes your flow to throw an error and stop. In this scenario, the 30 remaining records would *not* be persisted, since your flow did not complete successfully. When it runs next, it will attempt to process the first 20 records a second time. If processing and removing records is important, consider leveraging a [FIFO queue](https://prismatic.io/docs/integrations/triggers/fifo-queue.md). Alternatively, if you know an API is unreliable and may throw errors, you can configure [step-level error handling](https://prismatic.io/docs/integrations/low-code-integration-designer/error-handling.md#step-level-error-handling) to ignore errors from records that cannot be processed, or you can send bad records to a dead-letter queue that you can examine later. ##### Persisted state size limitations[​](#persisted-state-size-limitations "Direct link to Persisted state size limitations") Persisted state is ideal for storing small amounts of data in key-value storage between executions. When serialized to JSON, integration state, cross-flow state, and flow state combined should not exceed 64 MB. If you attempt to store more than 64 MB of state, you will encounter an error stating `Unable to complete execution, persisted state exceeded maximum limit of 67108864 bytes.` ##### When should I use alternative data stores?[​](#when-should-i-use-alternative-data-stores "Direct link to When should I use alternative data stores?") If you are attempting to persist large items (like PDFs or images), consider writing the files to a file storage system like [Amazon S3](https://prismatic.io/docs/components/aws-s3.md) or [Google Drive](https://prismatic.io/docs/components/google-drive.md). If you need to store thousands of key-value pairs, consider a purpose-built key-value store, like [Firebase](https://prismatic.io/docs/components/firebase.md) or [Amazon DynamoDB](https://prismatic.io/docs/components/aws-dynamodb.md). If you need to process records that you receive in order, consider leveraging a [FIFO queue](https://prismatic.io/docs/integrations/triggers/fifo-queue.md). #### The persist data component[​](#the-persist-data-component "Direct link to The persist data component") Data can be persisted between runs using the [Persist Data](https://prismatic.io/docs/components/persist-data.md) component. Data are stored in key-value pairs, and values can be strings, numbers, objects, or lists. You can choose to persist data with the `Flow -` actions - that lets you persist data scoped to the current flow. You can also use the `Cross Flow -` actions to persist data that can be shared between flows of an instance. Or you can use the `Integration -` actions to persist data between instances of the same integration (so multiple customers can share a data store). You can store a key/value pair using the [Save Value](https://prismatic.io/docs/components/persist-data.md#saveinstancevalue) action, or you can use [Persist Data](https://prismatic.io/docs/components/persist-data.md)'s other actions to append to a persisted list. If you would like to save a timestamp instead, you can use the [Save Current Time](https://prismatic.io/docs/components/persist-data.md#saveinstancecurrenttime) action to save the current time into a key of your choosing. Later, in a subsequent run, you can fetch the value you saved using the [Get Value](https://prismatic.io/docs/components/persist-data.md#getinstancevalue) action. If a key is not set, **Get Value** will return `null`. You can remove data from an array or remove a key/value pair altogether using [Persist Data](https://prismatic.io/docs/components/persist-data.md)'s other actions. #### Accessing persisted data in a code block or custom component[​](#accessing-persisted-data-in-a-code-block-or-custom-component "Direct link to Accessing persisted data in a code block or custom component") Persisted state is accessible through the `context` parameter, which can be referenced in custom components and code steps. ##### Reading persisted state programmatically[​](#reading-persisted-state-programmatically "Direct link to Reading persisted state programmatically") The `context` parameter contains execution state (`executionState`), flow state (`instanceState` for historical reasons), cross-flow state (`crossFlowState`), and integration state (`integrationState`). Within a `perform` function or code step, you can access variables like this: ```ts for (const item of context.crossFlowState["My Items"]) { // Process each item } ``` For more information, see [Execution, instance, and cross-flow state](https://prismatic.io/docs/custom-connectors/actions.md#execution-instance-and-cross-flow-state). ##### Writing persisted state programmatically[​](#writing-persisted-state-programmatically "Direct link to Writing persisted state programmatically") To set new values for persisted state keys, you can either return the new values in your return block or mutate `context.*` objects directly. ```ts return { data: "Some Data", crossFlowState: { exampleKey: "example value", anotherKey: [1, 2, 3] }, }; // or context.crossFlowState["exampleKey"] = "example value"; context.crossFlowState["anotherKey"] = [1, 2, 3]; ``` To delete a value from state, return a `null` value for the key you want removed: ```ts return { data: "Some Data", crossFlowState: { exampleKey: null }, }; ``` ##### Persisted data in code-native integrations[​](#persisted-data-in-code-native-integrations "Direct link to Persisted data in code-native integrations") Persisted data can be accessed in code-native integrations using the same `context` parameter, just as you do for custom connectors. See [Code-Native Flows](https://prismatic.io/docs/integrations/code-native/flows.md#persisting-data-between-executions). --- #### Preparing Integrations for Marketplace Once you've built your integration, it's time to offer it to your customers through the [embedded marketplace](https://prismatic.io/docs/embed/marketplace.md). Before you do that, however, you should take a moment to polish your integration, so it's as user-friendly to set up as possible. Here are several recommendations to prepare your integration: #### Name your integration after the third-party app you're integrating with[​](#name-your-integration-after-the-third-party-app-youre-integrating-with "Direct link to Name your integration after the third-party app you're integrating with") Generally speaking, you can name your integration after the third-party app you're integrating with. Your customer is already logged into your app and is aware that they're enabling an integration between your app and the third-party. They're also aware that you provide integrations, so including "integration" in your integration's name is extraneous. * ✅ Hubspot * ✅ Slack * ✅ Salesforce * ❌ Hubspot Integration * ❌ Acme -> Slack * ❌ Sync records Salesforce <> Acme #### Give your integration an icon[​](#give-your-integration-an-icon "Direct link to Give your integration an icon") Ensure you've added an [icon](https://prismatic.io/docs/integrations/low-code-integration-designer.md#assigning-an-icon-to-an-integration) to your integration. Typically, you can use the icon of the app you're integrating with. If you're integrating with an app that is in our [public connector library](https://prismatic.io/docs/components.md), you can download and use an icon from our documentation. If you're integrating with another app, try to find a square PNG between 128 and 512 pixels wide. ![Integration with icon in marketplace](/docs/assets/images/integration-with-icon-7e60ed5b5e1d4a02060e7fc61d708c96.png) #### Give your integration an eye-catching description[​](#give-your-integration-an-eye-catching-description "Direct link to Give your integration an eye-catching description") Your integration's **description** is what appears just under your integration's name in the embedded marketplace. The description is plain text and should be a short summary of what your integration does. For example, `Sync contact records with Salesforce`. Longer, more detailed information about your integration should appear in the [marketplace overview](https://prismatic.io/docs/integrations/preparing-for-marketplace.md#provide-context-and-helpful-text-in-the-marketplace-overview). #### Provide context and helpful text in the marketplace overview[​](#provide-context-and-helpful-text-in-the-marketplace-overview "Direct link to Provide context and helpful text in the marketplace overview") The marketplace overview is seen when a user selects an integration from your marketplace (but has not yet clicked **Configure**). The marketplace overview supports [markdown syntax](https://www.markdownguide.org/basic-syntax), and you can add headings, lists, hyperlinks, images, and more to your overview. This is where you should add additional detail about how your integration works, what information is transferred, what setup is required, etc. ![Integration marketplace overview screen](/docs/assets/images/marketplace-overview-051eabc2e8f17b576ca8fc3bddb2cff5.png) #### Add helpful text and links to your integration's config wizard[​](#add-helpful-text-and-links-to-your-integrations-config-wizard "Direct link to Add helpful text and links to your integration's config wizard") Within the config wizard, you can add [helpful headings, text, images, hyperlinks](https://prismatic.io/docs/integrations/config-wizard/config-pages.md#displaying-additional-helper-text-in-the-configuration-wizard) and more. You can add any HTML you'd like to your config wizard. If, for example, setting up authentication in a third-party app is a complex, multi-step process, you can include a step-by-step guide complete with screenshots and links to documentation. ![Netsuite auth guide in config wizard](/docs/assets/images/netsuite-auth-guide-62e6c5eb89ff1854ba27dd1def1a41e2.png) #### Ensure your integration's configuration is data source-driven[​](#ensure-your-integrations-configuration-is-data-source-driven "Direct link to Ensure your integration's configuration is data source-driven") Configuration of your integration should be low-effort on the part of the end user. Where possible, pull data from the third-party you're integrating with using [data sources](https://prismatic.io/docs/integrations/data-sources.md). For example, your SharePoint integration may require a SharePoint site ID. Rather than having your customer open their SharePoint account and copy/paste a site ID, use the [List Sites from SharePoint](https://prismatic.io/docs/components/ms-sharepoint.md#listsites) data source to allow them to select a site from a dropdown menu. [Custom data sources](https://prismatic.io/docs/custom-connectors/data-sources.md) can be written to dynamically fetch data from third-party apps and can be used to build dropdown menus or [custom field mappers](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md#customizing-field-mapper-layouts) using JSON Forms. #### Updating integration marketplace version[​](#updating-integration-marketplace-version "Direct link to Updating integration marketplace version") Your customers have access to the version of the integration available in the integration marketplace. You can update the version in two ways: 1. Select the **Marketplace configuration** link on the left-hand sidebar of the main screen and select your integration, and then select a version of your integration to present. 2. When you publish a new version of your integration, you can choose to update your integration marketplace version from the publish drawer in the integration designer. ![Update integration version in integration marketplace from designer](/docs/assets/images/update-integration-version-ec1e3c18bca1c8018faa02e92e85ff7e.png) #### Removing an integration from the integration marketplace[​](#removing-an-integration-from-the-integration-marketplace "Direct link to Removing an integration from the integration marketplace") To remove an integration from the integration marketplace, open the **Integration Marketplace** page from the left-hand sidebar. Select the integration you would like to remove, and then click **Remove Integration** on the bottom of the page. Activated Instances will not be removed If you remove an integration from the integration marketplace, instances of the integration that customers have activated will not automatically be removed. You can safely remove an integration marketplace offering without affecting existing deployments, and instances will still show up on the **Instances** page. --- #### Troubleshooting and Debugging Integrations #### Debug mode[​](#debug-mode "Direct link to Debug mode") Debug mode can be enabled in the [integration designer](https://prismatic.io/docs/integrations/troubleshooting.md#enabling-debug-mode-in-the-integration-designer) or for a [specific instance](https://prismatic.io/docs/integrations/troubleshooting.md#enabling-debug-mode-for-an-instance). When debug mode is enabled, two things happen: 1. After each step runs, a log is emitted detailing how long the step took to run and how much memory was consumed. 2. Each code step and component is provided a `debug` object, which you can use to emit additional debug logs or measure memory or time performance of your code. debug mode can cause performance issues Note that debug mode generates additional logs and performance metrics and can cause performance issues when left on. Please use debug mode selectively and deactivate it on instances when you are not actively debugging production issues. ##### Enabling debug mode in the integration designer[​](#enabling-debug-mode-in-the-integration-designer "Direct link to Enabling debug mode in the integration designer") To enable debug mode in the integration designer, click the gray **Debug Mode** button on the right side of the test runner drawer. Then, confirm that you truly want to enable debug mode (as logs can be verbose and can cause performance issues). ![Debug mode button in the integration designer](/docs/assets/images/integration-designer-debug-mode-83e9b9c996208e5979ea30eae9a5df51.png) To disable debug mode, click the same (now yellow) button again. ##### Enabling debug mode for an instance[​](#enabling-debug-mode-for-an-instance "Direct link to Enabling debug mode for an instance") To enable debug mode for a specific instance, open the instance's **Summary** tab and select **Disabled** under **Debug Mode**. ![Debug mode button in an instance](/docs/assets/images/instance-debug-mode-cc4212f970cc67a5d23b3ac903824afe.png) To disable debug mode on an instance, click the same (now yellow) **Enabled** button again. ##### Debug mode logs[​](#debug-mode-logs "Direct link to Debug mode logs") When debug mode is enabled, a log line will be emitted after each step that looks like this: Example line emitted when in debug mode ```json { "stepName": "codeBlock", "memoryUsage": { "rss": 381.484375, "heapTotal": 58.97265625, "heapUsed": 53.90106964111328, "external": 258.9808874130249, "arrayBuffers": 0.14438152313232422 }, "duration": 3.401575000025332, "platform": true } ``` Within the log line are two useful metrics: 1. `duration` indicates the number of milliseconds that the step took to run. 2. `memoryUsage` indicates how much memory was used when this step completed. The `rss` value is the most important metric - it represents the total amount of memory (in MB) that the runner was using. The other values are outlined [here](https://nodejs.org/api/process.html#processmemoryusage). Note that unlike NodeJS documentation (which displays memory values in **bytes**), the log line presents **MB** for readability. ##### Running steps conditionally based on debug mode[​](#running-steps-conditionally-based-on-debug-mode "Direct link to Running steps conditionally based on debug mode") A flow's trigger returns, among other things, the value of `globalDebug`. ![Trigger step global debug flag](/docs/assets/images/trigger-step-global-debug-1e3759413f4985f88c4adf72059f1023.png) You can reference that value in a [branch](https://prismatic.io/docs/components/branch.md) step to determine which branch to follow. #### Using debug mode in a code block or custom connector[​](#using-debug-mode-in-a-code-block-or-custom-connector "Direct link to Using debug mode in a code block or custom connector") The [context](https://prismatic.io/docs/custom-connectors/actions.md#the-context-parameter) parameter passed to a custom connector's actions and triggers contains an object, `debug`, which can be used to write additional debug logging or measure memory or time performance of specific parts of your code. ##### Checking if debug mode is enabled in a code block or custom connector[​](#checking-if-debug-mode-is-enabled-in-a-code-block-or-custom-connector "Direct link to Checking if debug mode is enabled in a code block or custom connector") The context parameter's `debug.enabled` property will be `true` if debug mode is enabled, and `false` otherwise. Determining if debug is enabled in a code step ```js module.exports = async (context, stepResults) => { const widgetId = stepResults.getWidget.results.id; if (context.debug.enabled) { context.logger.log(`Got widget ID "${widgetId}"`); } // ... }; ``` Similar concepts can be applied to custom connector code or code-native integrations using their `context` parameters. ##### Measuring time performance in a code block or custom connector[​](#measuring-time-performance-in-a-code-block-or-custom-connector "Direct link to Measuring time performance in a code block or custom connector") If a code block or custom action is running slowly, you can book-end a portion of potentially "slow" code with `context.debug.timeElapsed.mark` functions to note when the "slow" code started and finished. The `context.debug.timeElapsed.measure` function can then be used to determine the time between start and finish, giving you performance metrics for each portion of your code. Get metrics on runtime of parts of a code step ```ts async function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } const mySlowFunction = async () => sleep(2000); const mySlowerFunction = async () => sleep(5000); module.exports = async (context, stepResults) => { context.debug.timeElapsed.mark(context, "slowCode1_start"); await mySlowFunction(); // A "long-running" process that takes about 2 seconds context.debug.timeElapsed.mark(context, "slowCode1_end"); context.debug.timeElapsed.measure(context, "longRunning1", { start: "slowCode1_start", end: "slowCode1_end", }); context.debug.timeElapsed.mark(context, "slowCode2_start"); await mySlowerFunction(); // A "long-running" process that takes about 5 seconds context.debug.timeElapsed.mark(context, "slowCode2_end"); context.debug.timeElapsed.measure(context, "longRunning2", { start: "slowCode2_start", end: "slowCode2_end", }); return { data: null }; }; ``` Note that in the above code, two functions book-ended with `mark` functions take about 2000 and 5000ms to run respectively. We can find these durations in our instance's logs: ![Time metrics on a code step](/docs/assets/images/time-metrics-code-step-b121b00c51f3952c4c7ce424697d522e.png) ##### Measuring memory performance in a code block or custom connector[​](#measuring-memory-performance-in-a-code-block-or-custom-connector "Direct link to Measuring memory performance in a code block or custom connector") At any point in your code block or custom action or trigger, you can invoke `context.debug.memoryUsage`, which will take a snapshot of memory usage at a specific time. Here, we determine how much memory was used before and after fetching data from an API: Determine how much memory was used before and after fetching data ```js module.exports = async (context, stepResults) => { context.debug.memoryUsage(context, "Before Fetching Data"); const response = await fetch("https://jsonplaceholder.typicode.com/todos"); const todoItems = await response.json(); context.debug.memoryUsage(context, "After Fetching Data"); return { data: todoItems }; }; ``` Memory usage (`rss`) represents the total amount of memory consumed by the execution **in MB**. Here, we can see that fetching data consumed about 10MB of memory. ![Memory metrics on a code step](/docs/assets/images/memory-metrics-code-step-bc60a68a52ae932fd576bb58ba1b7636.png) #### Common error messages[​](#common-error-messages "Direct link to Common error messages") This section outlines common error messages that you may see and how to fix them. ##### P10001: Result too large to serialize[​](#p10001-result-too-large-to-serialize "Direct link to P10001: Result too large to serialize") You may see this error when dealing with large files (greater than 128MB) due to limitations on how large a JavaScript `Buffer` can be. Reduce the size of your step result if possible. Consider [processing files using streams](https://prismatic.io/docs/custom-connectors/handling-large-files-in-custom-components.md). ##### P10002: Result contains non-serializable functions[​](#p10002-result-contains-non-serializable-functions "Direct link to P10002: Result contains non-serializable functions") You may see this error if your step result contains JavaScript functions. Prismatic serializes step results using [MessagePack](https://msgpack.org/index.html). JavaScript functions are non-serializable. So, you cannot `return { data: { foo: () => "myReturnValue" } }`, for example. As a work-around, you can JSON stringify and JSON parse an object, and functions will be automatically removed. ```typescript const myReturnValue = { foo: 123, bar: "Hello, World", baz: () => { console.log("Hi!"); }, }; const sanitizedResults = JSON.parse(JSON.stringify(myReturnValue)); return { data: sanitizedResults }; // Returns {foo: 123, bar: "Hello, World"} ``` ##### P10003: Error serializing step results[​](#p10003-error-serializing-step-results "Direct link to P10003: Error serializing step results") An unknown error occurred when serializing your step results. Prismatic serializes step results using [MessagePack](https://msgpack.org/index.html). MessagePack supports a number of complex and primitive types (numbers, strings, objects, arrays, Buffers, null, etc.). Ensure that the data you are returning is included in the [MessagePack spec](https://github.com/msgpack/msgpack/blob/master/spec.md). --- #### Code-Native Integrations ##### Code-Native Integrations New to code-native? Are you new to code-native? Check out our [getting started guide](https://prismatic.io/docs/get-started/build-integrations/first-integration-code-native.md) to build your first integration. When building an integration, you can use either the [low-code designer](https://prismatic.io/docs/integrations/low-code-integration-designer.md) or create a TypeScript project in your favorite IDE. We call integrations built with code "code-native integrations" (or CNIs). This article explains how to build an integration entirely in code. ![A code-native integration being built in Visual Studio Code](/docs/assets/images/cni-in-vscode-919589cb9d3fb953459b25ffbe1d3aca.png) For example code-native integrations, please visit our [GitHub repository](https://github.com/prismatic-io/integration-templates). The rest of the platform is the same Regardless of how you build your integrations, the rest of the platform remains identical. Both code-native and low-code integrations are deployed to the same runner environment. Both can include OAuth 2.0 connections, data sources, and other advanced configuration wizard steps. Integrations built using either approach can include multiple flows and can be added to your integration marketplace. The same logging, monitoring, and alerting tools are available for both types of integrations. The key difference lies in how you build the integration - you either assemble a set of low-code steps in the designer or write TypeScript code to accomplish the same task. --- ##### Code-Native Config Wizard Like low-code integrations, code-native integrations include a [config wizard](https://prismatic.io/docs/integrations/config-wizard.md). The config wizard can include things like OAuth 2.0 connections, API key connections, dynamically-sourced UI elements (data sources), and other advanced configuration wizard steps. A config wizard consists of multiple pages. Each page has a title, which is derived from the `key` of the configPage object, and a `tagline` as well as a set of `elements` (individual config variables). For example, a config wizard might contain a page for a Slack OAuth 2.0 connection, a page where the user selects a channel from a dynamically-populated dropdown menu, and a page where a user enters two static string inputs: Example config pages definition ```typescript import { configPage, configVar } from "@prismatic-io/spectral"; import { slackConnectionConfigVar } from "./connections"; import { slackSelectChannelDataSource } from "./dataSources"; export const configPages = { Connections: configPage({ tagline: "Authenticate with Slack", elements: { "Slack OAuth Connection": slackConnectionConfigVar, }, }), "Slack Config": configPage({ tagline: "Select a Slack channel from a dropdown menu", elements: { "Select Slack Channel": slackSelectChannelDataSource, }, }), "Other Config": configPage({ elements: { "Acme API Endpoint": configVar({ stableKey: "acme-api-endpoint", dataType: "string", description: "The endpoint to fetch TODO items from Acme", defaultValue: "https://my-json-server.typicode.com/prismatic-io/placeholder-data/todo", }), "Webhook Config Endpoint": configVar({ stableKey: "webhook-config-endpoint", dataType: "string", description: "The endpoint to call when deploying or deleting an instance", }), }, }), }; ``` #### Config variable visibility in code-native[​](#config-variable-visibility-in-code-native "Direct link to Config variable visibility in code-native") Each config variable can have a `permissionAndVisibilityType` property with one of three values: * `customer` is the default value. Customer users can view and edit the config variable, and it will always appear in the config wizard. * `embedded` makes it so the config variable does not show up in the config wizard, but your app is able to [set it programmatically](https://prismatic.io/docs/embed/marketplace.md#dynamically-setting-config-variables-in-marketplace) through the embedded SDK. This is useful if you want to set an API key for a user during the configuration process but not allow the user to see or edit the value that is set. * `organization` makes it so the config variable is not visible to your customer and is not able to be set programmatically by your app. Config variables marked **organization** must have a default value, or else your team members will need to set the value on behalf of your customer. Additionally, `visibleToOrgDeployer` determines if an organization user will see this config variable in the config wizard UI. While organization team members always have programmatic access to instances' config variables and their values, this helps to visually conceal some config variable values like generated metadata from data sources, etc. Defaults to `true`. A debug config variable that is only visible to org team members ```ts configVar({ stableKey: "debug", dataType: "boolean", description: "Enable debug logging", defaultValue: "false", permissionAndVisibilityType: "customer", visibleToOrgDeployer: true, }); ``` #### Connections in code-native integrations[​](#connections-in-code-native-integrations "Direct link to Connections in code-native integrations") Generally, it's a best practice to use [customer](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md), [organization (customer)](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md), or [organization (global)](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-global.md) connections in your integrations. These connection types are reusable across multiple integrations and centrally managed in the Prismatic web app, which makes them easier to maintain and update over time. To use these connection types, see [Referencing existing connections in code-native](https://prismatic.io/docs/integrations/code-native/existing-components.md#using-existing-connections-in-code-native). Alternatively, you can define an integration-scoped connection directly in your code-native integration's `configPages` using the `connectionConfigVar` function - useful when the connection isn't adapted from an existing component. Connection definitions in CNI are very similar to [custom component connection definitions](https://prismatic.io/docs/custom-connectors/connections.md), but you use the `connectionConfigVar` function instead of the `connection` function. For example, an OAuth 2.0 connection might look like this: Example connection definition ```typescript export const acmeConnectionConfigVar = connectionConfigVar({ stableKey: "acme-oauth-connection", dataType: "connection", oauth2Type: OAuth2Type.AuthorizationCode, inputs: { authorizeUrl: { label: "Authorize URL", placeholder: "Authorize URL", type: "string", default: "https://acme.com/oauth/authorize", required: true, shown: false, comments: "The OAuth 2.0 Authorization URL for the API", }, tokenUrl: { label: "Token URL", placeholder: "Token URL", type: "string", default: "https://acme.com/api/token", required: true, shown: false, comments: "The OAuth 2.0 Token URL for the API", }, revokeUrl: { label: "Revoke URL", placeholder: "Revoke URL", type: "string", required: true, shown: false, comments: "The OAuth 2.0 Revocation URL for Acme", default: "https://acme.com/api/revoke-token", }, scopes: { label: "Scopes", placeholder: "Scopes", type: "string", required: false, shown: false, comments: "Space separated OAuth 2.0 permission scopes for the API", default: "widget:read widget:write", }, clientId: { label: "Client ID", placeholder: "Client ID", type: "string", required: true, shown: false, comments: "Client Identifier of your app for the API", default: ACME_CLIENT_ID, }, clientSecret: { label: "Client Secret", placeholder: "Client Secret", type: "password", required: true, shown: false, comments: "Client Secret of your app for the API", default: ACME_CLIENT_SECRET, }, signingSecret: { label: "Signing Secret", type: "password", required: true, shown: false, default: ACME_SIGNING_SECRET, }, }, }); ``` The above OAuth 2.0 connection would be rendered in a config wizard as a card with a **connect** button that a user can click to authenticate with Acme. Connection inputs yield objects. To access one of the fields in the connection definition (like the `signingSecret` input) in a trigger or `onExecution` function, you can access it using the `configVars` object in the `context` parameter. Accessing connection inputs in a trigger or onExecution function ```typescript // Access a field defined in the connection definition context.configVars["Acme OAuth Connection"].fields.signingSecret; // Access the access token from the OAuth 2.0 flow context.configVars["Acme OAuth Connection"].token?.access_token; ``` #### Data sources in code-native integrations[​](#data-sources-in-code-native-integrations "Direct link to Data sources in code-native integrations") Data sources are defined in CNI similar to how they are defined in [custom components](https://prismatic.io/docs/custom-connectors/data-sources.md), but you use the `dataSourceConfigVar` function rather than the `dataSource` function. For example, if you would like to add a picklist dropdown menu to your config wizard that displays a list of Slack channels, you could define a data source like this: Example data source definition ```typescript import { Connection, Element, dataSourceConfigVar, } from "@prismatic-io/spectral"; import { createSlackClient } from "./slackClient"; import { AxiosResponse } from "axios"; interface Channel { id: string; name: string; } interface ListChannelsResponse { ok: boolean; channels: Channel[]; response_metadata?: { next_cursor: string; }; } export const slackSelectChannelDataSource = dataSourceConfigVar({ stableKey: "slack-channel-selection", dataSourceType: "picklist", perform: async (context) => { const client = createSlackClient( context.configVars["Slack OAuth Connection"] as Connection, ); let channels: Channel[] = []; let cursor = null; let counter = 1; // Loop over pages of conversations, fetching up to 10,000 channels // If we loop more than 10 times, we risk hitting Slack API limits, // and returning over 10,000 channels can cause the UI to hang do { const response: AxiosResponse = await client.get( "conversations.list", { params: { exclude_archived: true, types: "public_channel", cursor, limit: 1000, }, }, ); if (!response.data.ok) { throw new Error( `Error when fetching data from Slack: ${response.data}`, ); } channels = [...channels, ...response.data.channels]; cursor = response.data.response_metadata?.next_cursor; counter += 1; } while (cursor && counter < 10); // Map conversations to key/label objects, sorted by name const objects = channels .sort((a, b) => (a.name < b.name ? -1 : 1)) .map((channel) => ({ key: channel.id, label: channel.name, })); return { result: objects }; }, }); ``` The above data source would be rendered in a config wizard as a picklist dropdown menu that displays a list of Slack channels. ![A picklist dropdown menu that displays a list of Slack channels](/docs/assets/images/config-page-datasource-dfd96f4640588afbb4b06ef49f0d53c7.png) You can build advanced UI elements, like field mappers, into your config wizard using [JSON Forms data sources](https://prismatic.io/docs/integrations/data-sources/json-forms.md). ##### Resetting JSON Forms data sources[​](#resetting-json-forms-data-sources "Direct link to Resetting JSON Forms data sources") When a JSON Forms data source is first configured, it can return default `data` that pre-fills the form for your users. If a user reconfigures the instance, their previous selections are retained by default, and the data source's default `data` is ignored. However, you may want to reset the data source to its default values when certain config variables change. For example, suppose your config wizard has three pages: * **Page 1**: Connect to Microsoft SharePoint with OAuth 2.0 * **Page 2**: Select a SharePoint site from a dropdown menu * **Page 3**: Present a JSON form with default data from the selected site If the user changes their site selection on page 2, you may want to reset the JSON form on page 3 to show fresh default data for the newly selected site. You can accomplish this by adding a `dataSourceReset` property to your JSON Forms data source config variable. The `dataSourceReset` object includes two properties: * **mode**: Determines how the reset behaves. Options are `"prompt"` (asks the user if they want to reset) or `"always"` (resets automatically) * **dependencies**: An array of config variable names to watch. When any of these config variables change, the reset logic is triggered Example JSON Forms data source with reset logic ```typescript import { dataSourceConfigVar } from "@prismatic-io/spectral"; dataSourceConfigVar({ stableKey: "posts", dataSourceType: "jsonForm", dataSourceReset: { mode: "prompt", dependencies: ["User"], }, perform: async (context) => { const response = await fetch( `https://jsonplaceholder.typicode.com/users/${context.configVars["User"]}/posts`, ); const posts = (await response.json()) as Post[]; const schema = { type: "array", items: { type: "string", oneOf: posts.map((post) => ({ title: post.title, const: `${post.id}`, })), }, }; const uiSchema = { type: "VerticalLayout", elements: [ { type: "Control", scope: "#", label: "Posts", }, ], }; const data = [`${posts[0].id}`, `${posts[1].id}`]; return { result: { schema, uiSchema, data, }, }; }, }); ``` In this example, whenever the **User** config variable changes, your customer will be asked if they want to reset the posts data source to its default values. **Reset modes:** * **prompt**: The user is notified that their data may be stale and given the option to reset the form to default values * **always**: The data source automatically resets to default values whenever a dependency changes This reset functionality works the same way as [handling form data updates](https://prismatic.io/docs/integrations/data-sources/json-forms/using-json-forms.md#handling-form-data-updates) in low-code integrations. #### Other config variable types in code-native integrations[​](#other-config-variable-types-in-code-native-integrations "Direct link to Other config variable types in code-native integrations") Other types of config variables that Prismatic supports (picklists, text inputs, schedules, etc) can be added to CNI integrations, as well. These are generally defined inline alongside other `elements` in a `configPage`: Example config variable definition ```typescript export const configPages = { // ... "Other Config": configPage({ "Acme API Endpoint": configVar({ stableKey: "1F886045-27E7-452B-9B44-776863F6A862", dataType: "string", description: "The endpoint to fetch TODO items from Acme", defaultValue: "https://my-json-server.typicode.com/prismatic-io/placeholder-data/todo", }), }), }; ``` ![A page in the config wizard with two static string inputs](/docs/assets/images/config-page-static-inputs-08019edda7f05af8aa991ff5daa4e033.png) #### Additional config wizard helper text in code-native integrations[​](#additional-config-wizard-helper-text-in-code-native-integrations "Direct link to Additional config wizard helper text in code-native integrations") In addition to config variables, you can add helpful text and images to guide your customers as they work through your config wizard. To add HTML to the config wizard (which can include links, images, etc), include a string `element` to a `configPage` definition: Include helper text in the config wizard ```typescript export const configPages = { Connections: configPage({ elements: { helpertext1: "

Asana Instructions

", helpertext2: "To generate an Asana API Key, visit the " + 'developer portal ' + 'and select "Create new token".', "Asana API Key": connectionConfigVar({ stableKey: "f0eab60f-545b-4b46-bebf-04d3aca6b63c", dataType: "connection", inputs: { // ... }, }), }, }), }; ``` ![A page in the config wizard with additional helper text](/docs/assets/images/helper-text-d5e8391937ad78ed6934314bbaf0b7d7.png) #### User-level config wizards in code-native integrations[​](#user-level-config-wizards-in-code-native-integrations "Direct link to User-level config wizards in code-native integrations") If your integration relies on [user-level config](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md), you can add a user-level config wizard similar to how you create the integration's config wizard. Within `configPages.ts` create a `userLevelConfigPages` object that has the same shape as `configPages`: User-level config wizard ```typescript export const userLevelConfigPages = { Options: configPage({ elements: { "My ULC Config Variables": configVar({ dataType: "string", stableKey: "my-ulc-config-var", description: "Enter a widget value", }), }, }), }; ``` Then, in `index.ts` import the `userLevelConfigPages` object. Provide the object as an export of your project (so TypeScript can infer types via `.spectral/index.ts`), and include it in your `integration()` definition: Including user-level config in your component ```typescript import { integration } from "@prismatic-io/spectral"; import flows from "./flows"; import { configPages, userLevelConfigPages } from "./configPages"; import { componentRegistry } from "./componentRegistry"; export { configPages, userLevelConfigPages } from "./configPages"; export { componentRegistry } from "./componentRegistry"; export default integration({ name: "ulc-example", description: "My user-level config example integration", iconPath: "icon.png", flows, configPages, userLevelConfigPages, componentRegistry, }); ``` #### Config variable stable keys[​](#config-variable-stable-keys "Direct link to Config variable stable keys") Config variables each have a user-supplied `stableKey` property. These keys are used to uniquely identify the config variable in the Prismatic API, and help guard against inadvertent changes to the name of the config variable. Without a stable key, if a config variable's name can be changed the Prismatic API will treat it as a new config variable and existing values assigned to the config variable will be lost. With a stable key, the Prismatic API will be able to map the old config variable to the renamed one, and retain config variable values. Stable keys can be any user-supplied string. You can choose a random UUID, or a string that describes the flow or config variable. --- ##### Code-Native Endpoint Configuration By default, instances of integrations that you deploy will be assigned unique webhook URLs - one URL for each flow. We call this **Instance and Flow Specific** endpoint configuration. Alternatively, you can choose **Instance Specific** endpoint configuration (each instance gets its own webhook URL and all flows share the single URL) or **Shared** endpoint configuration, where all flows of all instances share one URL. To specify endpoint type, add an `endpointType` property to the `integration()` definition in `src/index.ts`. It can have values `"instance_specific"`, `"flow_specific"` or `"shared_instance"` and defaults to `"flow_specific"`: ```typescript import { integration } from "@prismatic-io/spectral"; import flows from "./flows"; import { configPages } from "./configPages"; import { componentRegistry } from "./componentRegistry"; export default integration({ name: "shared-endpoint-example", description: "Shared Endpoint Example", iconPath: "icon.png", flows, configPages, componentRegistry, endpointType: "instance_specific", }); ``` When **Instance Specific** or **Shared** endpoint configuration is selected, you need some logic to determine which flow (and which customer's instance in the case of **Shared**) should be run. This can be done with or without a [preprocess flow](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md#instance-specific-endpoint-with-a-preprocess-flow), and both methods are described below. Full documentation on endpoint configuration is available in the [Endpoint Configuration](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md) article. #### Endpoint configuration in code-native without preprocess flow[​](#endpoint-configuration-in-code-native-without-preprocess-flow "Direct link to Endpoint configuration in code-native without preprocess flow") If the flow that you want to run is specified in the webhook request's body or in a header, you can configure shared endpoint without a preprocess flow. If, for example, the flow you want to run is specified using a header named `x-acme-flow`, note that header's name in your integration definition using the `triggerPreprocessFlowConfig` property: Instance specific endpoint configuration without a preprocess flow ```typescript import { integration } from "@prismatic-io/spectral"; import flows from "./flows"; import { configPages } from "./configPages"; import { componentRegistry } from "./componentRegistry"; export default integration({ name: "shared-endpoint-example", description: "Shared Endpoint Example", iconPath: "icon.png", flows, configPages, componentRegistry, endpointType: "instance_specific", triggerPreprocessFlowConfig: { flowNameField: "headers.x-acme-flow", }, }); ``` To invoke an instance of an execution that has been deployed, this `curl` command would invoke the flow named "Create Opportunity": Invoke an instance specific endpoint with a flow name specified in a header ```bash curl https://hooks.prismatic.io/trigger/SW5ExampleInstanceSpecificEndpoint \ -X POST \ --header "content-type: application/json" \ --header "x-acme-flow: Create Opportunity" \ --data '{ "opportunity": { "name": "Foo", "amount": 10000 } }' ``` If all of your instances share an endpoint, you can similarly specify a customer external ID from the request body or headers: Shared endpoint configuration without a preprocess flow ```typescript import { integration } from "@prismatic-io/spectral"; import flows from "./flows"; import { configPages } from "./configPages"; import { componentRegistry } from "./componentRegistry"; export default integration({ name: "shared-endpoint-example", description: "Shared Endpoint Example", iconPath: "icon.png", flows, configPages, componentRegistry, endpointType: "shared_instance", triggerPreprocessFlowConfig: { flowNameField: "headers.x-acme-flow", externalCustomerIdField: "body.data.acmeAccountId", }, }); ``` Invoke a shared endpoint with a flow name header and customer ID in the body ```bash curl https://hooks.prismatic.io/trigger/SW5ExampleSharedEndpoint \ -X POST \ --header "content-type: application/json" \ --header "x-acme-flow: Create Opportunity" \ --data '{ "acmeAccountId": "abc-123", "opportunity": { "name": "Foo", "amount": 10000 } }' ``` #### Endpoint configuration in code-native with preprocess flow[​](#endpoint-configuration-in-code-native-with-preprocess-flow "Direct link to Endpoint configuration in code-native with preprocess flow") A preprocess flow allows you to run custom logic to determine which flow should be run (and in the case of **Shared** endpoint config, which customer should be run). One of your flows can look at the request body or headers, make API calls, etc., and then return the name of the flow (and customer) to run. If you use a preprocess flow, one (and exactly one) of your flows must be marked as the preprocess flow. You cannot specify both a preprocess flow and a `triggerPreprocessFlowConfig` property. This example preprocess flow has an `onExecution` function (like any other flow). This flow returns two properties: `myFlowName` and `myCustomerId` - you can name those properties whatever you like. The `preprocessFlowConfig` property specifies which properties to look for in the response from the preprocess flow: Shared endpoint configuration with a preprocess flow ```typescript import axios from "axios"; import { flow } from "@prismatic-io/spectral"; const flowMapper = { create_opportunity: "Create Opportunity", update_opportunity: "Update Opportunity", }; interface CreateOpportunityPayload { event: "create_opportunity"; acctId: string; opportunity: { name: string; amount: number; }; } interface UpdateOpportunityPayload { event: "update_opportunity"; acctId: string; opportunity: { id: string; name: string; amount: number; }; } type Payload = CreateOpportunityPayload | UpdateOpportunityPayload; export const myPreprocessFlow = flow({ name: "My Preprocess Flow", stableKey: "my-preprocess-flow", preprocessFlowConfig: { flowNameField: "myFlowName", externalCustomerIdField: "myCustomerId", }, description: "This determines which sibling flow should be invoked", onExecution: async (context, params) => { const { event, acctId } = params.onTrigger.results.body.data as Payload; const customerIdResponse = await axios.post( "https://api.example.com/get-customer-id", { acmeAcctId: acctId, }, ); return Promise.resolve({ data: { myFlowName: flowMapper[event], myCustomerId: customerIdResponse.data.customerId, }, }); }, }); ``` The above preprocess flow will look at a property named `event` in the request body and map an event of `create_opportunity` to the string `Create Opportunity`, returning `Create Opportunity` as the name of the flow to run. It will also extract an `acctId` from the request body and make an HTTP request to `https://api.example.com/get-customer-id` to get an external customer ID, returning that customer ID as well. ```bash curl https://hooks.prismatic.io/trigger/SW5ExampleSharedEndpoint \ -X POST \ --header "content-type: application/json" \ --data '{ "event": "create_opportunity", "acctId": "abc-123", "opportunity": { "name": "Foo", "amount": 10000 } }' ``` To create a preprocess flow for **Instance Specific** endpoint configuration, omit the `externalCustomerIdField` property from the `preprocessFlowConfig` object. --- ##### Existing Components in Code-Native Prismatic provides a number of [existing components](https://prismatic.io/docs/components.md) that you can use in your code-native integrations. Using an existing component can save you time and effort by reusing existing functionality. An example integration that uses the existing Slack OAuth 2.0 connection, Slack "Select Channel" data source, and Slack "Post Message" action is available in [GitHub](https://github.com/prismatic-io/examples/tree/main/integrations/code-native-integrations/slack-with-components). #### Options for adding existing components to your code-native integration[​](#options-for-adding-existing-components-to-your-code-native-integration "Direct link to Options for adding existing components to your code-native integration") You have two options for adding existing component logic to your code-native integration: 1. Copy/adapt the existing component's code. All components are open source and available in [Prismatic's GitHub repository](https://github.com/prismatic-io/components). You can copy a component's action, trigger or data source code into your code-native integration and modify it as needed. This option has the advantage of giving you full control over the code that runs, but it also means that you will need to maintain the component's code in your integration. This also gives you the ability to unit test the code-native integration locally, since the code is all in one place. 2. Use the existing component's manifest. Each component has a **manifest** that describes the component's connections, actions, triggers, and data sources. The manifest is not the component's source code, but a pointer to the component's trigger, action, or data source in the Prismatic platform. The advantage of a manifest is that you can use the component's functionality without having to copy the code into your integration. If Prismatic makes a change to the component's code, you can use the new version of the component my re-generating the component's manifest. If you choose to pull in the existing component's code, we recommend using the [AI Coding Assistant Plugin](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) which includes a skill for adapting existing component code to your code-native integration. The rest of this article focuses on using existing component manifests in your code-native integration. #### Adding component manifests to your code-native project[​](#adding-component-manifests-to-your-code-native-project "Direct link to Adding component manifests to your code-native project") Tip: Leverage prism-mcp If you are using an AI coding agent like [Cursor](https://cursor.com/en) or [GitHub Copilot](https://github.com/features/copilot), you can leverage [prism-mcp](https://github.com/prismatic-io/prism-mcp?tab=readme-ov-file#codegen) to accelerate code-native integration development. `prism-mcp` comes with tools that generate code snippets for referencing existing components. To use an existing trigger, connection, data source, or action, you need to add the component's **manifest** (files that describe the component's connections, actions, triggers, and data sources) to your code-native project. This can be done in one of two ways: 1. **Recommended Method**: Generate a component manifest from Prismatic's API. From a terminal in your code-native directory, run: Generate a component manifest from Prismatic's API for Slack ```bash npx cni-component-manifest slack ``` The component **key** (a unique identifier for the component) that you supply to the `cni-component-manifest` command can be found at the top of a component's docs page. A private component manifest can be generated by adding the `--private` flag to the command: Generate a component manifest from a private component ```bash npx cni-component-manifest my-private-component --private ``` 2. *Legacy Method*: Manifests for Prismatic-provided public components are available through Prismatic's component manifests repository. When you initialized your code-native integration, Prismatic created an `.npmrc` file that read: .npmrc ```text @component-manifests:registry=https://app.prismatic.io/packages/npm ``` That instructs your package manager to look for packages that begin with `@component-manifests` in the Prismatic repository. To add a component's manifest package to your code-native project, take note of the component's key and run: Add the Slack component's manifest to a code-native project ```bash npm install @component-manifests/slack ``` Update to new component reference syntax If you previously added a component manifest using the legacy method, see our [Spectral 10.6 migration guide](https://prismatic.io/docs/spectral/spectral-10-6-upgrade-guide.md) for instructions on how to use new component reference syntax. ##### Including components in your component registry[​](#including-components-in-your-component-registry "Direct link to Including components in your component registry") Once the component manifest is installed, add it to `componentRegistry.ts`: componentRegistry.ts ```ts import { componentManifests } from "@prismatic-io/spectral"; import slack from "./manifests/slack"; // Or, if you installed the manifest as an npm dependency, // import slack from "@component-manifests/slack"; export const componentRegistry = componentManifests({ slack, }); ``` The **component registry** tells your code-native integration which component manifests are available for use. Behind the scenes, `.spectral/index.ts` will inspect your code-native project's exported `componentRegistry` object and will provide type hinting to your TypeScript based on which components are included in your component registry. #### Using existing connections in code-native[​](#using-existing-connections-in-code-native "Direct link to Using existing connections in code-native") Typically, connections are created outside of the code-native project as reusable [customer connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md), [organization (customer) connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md), or [organization (global) connections](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-global.md). For example, you can establish a reusable customer connection for Salesforce in the Prismatic web app, and then reference the connection in your code-native integration. Take note of the connection's **Stable ID** in the Prismatic web app and be sure to generate your component manifest *after* creating the connection so the manifest includes the connection's Stable ID. You can either use the component manifest's `*ReusableConnection` wrapper function, which has some type hinting for available stable keys, or a `customerActivatedConnection` wrapper function - both do the same thing. Reference an existing connection in code-native ```ts import { configPage, customerActivatedConnection, } from "@prismatic-io/spectral"; import { slackReusableConnection } from "./manifests/slack/connections"; export const configPages = { Connections: configPage({ elements: { "Slack Connection": slackReusableConnection("my-connection-stable-key"), // OR "Slack Connection": customerActivatedConnection("my-connection-stable-key"), }, }), }; ``` If you'd prefer to create an [integration-scoped connection](https://prismatic.io/docs/integrations/connections/integration-specific.md), you can specify your connection information directly in your code-native project. After adding an existing component's manifest to your code-native project, you can use a component's connection in your code-native integration's `configPages` definition. The component manifest includes a connection definition wrapper function that you can reference. Referencing an existing component's connection as an integration-scoped connection in a code-native integration ```ts import { configPage } from "@prismatic-io/spectral"; import { slackOauth2 } from "./manifests/slack/connections/oauth2"; export const configPages = { Connections: configPage({ tagline: "Authenticate with Slack", elements: { "Slack OAuth Connection": slackOauth2("my-slack-connection", { clientId: { value: "REPLACE_ME_WITH_YOUR_CLIENT_ID", permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, clientSecret: { value: "REPLACE_ME_WITH_YOUR_CLIENT_SECRET", permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, signingSecret: { value: "REPLACE_ME_WITH_YOUR_SIGNING_SECRET", permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, scopes: { value: "chat:write chat:write.public channels:read", permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, authorizeUrl: { permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, isUser: { permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, tokenUrl: { permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, revokeUrl: { permissionAndVisibilityType: "organization", visibleToOrgDeployer: false, }, }), }, }), }; ``` #### Using existing data sources in code-native[​](#using-existing-data-sources-in-code-native "Direct link to Using existing data sources in code-native") After adding an existing component's manifest to your code-native project, you can use a component's data source in your code-native integration's `configPages` definition. At the top of your config pages file, import your data source wrapper function from the component's manifest: Using an existing data source in a code-native integration ```ts import { configPage } from "@prismatic-io/spectral"; import { slackSelectChannels } from "./manifests/slack/dataSources/selectChannels"; export const configPages = { // ... "Slack Config": configPage({ tagline: "Select a Slack channel from a dropdown menu", elements: { "Select Slack Channel": slackSelectChannels("my-slack-channel-picklist", { connection: { configVar: "Slack OAuth Connection" }, includePublicChannels: { value: true }, }), }, }), }; ``` If the data source requires a connection, you can pass a connection to the data source using the appropriate input value by specifying a `configVar` by name (`connection: { configVar: "Slack OAuth Connection" }` in the example above). #### Using existing triggers in code-native[​](#using-existing-triggers-in-code-native "Direct link to Using existing triggers in code-native") After adding an existing component's manifest to your code-native project, you can use a component's trigger in your code-native integration's `flow` definition. At the top of your config pages file, import your trigger wrapper function from the component's manifest: Using an existing trigger in a code-native integration ```ts import { hashHmacWebhookTrigger } from "./manifests/hash/triggers/hmacWebhookTrigger"; export const existingComponentTriggerFlow = flow({ name: "Existing Component Trigger Flow", stableKey: "6f58c32c-b29a-4f55-97e6-b86bf9e24551", description: "This flow uses an existing component trigger", onTrigger: hashHmacWebhookTrigger({ hmacHeaderName: { value: "x-signature-256" }, secretKey: { configVar: "My Secret Key Config Var" }, hashFunction: { value: "sha256" }, headers: { value: [] }, }), onExecution: async (context, params) => { return Promise.resolve({ data: null }); }, }); export default [existingComponentTriggerFlow]; ``` If the trigger takes inputs, those inputs are passed to the trigger as `values`, and values can be either static string `value` or can reference config variables with `configVar: "Config Variable Name"`. How do lifecycle functions work with existing component triggers? If a trigger has a [lifecycle function](https://prismatic.io/docs/integrations/lifecycle.md), it will be called automatically when the integration is deployed as an instance. If your flow has `onInstanceDeploy`, `onInstanceDelete`, `webhookLifecycleHandlers.create`, or `webhookLifecycleHandlers.delete` functions, the trigger's function will run first, followed by the flow's lifecycle function. #### Using existing actions in code-native[​](#using-existing-actions-in-code-native "Direct link to Using existing actions in code-native") After importing an existing component's manifest and adding it to your `componentManifests` export, you can call one of the component's actions from within your `flow` by importing the action from your component manifest. For example, Using an existing action in a code-native integration ```typescript import { flow, util } from "@prismatic-io/spectral"; import slackActions from "../manifests/slack/actions"; export const existingComponentTriggerFlow = flow({ name: "Send a Slack Message", stableKey: "send-a-slack-message", description: "Send 'Hello World' to a Slack channel", onExecution: async (context, params) => { await slackActions.postMessage.perform({ channelName: util.types.toString( context.configVars["Select Slack Channel"], ), connection: context.configVars["Slack OAuth Connection"], message: "Hello World", }); return { data: null }; }, }); ``` #### Salesforce CNI example[​](#salesforce-cni-example "Direct link to Salesforce CNI example") In this example, we use the existing Salesforce component's [OAuth 2.0 connection](https://prismatic.io/docs/components/salesforce.md#oauth2), [Flow Outbound Message Webhook](https://prismatic.io/docs/components/salesforce.md#flowoutboundmessagetrigger) trigger, and [Get Record](https://prismatic.io/docs/components/salesforce.md#getrecord) action to create a flow that listens for Account notifications from Salesforce and fetches the full record for each notification. * configPages.ts * flows.ts ```ts import { configPage } from "@prismatic-io/spectral"; import { salesforceOauth2 } from "./manifests/salesforce/connections/oauth2"; if ( !process.env.SALESFORCE_CLIENT_ID || !process.env.SALESFORCE_CLIENT_SECRET ) { throw new Error( "Missing Salesforce OAuth2 client ID or client secret. Please set the SALESFORCE_CLIENT_ID and SALESFORCE_CLIENT_SECRET environment variables.", ); } export const configPages = { Connections: configPage({ elements: { "Salesforce Connection": salesforceOauth2("salesforce-connection", { clientId: { value: process.env.SALESFORCE_CLIENT_ID, permissionAndVisibilityType: "organization", }, clientSecret: { value: process.env.SALESFORCE_CLIENT_SECRET, permissionAndVisibilityType: "organization", }, }), }, }), }; ``` ```ts import { flow } from "@prismatic-io/spectral"; import { salesforceFlowOutboundMessageTrigger } from "./manifests/salesforce/triggers/flowOutboundMessageTrigger"; import salesforceActions from "./manifests/salesforce/actions"; interface SalesforceNotification { Id: string; Name: string; type: string; } export const salesforceAccountNotifications = flow({ name: "Listen for Salesforce Account Notifications", stableKey: "salesforce-account-notifications", description: "This flow uses an existing component trigger to listen for Account notifications from Salesforce.", onTrigger: salesforceFlowOutboundMessageTrigger({ connection: { configVar: "Salesforce Connection" }, prefix: { value: "acme" }, triggerObject: { value: "Account" }, fields: { value: ["Id", "Name"] }, }), onExecution: async (context, params) => { const notifications = params.onTrigger.results.body .data as SalesforceNotification[]; for (const notification of notifications) { const record = await salesforceActions.getRecord.perform({ connection: context.configVars["Salesforce Connection"], recordId: notification.Id, recordType: notification.type, }); context.logger.info("Fetched record", { record }); } return { data: null }; }, }); export default [salesforceAccountNotifications]; ``` --- ##### Code-Native Flows Like low-code integrations, code-native integrations can include multiple flows. Flows consist of a `name` and `description`, and a `stableKey` that uniquely identifies the flow. They also contain four functions: * `onTrigger` is called when a flow is invoked. If a flow is invoked asynchronously, you can return a custom HTTP response to the caller from the trigger. * `onExecution` is run immediately after the `onTrigger` function and is where the bulk of the flow's work is done. * `onInstanceDeploy` is called when a new instance of the integration is deployed. These functions are optional and can be used to set up resources or perform other tasks when an instance is deployed. See [lifecycle functions](https://prismatic.io/docs/integrations/lifecycle.md). * `onInstanceDelete` is called when an instance of the integration is deleted. * `webhookLifecycleHandlers` functions are run when an instance is deployed or deleted, and also if you enter [listening mode](https://prismatic.io/docs/integrations/triggers/listening-mode.md) in the integration designer. These functions are useful for creating and deleting webhooks in third-party applications that your integration interacts with. #### Code-native flow triggers[​](#code-native-flow-triggers "Direct link to Code-native flow triggers") The `onTrigger` function of a flow is called when the flow is invoked. A simple no-op trigger that is called asynchronously can simply return the payload it was called with: ```typescript flow({ // ... onTrigger: async (context, payload) => { return Promise.resolve({ payload }); }, }); ``` Use the generic webhook trigger If you omit the `onTrigger` function, the Prismatic platform will automatically use the generic Webhook trigger. This will yield the payload to the next step, the `onExecution` function. If you want to return a custom HTTP response to the caller or would like to complete additional work in the trigger, you can additionally return an `HttpResponse` object from the trigger. In this example, suppose the trigger is invoked via an XML webhook payload that looks like this: ```xml new_account 067DEAB4-B89C-4211-9767-84C96A39CF8C Nelson Bighetti Hooli Palo Alto CA ``` The app calling the trigger requires that you parse the XML payload and return the `challenge` property in the HTTP response body with an HTTP 200 Response. You could write a trigger that parses the XML payload and returns the `challenge` property: ```typescript import { HttpResponse, flow, util } from "@prismatic-io/spectral"; import { XMLParser } from "fast-xml-parser"; flow({ // ... onTrigger: async (context, payload) => { // Parse the raw XML from the webhook request const parser = new XMLParser(); const parsedBody = parser.parse(util.types.toString(payload.rawBody.data)); // Respond to the request with a plaintext response that includes the challenge key const response: HttpResponse = { statusCode: 200, contentType: "text/plain", body: parsedBody.notification.challenge, }; // Ensure that the payload is updated with the parsed body return Promise.resolve({ payload: { ...payload, body: { data: parsedBody } }, response, }); }, }); ``` ##### Cross-flow invocations[​](#cross-flow-invocations "Direct link to Cross-flow invocations") If you'd like one of your flows to invoke an execution of a sibling flow, you can use the flow's `context.invokeFlow` function to invoke a sibling flow. See the example [here](https://prismatic.io/docs/integrations/triggers/cross-flow.md#using-cross-flow-triggers-in-code-native). #### Running code-native flows on a schedule[​](#running-code-native-flows-on-a-schedule "Direct link to Running code-native flows on a schedule") Code-native flows support running on a schedule. The schedule can either be a static schedule that you define in your code, or you can create a config variable of `"schedule"` and let your customer define the schedule. In the example below, the first flow defines a static schedule of "Run at 10:20 every day on US Central time". For tips on creating cron strings, check out [crontab.guru](https://crontab.guru/). The second flow uses a config variable to define the schedule. ```ts import { configPage, flow, integration } from "@prismatic-io/spectral"; const scheduleWithCronExpression = flow({ name: "Schedule Trigger with cron expression", description: "This flow is triggered by schedule following a cron expression", stableKey: "schedule-trigger-cron-expression", onExecution: async (context) => { const now = new Date(); context.logger.info(`Flow executed at ${now}`); return Promise.resolve({ data: null }); }, schedule: { value: "20 10 * * *", timezone: "America/Chicago" }, // Run at 10:20 AM CST }); const scheduleWithConfigVar = flow({ name: "Schedule with config var", description: "This flow is triggered by a schedule following a config var", stableKey: "schedule-trigger-config-var", onExecution: async (context) => { const now = new Date(); context.logger.info(`Flow executed at ${now}`); return Promise.resolve({ data: null }); }, schedule: { configVar: "My Schedule" }, // Run on a user-defined schedule }); export default integration({ name: "schedule-trigger-test", description: "Schedule Trigger Test", iconPath: "icon.png", flows: [scheduleWithCronExpression, scheduleWithConfigVar], configPages: { "Page One": configPage({ elements: { "My Schedule": { dataType: "schedule", stableKey: "my-schedule", }, }, }), }, }); ``` #### Enabling singleton executions for code-native flows[​](#enabling-singleton-executions-for-code-native-flows "Direct link to Enabling singleton executions for code-native flows") To ensure that only one execution of your schedule-based code-native flow runs at a time, you can enable [singleton executions](https://prismatic.io/docs/integrations/triggers/schedule.md#ensuring-singleton-executions-for-scheduled-flows). Add a `queueConfig.singletonExecutions` property to your flow that runs on a schedule: Run every 5 minutes, unless the previous execution is still running ```ts export const salesforceAccountNotifications = flow({ name: "Fetch data every 5 minutes", stableKey: "fetch-data", description: "Fetch and import data every 5 minutes", schedule: { value: "*/5 * * * *" }, queueConfig: { singletonExecutions: true }, onExecution: async (context, params) => { return Promise.resolve({ data: context.configVars }); }, }); ``` #### Code-native lifecycle events[​](#code-native-lifecycle-events "Direct link to Code-native lifecycle events") Code-native flows support `onInstanceDeploy`, `onInstanceDelete` and `webhookLifecycleHandlers` functions. These functions run when an instance of the integration is deployed or deleted. These functions are useful for setting up resources or performing other tasks when an instance is deployed or deleted and are often used to set up webhooks in third-party apps. The functions work the same as custom trigger functions, which are documented in the [Writing Custom Components](https://prismatic.io/docs/custom-connectors/triggers.md#app-event-webhook-triggers) article. #### Code-native flow onExecution[​](#code-native-flow-onexecution "Direct link to Code-native flow onExecution") The `onExecution` function runs immediately after the `onTrigger` function and is where the bulk of the flow's work is done. The `onExecution` function takes two parameters: * `context` - in addition to the [attributes](https://prismatic.io/docs/custom-connectors/actions.md#the-context-parameter) that a normal custom component receives (like a logger, persisted data, metadata about the integration, customer, and instance), a CNI flow's `context` object also contains a `configVars` object that has the values of all config variables that your integration includes. * `params` - the `params` object contains the payload that was returned from the `onTrigger` function. This example `onExecution` function performs the same logic that the low-code [Build Your First Integration](https://prismatic.io/docs/get-started/build-integrations/first-integration-low-code.md) integration did, but in TypeScript: ```typescript import { flow } from "@prismatic-io/spectral"; import axios from "axios"; import { createSlackClient } from "../slackClient"; interface TodoItem { id: number; completed: boolean; task: string; } export const todoAlertsFlow = flow({ // ... onExecution: async (context) => { // Config variables are accessed using the context object const { logger, configVars } = context; // Make an HTTP request to the Acme API using the config variable const { data: todoItems } = await axios.get( configVars["Acme API Endpoint"], ); // Create an HTTP Slack client using the Slack OAuth connection const slackClient = createSlackClient(configVars["Slack OAuth Connection"]); // Loop over the todo items for (const item of todoItems) { if (item.completed) { logger.info(`Skipping completed item ${item.id}`); } else { // Send a message to the Slack channel for each incomplete item logger.info(`Sending message for item ${item.id}`); try { await slackClient.post("chat.postMessage", { channel: configVars["Select Slack Channel"], text: `Incomplete task: ${item.task}`, }); } catch (e) { throw new Error(`Failed to send message for item ${item.id}: ${e}`); } } } // Asynchronously-invoked flows should return null return { data: null }; }, }); ``` ##### Referencing the trigger payload in the onExecution function[​](#referencing-the-trigger-payload-in-the-onexecution-function "Direct link to Referencing the trigger payload in the onExecution function") The trigger will generally return the payload it received, but you can also return a modified payload from the trigger. The `onExecution` function will receive the payload that was returned from the trigger. The trigger may receive a payload of any format, so annotating a TypeScript `interface` is helpful for type hinting and code completion: Reference the trigger payload in the onExecution function ```typescript import { createSlackClient } from "../slackClient"; interface AccountNotification { notification: { type: string; challenge: string; account: { first: string; last: string; company: { name: string; city: string; state: string; }; }; }; } const sendMessagesFlow = flow({ // ... onExecution: async (context, params) => { const { configVars } = context; const slackClient = createSlackClient(configVars["Slack OAuth Connection"]); // The parsed XML payload is available in the params object const data = params.onTrigger.results.body.data as AccountNotification; // Construct a message to send to Slack const message = `New account received:\n` + `Name: ${data.notification.account.first} ${data.notification.account.last}\n` + `Company: ${data.notification.account.company.name}\n` + `Location: ${data.notification.account.company.city}, ${data.notification.account.company.state}\n`; await slackClient.post("chat.postMessage", { channel: configVars["Select Slack Channel"], text: message, }); return { data: null }; }, }); ``` #### Flow stable keys[​](#flow-stable-keys "Direct link to Flow stable keys") Flows have a user-supplied `stableKey` property. These keys are used to uniquely identify the flow in the Prismatic API, and help guard against inadvertent changes to the name of a flow. Without a stable key, if a flow name is changed the Prismatic API will treat it as a new flow, and deployed flows will receive new webhook URLs. With a stable key, the Prismatic API will be able to map the renamed flow and retain its webhook URL. Stable keys can be any user-supplied string. You can choose a random UUID, or a string that describes the flow or config variable. #### Persisting data between executions[​](#persisting-data-between-executions "Direct link to Persisting data between executions") Code-native flows can persist data between executions using the `context.instanceState`, `context.crossFlowState`, and `context.integrationState` objects. * `context.instanceState` (named for historical reasons) is scoped to the current flow of the current instance. Only the current flow can read and write to this state. * `context.crossFlowState` is scoped to the current instance, but can be read and written by any flow in the current instance. * `context.integrationState` is scoped to the entire integration, and can be read and written by any instance of the integration deployed to any customer. These state objects behave like simple key-value stores. To set a value, assign a value to a key on the state object. ```ts context.instanceState["lastRun"] = new Date().toISOString(); ``` To read instance state, access the key on the state object. ```ts const lastRun = context.instanceState["lastRun"]; if (lastRun) { context.logger.info(`The last run was at ${lastRun}`); } else { context.logger.info("This is the first run"); } ``` **What about `executionState`?** The `context.executionState` object is handy in the low-code designer as an accumulator or temporary variable holder, but in code-native flows you can use Node.js variables in your `onExecution` function. #### Building AI-compatible code-native flows[​](#building-ai-compatible-code-native-flows "Direct link to Building AI-compatible code-native flows") Flows can be invoked by AI agents using Prismatic's [MCP flow server](https://prismatic.io/docs/ai/model-context-protocol.md). To make your flows AI-compatible, add a `schemas.invoke` property to your flow definition. See [Code-Native Agentic Flows](https://prismatic.io/docs/ai/agentic-flows/code-native.md) for an example. --- ##### Convert Low-Code Integrations to Code-Native You might build a proof-of-concept integration in the [low-code builder](https://prismatic.io/docs/integrations/low-code-integration-designer.md) but later want to switch to [code-native](https://prismatic.io/docs/integrations/code-native.md). You can convert your low-code integration to a code-native project two ways: 1. Using the in-app [*Convert to Code* feature](https://prismatic.io/docs/integrations/code-native/get-started/convert-low-code-code-native.md#option-1-convert-an-integration-in-app) 2. Using the [`prism integrations:convert` command](https://prismatic.io/docs/integrations/code-native/get-started/convert-low-code-code-native.md#option-2-convert-an-integration-using-the-cli) This is a one-way conversion All concepts (running steps, branching, looping, etc) in low-code have a code-native equivalent. A "repeat for each" loop step will become a `for ... of` loop in code-native, for example. The same is not true in reverse: some code-native concepts (like using `Promise.all` to run steps in parallel) do not have a low-code equivalent. Converting a low-code integration to code-native is a one-way operation. You cannot convert a code-native integration back to low-code. #### Option 1: Convert an integration in-app[​](#option-1-convert-an-integration-in-app "Direct link to Option 1: Convert an integration in-app") Within the low-code builder, click the **Convert to Code** button in the top-right corner of the builder. This will trigger the conversion process. From the modal that appears, you can preview the generated code-native code for your integration, its flows and configuration wizard. ![In-app convert to code modal showing generated code preview](/docs/assets/images/in-app-9338f0b40414a829bcf616bef20c95f8.png) After reviewing the generated code, click **Download** to download a zip file of your code-native integration project. #### Option 2: Convert an integration using the CLI[​](#option-2-convert-an-integration-using-the-cli "Direct link to Option 2: Convert an integration using the CLI") If you would like to use the CLI tool to convert your low-code integration to code-native, take note of the integration's ID (it's the `SW5...` portion of the URL when you view your integration). Next, ensure that you have the latest version of the [prism CLI tool](https://prismatic.io/docs/cli.md) installed. Then run the converter command: Convert a low-code YAML definition to code-native ```txt $ prism integrations:convert SW50ExampleExampleExample Converting low-code integration to code-native integration... done Conversion completed successfully! Download URL: Next steps: 1. Download the zip file from the URL above 2. Extract it to your desired location 3. Run: npm install && npm update --save && npm run format If installation issues occur during step 3, double check your package.json file and component registry set-up. For documentation on code-native integrations, visit https://prismatic.io/docs/integrations/code-native/ ``` The command above will yield a link to a zip file containing your generated code-native integration. #### Installing dependencies[​](#installing-dependencies "Direct link to Installing dependencies") Regardless of how you obtained your generated code-native integration (in-app or CLI), navigate to the extracted project directory and install dependencies and automatically format your code: ```bash cd path-to-unzipped-integration npm install npm update --save npm run format ``` #### Handling custom components[​](#handling-custom-components "Direct link to Handling custom components") If your low-code integration uses custom components, you can either: 1. Abstract the logic in the custom component into a package that both your custom component and code-native integration use 2. Invoke the custom component actions, connections, triggers, and data sources from your code-native integration If you would like to do the latter, you likely use a custom package registry prefix for your custom component manifests. You can specify your custom component package prefix with the `--registryPrefix` flag: Include custom components ```bash prism integrations:convert SW50ExampleExampleExample --registryPrefix "@acme-connectors" ``` #### Post-generation instructions[​](#post-generation-instructions "Direct link to Post-generation instructions") Depending on your integration, a few manual steps may be required to get your integration to compile and run properly. ##### Remove unused step result assignments[​](#remove-unused-step-result-assignments "Direct link to Remove unused step result assignments") If you had a low-code step that is not referenced by a subsequent step, you may see `myStep is declared but its value is never read`. Remove the variable assignment: ```ts // go from: const myAction = await components.myComponent.myAction({}); // to: await components.myComponent.myAction({}); ``` ##### Provide TypeScript types for each step[​](#provide-typescript-types-for-each-step "Direct link to Provide TypeScript types for each step") By default, a step returns an object of `unknown` type. Use [generics](https://www.typescriptlang.org/docs/handbook/2/generics.html) to provide your step with a return type. In this example, an HTTP - Get step returns an array of todo items. We create a TypeScript interface and provide that interface as a generic for the step invocation: Add TypeScript types to steps ```ts interface TodoItem { id: number; completed: boolean; task: string; } export const flow1 = flow({ //... onExecution: async (context, params) => { const getToDoTasks = await context.components.http.httpGet<{ data: TodoItem[]; }>({ url: "https://my-json-server.typicode.com/prismatic-io/placeholder-data/todo", responseType: "json", }); for (const item of getToDoTasks.data) { // TypeScript now knows item is of type TodoItem } }, }); ``` With type generics, TypeScript now knows the shape of our `getToDoTasks` variable. ##### Simplify conditionals in branches[​](#simplify-conditionals-in-branches "Direct link to Simplify conditionals in branches") To ensure that generated code-native code behaves identically to low-code branching conditionals, we import `isEqual` and other functions from Spectral. These can generally be safely converted to JavaScript equivalents. ```ts // Generated code import { isEqual } from "@prismatic-io/spectral/dist/conditionalLogic"; if (isEqual(val1, val2)) { doSomething(); } // Likely equivalent code with no import if (val1 === val2) { doSomething(); } ``` ##### Code component usage[​](#code-component-usage "Direct link to Code component usage") If your low-code integration used [code steps](https://prismatic.io/docs/components/code.md), invoking a code step in code-native is redundant. You can refactor your code so that you do not invoke the code step and instead run the code within your flow's code. This may require updating subsequent steps' references to your code step. ##### Conditional branch names[​](#conditional-branch-names "Direct link to Conditional branch names") The [branch](https://prismatic.io/docs/components/branch.md) component's result is the name of the branch that was traversed. Some low-code integrations use that value to determine what to do once the branch has completed. The code-native equivalent looks like this: ```ts let myBranchStep = "Else"; if (something()) { doSomethingElse(); myBranchStep = "Unexpected Error"; } else { doYetAnotherThing(); myBranchStep = "Else"; } ``` You can remove `myBranchName` if your low-code integration did not make use of the branch step's result. #### Importing your converted code-native integration[​](#importing-your-converted-code-native-integration "Direct link to Importing your converted code-native integration") You can import your converted code-native integration the same way you would import any code-native integration (with `prism integrations:import`). **Note:** by default, a safeguard exists to prevent accidentally overwriting a low-code integration with a code-native integration. When importing your integration, by default, a *new* integration will be created. If you would like to overwrite your existing low-code integration with your new code-native one, run the import with a `--replace` flag: Overwrite a low-code integration with a code-native one ```bash prism integrations:import --integrationId SW5example --replace ``` Use caution when replacing a low-code integration with code-native We strongly recommend that you make a backup of your low-code integration's YAML file prior to replacing it with a code-native integration. If you'd like to revert a code-native integration back to low code, you can issue a similar `prism` command using your low-code integration's definition file: ```text prism integrations:import --integrationId SW5example --replace --path /path/to/low-code/file.yaml ``` If you don't have your integration's YAML definition, you can view the YAML definition of a previously published version of your integration from the **Management** > **Version history** drawer. --- ##### Testing Code-Native Integrations #### Testing a code-native integration[​](#testing-a-code-native-integration "Direct link to Testing a code-native integration") There are two types of testing that you can do with a code-native integration: you can run unit tests of your code locally in your IDE, and you can import the integration and test it in the Prismatic runner. * Unit tests in your IDE are great for testing the logic of your integration and testing modular portions of your code * Testing in the Prismatic runner is great for trying out the configuration wizard experience you've built and testing the integration's behavior in a real-world environment. You will probably want to incorporate both types of testing into your development process. ##### Testing a code-native integration in Prismatic[​](#testing-a-code-native-integration-in-prismatic "Direct link to Testing a code-native integration in Prismatic") After building with `npm run build` and importing your code-native integration with `prism integrations:import --open`, you can test your integration in the Prismatic runner similar to how you test a low-code integration. ![Testing a code-native integration in the Prismatic runner](/docs/assets/images/testing-cni-in-prismatic-c9fb54730f67f4b5a2e6c56d3efa43eb.png) To test your config wizard experience, click the **Test Configuration** button. To run a test of a flow, select the flow from the dropdown menu on the top right of the page and then click the green **Run** button. When you're satisfied with your integration, you can click **Publish** to publish a new version of your integration and manage instances of your integration from the **Management** tab. Use a debug logger A code-native integration has no steps - just a trigger and `onExecution` function, so there are no step results to inspect. To debug your integration, use the `context.logger` object in your `onExecution`. You can even conditionally log lines based on whether or not your test instance has [debug mode](https://prismatic.io/docs/integrations/troubleshooting.md#debug-mode) enabled. ##### Testing a code-native integration from the CLI[​](#testing-a-code-native-integration-from-the-cli "Direct link to Testing a code-native integration from the CLI") If you would like to test your flow from within your IDE, use the `prism integrations:flows:test` command. After importing your code-native integration with `prism integrations:import`, a test instance of your integration is deployed and can be invoked [from the Prismatic UI](https://prismatic.io/docs/integrations/code-native/testing.md#testing-a-code-native-integration-in-prismatic). The same testing you can do in the UI can be done from the command line. We recommend using the `--tail-logs` flag to watch for logs from your invocation. If you would like to send a custom payload to your flow's trigger, use the `--payload` flag to send a local file as an HTTP body to your flow's webhook URL. ```text > prism integrations:flows:test --tail-logs --payload ./my-payload.json ? Select the flow to test: Flow 1 (flow-1) Starting execution...... done To re-run this flow directly: prism integrations:flows:test -u=https://hooks.prismatic.io/trigger/SW5zdGFuY2VGbG93Q29uZmlnOjhjYTZiZGU2LWM1MTktNGI5Ni1iYzVjLTc5NWJiZDMxMTcyNw== -p=./my-payload.json --tail-logs {"executionId":"SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6ZGVkOGE0YWEtMTYzZC00OTMwLWE0NTMtOTNlNGVmODlkYWQw"} › Warning: While the timestamps are accurate, logs & step results may not arrive in chronological order. Press CMD+C/CTRL+C to stop polling. This process will timeout after 20 minutes. 2025-05-12T17:05:01.197000+00:00 LOG_INFO Starting Instance 'My First Integration'. Total Concurrent Executions: 1 2025-05-12T17:05:05.115000+00:00 LOG_INFO "Select a database" is already marked complete. 2025-05-12T17:05:05.687000+00:00 LOG_INFO "Fix CORS configuration on API gateway" is already marked complete. 2025-05-12T17:05:06.013000+00:00 LOG_INFO "Document API authentication" is already marked complete. 2025-05-12T17:05:08.237000+00:00 LOG_INFO Ending Instance 'My First Integration' ``` ##### Listening for webhook invocations from the CLI[​](#listening-for-webhook-invocations-from-the-cli "Direct link to Listening for webhook invocations from the CLI") If your code-native integration includes a webhook trigger, you can use the [`integrations:flows:listen`](https://prismatic.io/docs/cli/prism.md#integrationsflowslisten) command to listen for incoming webhook invocations. ```bash prism integrations:flows:listen --integration-id SW50ZWdEXAMPLE ``` When you run this command and select one of your integration's flows, your test instance will enter [listening mode](https://prismatic.io/docs/integrations/triggers/listening-mode.md) and wait for incoming webhook invocations. When a webhook invocation is received, the payload will be saved to a local file. By default, the file will be saved as `./payloads/payload--.json`, but you can specify a different output directory with the `--output` flag. Once you have a saved payload file, you can use the `--payload` flag with the `integrations:flows:test` command [described above](https://prismatic.io/docs/integrations/code-native/testing.md#testing-a-code-native-integration-from-the-cli)to re-send the payload to your flow for testing. ##### Measuring performance of a code-native integration[​](#measuring-performance-of-a-code-native-integration "Direct link to Measuring performance of a code-native integration") When in [debug mode](https://prismatic.io/docs/integrations/troubleshooting.md#debug-mode), you can leverage functions of `context.debug` to measure [how long](https://prismatic.io/docs/integrations/troubleshooting.md#measuring-time-performance-in-a-code-block-or-custom-connector) specific portions of your flows take to run and [how much memory](https://prismatic.io/docs/integrations/troubleshooting.md#measuring-memory-performance-in-a-code-block-or-custom-connector) they consume (see links for examples). ##### Unit tests for code-native integrations[​](#unit-tests-for-code-native-integrations "Direct link to Unit tests for code-native integrations") You can also write unit tests for your code-native integration, similar to [unit tests for custom components](https://prismatic.io/docs/custom-connectors/unit-testing.md). The `invokeFlow` function from the custom component SDK is used to invoke a test of a flow in a code-native integration. You can specify a sample payload to "send" to your flow's `onTrigger` function, and the `invokeFlow` function will run both `onTrigger` and `onExecution` and return the result of the flow's `onExecution` function. Unit testing only works for integrations that do not leverage existing components If you use [existing components](https://prismatic.io/docs/integrations/code-native/existing-components.md) within your flows, you will not be able to build unit tests for your flows, since your local dev environment does not have access to the existing components. Please consider testing your flow [from the CLI](https://prismatic.io/docs/integrations/code-native/testing.md#testing-a-code-native-integration-from-the-cli) instead. Below is a simple flow that takes a payload and sends the payload to an API, returning the results of the API call. The corresponding unit test code invokes the flow, "sending" a sample payload and verifying that the results received are as expected. * CNI Code * Unit test code index.ts ```typescript import { configPage, configVar, flow, integration, } from "@prismatic-io/spectral"; import axios from "axios"; import { componentRegistry } from "./componentRegistry"; const configPages = { "Acme Config": configPage({ elements: { "Acme API Endpoint": configVar({ stableKey: "1F886045-27E7-452B-9B44-776863F6A862", dataType: "string", description: "The endpoint to fetch TODO items from Acme", defaultValue: "https://my-json-server.typicode.com/prismatic-io/placeholder-data/todo", }), "Acme API Key": configVar({ stableKey: "webhook-config-endpoint", dataType: "string", description: "The endpoint to call when deploying or deleting an instance", }), }, }), }; export const myFlow = flow({ name: "Create Acme Opportunity", stableKey: "create-acme-opportunity", description: "Create an opportunity in Acme", onExecution: async (context, params) => { const { id, name, value } = params.onTrigger.results.body.data as { id: string; name: string; value: number; }; if (value < 0) { throw new Error("Invalid value - values cannot be negative"); } const acmeEndpoint = context.configVars["Acme API Endpoint"]; const response = await axios.post( `${acmeEndpoint}/opportunity`, { id, name, value }, { headers: { Authorization: `Bearer ${context.configVars["Acme API Key"]}`, }, }, ); return { data: response.data }; }, }); export default integration({ name: "acme-cni", description: "Acme CNI", iconPath: "icon.png", flows: [myFlow], configPages, componentRegistry, }); ``` index.test.ts ```typescript import { myFlow } from "."; import { invokeFlow, defaultTriggerPayload, } from "@prismatic-io/spectral/dist/testing"; interface MyFlowResponse { externalId: string; id: string; name: string; value: number; } describe("test myFlow", () => { test("Verify that the API returns an external ID that matches the specified ID", async () => { const { result } = await invokeFlow( myFlow, { "Acme API Endpoint": "https://staging.api.example.com", "Acme API Key": "my-api-key", }, {}, { ...defaultTriggerPayload(), body: { data: { id: "123", name: "my-opportunity", value: 1000 }, contentType: "application/json", }, }, ); expect((result?.data as MyFlowResponse).externalId).toBe("123"); }); test("Verify that errors are thrown when provided negative values", async () => { await expect( invokeFlow( myFlow, { "Acme API Endpoint": "https://staging.api.example.com", "Acme API Key": "my-api-key", }, {}, { ...defaultTriggerPayload(), body: { data: { id: "123", name: "my-opportunity", value: -1000 }, contentType: "application/json", }, }, ), ).rejects.toThrow("Invalid value - values cannot be negative"); }); }); ``` You can run a unit test with ```bash npm run test ``` ![Running a unit test for a code-native integration](/docs/assets/images/cni-unit-test-09e9c4ef85fa34d93bed44163db08779.png) Testing code-native integrations with component references Note that if your code-native integration depends on existing components' actions, your local environment does not have the necessary component code and you must test your integration [within Prismatic](#testing-a-code-native-integration-in-prismatic). ###### Unit testing a code-native integration with an OAuth 2.0 connection[​](#unit-testing-a-code-native-integration-with-an-oauth-20-connection "Direct link to Unit testing a code-native integration with an OAuth 2.0 connection") If your integration includes an OAuth 2.0 connection, you can use the same strategy outlined in the [Unit Testing Custom Components](https://prismatic.io/docs/custom-connectors/unit-testing.md) guide. Both custom components and code-native integrations can take advantage of the `prism components:dev:run` command to fetch an established connection from an existing test instance. --- #### Common Integration Patterns ##### Integration Types Most integrations on Prismatic fall into one of these patterns. Pick the one that best describes what you're building and follow the linked guide for implementation details. | Pattern | What it does | | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | [**Data sync**](https://prismatic.io/docs/intro/guidelines/data-sync-guidelines.md) | Keep records in sync between your app and an external system, with initial backfill and ongoing updates | | [**Event-driven**](https://prismatic.io/docs/intro/guidelines/event-driven-guidelines.md) | Respond to webhooks from an external system and process events as they arrive | | [**Alerting and notifications**](https://prismatic.io/docs/intro/guidelines/alerting-notifications-guidelines.md) | Notify customers via Slack, email, SMS, or other channels when something happens in your app | | [**File import and export**](https://prismatic.io/docs/intro/guidelines/file-import-export-guidelines.md) | Transfer and process files between your app and external systems like SFTP or cloud storage | | [**AI agent**](https://prismatic.io/docs/intro/guidelines/ai-guidelines.md) | Expose integration flows as tools that an AI agent can invoke on behalf of your customers | --- ##### Using a FIFO Queue to Ensure In-Order Processing Use native queueing This tutorial walks through implementing your own FIFO queue using a third-party queuing service, which can give you granular control over how messages are queued and processed. Prismatic also offers [native queueing](https://prismatic.io/docs/integrations/triggers/fifo-queue.md). Prismatic's integration runner is designed to process requests in parallel. If you invoke an integration with multiple requests in quick succession, the runner will scale and process all of the requests simultaneously. If you have an integration that requires requests to be processed in a specific order, or an integration that requires you to process only one record at a time, you'll need to take additional steps to ensure that the requests are processed sequentially. Queuing systems, like [Amazon SQS](https://aws.amazon.com/sqs/) or [Azure Service Bus](https://learn.microsoft.com/en-us/azure/service-bus-messaging/) often offer a FIFO (first-in, first-out) queue type which allows you to write messages to the queue, and retrieve them in the order that they were added. You can use a FIFO queue in an integration to queue up requests your integration receives and process them one by one. #### First-in, first-out (FIFO) flows[​](#first-in-first-out-fifo-flows "Direct link to First-in, first-out (FIFO) flows") Regardless of which queuing system you use, the general flow of a FIFO integration is the same: * One flow receives requests in parallel and quickly writes them to the queue. * Another flow runs on a regular schedule, reads one message from the queue at a time, and processes messages in series. Additional flows can be added to the integration to handle other tasks, like configuring queues or cleaning up unprocessable requests from a [dead letter queue](https://en.wikipedia.org/wiki/Dead_letter_queue). #### FIFO queues in Amazon SQS[​](#fifo-queues-in-amazon-sqs "Direct link to FIFO queues in Amazon SQS") You can use the built-in [Amazon SQS](https://prismatic.io/docs/components/aws-sqs.md) component to ensure that your integration processes requests one at a time. [Example Integration](https://github.com/prismatic-io/examples/blob/main/integrations/amazon-sqs-fifo-queue.yml) An Amazon SQS-based FIFO integration will generally have four flows: 1. A "setup" flow that creates and configures the SQS queue. The flow is triggered by an [instance deploy trigger](https://prismatic.io/docs/components/management-triggers.md#instancedeploy) so that it runs when a customer deploys an instance. It contains a single action - [Create Queue](https://prismatic.io/docs/components/aws-sqs.md#createqueue) - which is idempotent and can be run many times. You can use the instance's ID as the queue name to ensure that each instance has its own queue. 2. A "write" flow that receives requests and writes them to the queue. This flow is triggered by a webhook request, and can run several executions in parallel. This flow contains two steps - one step that fetches the queue's URL based on its name, and another step that writes the trigger's payload to the queue. You can again use the instance's ID as the group ID to ensure only one message is processed at a time. 3. A "read" flow that reads messages from the queue and processes them. This flow is triggered on a schedule (as often as every minute). The flow enters a loop and requests a message from the queue. If the queue is empty, the flow exits. If a message is returned, the flow processes the message and deletes it from the queue. 4. A "cleanup" flow that deletes the queue. This flow is triggered by an [Instance Remove trigger](https://prismatic.io/docs/components/management-triggers.md#instanceremove) so that it runs when a customer deletes an instance. It contains two actions - one that fetches the queue's URL based on its name, and another that deletes the queue. ##### Message deduplication in Amazon SQS[​](#message-deduplication-in-amazon-sqs "Direct link to Message deduplication in Amazon SQS") When creating an Amazon SQS queue, you have the option to enable [content-based deduplication](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/FIFO-queues.html#FIFO-queues-exactly-once-processing). When enabled, if two messages with the same content are added to the queue within a 5-minute window, only one message will be added to the queue. This is helpful if your integration receives duplicate requests. If you have your own mechanism for determining whether a message is a duplicate, you can disable content-based deduplication and use the [Message Deduplication ID](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/using-messagededuplicationid-property.html) in the "write" flow to determine whether a message is a duplicate. ##### Assured ordering in Amazon SQS[​](#assured-ordering-in-amazon-sqs "Direct link to Assured ordering in Amazon SQS") One concern you may have with this approach is that if the "read" flow runs every minute, and one flow takes time to process a large batch of messages, another "read" flow may begin running. *Will this cause messages to be processed out of order?* No. Amazon SQS won't return additional messages to any reader until you process or delete the current message (or the message expires past its [visibility timeout](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html)). If one "read" flow is already processing a message, an additional "read" flow will be told that no new messages are available, and will exit. ##### Real-time processing in an Amazon SQS FIFO integration[​](#real-time-processing-in-an-amazon-sqs-fifo-integration "Direct link to Real-time processing in an Amazon SQS FIFO integration") The "read" flow runs every minute to process messages. If you need to process messages in real-time, you can use a webhook trigger to trigger the "read" flow from the "write" flow after a message is written to the queue. ##### Handling AWS credentials in an Amazon SQS FIFO integration[​](#handling-aws-credentials-in-an-amazon-sqs-fifo-integration "Direct link to Handling AWS credentials in an Amazon SQS FIFO integration") If you use Amazon SQS in your integration, you'll need to provide AWS credentials to the integration runner. You can either: * Have your customer provide their own AWS credentials when they deploy an instance. This requires that your customer have an AWS account and create an IAM user with the appropriate permissions. * Use your own AWS account and credentials. You can provide default credentials in the config wizard designer, but mark the connection as [organization-visible](https://prismatic.io/docs/integrations/config-wizard/config-variables.md#config-variable-visibility). Your customers' instances will then use your credentials to access AWS, but they will not be able to see the credentials in the config wizard or through the API. --- ##### Large Data Sync Many integrations need to do an initial bulk import of data when first deployed, and then keep data up to date by processing incoming webhooks in real time. Or, they need to periodically re-sync data from a source system on a schedule. The code-native `batchFlowTrigger` pattern handles both cases through a single, unified execution path. ![Large data sync instance execution](/docs/assets/images/execution-results-31977ac3a25800a959b219f73aaa90db.png) #### How it works[​](#how-it-works "Direct link to How it works") If you'd like a video walkthrough of this pattern, click [here](https://prismatic.io/docs/get-started/build-integrations/large-data-syncs.md). A flow using `batchFlowTrigger` defines two trigger functions: * **`onDeploy`** - runs when the instance is deployed. Fetches one page of records and returns them, along with a `paginationState` cursor so the next page can pick up where this one left off. Prismatic calls `onDeploy` repeatedly until you return `null` for `paginationState`, signaling that the initial sync is complete. * **`onTrigger`** - runs each time the flow's webhook fires. Like `onDeploy`, it can return `items` and a `paginationState` to page through data - useful when a single webhook event should trigger a batched re-sync. For simple real-time events, it typically returns just the incoming records with no pagination state. Both functions produce `items` - an array of records - that Prismatic hands to `onExecution` in chunks. Your `onExecution` function processes records the same way regardless of which function produced them. #### Basic structure[​](#basic-structure "Direct link to Basic structure") flows.ts ```ts import { batchFlowTrigger, flow } from "@prismatic-io/spectral"; // The shape of a single record type Post = { id: number; title: string }; // The cursor carried between backfill pages to remember where we left off type PostCursor = { startId: number }; export const importPosts = flow({ name: "Import Posts", stableKey: "import-posts", // How many records onExecution receives at once, and how many // batches can run concurrently. Note - `onDeploy` can return // any number of records, and they'll be split into batches of // this size for `onExecution`. batchConfig: { batchSize: 5, concurrentBatchLimit: 3 }, trigger: batchFlowTrigger({ onDeploy: async (context, payload) => { // Get previous pagination state, or start at 0 if this is the first page const startId = payload.paginationState?.startId ?? 0; // Assume `fetchPage` returns `{ data: Post[] }` for the next page of posts const response = await fetchPage(startId, 20); return { items: response.data, // Return null when the page is empty - signals the sync is done paginationState: response.data.length > 0 ? { startId: startId + response.data.length } // Increment the cursor for the next page : null, }; }, // Receive incoming webhook events and return them as a `Post[]` array to be processed by onExecution. onTrigger: async (context, payload) => { const post = payload.body.data as Post; return { items: [post], response: { statusCode: 200, contentType: "text/plain", body: "ok" }, }; }, }), onExecution: async (context, params) => { // Both onDeploy and onTrigger deliver records here const posts = params.onTrigger.results.body.data as Post[]; for (const post of posts) { context.logger.info(`Processing post ${post.id}: ${post.title}`); } return { data: null }; }, }); ``` #### Key concepts[​](#key-concepts "Direct link to Key concepts") ##### `items` and `paginationState`[​](#items-and-paginationstate "Direct link to items-and-paginationstate") `onDeploy` returns an object with two fields: * **`items`** - the records fetched from this page. These are what `onExecution` will receive when the set of records is split into batches. * **`paginationState`** - any serializable value that represents your position in the dataset. Prismatic passes this back to `onDeploy` on the next call as `payload.paginationState`. Return `null` (or `undefined`) when there are no more pages. You choose the shape of `paginationState`. A simple page offset, a last-seen ID, or an API-provided cursor token all work well. ##### `onDeploy` runs repeatedly until pagination ends[​](#ondeploy-runs-repeatedly-until-pagination-ends "Direct link to ondeploy-runs-repeatedly-until-pagination-ends") Prismatic calls `onDeploy` in a loop: 1. First call: `payload.paginationState` is `undefined`. Fetch page 1 and return `paginationState` pointing to page 2. 2. Second call: `payload.paginationState` is whatever you returned previously. Fetch page 2, return `paginationState` pointing to page 3. 3. Continue until a page returns no data - return `paginationState: null` to stop. Each call to `onDeploy` produces a set of items that are immediately dispatched to `onExecution`, so processing starts while the next page is still being fetched. ##### `batchConfig` controls throughput[​](#batchconfig-controls-throughput "Direct link to batchconfig-controls-throughput") ```ts batchConfig: { batchSize: 5, concurrentBatchLimit: 3 } ``` * **`batchSize`** - `onExecution` is called once for every `batchSize` records. If `onDeploy` fetches 20 records with `batchSize: 5`, `onExecution` is invoked four times. * **`concurrentBatchLimit`** - the number of `onExecution` calls that can run in parallel. Tune this against your downstream system's rate limits. ##### `onTrigger` and `onDeploy` both feed `onExecution`[​](#ontrigger-and-ondeploy-both-feed-onexecution "Direct link to ontrigger-and-ondeploy-both-feed-onexecution") `onTrigger` runs whenever the flow's webhook fires, and it supports the same return shape as `onDeploy`: `items`, an optional `paginationState`, and an optional HTTP `response`. This means `onTrigger` can also page through data in batches - for example, if a webhook event signals that a new batch of records is available, `onTrigger` can fetch and page through them the same way `onDeploy` does. For simple real-time events (a single record arrives in the webhook body), `onTrigger` typically returns just that record with no pagination state. Either way, the items flow into the same `onExecution` path. Inside `onExecution`, records are always available at: ```ts params.onTrigger.results.body.data; ``` This is an array regardless of whether they came from `onDeploy` or `onTrigger`. #### Regular data syncs[​](#regular-data-syncs "Direct link to Regular data syncs") The `onDeploy` example above shows how to *backfill* a large dataset when the integration is first deployed. If you want to run the data sync on a regular schedule instead of just on deploy, you can omit `onDeploy` and instead use the same `items` / `paginationState` pattern in `onTrigger`. For example, a scheduled flow could run every hour and page through a source API to fetch new records, returning them to `onExecution` in batches. #### FAQ[​](#faq "Direct link to FAQ") **Is there a limit to the number of records I can backfill?** To prevent runaway infinite loops, an `onDeploy` or `onTrigger` will loop a maximum of 1000 times. If you need to fetch more than 1000 pages of records, consider fetching multiple pages at once and returning them in a single `items` array. **I only see a few batches when testing - why?** It's easy to accidentally create an infinite loop in an `onDeploy` or `onTrigger` function if you handle pagination incorrectly. The test runner will stop after 2 iterations to give you an opportunity to see sample results of your batch processing, but will not execute a full backfill. If you want to test a full backfill, deploy the integration and watch the instance execution in real time. #### Example integrations[​](#example-integrations "Direct link to Example integrations") Two reference integrations show this pattern end to end: * **[Simple initial data sync](https://github.com/prismatic-io/integration-templates/tree/main/simple-initial-data-sync)** - pages through a public JSON API using a numeric offset cursor and processes records through a unified `onExecution`. A good starting point. * **[Salesforce initial data sync](https://github.com/prismatic-io/integration-templates/tree/main/salesforce-initial-data-sync)** - uses a last-seen ID cursor to page through Salesforce leads via SOQL, and also sets up a Salesforce Outbound Message to receive new leads in real time. #### Additional resources[​](#additional-resources "Direct link to Additional resources") * [Get Started with Large Data Syncs](https://prismatic.io/docs/get-started/build-integrations/large-data-syncs.md) - a video walkthrough of this pattern * [Handling Large Data Sets](https://prismatic.io/docs/insider.md#handling-large-data-sets-2026-06-30) - webinar discussing large data syncs --- ##### Handling Large Files If your integration transfers large files between your app and a partner app, you'll encounter several runner limitations: * The payload you can send to a webhook URL is limited to approximately 6MB * Your webhook request must complete within 30 seconds * Your flow can run for a maximum of 15 minutes * The runner is allocated 1GB of RAM A complete list of runner limits can be found [here](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md). When you send large files through a flow, they may exceed upload size and time limits, or require more memory than available on the runner (resulting in an out-of-memory error). There are several strategies you can use to handle large files in your Prismatic integration. #### Upload files directly to a file storage system[​](#upload-files-directly-to-a-file-storage-system "Direct link to Upload files directly to a file storage system") If you and your partner app both use a file storage system like Amazon S3 or Dropbox, you can upload files directly to that system using the file storage system's API, where you can request a temporary or presigned URL that allows you to upload a file directly from your application to the file storage system. ##### Upload files directly to Amazon S3[​](#upload-files-directly-to-amazon-s3 "Direct link to Upload files directly to Amazon S3") To upload a file directly to your customer's Amazon S3 bucket, you can use the [Generate Presigned URL](https://prismatic.io/docs/components/aws-s3.md#generatepresignedurl) action from the Amazon S3 component. ![Screenshot of generating a presigned URL from S3](/docs/assets/images/generate-presigned-url-s3-12430e509c2f049e482b66f9599d4216.png) If you'd like your flow to return a presigned upload URL whenever it is invoked: 1. Ensure that the trigger has a **Response Type** of **Synchronous** 2. Ensure that the **Generate Presigned URL** action is the last action of your flow. If those conditions are met, when your app calls the instance's flow webhook URL, it receives a response containing the presigned URL from the **Generate Presigned URL** action. You can use the returned presigned URL to upload a file directly to Amazon S3 through an HTTP PUT request: ```bash # Fetch the presigned URL from the webhook response and remove double-quotes $ curl 'https://hooks.dev.prismatic-dev.io/trigger/SW5zdEXAMPLE==' --location | tr -d '"' https://example-bucket.s3.us-west-2.amazonaws.com/my-file.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20240221%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240221T191715Z&X-Amz-Expires=3600&X-Amz-Signature=82a604673c3fffc2671b2dd7c7a86036af67693509ba0d01f172ef0b1f84fb20&X-Amz-SignedHeaders=host&x-id=PutObject # Use the presigned URL to upload a file directly to Amazon S3 $ curl --request PUT \ --upload-file ./my-example-file.png \ https://example-bucket.s3.us-west-2.amazonaws.com/my-file.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20240221%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240221T191715Z&X-Amz-Expires=3600&X-Amz-Signature=82a604673c3fffc2671b2dd7c7a86036af67693509ba0d01f172ef0b1f84fb20&X-Amz-SignedHeaders=host&x-id=PutObject ``` ##### Upload files directly to Dropbox[​](#upload-files-directly-to-dropbox "Direct link to Upload files directly to Dropbox") Similar to presigned URLs for Amazon S3, you can use the [Generate Temporary Upload Link](https://prismatic.io/docs/components/dropbox.md#gettemporarylink) action from the Dropbox component to upload a file directly to Dropbox. ![Screenshot of generating a presigned URL from Dropbox](/docs/assets/images/generate-temporary-upload-link-dropbox-b6b8ffb0bae79279c337dad794cd0722.png) Provided that you have a synchronous trigger and the **Generate Temporary Upload Link** action is the last action in your flow, the presigned URL will be returned to the caller of your webhook. ```bash # Invoke a flow that generates a presigned URL, and parse the URL from the JSON response $ curl 'https://hooks.dev.prismatic-dev.io/trigger/SW5zdEXAMPLE==' --location | jq -r .result.link https://content.dropboxapi.com/apitul/1/ExAmPlE # Use the presigned URL to upload a file directly to Dropbox $ curl --request POST \ --upload-file ./my-example-file.png \ --header "content-type: application/octet-stream" \ https://content.dropboxapi.com/apitul/1/ExAmPlE ``` Note that Dropbox expects a `POST` request (unlike Amazon S3, which expects a `PUT` request) and requires a `content-type` header with a value of `application/octet-stream`. ##### Query an instance's connection config variable and upload a file directly to a third-party[​](#query-an-instances-connection-config-variable-and-upload-a-file-directly-to-a-third-party "Direct link to Query an instance's connection config variable and upload a file directly to a third-party") If you need to upload a file to a third-party service that does not support presigned upload URLs, but your customer provided connection information for the third-party service when they configured an instance of your integration, you can query the Prismatic API for the instance's connection config variables and use those credentials to upload the file directly to the third-party service. To query an instance's config variables, you can query for an `instance` object's `configVariables` property - particularly their `inputs.nodes.value` fields: Query instance connection config variables ```graphql query getAcmeConnectionInfo($myInstanceId:ID!) { instance(id: $myInstanceId) { configVariables { nodes { id requiredConfigVariable { key } meta inputs { nodes { name value } } } } } } ``` Query Variables ```json { "myInstanceId": "SW5zdGFuY2U6NDY2MGQ4MDgtYjUwZS00NDdhLThhZmQtOWU0NzAxMzJkZThk" } ``` [Try It Out ❯](https://prismatic.io/docs/explorer?query=query+getAcmeConnectionInfo%28%24myInstanceId%3AID%21%29+%7B%0A++++instance%28id%3A+%24myInstanceId%29+%7B%0A++++++configVariables+%7B%0A++++++++nodes+%7B%0A++++++++++id%0A++++++++++requiredConfigVariable+%7B%0A++++++++++++key%0A++++++++++%7D%0A++++++++++meta%0A++++++++++inputs+%7B%0A++++++++++++nodes+%7B%0A++++++++++++++name%0A++++++++++++++value%0A++++++++++++%7D%0A++++++++++%7D%0A++++++++%7D%0A++++++%7D%0A++++%7D%0A++%7D\&query_variables=%7B%0A++%22myInstanceId%22%3A+%22SW5zdGFuY2U6NDY2MGQ4MDgtYjUwZS00NDdhLThhZmQtOWU0NzAxMzJkZThk%22%0A%7D) This query will return a result like this: ```json { "data": { "instance": { "configVariables": { "nodes": [ { "id": "SW5zdGFuY2VDb25maWdWYXJpYWJsZTo3MzkzYjU0YS1kNWMxLTQzYjEtOTM3ZS1iNTM0ZDhiYTA1NzA=", "requiredConfigVariable": { "key": "My String Config Variable" }, "meta": null, "inputs": { "nodes": [] } }, { "id": "SW5zdGFuY2VDb25maWdWYXJpYWJsZTo1OTY1ZjEwMy0xNGIyLTRmZWItYmI4My1hZWI4NmViNmRhYmI=", "requiredConfigVariable": { "key": "Acme Inc Connection" }, "meta": null, "inputs": { "nodes": [ { "name": "password", "value": "my-pass" }, { "name": "username", "value": "my-user" } ] } } ] } } } } ``` From the result you can extract connection information (like a username and password or API key) and use those credentials to make an API request directly to the file storage system. If the file storage system uses OAuth 2.0, your customer's API key will be present in the config variable's `meta` property. For more information on querying the Prismatic API, see the [Prismatic API documentation](https://prismatic.io/docs/api.md). #### Instruct Dropbox to download a file from a URL[​](#instruct-dropbox-to-download-a-file-from-a-url "Direct link to Instruct Dropbox to download a file from a URL") If you have a file that is publicly available at a certain URL, you can use the [Save from URL](https://prismatic.io/docs/components/dropbox.md#savefromurl) action from the Dropbox component to instruct Dropbox to download the file from the internet. Provide the URL where the file is located, and Dropbox downloads the file and saves it to your specified path in the user's Dropbox account. ![Screenshot of saving a file from a URL to Dropbox](/docs/assets/images/save-from-url-dropbox-a5339ffe948064a660887f1e3548144d.png) #### Pass a reference to a file via webhook[​](#pass-a-reference-to-a-file-via-webhook "Direct link to Pass a reference to a file via webhook") If you have a file that is too large to upload directly via webhook, but is small enough that the 1GB of memory available to the runner can handle it, you can pass a reference to the file to the runner via a webhook request. This reference could be a URL where the file is located, or a unique identifier that the runner can use to download the file from a file storage system. Your flow can use the [HTTP - GET](https://prismatic.io/docs/components/http.md#httpget) action or a comparable FTP or SFTP action to download the file from the URL, or use the file storage system's API to download the file using the unique identifier, and can then process the file as if it were uploaded directly to the flow via webhook request. #### Stream data using a custom action[​](#stream-data-using-a-custom-action "Direct link to Stream data using a custom action") If you need to load a file from one location, process its data, and store the file in another location, you can build a custom component and leverage Node.js streams to process small portions of the file at a time. See [Handling Large Files in Custom Components](https://prismatic.io/docs/custom-connectors/handling-large-files-in-custom-components.md) for examples. --- ##### Loop Over and Process Files In this tutorial we will build an integration that downloads and processes files stored in [Google Cloud Storage](https://prismatic.io/docs/components/google-cloud-storage.md), but similar concepts can be applied to files stored in Dropbox, Amazon S3, Azure Blob Storage, and SFTP server, or any other file storage system. For this integration, assume that some third-party service writes an XML file to a Google Cloud Platform (GCP) storage bucket whenever they process an order. We'll configure our integration to run every five minutes, and our integration will do the following: * Look for files in the `unprocessed/` directory of our GCP Storage bucket * For each file that we find: * Download the file * Deserialize the XML contained in the file * Perform data mapping * Post the transformed data to an HTTP endpoint * Move the file from the `unprocessed/` directory to a `processed/` directory Our integration will leverage the [loop](https://prismatic.io/docs/components/loop.md) component to process files one by one. If you would like to view the YAML definition of this example integration, it's available on [GitHub](https://github.com/prismatic-io/examples/blob/main/integrations/example-with-loop.yaml). You can import it by creating a new integration and selecting **Import**. #### List files[​](#list-files "Direct link to List files") We'll start by adding a Google Cloud Storage **List Files** action to our integration. This will automatically create a Google Cloud Storage connection to our config wizard. We'll want our customers to be able to specify their own bucket, so let's add a [Select Bucket](https://prismatic.io/docs/components/google-cloud-storage.md#selectbucket) data source config variable to our config wizard: ![Google Cloud Storage - Select Bucket data source](/docs/assets/images/select-bucket-data-source-f4cee6e6f5cdcc85754e7a9175b1c59c.png) We can also add two more string config variables to represent the `unprocessed/` and `processed/` directories in the bucket (which our customer may want to change, so it's handy to make these config variables). Now, configure the **List Files** action to reference our config variables: ![Google Cloud Storage - List Files inputs](/docs/assets/images/list-files-inputs-96aabc48772fca44b78869a7b660f285.png) Next, we'll open the **Test Configuration** drawer and select **Test-instance configuration** to set some test credentials and config variable values. ![Google Cloud Storage - Test config variables](/docs/assets/images/test-config-variables-93b237b6a7971ded9bbd1c221734e852.png) Finally, we'll click **Run**. If you see any errors about permissions, ensure that the Google IAM account you created has the proper permissions to the bucket you created. You should see the files in your `unprocessed/` directory: ![Google Cloud Storage - List Files result](/docs/assets/images/list-files-results-846d8fc00d4769280a9850a11abc4966.png) #### Create our loop[​](#create-our-loop "Direct link to Create our loop") Next, we'll loop over the files that our **List Files** step found. We'll add a **Repeat for Each** step. Under the **Items** input we will reference the list of files our previous step returned: ![Repeat for each in Prismatic integration designer](/docs/assets/images/loop-step-46c1b9fac52a2472ccd33a26651433fd.png) #### Add tasks to the loop[​](#add-tasks-to-the-loop "Direct link to Add tasks to the loop") Our loop is now configured to run once for each file that was found in the `unprocessed/` directory in our GCP bucket. Our loop will contain several steps to process and send the data to an external system. ##### Download the file we're currently looping over[​](#download-the-file-were-currently-looping-over "Direct link to Download the file we're currently looping over") First, we'll download the file we're currently processing. The item that we're currently processing from our loop is accessible using the `currentItem` key of the loop. We'll add a **Download File** action from the GCP component. * For **File Name** we'll reference the loop's `currentItem`. * For **Bucket Name** we'll reference the bucket name config variable we created. * Our **Connection** is already set up for us. For example, if there's a file named `unprocessed/order-123.xml` in our bucket, `loopOverEachFile.currentItem` would be equal to `"unprocessed/order-123.xml"`: ![Download current file inputs](/docs/assets/images/download-file-inputs-98002ee805ba39f4841d66db42f1cc34.png) Because we're downloading an XML file, this action will return parsed XML in a string format. ![Download current file results](/docs/assets/images/download-file-results-cb56f7fc06e53d757b8f7e62e9b9b627.png) ##### Deserialize the XML[​](#deserialize-the-xml "Direct link to Deserialize the XML") Next, we'll use the [Deserialize XML](https://prismatic.io/docs/components/change-data-format.md#deserializefromxml) action to convert the XML string into a JavaScript object whose keys can be referenced by subsequent steps. ![Download current file results](/docs/assets/images/deserialize-xml-results-5fde8efc857a8251676a2ef354a0d6a7.png) ##### Map the data[​](#map-the-data "Direct link to Map the data") Next, suppose the API we're sending the data to expects a different format ("quantity" instead of "qty", etc). We can use the [Collection Tools](https://prismatic.io/docs/components/collection-tools.md) **Create Object** action to create a new object for us, referencing the results of the **Deserialize XML** step: ![Create object inputs](/docs/assets/images/create-object-inputs-609ad8c14f880c9c8b78c7720b36c6fb.png) ##### Send the data[​](#send-the-data "Direct link to Send the data") Next, we'll use the [HTTP](https://prismatic.io/docs/components/http.md) component's **POST Request** action to send the data we generated. As a placeholder for an external API, we'll post the data to [Postman's](https://www.postman.com/) `https://postman-echo.com/post` endpoint. For our **Data** input, we'll reference the **Create Object**'s results: ![HTTP POST inputs](/docs/assets/images/http-post-inputs-83afb755799afeb3ed5fa882dad820ef.png) ##### Move the file to a processed directory[​](#move-the-file-to-a-processed-directory "Direct link to Move the file to a processed directory") Finally, we'll move the file that we downloaded out of the way by moving the file from `unprocessed/` to `processed/`. First, we need to replace the word `unprocessed` with `processed`. We'll use the [Text Manipulation](https://prismatic.io/docs/components/text-manipulation.md) component's **Find & Replace** action for that, once again referencing the loop's `currentItem`: ![Find-and-replace inputs](/docs/assets/images/find-and-replace-inputs-db2d157db5054e2dca5b6049d3b088e7.png) We'll add a Google Cloud Storage **Move File** action to move our file from one directory to another: ![Move File inputs](/docs/assets/images/move-file-inputs-844dfbb1b8bcac888d90ffe3b2373f51.png) #### Conclusion[​](#conclusion "Direct link to Conclusion") That's it! At this point we have an integration that loops over files in a directory, processes them, and sends the data to an HTTP endpoint. This integration can be published, and [instances](https://prismatic.io/docs/instances.md) of this integration can be configured and deployed to customers. --- ##### Loop Over a Paginated API When the number of records that an API stores is large, it's not economical to return all possible records at once. Instead, many APIs implement **pagination**. This means that the API returns a small number of records at a time. You as the consumer of the API can "page" through the records, and request more records by requesting the next "page". In an integration you can loop over a paginated API to process all records. In this tutorial, you'll learn how to use the [loop component](https://prismatic.io/docs/components/loop.md) to loop over a paginated API. #### JSON Placeholder API[​](#json-placeholder-api "Direct link to JSON Placeholder API") For illustration purposes, you'll use Typicode's [JSON Placeholder](https://jsonplaceholder.typicode.com/). You can request all 100 "posts" in JSON Placeholder by making a request to , and you can request fewer posts by making the same request with a `_limit` parameter. You can also choose an offset by passing in a `_start` parameter. For example, if you want 10 posts, but you want to start at the 25th post, you can make a request to . In this exercise you'll page through all 100 posts, 25 at a time, making requests to `/posts?_limit=25&_start=`, then `/posts?_limit=25&_start=25`, `/posts?_limit=25&_start=50`, etc. until there are no more posts left to process. This works for any number of records Note: with paginated APIs you often don't know how many total results exist. In this example you'll fetch 100 total "posts", but the looping strategy covered here will accommodate any unknown number of posts. You'll loop until there are no more records left to process. #### The paginated integration[​](#the-paginated-integration "Direct link to The paginated integration") The integration will follow this flow to process all posts in the paginated API: * Start a loop * Fetch a page of up to 25 posts * Are we out of posts to process? * If so, break out of the loop * If not, loop over each post * Do something with the post (we'll just log out the post's title here) * Is this the last post? * If so, make a note of what `_start` we should use for the next loop iteration * Go back to the start of the loop Let's get building! ##### The main loop[​](#the-main-loop "Direct link to The main loop") First, you'll create a [Repeat X Times](https://prismatic.io/docs/components/loop.md#loopntimes) loop step to loop over pages. Under "Number of Iterations" you'll put in some high number, like 20, even though you'll break out of the loop before 20 iterations. Having a maximum number of iterations in a loop helps guard against an infinite loop - you don't want to introduce unintended load on the vendor's API from an infinite loop. If you know that you're going to loop over thousands of pages, you can choose a higher number of maximum iterations. ##### Fetching a page of data[​](#fetching-a-page-of-data "Direct link to Fetching a page of data") Next, you'll add two steps: 1. A [Persist Data - Get Execution Value](https://prismatic.io/docs/components/persist-data.md#getexecutionvalue) step will help to track the `_start` value in the API request. This action references a variable that is scoped to the current execution. That variable's value will be set by another step in the loop later. You'll provide a variable name, `Latest Post ID`, and default value `0` (since it hasn't been set yet): ![Persist Data - Get Execution Value in Prismatic integration designer](/docs/assets/images/get-execution-value-4a316ab670b8bf6685a7b23c37c65db5.png) 2. Next, you'll use the value from the previous step to make a call to JSON Placeholder. You'll add an [HTTP - GET Request](https://prismatic.io/docs/components/http.md#httpget) step and fetch `https://jsonplaceholder.typicode.com/posts` with a `_limit` search parameter of `25` and a `_start` search parameter of the value you retrieved: ![HTTP Get Request in Prismatic integration designer](/docs/assets/images/get-posts-2d8b20a6f693aa17e0a8ce2d4bbe10f5.png) ##### Are you done?[​](#are-you-done "Direct link to Are you done?") Next, you'll determine if you're done fetching posts. You'll do this by adding an [If Condition is Met](https://prismatic.io/docs/components/branch.md#branchonexpression) step, and you'll check to see if the "Get Posts" step you invoked returned an empty array: ![Check if list of files is empty](/docs/assets/images/check-if-empty-b7bcc0db762c8f52e1d7d09d6050c783.png) If the array returned was empty, you know there are no more results to page through. In that case you'll add a [Break Loop](https://prismatic.io/docs/components/loop.md#breakloop) step to exit the main loop: ![Break Loop in Prismatic integration designer](/docs/assets/images/break-loop-3607f715c0f47053bee4e94f9b6afc91.png) ##### Process posts[​](#process-posts "Direct link to Process posts") Assuming there are posts to process, you'll create an interior loop to loop over each post. 1. Add a [Repeat for Each](https://prismatic.io/docs/components/loop.md#loopoveritems) action that takes the results from the "Get Posts" step to loop over the results: ![Repeat for Each in Prismatic integration designer](/docs/assets/images/loop-over-posts-3b3ab4f9ada4d8b361df0c3de9f7e9c2.png) 2. You'll "process" each post. For illustration purposes you'll log out the post's **id** and **title**. You can access each post's title by referencing the interior loop's `currentItem` property: ![Log Write Message in Prismatic integration designer](/docs/assets/images/log-out-post-f1e6f09f89646bdc0b5c8c702940159c.png) ##### Store the last item's ID[​](#store-the-last-items-id "Direct link to Store the last item's ID") Finally, you'll determine the ID of the last post in the page you loaded, so you can adjust the `_start` parameter for the next loop. 1. You'll use an [If Condition is Met](https://prismatic.io/docs/components/branch.md#branchonexpression) action to determine if the `currentItem` has `isLast=true` (which indicates if you're looping over the last post): ![If condition is met to check for last post in Prismatic integration designer](/docs/assets/images/is-last-post-a4c2128fc313a5b25b2c07c6f01eeffc.png) 2. If you are on the last post, you'll use a [Persist Data - Save Execution Value](https://prismatic.io/docs/components/persist-data.md#saveexecutionvalue) to save the ID of the last post. You'll use the same variable name - `Latest Post ID` - that you used before, and you'll save out the loop's `currentItem.id`. The "Get Execution Value" step you added at the beginning of the integration will pick up this value when the loop runs again: ![Persist Data - Save Execution Value in Prismatic integration designer](/docs/assets/images/save-execution-value-da829a8a3a25533ba72625aa42b87c49.png) If you run the integration and look at logs, you can see that IDs and titles of the posts you loaded were logged out. After every 25 posts (after post ID 25, 50, etc.) you can also see that the main loop ran again and an additional page of posts was loaded. You'll see the logs from all loops in the test runner drawer: ![Logs are displayed for all posts that were fetched](/docs/assets/images/logs-are-displayed-8d6cc294161896156d031eba4574ebed.png) #### Pagination implementations[​](#pagination-implementations "Direct link to Pagination implementations") Different APIs implement their pagination differently. Some page payloads contain a value indicating if you're on the last page or not. Others contain a value to let you know what value to ask for with your next API call. Your pagination loop implementation may look slightly different than this one, but hopefully this provided you with a general idea of how to implement pagination in an integration. --- ##### Processing Data in Parallel When you have a large set of records to process, you may want to process data in parallel to accelerate computation. This tutorial demonstrates how to process data in parallel by splitting the data into manageable chunks, and simultaneously processing each chunk. For this example, you'll fetch a "large" dataset from the internet - here you'll pull down 500 "comment" records from the JSONPlaceholder API: [Example Integration](https://github.com/prismatic-io/examples/blob/main/integrations/split-payload-example.yml) #### Split the data into manageable chunks[​](#split-the-data-into-manageable-chunks "Direct link to Split the data into manageable chunks") Once you've fetched the data, you can use the [Collection Tools](https://prismatic.io/docs/components/collection-tools.md#chunks) component's **Chunks** action to split the data into manageable chunks. Here, you split the 500 records into 10 groups of 50 records each. ![Configuring the Chunks action to split the data into 10 groups of 50 records each.](/docs/assets/images/configure-chunks-eef22227ec8151a01f73f307eb45dafb.png) If your data is not evenly divisible by the number of elements you specify, the Chunks action puts the remaining elements in the last chunk. For example, if you have 108 records, and split them into chunks of 25, you'll get 4 chunks of 25 records, and 1 chunk of 8 records. If you open the chunks action's results, you can see 10 groups of 50 records each. ![The results of the Chunks action, showing 10 groups of 50 records each.](/docs/assets/images/chunks-results-593139d1b3a6d732eec7c13513d116bf.png) #### Loop over each chunk[​](#loop-over-each-chunk "Direct link to Loop over each chunk") Next, add a [Repeat for Each](https://prismatic.io/docs/components/loop.md#loopoveritems) action to your integration and configure it to loop over the chunks you generated. ![Configuring the Repeat for Each action to loop over the chunks generated in the previous step.](/docs/assets/images/loop-over-chunks-29773cb352a236ac45d201ac5e18fe34.png) #### Send each chunk to a sibling flow[​](#send-each-chunk-to-a-sibling-flow "Direct link to Send each chunk to a sibling flow") You need to send each chunk of records to a sibling flow. To accomplish that, you'll use [cross-flow](https://prismatic.io/docs/integrations/triggers/cross-flow.md) invocations. You'll add an [Invoke Flow](https://prismatic.io/docs/components/cross-flow.md#invokeflow) step to your integration, and select a sibling flow to send the chunk to. So, first add a sibling flow that has a [Cross-Flow Trigger](https://prismatic.io/docs/components/cross-flow.md#crossflow). Then, select the sibling flow for your **Invoke Flow** step's **Flow Name** input. Reference the **Repeat for Each**'s `currentItem` property for the **Data** input of the **Invoke Flow** action - that'll represent the current chunk of records and will configure the step to send the chunk of records to the sibling flow. ![Invoke a sibling flow by sending the current chunk of records to the sibling flow](/docs/assets/images/configure-invoke-flow-action-42b75688169c1e4a27a6811d7820ead7.png) If you open the **Process Records** flow after running a test of the parent flow, you can see that **Process Records** was invoked ten times, and each invocation received a chunk of 50 records. ![The Process Records flow was invoked 10 times, and each invocation received a chunk of 50 records.](/docs/assets/images/ten-invocations-b5328e1cb5a13252bb4807991cd07995.png) What makes this parallel? By default, Prismatic executions are asynchronous, meaning that our main flow will not wait for an invocation of the **Process Records** flow to complete before beginning the next invocation. This allows us to process multiple chunks of records simultaneously, effectively processing data in parallel. #### Process each chunk in the sibling flow[​](#process-each-chunk-in-the-sibling-flow "Direct link to Process each chunk in the sibling flow") Now that the integration sends records to the sibling flow, you can process each chunk of records in parallel. You'll need to add business logic to your integration and connect it to APIs to fetch or update records. For this example, you'll capitalize the body of each comment: ![The Process Records flow was invoked 10 times, and each invocation received a chunk of 50 records.](/docs/assets/images/capitalize-bodies-79bfdee53f512a3e387fa37451990efa.png) #### (Optional) Aggregate the results[​](#optional-aggregate-the-results "Direct link to (Optional) Aggregate the results") If you're pulling data from a source and sending it to a destination, you may not need to aggregate the results. But, if your integration is bidirectional or if you need to aggregate the results for any other reason, you can fetch the results of each parallel invocation using the execution IDs that the HTTP POST action returned. If you look at the step results of the *Repeat for Each* action, you'll see that the action returns an array of execution IDs from the HTTP POST action. ![The Repeat for Each action returns an array of execution IDs from the HTTP POST action.](/docs/assets/images/execution-ids-765a4e71d82f40426d8ae26cbc0cac79.png) You can loop over these execution IDs and fetch the results of each invocation. To accomplish that, you'll: * Loop over the execution IDs * Loop up to 10 times using the **Repeat X Times** action * Check if the execution is finished by querying the Prismatic API * If it's finished, fetch the step results of a step in the sibling flow. Break the inner loop. * If it's not finished, sleep for a few seconds and check again ##### Fetch step results from the Prismatic API[​](#fetch-step-results-from-the-prismatic-api "Direct link to Fetch step results from the Prismatic API") You can use the Prismatic component's **Raw GraphQL Request** action to query the step results of a step in the sibling flow. ```graphql query myGetExecutionResults($executionId: ID!, $stepName: String!) { executionResult(id: $executionId) { id endedAt stepResults(displayStepName: $stepName) { nodes { resultsUrl } } } } ``` ![Fetch data from the Prismatic API](/docs/assets/images/prismatic-api-3345dd9f5ea31d2b437bbe8ae5729724.png) If the execution is finished (indicated by whether or not `endedAt` has a value), you can use the `resultsUrl` to fetch the step results of a step in the sibling flow, and then break the loop. If the execution is not finished, the integration sleeps and then checks again. ![Fetch results from S3](/docs/assets/images/fetch-step-results-69bc56d4d9647041c04733d4234fc719.png) A few notes: * Prismatic stores step results as binary files in S3 and compresses them using [MessagePack](https://msgpack.org/index.html). Since they are binary files, you need to set the GET Request action's **Response Type** to **Binary**. * You need to decompress the step results using the MessagePack **Decompress** action. * You can either process the step results in the loop, or save the results to an array using the Persist Data's **Execution - Append Value to List** action to aggregate the results into a single array. That's what the example integration does. ##### Process the results[​](#process-the-results "Direct link to Process the results") Finally, you can load the array of step results using an **Execution - Get Value** action. The results will be an array of arrays, so you can use the Collection Tools component's **Flatten** action to flatten the array of arrays into a single array. When you do that in the example, you get an array of 500 records, each with a capitalized body. ![Flatten the array of step results into a single array.](/docs/assets/images/flatten-results-405a67b50e4e8661d67de3fb3572499c.png) You can then process each record in the array of results. #### Limitations and considerations[​](#limitations-and-considerations "Direct link to Limitations and considerations") There are several limitations and considerations to keep in mind when processing data in parallel: **Simultaneous Execution Limit**. The number of concurrent executions your organization can run is determined by your pricing plan. If you try to run more than that many executions at once, you may receive a `429 Too Many Requests` error, and will need to handle that in your integration. **Execution Time Limit**. An execution can run for up to 15 minutes. If your execution takes longer than 15 minutes, it will be terminated. When sizing chunks of records, consider how many can be processed within 15 minutes. **Payload size limit**. A webhook request can be up to approximately 6MB in size. If a chunk of records exceeds 6MB, the chunks will need to be smaller. **Rate limits**. The APIs you integrate with may have rate limits, and parallelizing requests may exceed those limits. Be sure to check the rate limits of the APIs you integrate with. If you run into rate limiting constraints, consider running your flows in sequence with a [large data sync](https://prismatic.io/docs/integrations/common-patterns/large-data-sync.md). For information on Prismatic integration limits, see [Integration Limits](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md). --- ##### Processing Data with Recursive Flows Suppose your customers have a large number of records in a CRM that you need to sync to your app when an integration is deployed. Processing large amounts of data can take considerable time. For example, if your customers have around 100,000 records, and you know from testing that you can fetch and process about 10 records per second, then doing some back-of-the-envelope math you'll find it'll take almost 3 hours to do an initial sync of those records. But, an execution can run for a maximum of [15 minutes](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md#execution-time-limitations). To process 3 hours of data, you'll need to split the work across at least 12 executions. Prefer batchFlowTrigger for code-native integrations If you're building a [code-native integration](https://prismatic.io/docs/integrations/code-native.md), the [`batchFlowTrigger`](https://prismatic.io/docs/integrations/common-patterns/large-data-sync.md) pattern is the recommended approach for large data syncs. It handles pagination, batching, and real-time webhook processing in a single, unified trigger - no manual recursion required. You can accomplish this in one of two ways: 1. You can parallelize the work, running 12 executions concurrently. [Processing Data in Parallel](https://prismatic.io/docs/integrations/common-patterns/processing-data-in-parallel.md) documents how to do that. Note that with this strategy you may run into execution concurrency or third-party API rate limits if you run too many executions in parallel. 2. Run several executions in series, allowing one execution to process a chunk of data and then call itself with a cursor noting where it left off. This document details how to run a flow recursively to process large amounts of data. #### Recursive flows[​](#recursive-flows "Direct link to Recursive flows") A recursive flow is a flow that calls itself. In Prismatic, they're useful when processing large datasets that take longer than 15 minutes to process. One execution processes a number of records you know can be processed within the time constraints, and then it calls itself with a cursor indicating where it left off. The next execution continues the work. Typically, a recursive flow looks something like this: If the API you're integrating with is paginated (i.e. you fetch page 1 of records, then page 2, etc.), your first execution might loop 20 times, fetch pages 1-20, and then it'll call itself with a cursor of `21`, indicating that the next execution should process pages 21-40. #### The recursive flow component[​](#the-recursive-flow-component "Direct link to The recursive flow component") The [recursive flow](https://prismatic.io/docs/components/recursive-flow.md) component can be used to build recursive flows. It contains a trigger and three actions: * The [Recursive Trigger](https://prismatic.io/docs/components/recursive-flow.md#recursivetrigger) takes a JSON payload in the shape of `{"cursor": "some-cursor"}`. If a cursor is passed to it, it saves that cursor to [execution state](https://prismatic.io/docs/integrations/persist-data.md) for use by the flow. If a cursor is not present, it defaults to some default value that you provide as an input. * The [Invoke Recursive Trigger](https://prismatic.io/docs/components/recursive-flow.md#invokerecursive) action reads the current cursor from execution state and calls its own trigger to start a new execution. * The [Get Recursive Cursor](https://prismatic.io/docs/components/recursive-flow.md#getcursor) action reads the current cursor from execution state and returns it. This is handy to use at the top of a loop to fetch the current cursor value. * The [Set Recursive Cursor](https://prismatic.io/docs/components/recursive-flow.md#setcursor) action saves a given value to the cursor in execution state, which is then read by a Get Recursive Cursor or Invoke Recursive Trigger action. ##### Running an initial data import[​](#running-an-initial-data-import "Direct link to Running an initial data import") If you would like your recursive flow to run when a customer deploys an instance of your integration, toggle the trigger's **Run on Deploy?** to `true`. Supply a reasonable **Default Cursor Value** which will be used in the first execution of the flow. For example, you could enter `1970-01-01 00:00:00` if your cursor is an "Updated At" timestamp, or `0` if your cursor is a paginated API page value. Deploy flows run each time an instance is deployed Note that a deploy flow runs each time an instance is deployed. So, if a customer deploys an instance, and then reconfigures the instance and re-deploys it, the recursive trigger will begin twice. Ensure that your flows are built in an [idempotent](https://en.wikipedia.org/wiki/Idempotence) way. You could, for example, set a [flow state](https://prismatic.io/docs/integrations/persist-data.md) persisted value when initial import completes, and short-circuit your flow if an initial import has previously completed. ##### Calling the recursive trigger yourself[​](#calling-the-recursive-trigger-yourself "Direct link to Calling the recursive trigger yourself") If you would like to call a recursive trigger yourself to begin a series of executions, you can invoke your flow's webhook URL (as you would a standard [webhook trigger](https://prismatic.io/docs/integrations/triggers/webhook.md)). If you would like to override the default cursor value, and supply your own cursor value, `POST` a request in the format `{"cursor": "some-cursor"}`. For example, ```bash curl 'https://hooks.prismatic.io/trigger/SW5zexample==' \ --location \ --header "Content-Type: application/json" \ --data '{"cursor":"2000-01-01 00:00:00"}' ``` This is helpful if the initial recursive cursor you want to use is a dynamic value - you can have one flow compute the value (e.g. "Datetime 3 years ago"), and call your recursive flow with that value. #### Stopping a recursive flow[​](#stopping-a-recursive-flow "Direct link to Stopping a recursive flow") When a flow calls itself, it's easy to accidentally create an infinite loop. Ensure that you have logic within your flow that leads away from an **Invoke Recursive Trigger** action when data processing completes. If you do run into an infinite loop: * If you're in the integration designer, add a [Stop Execution](https://prismatic.io/docs/components/stop-execution.md) step to the top of your flow and hit 'save' to cause the next execution to stop before it calls itself again. * If you have an instance deployed to a customer that is in an infinite loop, [disable](https://prismatic.io/docs/instances/managing.md#enabling-and-disabling-instances) the instance for a short time. The next time the instance's flow attempts to call itself, it won't be able to. #### Example recursive flows[​](#example-recursive-flows "Direct link to Example recursive flows") This integration in our GitHub examples repository contains four flows that illustrate how to loop over various paginated APIs: [Example Integration](https://github.com/prismatic-io/examples/blob/main/integrations/recursive-flow-examples.yml) You can [import](https://prismatic.io/docs/integrations/low-code-integration-designer.md#yaml-definition) the integration into your own tenant for testing. ##### JSON Placeholder example recursive flow[​](#json-placeholder-example-recursive-flow "Direct link to JSON Placeholder example recursive flow") The **JSON Placeholder Example** flow loops over JSON Placeholder's [comments API](https://jsonplaceholder.typicode.com/comments), retrieving 5 pages of 15 records each execution. JSON Placeholder uses page numbers as pagination tokens (i.e. page 1, page 2, etc.), so this flow processes pages 1 through 5 during its first execution, then 6-10, 11-15, etc. No authentication is required for this flow. ##### PostgreSQL example recursive flow[​](#postgresql-example-recursive-flow "Direct link to PostgreSQL example recursive flow") The **PostgreSQL Example** flow loops over records in a PostgreSQL table. It uses a `createdat` column on the table as a cursor. It starts by querying all records created after UNIX epoch (1970-01-01), takes note of the last record's `createdat` value as a cursor, and then the next loop or execution fetches records with a `createdat` value larger than the cursor it stored. To test this flow locally, you'll need to spin up a PostgreSQL database that is publicly accessible (or accessible with an [on-prem agent](https://prismatic.io/docs/integrations/connections/on-prem-agent.md)). Then, you'll need to create a table with records that have a `createdat` timestamp. ##### Salesforce example recursive flow[​](#salesforce-example-recursive-flow "Direct link to Salesforce example recursive flow") The **Salesforce Example** flow loops over `Contact` records in SFDC. It uses the [SOQL](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql.htm) query language to order and fetch records. ![SOQL recursive example](/docs/assets/images/soql-example-83b7dc2440e70d993b4b1a22106aaa07.png) This flow uses SOQL's `OFFSET` property to fetch pages. First it fetches records 1-5, then it fetches 5 more records with `OFFSET 5`, so it gets records 6-10, etc. To test this flow, you'll need a Salesforce account with basic auth, or you will need to update the connection to use OAuth 2.0. ##### Prismatic example recursive flow[​](#prismatic-example-recursive-flow "Direct link to Prismatic example recursive flow") The **Prismatic API Example** flow dog-foods Prismatic's API and loops over components (connectors) in Prismatic, fetching 5 pages of 10 connectors each execution. Prismatic's API returns a cursor that can be used in a subsequent query to fetch additional records. To test this flow, run `prism me:token --type refresh` to generate a refresh token for the Prismatic connection to use. #### Tracking recursive invocation lineage[​](#tracking-recursive-invocation-lineage "Direct link to Tracking recursive invocation lineage") When a flow invokes another flow, either with the [recursive flow](https://prismatic.io/docs/components/recursive-flow.md) or [cross-flow](https://prismatic.io/docs/components/cross-flow.md) components, that call lineage is tracked. This way, you can see that execution A called execution B, which called execution C, etc. To view lineage in the low-code designer, select **View linked executions** from the first execution that ran. There, you will see which execution invoked which subsequent execution, as well as what cursor was sent to each trigger. ![Execution lineage for recursive flows](/docs/assets/images/lineage-72060b2f3e5c9c63c8ab1eb6fdd3c04d.png) #### Recursive flows in code-native[​](#recursive-flows-in-code-native "Direct link to Recursive flows in code-native") If you're building a recursive flow in a [code-native integration](https://prismatic.io/docs/integrations/code-native.md), you can accomplish the same pattern using `context.invokeFlow` with the current flow's name and a cursor value. Your flow's `onExecution` function can reference the cursor from the trigger's `results.body.data.cursor` property (if it exists), or default to some value. Here, we use numeric pagination, defaulting to 0 and incrementing the cursor by 1 each time. You can adapt this pattern to whatever cursor type you're using. Recursive flow in code-native ```ts export const exampleRecursiveFlow = flow({ name: "Example Recursive Flow", stableKey: "example-recursive-flow", description: "Example of a recursive flow", onExecution: async (context, stepResults) => { // Get cursor from trigger payload (or default to 0) let cursor = (stepResults.onTrigger.results.body.data as Record) .cursor || 0; context.logger.info(`Cursor value is ${cursor}`); /* Do work here */ // Increment cursor and invoke the current flow with the updated cursor await context.invokeFlow(context.flow.name, { cursor: ++cursor }); return { data: null }; }, }); ``` --- #### Config Wizard ##### Config Wizard Overview The integrations you build are meant to be reusable and deployable across your heterogeneous customer base. To achieve this, you need to provide customers with a mechanism to configure integrations for their specific environments. When customers configure and activate an instance of your integration, they follow a configuration wizard where they authenticate with third-party apps and provide any additional information required for the integration to function. Some integrations have simple configuration wizards that only require third-party authentication, while others may involve multiple configuration pages with dynamically generated dropdown menus, toggles, text fields, and other input types. --- ##### Config Pages #### Config pages overview[​](#config-pages-overview "Direct link to Config pages overview") Customers enable and configure an instance of an integration through a **Configuration Wizard**. They work through **Configuration Pages**, authenticating with third-party apps and setting config variables. **Configuration pages** can contain config variables of various types and helper text and images to guide the user on where to look. If your integration requires manual configuration of webhooks, the config wizard can also display the instance's webhook endpoints and API keys (see [Endpoint API keys in the config wizard](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md#endpoint-api-keys-in-the-config-wizard)). * Low-Code * Code-Native If you're building your integration with low-code, you can use the Config Wizard Designer within the integration designer to create your customers' configuration experience. ![Screenshot of the configuration wizard designer](/docs/assets/images/configuration-wizard-designer-7a2094ffebc127375200e12b0ec030da.png) You can add a configuration page by clicking **+ Config Page**, and you can rename a config page or add a short description to the page by clicking the pencil icon beside the page. Like low-code integrations, code-native integrations include a [config wizard](https://prismatic.io/docs/integrations/config-wizard.md). The config wizard can include things like OAuth 2.0 connections, API key connections, dynamically-sourced UI elements (data sources), and other advanced configuration wizard steps. A config wizard consists of multiple pages. Each page has a title, which is derived from the `key` of the configPage object, and a `tagline` as well as a set of `elements` (individual config variables). For example, a config wizard might contain a page for a Slack OAuth 2.0 connection, a page where the user selects a channel from a dynamically-populated dropdown menu, and a page where a user enters two static string inputs: Example config pages definition ```typescript import { configPage, configVar } from "@prismatic-io/spectral"; import { slackConnectionConfigVar } from "./connections"; import { slackSelectChannelDataSource } from "./dataSources"; export const configPages = { Connections: configPage({ tagline: "Authenticate with Slack", elements: { "Slack OAuth Connection": slackConnectionConfigVar, }, }), "Slack Config": configPage({ tagline: "Select a Slack channel from a dropdown menu", elements: { "Select Slack Channel": slackSelectChannelDataSource, }, }), "Other Config": configPage({ elements: { "Acme API Endpoint": configVar({ stableKey: "acme-api-endpoint", dataType: "string", description: "The endpoint to fetch TODO items from Acme", defaultValue: "https://my-json-server.typicode.com/prismatic-io/placeholder-data/todo", }), "Webhook Config Endpoint": configVar({ stableKey: "webhook-config-endpoint", dataType: "string", description: "The endpoint to call when deploying or deleting an instance", }), }, }), }; ``` For full documentation, see the [Build Code-Native](https://prismatic.io/docs/integrations/code-native/config-wizard.md) article. #### Displaying additional helper text in the configuration wizard[​](#displaying-additional-helper-text-in-the-configuration-wizard "Direct link to Displaying additional helper text in the configuration wizard") * Low-Code * Code-Native To add **helper text**, including headings (H1 - H6) or paragraphs, click the **+ Text/Image** button and select the type of text you'd like to add. To add an **image**, your image will need to be publicly accessible online. Enter the public URL of the image you'd like shown on your config page. For further customization, you can choose to add **Raw HTML** to your config page. In addition to config variables, you can add helpful text and images to guide your customers as they work through your config wizard. To add HTML to the config wizard (which can include links, images, etc), include a string `element` to a `configPage` definition: Include helper text in the config wizard ```typescript export const configPages = { Connections: configPage({ elements: { helpertext1: "

Asana Instructions

", helpertext2: "To generate an Asana API Key, visit the " + 'developer portal ' + 'and select "Create new token".', "Asana API Key": connectionConfigVar({ stableKey: "f0eab60f-545b-4b46-bebf-04d3aca6b63c", dataType: "connection", inputs: { // ... }, }), }, }), }; ``` ![A page in the config wizard with additional helper text](/docs/assets/images/helper-text-d5e8391937ad78ed6934314bbaf0b7d7.png) #### Displaying webhook information in the configuration wizard[​](#displaying-webhook-information-in-the-configuration-wizard "Direct link to Displaying webhook information in the configuration wizard") Your instance's webhook endpoints and API keys can be displayed in the configuration wizard. Click **+ Text/Image** and then select **Trigger Details** as the **Element Type**. You can opt to show all flows' URLs, or the URL for a specific flow. ![Add trigger details to the configuration wizard](/docs/assets/images/add-trigger-details-9c13dadbd7377543c91f166e29b040b8.png) When your customers deploy an instance of your integration, they'll see the webhook information on the configuration page. This is helpful if they need to manually configure webhooks in a third-party app. ![Display trigger details in the configuration wizard](/docs/assets/images/display-trigger-details-db00466142188e8a8c6ef63a971cff87.png) --- ##### Config Variables You can define names, descriptions, variable types, and optional default values of config variables for your configuration wizard from the config wizard designer in the low-code builder, and you'll reference the values that customers set for each in your integration. ![Config variables drawer in Prismatic application](/docs/assets/images/integration-config-vars-fcf8fd38bf1d8a4c0e5007fc2d18abe8.png) When it comes time for your customer-facing teams to deploy your integration, they can enter or select configuration options and tailor the integration for a particular customer without the involvement of integration builders. Config variables that you define in the config variable drawer can be used within your integration as inputs to steps, or through the [Branch](https://prismatic.io/docs/integrations/low-code-integration-designer/branching.md) component to drive branching logic. Use only letters, numbers and spaces as config variable names The config variable name is used to reference the config variable's value. Please use only letters, numbers and spaces as config variable names. Why? If you have a config variable name like `MyApp.com Connection`, the `.` character can make config variable reference difficult, since `configVars.MyApp.com Connection` is ambiguous - it's unclear if `com Connection` is a property of config variable `MyApp`, or if `MyApp.com Connection` is the full config variable name. Some components may throw an error if they encounter a config variable with a `.` character, throwing error `Cannot read properties of undefined (reading 'key')`. #### Config variable data types[​](#config-variable-data-types "Direct link to Config variable data types") There are several types of configuration variables: * **String** is a standard string of characters * **Date** follows the form `mm/dd/yyyy`, and presents end users a calendar widget to choose a date * **Timestamp** follows the form `mm/dd/yyy, HH:MM [AM/PM]`, and presents end users a calendar and time widget to choose a date and time * **Picklist** allows you to define a series of options that your end user can choose from. Picklists are presented to end users as a dropdown menu of options. A picklist value can be up to 64 characters in length. * **Code** lets your end user enter JSON, XML, or other formatted code blocks. This is helpful if customers have unique formats for recurring reports, or other formatted documents that differ from customer to customer. Choose a Code Language when you create the config variable for syntax highlighting. * **Boolean** allows your end user to choose either true or false. * **Number** allows your end user to enter a number (integer or decimal). * **Object Selection** allows your end user to select zero or more objects from a list. This config variable type always sources data from a [data source](https://prismatic.io/docs/integrations/data-sources.md). * **Object Field Map** allows your end user to map a series of fields. This config variable type always sources data from a [data source](https://prismatic.io/docs/integrations/data-sources.md). * **JSON Form** allows you to leverage [JSON Forms](https://jsonforms.io/) to build your users' configuration experience. The code backing JSON Form config variables are developed in [custom components](https://prismatic.io/docs/custom-connectors.md) and return objects that are made up of key/value pairs. * **Connection** is made up of multiple fields that determine how a component should connect to an external API. It might include a username, password, API key, endpoint URL, or several other things. Note that connection config variables can only be added to the first config page, as subsequent pages may use the connection to dynamically generate other config variables. Inputs are sent to actions as strings The type of config variable you choose affects the UI that the end user interacts with (they get toggles for booleans, date pickers for timestamps, an editor with syntax highlighting for code, etc). Regardless of what type of config variable you choose, though, all values are presented to actions as strings. If you're [writing a custom component](https://prismatic.io/docs/custom-connectors.md), note that you will need to cast your action's input to the correct format. For example, you can `JSON.parse()` a JSON string, or run `util.types.toNumber()` or `util.types.toBool()` on a number or boolean input. You can use a [`clean`](https://prismatic.io/docs/custom-connectors/inputs.md#cleaning-inputs) function to simplify type casting. Once you've added a config variable, you can use it as an input to actions within your integration. ##### List and key/value list config variables[​](#list-and-keyvalue-list-config-variables "Direct link to List and key/value list config variables") In addition to representing a **single** value, some config variable types can represent a **list** of values, or a list of **key/value pairs**. This is helpful for when you want your users to be able to enter an unknown number of items as the values of a config variable. For example, you may want users to select one or more values from a **picklist** menu. Config variables with a data type of **string**, **date**, **timestamp**, **picklist**, **code**, or **boolean** can be configured as lists or key/value lists. * Low-Code * Code-Native To create a **list** config variable, create a new config variable and select **LIST** under **Config Var Type**: ![Create list config variable in Prismatic application](/docs/assets/images/list-config-variable-c0c167706da09f33fdbe44cc469c5673.png) When a list config variable is referenced by a step's input, that step's action receives a JavaScript array of values. To create a **key/value list** config variable, create a new config variable and select **KEY/VALUE LIST** under **Config Var Type**: ![Create key/value list config variable in Prismatic application](/docs/assets/images/key-value-list-config-variable-9895c561303ea418bc0ebad9d2eca7e3.png) To create a **list** or **key/value list** config variable in a code-native integration, give your config variable a `collectionType` property of `valuelist` or `keyvaluelist`: Create a valuelist config variable in code-native ```ts configVar({ dataType: "string", stableKey: "my-vals", collectionType: "valuelist", description: "Provide a list of vals", }); ``` When a **list** config variable is referenced by a step in the low-code designer or by a flow in a code-native integration, the config variable contains an array of strings like `["First Option", "Third Option", "Second Option"]`. When a **key/value list** config variable is referenced by a low-code step or code-native flow, the config variable contains an array of key/value pairs. ```javascript [ { key: "some-key", value: "Some value", }, { key: "another-key", value: "Another value", }, ]; ``` #### Config variable visibility[​](#config-variable-visibility "Direct link to Config variable visibility") By default, config variables that you add to your integration's configuration wizard are visible to customers who deploy instances of your integration. But, there are some situations where you may want to hide a config variable from the config wizard. For example: * All instances of your integration might share an API key to a third-party application. You may want to set that API key as a config variable, but not make it accessible or visible to your customer. * Your customer's instance needs an API key to access your application, but you want to set it on their behalf as part of the instance deployment process. In that case, you want the customer user to be able to set it programmatically behind the scenes, but not see its value in the UI. - Low-Code - Code-Native To configure visibility in the low-code designer, open the config wizard designer and select a config variable. Then, select an option from the **Permission and Visibility** dropdown menu. You have three options: * **Customer** is the default value. Customer users can view and edit the config variable, and it will always appear in the config wizard. * **Embedded** makes it so the config variable does not show up in the config wizard, but your application is able to [set it programmatically](https://prismatic.io/docs/embed/marketplace.md#dynamically-setting-config-variables-in-marketplace) through the embedded SDK. This is helpful if you want to set an API key for a user during the configuration process, but not allow the user to see or edit the value that is set. * **Organization** makes it so the config variable is not visible to your customer, and is not able to be set programmatically by your application. Config variables marked **organization** must have a default value, or else your team members will need to set the value on behalf of your customer. ![Set visibility for config variables in Prismatic application](/docs/assets/images/config-var-visibility-523666bdfd95cb97bc5ae2ff392debb6.png) Additionally, you can toggle the **Visible to Organization** toggle to false to hide the config variable from organization team members who open a customer's instance config wizard screen. The config variable is still available programmatically to organization members, but this prevents a sensitive config variable from being displayed unintentionally on an organization team member's screen. If you are building a code-native integration, each config variable can have a `permissionAndVisibilityType` property with one of three values: * `customer` is the default value. Customer users can view and edit the config variable, and it will always appear in the config wizard. * `embedded` makes it so the config variable does not show up in the config wizard, but your application is able to [set it programmatically](https://prismatic.io/docs/embed/marketplace.md#dynamically-setting-config-variables-in-marketplace) through the embedded SDK. This is helpful if you want to set an API key for a user during the configuration process, but not allow the user to see or edit the value that is set. * `organization` makes it so the config variable is not visible to your customer, and is not able to be set programmatically by your application. Config variables marked **organization** must have a default value, or else your team members will need to set the value on behalf of your customer. Additionally, `visibleToOrgDeployer` determines if an organization user will see this config variable in the config wizard UI. While organization team members always have programmatic access to instances' config variables and their values, this helps to visually conceal some config variable values like generated metadata from data sources, etc. Defaults to `true`. A debug config variable that is only visible to org team members ```ts configVar({ stableKey: "debug", dataType: "boolean", description: "Enable debug logging", defaultValue: "false", permissionAndVisibilityType: "customer", visibleToOrgDeployer: true, }); ``` #### Connection config variables[​](#connection-config-variables "Direct link to Connection config variables") Connections are a special type of config variable that contain the information necessary to connect to a third-party application. A connection might include a simple username and password pair, or might declare all the fields required for OAuth 2.0 (like auth URL, client ID, etc.). To read more about OAuth 2.0 connections, see the [What is OAuth 2.0?](https://prismatic.io/docs/integrations/connections/oauth2.md). ##### Write-only connection inputs[​](#write-only-connection-inputs "Direct link to Write-only connection inputs") In some situations, it can be helpful to make a connection input **write-only** (e.g. a user can write a value, but not read it). For example, 1. Bob and Sue may be two customer users within the same customer. Sue is an administrator for a third-party app, and Bob is not, but Bob knows more about your integration's configuration. It can be helpful to have Sue enter her credentials into their instance, but have Bob take care of configuring or reconfiguring the instance. By making the connection inputs write-only, Bob will not be able to see Sue's credentials, but can view and change other config variables on the instance. 2. Your support team may want to view the configuration of a customer's instance. But, you don't want your support team to accidentally view your customer's API keys. By setting a connection's inputs to write-only, your customer can configure their instance and your team can observe the rest of the instance's config variables (but not the write-only values). To configure a connection's input to be write-only, open a connection config variable within the config wizard designer and click the gear icon next to an input. Toggle **Write Only**. ![Enable write-only on an input value](/docs/assets/images/enable-write-only-6a6c5edb9da1ac4ad9f3df811fcabe13.png) Note that once you set a connection input to **Write Only** and save your integration, you will be unable to disable the write-only setting. When a customer first deploys an instance of your integration, they will see inputs like they normally would, but with text indicating that the values are write-only. ![Customer enters their write-only credentials the first time](/docs/assets/images/write-only-first-time-dd949bdac85bf0ab75a664b0926e43f7.png) If the customer reconfigures the instance, the sensitive values are not accessible via the API and masked placeholders are presented instead. A customer user can choose to overwrite the write-only values with new values, but cannot view the existing values. ![Customer enters their write-only credentials on subsequent times](/docs/assets/images/write-only-subsequent-times-bd10c944203e7dd5e3882ba9a6fdd38d.png) #### Config variable limits[​](#config-variable-limits "Direct link to Config variable limits") An integration can have up to 100 config variables. If you need more than 100 config variables, consider whether some of them can be combined into a single config variable (for example, using a [JSON Form](https://prismatic.io/docs/integrations/data-sources/json-forms.md) config variable to group related fields together). --- ##### User Level Configuration Typically, one instance of an integration is configured and deployed to one customer. That "one instance for one customer" setup works well for many integrations, but what do you do when multiple users of a single customer each have their own third-party credentials or configuration requirements? **User Level Configuration** (ULC) allows your customers' users to each configure user-specific settings on an instance of an integration. One instance is deployed to a customer, but it contains user configuration information for one or more users within that customer. The instance then runs using the appropriate user's configuration depending on rules you define. **Example use case**: Suppose your customers use Dropbox for file storage. Your customer would like to sync data from your app with some subset of their users' Dropbox accounts. Using ULC, your customer would deploy an instance of your Dropbox integration using the standard deployment process. Then, the users who would like to use the integration within that customer would each go through a ULC configuration, each supplying connection information for their Dropbox account. ULC is an opt-in feature ULC is enabled on an as-needed basis. Please contact [support](mailto:support@prismatic.io) to discuss enabling ULC for your organization. #### User level configuration wizard[​](#user-level-configuration-wizard "Direct link to User level configuration wizard") * Low-Code * Code-Native When ULC is enabled in your account, you will see a **User Level Configuration Wizard** button next to the standard **Configuration Wizard** button in the integration designer. ![ULC button in the integration designer](/docs/assets/images/ulc-button-e6b30a5890f59173c4870bf9fd6a2027.png) The ULC configuration wizard designer is very similar to the [configuration wizard designer](https://prismatic.io/docs/integrations/config-wizard/config-pages.md) - you can add connections and other types of config variables like you would in the normal configuration wizard. * The configuration that you define in the **Configuration Wizard Designer** will be seen by an admin user of your customer. That user will initially create the instance of the integration for the customer and fill in any customer-wide configuration. * The configuration that you define in the **User Level Configuration Wizard Designer** will be seen by standard users of your customer. It will prompt individual users for user-specific credentials and config variables. To add a ULC config wizard to a code-native integration, create a `userLevelConfigPages` object within `configPages.ts` that has the same shape as `configPages`: User-level config wizard ```typescript export const userLevelConfigPages = { Options: configPage({ elements: { "My ULC Config Variables": configVar({ dataType: "string", stableKey: "my-ulc-config-var", description: "Enter a widget value", }), }, }), }; ``` Then, in `index.ts` import the `userLevelConfigPages` object. Provide the object as an export of your project (so TypeScript can infer types via `.spectral/index.ts`), and include it in your `integration()` definition: Including user-level config in your component ```typescript import { integration } from "@prismatic-io/spectral"; import flows from "./flows"; import { configPages, userLevelConfigPages } from "./configPages"; import { componentRegistry } from "./componentRegistry"; export { configPages, userLevelConfigPages } from "./configPages"; export { componentRegistry } from "./componentRegistry"; export default integration({ name: "ulc-example", description: "My user-level config example integration", iconPath: "icon.png", flows, configPages, userLevelConfigPages, componentRegistry, }); ``` #### Testing user level configuration in the integration designer[​](#testing-user-level-configuration-in-the-integration-designer "Direct link to Testing user level configuration in the integration designer") To test ULC in the integration designer, open up the **Test Runner** drawer and complete both the standard config wizard as well as user-specific config wizard. ![ULC button in the integration designer](/docs/assets/images/test-runner-drawer-5549632740686c298f9a6797a169d049.png) Tests you run will load the test user level configuration that you set. #### User level configuration in embedded marketplace[​](#user-level-configuration-in-embedded-marketplace "Direct link to User level configuration in embedded marketplace") When you [authenticate users in the embedded marketplace](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md) you will need to include a `role` property in your signed JWT that has a value of either `"admin"` or `"user"`. If a `role` property is omitted, it defaults to `"admin"`. If you plan to use shared endpoints for ULC integrations, ensure your signed JWT includes an `external_id` property. This represents the customer *user's* external ID. This property generally matches `sub`, and is used to invoke ULC instances with shared endpoints. ##### Marketplace admins[​](#marketplace-admins "Direct link to Marketplace admins") A user with `role: "admin"` can deploy an instance of a ULC integration for the customer. They can also supply user-specific configuration on their own behalf after creating the instance. An admin user will [configure](https://prismatic.io/docs/instances/deploying.md) a ULC instance like they would a non-ULC instance - by stepping through a configuration wizard to set up customer-wide configuration settings. Once an instance is deployed, a marketplace admin can click **Configure User Level Configuration** to add their own user-specific credentials and config variables to the instance. The admin user is not required to enter those - they only need to set them if they themselves would like to use the integration. ![Configure user level config button in embedded](/docs/assets/images/configure-ulc-button-fa040293eb66e1bc989e203553f8c4a8.png) ##### Marketplace users[​](#marketplace-users "Direct link to Marketplace users") A user with a `role: "user"` cannot deploy the instance, but can add user-specific configuration on their own behalf. A standard user will only see ULC integrations on the list view screen within the embedded marketplace. When a standard user selects an integration to configure, they will walk through the [user level configuration wizard](#user-level-configuration-wizard). ![User level config wizard](/docs/assets/images/standard-user-ulc-config-wizard-62706045c1872b57488fc78f955a817e.png) A standard user will *not* be able to see the instance's executions, logs, or other tabs. Clicking an integration again will show the ULC config wizard, where they can update or remove their user-specific configuration. #### Managing an instance's user level config[​](#managing-an-instances-user-level-config "Direct link to Managing an instance's user level config") An organization user can view the various user-specific configurations by opening an instance and then opening the **User Configurations** tab. ![Instance user configurations](/docs/assets/images/instance-user-configurations-d5ac3cd2a5cd339f3fb644398f3f960d.png) You can click the **Details** button beside a user to view a user-specific webhook URL that can be used to call the instance using that user's configuration. #### User level configuration and endpoint config[​](#user-level-configuration-and-endpoint-config "Direct link to User level configuration and endpoint config") You have the same options for [endpoint configuration](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md) in ULC integrations that you do for non-ULC integrations, with the caveat that when you use shared endpoints you also need to specify a user by external user ID (like you do for flow name and external customer ID): * When endpoint type is **Instance and Flow Specific**, each flow for each user configured for each instance receives its own unique webhook URL. In other words, Bob's "Import Records" flow webhook URL differs from Jane's "Import Records" flow webhook URL and differs from Bob's "Export Documents" flow webhook URL. User config is loaded based on unique webhook URL when the instance is invoked. * When endpoint type is **Instance Specific**, all flows for all users of a particular customer share a webhook URL. Executions are dispatched to specific flows running with user-specific configurations by sourcing a **Flow Name** and **External Customer User ID**. The external customer user ID should match the `external_id` that you set in your embedded users' JWT tokens, and often matches the JWT's `sub` property. ![Instance specific endpoint configurations](/docs/assets/images/instance-specific-endpoint-configuration-a2e05fe7c632e8636ca6fd7a6218048b.png) * When endpoint type is **Shared**, all users and their flows of all customers share a single webhook URL. In addition to specifying **Flow Name** and **External Customer User ID**, like in **Instance Specific**, you also need to specify **External Customer ID**. ULC Shared Endpoints require ULC configurations for customer users An organization team member can create a ULC configuration on an instance deployed to a customer. However, such configurations are only meant for testing purposes from within the Prismatic web app. If you try to invoke a shared endpoint for a ULC instance and include an organization user's external ID (rather than a customer user's external ID), the execution will fail as the organization team member is not a customer user. Like non-ULC integrations, ULC integrations with **Instance Specific** and **Shared** endpoint configuration can leverage a [preprocess flow](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md#shared-endpoint-without-a-preprocess-flow) to process incoming data and dispatch executions to the proper instance / user / flow. #### User level configuration information in trigger payloads[​](#user-level-configuration-information-in-trigger-payloads "Direct link to User level configuration information in trigger payloads") The default webhook trigger and most other non-custom triggers include information about the user whose user level config was used for an execution. To get ULC information for the current execution within an integration, reference the trigger's `results.user`, which is an object containing the user's `id`, `email` (which may be a UUID or their ID), `name` and `externalId`. --- #### Low-Code Integrations ##### Low-Code Integration Designer New to the low-code designer? Are you new to the low-code designer? Check out our [getting started guide](https://prismatic.io/docs/get-started/build-integrations/first-integration-low-code.md) to build your first integration. When building an integration, you can use either the low-code designer or create a TypeScript project in your favorite IDE using the [Code-Native SDK](https://prismatic.io/docs/integrations/code-native.md). This article explains how to build an integration with the low-code designer. After [creating a new integration](#creating-a-new-integration) or selecting one from your list of integrations, you'll find yourself in the low-code **integration designer**. Here, you can build, test, and publish integrations. The integration designer has four main features: 1. Configuration menus that let you edit your integration's name, description, and other metadata, publish versions, add integrations to your marketplace, deploy instances to customers and more. 2. A step configuration drawer that lets you configure integration steps and modify runtime/webhook settings. 3. A testing drawer that lets you [run integration tests](https://prismatic.io/docs/integrations/low-code-integration-designer/testing.md), supply sample payloads, and view test results. 4. The integration editor pane, which occupies most of the page. Here, you can add steps to your integration, create branches and loops, and arrange the flow of your integration. You can also create multiple **flows** - each with its own [trigger](https://prismatic.io/docs/integrations/triggers.md) and series of steps to execute. ![Prismatic integration designer highlighting configuration drawer, testing drawer, version history drawer, and integration editor pane](/docs/assets/images/integration-designer-a9e9dcca307fea825b8d61ffd5c385b0.png) #### Creating a new integration[​](#creating-a-new-integration "Direct link to Creating a new integration") To create a new integration in the web application, click **Integrations** in the left-side menu, then click the **+ Add Integration** button in the upper-right. When creating a new low-code integration, you have several options: * Select **Get Started** to create a **blank** new integration * Select **View Templates** to start from an existing [template](#integration-templates) - either one you've created or a Prismatic-built example. * Select **Import from YAML** to import an integration from a YAML file or clipboard (integrations are saved as [YAML definitions](https://prismatic.io/docs/configure-prismatic/integrations-multiple-regions.md#exporting-an-integrations-yaml-definition) behind the scenes). ![Configure a new integration](/docs/assets/images/configure-new-integration-da5fc0d54772ba3982829ab4e2549f90.png) You'll need to provide a **name** for your integration and select a trigger for its first flow. The trigger determines when your integration's flow will run, and you can modify it at any time. ![Add integration in Prismatic app](/docs/assets/images/new-integration-name-f444f43b6b48d94c6579bef9f32a7d1c.png) #### Assigning an icon to an integration[​](#assigning-an-icon-to-an-integration "Direct link to Assigning an icon to an integration") To make integrations more visually appealing in the [integration marketplace](https://prismatic.io/docs/embed/marketplace.md), you can assign them icons. To add an icon to an integration in the designer, click the icon space to the left of your integration's name: ![Add icon to integration in Prismatic app](/docs/assets/images/add-icon-99682ca60c5d9d46aef79bb1bbfa68f0.png) #### Assigning labels to an integration[​](#assigning-labels-to-an-integration "Direct link to Assigning labels to an integration") You can assign multiple labels to an integration through the **Integration details** menu in the designer: ![Assign labels to integration in Prismatic app](/docs/assets/images/labels-d257764eee9d0a69604e93ebf09d7e13.png) #### Categorizing integrations[​](#categorizing-integrations "Direct link to Categorizing integrations") Integrations can be assigned categories for easy sorting and filtering. For example, you might have several "ERP" integrations and some "Inventory Management" integrations. Categorizing integrations helps your team and customers in the [integration marketplace](https://prismatic.io/docs/embed/marketplace.md) view integrations sorted by category. To set a category for an integration, click the **Integration details** button in the top-left of the integration designer: ![Set category for integration in Prismatic app](/docs/assets/images/edit-integration-category-989f0147524309c0a863d6dbb9a555d0.png) #### Publishing an integration[​](#publishing-an-integration "Direct link to Publishing an integration") **Publishing** an integration marks it as ready for customer deployment. To publish an integration, open the **Version history** tab on the left side of the page. If you have unpublished changes, you'll see an **Unpublished Draft** listed among the integration's versions. Enter a note describing your changes, then click **Save & Publish** to release a new version: ![Publish integration in Prismatic app](/docs/assets/images/version-history-7c10d4dff5871c07c9c7cccd8d256a79.png) Integration versions can be marked **Available** or **Unavailable** using the blue toggles to the right of each version. Marking a version **Unavailable** prevents it from being deployed as an [instance](https://prismatic.io/docs/instances.md) to customers. #### Integration templates[​](#integration-templates "Direct link to Integration templates") Many integrations share similar patterns. For example, if you're importing opportunities and accounts from multiple CRM vendors, the data fetching steps will differ, but the steps that send data to your application will remain consistent. Rather than recreating these common steps repeatedly, you can save time by creating an integration template for your team to use when building similar integrations. To convert your integration into a template, first [publish](#publishing-an-integration) a version. Then, open the **Integration details** modal and select **Available as Template**. ![Create a new template of an integration](/docs/assets/images/available-as-template-a6871c38fada861c88e94d54b1cf7bdc.png) You can make the template available only to organization users, or if you offer the [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md), you can make it available to customer integration builders. #### Integration attachments[​](#integration-attachments "Direct link to Integration attachments") Your team can share integration-related documents by clicking the **Documentation & attachments** button on the left side of the integration designer. ![Add attachments to integration in Prismatic app](/docs/assets/images/attachments-cd7211520319e3b7aeedc18cc3b94983.png) #### Internal integration documentation[​](#internal-integration-documentation "Direct link to Internal integration documentation") Sharing documentation, specifications, and notes about an integration with team members is valuable. You can add internal (non-customer-facing) notes and documentation by clicking the **Documentation & attachments** button on the left side of the integration designer. This space allows you to share notes, links, and other documentation with your team. ![Add internal documentation to integration in Prismatic app](/docs/assets/images/integration-documentation-ba342adcabd4a4740432e6c9498b9a4e.png) #### Integration metadata[​](#integration-metadata "Direct link to Integration metadata") You can programmatically attach metadata to your integration, which is useful for assigning properties not covered by integration [categories](https://prismatic.io/docs/integrations/low-code-integration-designer.md#categorizing-integrations) or [labels](https://prismatic.io/docs/integrations/low-code-integration-designer.md#assigning-labels-to-an-integration). Metadata must be JSON-formatted and is accessible only through the [Prismatic GraphQL API](https://prismatic.io/docs/api.md). To set metadata on an integration, use the [`updateIntegration`](https://prismatic.io/docs/api/schema/mutations.md#updateintegration)) mutation: ```graphql mutation setIntegrationMetadata { updateIntegration( input: { id: "SW50ZWdyYXRpb246MGVjNDlhZmYtNjE2YS00NmU2LWExMTQtN2RjOThjY2Q1MzU4" metadata: "{\"price\": 100, \"compatible_plans\": [\"professional\", \"enterprise\"]}" } ) { integration { metadata } errors { field messages } } } ``` You can read this metadata as a property of an `integration` record: ```graphql query getIntegrationMetadata { integration( id: "SW50ZWdyYXRpb246MGVjNDlhZmYtNjE2YS00NmU2LWExMTQtN2RjOThjY2Q1MzU4" ) { name metadata } } ``` Important notes about metadata: * It must be a valid JSON string * Maximum length is 4096 characters * Only accessible via the API, not visible in the UI * Not included in an integration's [YAML definition](https://prismatic.io/docs/integrations/low-code-integration-designer.md#yaml-definition) #### YAML definition[​](#yaml-definition "Direct link to YAML definition") Integrations are represented in YAML behind the scenes. To view the YAML that defines your integration's flows, steps, inputs, connections, and config variables, click the **Integration details** button at the top-left of the integration designer and select **View YAML**. ![YAML for integration in Prismatic app](/docs/assets/images/yaml-definition-5dffbc35b35b0a75a8e9645989b9f7ae.png) When exporting an integration for import in a different region (e.g., US to EU), make sure to click the **latest component versions** button, as component versions may differ between regions. Track integration changes The YAML shown corresponds to the currently displayed integration version. To view the YAML of a previous version, open the **VERSION HISTORY** drawer and select an older version. To identify differences between versions, compare their YAML definitions using your preferred diff tool (VSCode includes an excellent built-in [diff tool](https://vscode.one/diff-vscode/)). --- ##### Branching The [branch](https://prismatic.io/docs/components/branch.md) component allows you to add branching logic to your integration. Think of **branches** as logical paths that your integration can take. Given some information about config variables or step results, your integration can follow one of many paths. Branch actions are useful when you need to conditionally execute some steps. Here are a couple of examples of things you can accomplish with branching: **Example 1:** The webhook request your integration receives could contain an "Order Created", "Order Updated", or "Order Deleted" payload. You need to branch accordingly. **Example 2:** Your customers want to be alerted when their rocket fuel level is below a certain threshold. You can branch into "send an alert" and "fuel level is okay" branches depending on results of a "check rocket fuel level" step. **Example 3:** You want to [upsert](https://en.wikipedia.org/wiki/Merge_\(SQL\)) data into a system that doesn't support upsert. You can check if a record exists and branch into "add a new record" or "update the existing record" branches depending on whether the record exists. **For More Information**: [The Branch Component](https://prismatic.io/docs/components/branch.md), #### Branching on a value[​](#branching-on-a-value "Direct link to Branching on a value") Adding an [If Value Equals](https://prismatic.io/docs/components/branch.md#branchonvalue) action to your integration allows you to create a set of branches based on the value of some particular variable. It's very similar to the switch/case construct present in many programming languages. Consider **Example 1** above. Suppose the webhook request you receive has a header, `payload-type`, that can be one of three values: `order-create`, `order-update`, or `order-delete`. You can look at that value and branch accordingly. ![Branch if value equals in Prismatic app](/docs/assets/images/if-value-equals-1d5586e01bc00cda85dca45d31a67fb5.png) **For More Information**: [Branch If Value Equals action](https://prismatic.io/docs/components/branch.md#branchonvalue) #### Branching if a condition is met[​](#branching-if-a-condition-is-met "Direct link to Branching if a condition is met") The [If Condition is Met](https://prismatic.io/docs/components/branch.md#branchonexpression) action allows you to create branches within your integration based on more complex inputs. You can compare values, like config variables, step results, or static values, and follow a branch based on the results of the comparisons. Consider **Example 2** above. You have a step that checks rocket fuel level for a customer, and you want to alert users in different ways if their fuel levels are low. You can express this problem with some pseudocode: ```python if fuelLevel < 50: sendAnSMS() else if fuelLevel < 100: sendAnEmail() else: doNothing() ``` To express this pseudocode in an integration, add a step that looks up rocket fuel level. Then, add an **If Condition is Met** action to your integration. Create one branch named **Fuel Critical** and under **Condition Inputs** check that `results` of the fuel level check step **is less than** 50. Then, create another branch named **Fuel warning** and check that `results` of the fuel level check step **is less than 100**. This will generate a branching step that will execute the branch **Send Alert SMS** if fuel levels are less than 50, **Send Warning Email** if fuel levels are less than 100, or will follow the **Else** branch if fuel levels are 100 or above. ![Branch if condition is met in Prismatic app](/docs/assets/images/if-condition-is-met-f3ce11396221077e2ec2f0a6bf2b8a85.png) #### Branch if condition is met operators[​](#branch-if-condition-is-met-operators "Direct link to Branch if condition is met operators") You can compare config variables, results from previous steps, or static values to one another using a variety of comparison operators. These operators each evaluate to `true` or `false` and can be chained together with **And** and **Or** clauses. ##### Equals branch operator[​](#equals-branch-operator "Direct link to Equals branch operator") The **equals** operator evaluates if two fields are equal to one another, regardless of type. | Left Field | Right Field | Result | Comments | | --------------- | --------------- | ------- | -------------------------------------------------------------- | | `5.2` | `5.2` | `true` | | | `5.2` | `5` | `false` | | | `"5.2"` | `5.2` | `true` | Strings are cast to numbers when compared to numbers | | `"Hello"` | `"Hello"` | `true` | | | `"Hello"` | `"hello"` | `false` | String comparison is case-sensitive | | `false` | `0` | `true` | Boolean `false` evaluates to `0`, and `true` evaluates to `1`. | | `[1,2,3]` | `[1,2,3]` | `true` | Arrays whose elements are the same are considered equal | | `{"foo":"bar"}` | `{"foo":"bar"}` | `true` | Objects with the same keys/values are equal | ##### Does not equal branch operator[​](#does-not-equal-branch-operator "Direct link to Does not equal branch operator") The **does not equal** operator evaluates if two fields are *not* equal to one another, regardless of type. | Left Field | Right Field | Result | | ---------- | ----------- | ------ | | `5.3` | `5.2` | true | | `[1,2,3]` | `[1,2,4]` | true | ##### Is greater than branch operator[​](#is-greater-than-branch-operator "Direct link to Is greater than branch operator") The **is greater than** operator evaluates if the left field is greater than the right field and is an implementation of the JavaScript [greater than operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Greater_than). | Left Field | Right Field | Result | Comments | | ---------- | ----------- | ------- | ------------------------------------------------------------------------------ | | `5.2` | `5.3` | `false` | | | `5.3` | `5.3` | `false` | The values are equal; one is not greater than the other | | `"5.3"` | `5.2` | `true` | Strings are cast to numbers when compared to numbers | | `"Hello"` | `"World"` | `false` | Strings are compared alphabetically - `"Hello"` does not occur after `"World"` | | `"hello"` | `"World"` | `true` | The ASCII value for `"h"` occurs [after](https://www.asciitable.com/) `"W"` | | `true` | `false` | `true` | `true` (1) is greater than `false` (0) | ##### Is greater than or equal to branch operator[​](#is-greater-than-or-equal-to-branch-operator "Direct link to Is greater than or equal to branch operator") The **is greater than or equal to** operator is similar to **is greater than** but also returns true if the values being compared are equal to one another. | Left Field | Right Field | Result | Comments | | ---------- | ----------- | ------ | ---------------------------------------------------- | | `5.3` | `"5.3"` | `true` | Strings are cast to numbers when compared to numbers | ##### Is less than branch operator[​](#is-less-than-branch-operator "Direct link to Is less than branch operator") The **is less than** operator evaluates if the left field is less than the right field. | Left Field | Right Field | Result | Comments | | ---------- | ----------- | ------ | --------------------------------------- | | `3` | `4` | `true` | | | `"abc"` | `"daa"` | `true` | `"a"` is less than `"d"` alphabetically | ##### Is less than or equal to branch operator[​](#is-less-than-or-equal-to-branch-operator "Direct link to Is less than or equal to branch operator") The **is less than or equal to** operator is similar to **is less than** but also returns true if the values being compared are equal to one another. ##### Contained in branch operator[​](#contained-in-branch-operator "Direct link to Contained in branch operator") The **contained in** operator evaluates if the value of the left field is contained in the right field. The right field must be an array or a string. | Left Field | Right Field | Result | Comments | | ---------- | ----------------- | ------- | ----------------------------------------------------------------- | | `"world"` | `"Hello, world!"` | `true` | | | `"World"` | `"Hello, world!"` | `false` | String comparison is case-sensitive | | `2` | `[1,2,3]` | `true` | | | `"2"` | `[1,2,3]` | `false` | The string `"2"` does not occur in the array of numbers `[1,2,3]` | ##### Not contained in branch operator[​](#not-contained-in-branch-operator "Direct link to Not contained in branch operator") The **not contained in** operator evaluates if the value of the left field does not appear in the right field. | Left Field | Right Field | Result | | ---------- | ----------- | ------- | | `2` | `[1,2,3]` | `false` | | `'Hi'` | `'Hello'` | `true` | ##### Is empty branch operator[​](#is-empty-branch-operator "Direct link to Is empty branch operator") The **is empty** operator evaluates if the given value is an empty string or an empty array. | Field | Result | | --------- | ------- | | `""` | `true` | | `"hello"` | `false` | | `[]` | `true` | | `[1,2,3]` | `false` | ##### Exactly matches branch operator[​](#exactly-matches-branch-operator "Direct link to Exactly matches branch operator") The **exactly matches** operator evaluates if the two fields are equal to one another, taking the type of the values into consideration. | Left Field | Right Field | Result | Comments | | ---------- | ----------- | ------- | ----------------------------------------------- | | `"5"` | `5` | `false` | The string `"5"` is not equal to the number `5` | ##### Does not exactly match branch operator[​](#does-not-exactly-match-branch-operator "Direct link to Does not exactly match branch operator") The **does not exactly match** operator evaluates if the two fields are not equal to one another, taking the type of the values into consideration. | Left Field | Right Field | Result | Comments | | ---------- | ----------- | ------ | ----------------------------------------------- | | `"5"` | `5` | `true` | The string `"5"` is not equal to the number `5` | ##### Starts the string branch operator[​](#starts-the-string-branch-operator "Direct link to Starts the string branch operator") The **starts the string** operator evaluates if the right field's value begins with the left field's value. Both right and left values must be strings. | Left Field | Right Field | Result | Comments | | ---------- | ------------------- | ------- | -------------------------------------------------------------- | | `"Test"` | `"Testing Value"` | `true` | | | `"test"` | `"Testing Value"` | `false` | Comparisons are case-sensitive | | `"Test"` | `"A Testing Value"` | `false` | The right field must start with (not *contain*) the left value | ##### Does not start the string branch operator[​](#does-not-start-the-string-branch-operator "Direct link to Does not start the string branch operator") The **does not start the string** operator returns the opposite of the **starts with** operator. ##### Ends the string branch operator[​](#ends-the-string-branch-operator "Direct link to Ends the string branch operator") The **ends the string** operator evaluates if the right field ends with the left field. Both right and left values must be strings. | Left Field | Right Field | Result | | ---------- | --------------- | ------- | | `orld!` | `Hello, World!` | `true` | | `orld` | `Hello, World!` | `false` | ##### Does not end the string branch operator[​](#does-not-end-the-string-branch-operator "Direct link to Does not end the string branch operator") The **does not end the string** operator returns the opposite of the **ends with** operator. Accepted DateTime Formats The following three comparison operators accept date/times as ISO strings (like `2021-03-20` or `2021-03-20T11:52:21.881Z`), Unix epoch timestamps in milliseconds (for example, the number `1631568050` represents a time in 2021-09-13), or `Date()` JavaScript objects. ##### Is after (date/time) branch operator[​](#is-after-datetime-branch-operator "Direct link to Is after (date/time) branch operator") The **is after (date/time)** operator attempts to parse the left and right fields as dates and evaluates if the left field occurs after the right field. | Left Field | Right Field | Result | Comments | | ---------------------------- | ---------------------------- | ------- | --------------------------------------------------------------------------------- | | `"2021-03-20"` | `"2021-04-13"` | `false` | | | `"2021-03-20T12:50:30.105Z"` | `"2021-03-20T11:52:21.881Z"` | `true` | When dates are equivalent, time is compared | | `"2021-03-20"` | `1631568050` | `false` | `1631568050` is the UNIX epoch time for 2021-09-13, which occurs after 2021-03-05 | ##### Is before (date/time) branch operator[​](#is-before-datetime-branch-operator "Direct link to Is before (date/time) branch operator") The **is before (date/time)** operator attempts to parse the left and right fields as dates and evaluates if the left field occurs before the right field. ##### Is the same (date/time) branch operator[​](#is-the-same-datetime-branch-operator "Direct link to Is the same (date/time) branch operator") The **is the same (date/time)** operator attempts to parse the left and right fields as dates and evaluates if the timestamps are identical. | Left Field | Right Field | Result | Comments | | ---------------------------- | ---------------------------- | ------- | -------------------------------------------------------------------------------------- | | `"2021-03-20T12:50:30.105Z"` | `"2021-03-20T12:50:30.105Z"` | `true` | | | `"2021-03-20T12:50:30Z"` | `1616244630000` | `true` | `1616244630` is the millisecond UNIX epoch representation of `March 20, 2021 12:50:30` | | `"2021-03-20T12:50:30Z"` | `"2021-03-20T12:50:31Z"` | `false` | | ##### Is true branch operator[​](#is-true-branch-operator "Direct link to Is true branch operator") The **is true** operator evaluates if an input field is "truthy". Common "truthy" values include `true`, `"true"`, `"True"`, `"Yes"`, `"yes"`, `"Y"`, and `"y"`. Common "falsy" values include `false`, `"false"`, `"False"`, `"No"`, `"no"`, `"N"`, and `"n"` and evaluate to `false`. Other values that evaluate to `false` are `0`, `null`, `undefined`, `NaN`, and `""`. All other values (a non-zero number, a non-empty string, any array or object, etc.) evaluate to `true`. | Field | Result | | --------- | ------- | | `"Yes"` | `true` | | `"True"` | `true` | | `[]` | `true` | | `{}` | `true` | | `"Hello"` | `true` | | `-5` | `true` | | `"n"` | `false` | | `false` | `false` | | `""` | `false` | | `null` | `false` | | `0` | `false` | ##### Is false branch operator[​](#is-false-branch-operator "Direct link to Is false branch operator") The **is false** operator returns the opposite of the **is true** operator. ##### Does not exist branch operator[​](#does-not-exist-branch-operator "Direct link to Does not exist branch operator") The **does not exist** operator evaluates to `true` if the presented value is one of the following: `undefined`, `null`, `0`, `NaN`, `false` or `""`. | Field | Result | | ----------- | ------- | | `undefined` | `true` | | `NaN` | `true` | | `1` | `false` | | `"Hello"` | `false` | ##### Exists branch operator[​](#exists-branch-operator "Direct link to Exists branch operator") The **exists** operator returns the opposite of the `does not exist` operator. #### Combining multiple comparison operators[​](#combining-multiple-comparison-operators "Direct link to Combining multiple comparison operators") Multiple expressions can be grouped together with **And** or **Or** clauses, which execute like programming **and** and **or** clauses. Take, for example, this programming expression: ```text if ((foo > 500 and bar <= 20) or ("b" in ["a","b","c"])) ``` The same logic can be represented with a group of conditionals in an [If Condition is Met](https://prismatic.io/docs/components/branch.md#branchonexpression) action: ![Branch if condition is met using conditionals in Prismatic app](/docs/assets/images/if-condition-is-met-logic-df09ab973a8436b803417adb25bd7eba.png) **For More Information**: [If Condition is Met action](https://prismatic.io/docs/components/branch.md#branchonexpression) #### Converging branches[​](#converging-branches "Direct link to Converging branches") Regardless of which branch is followed, branches always converge to a single point. Once a branch has executed, the integration will continue with the next step listed below the branch convergence. This presents a problem: how do steps below the convergence reference steps in branches that may or may not have executed (depending on which branch was followed)? In your integration you may want to say "if branch *foo* was executed, get the results from *step A*, and if branch *bar* was executed, get the results instead from *step B*." Prismatic provides the [Select Executed Step Result](https://prismatic.io/docs/components/branch.md#selectexecutedstepresult) to handle that scenario. Imagine that you have two branches - one for incoming invoices, and one for outgoing invoices, with different logic contained in each. Regardless of which branch was executed, you'd like to insert the resulting data into an ERP. You can leverage the [Select Executed Step Result](https://prismatic.io/docs/components/branch.md#selectexecutedstepresult) action to say "get me the incoming or outgoing invoice - whichever one was executed." This action iterates over the list of step results that you specify, and returns the first one that has a non-null value (which indicates that it ran). ![Select executed step result while branching in Prismatic app](/docs/assets/images/select-executed-step-result-246731a2a3eac6004c54d87bc439670e.png) Within the component configuration drawer, select the step(s) whose results you would like, and the **Select Executed Step Result** step will yield the result of whichever one was executed. --- ##### Code Step Overview The [code component](https://prismatic.io/docs/components/code.md) allows you to execute product or industry-specific code within an integration. This page outlines when and how to use a code component. #### Why use the code component?[​](#why-use-the-code-component "Direct link to Why use the code component?") You will likely have integration logic that can't be solved using the [standard components](https://prismatic.io/docs/components.md) Prismatic provides. The portion of your integrations that are specific to your product or industry can be accomplished using code component steps or [custom components](https://prismatic.io/docs/custom-connectors.md). #### Code component vs custom connector[​](#code-component-vs-custom-connector "Direct link to Code component vs custom connector") Generally, the code component is useful if: * Your code does not depend on external libraries * Your code is short and succinct * Your code is step-specific and not reusable elsewhere You should consider building a [custom connector](https://prismatic.io/docs/custom-connectors.md) if: * Your code relies on external libraries * You would like to unit test your code independent of your integration * Your code is reusable and could be duplicated in multiple flows or integrations #### Adding a code step to an integration[​](#adding-a-code-step-to-an-integration "Direct link to Adding a code step to an integration") Within the integration designer, [add a step](https://prismatic.io/docs/integrations/low-code-integration-designer/steps.md#adding-steps-to-integrations) to your integration. Select the **Code** component, **Code Block** action. A new code step appears in your integration. Click the **Edit** button to open the code editor: ![Code editor in Prismatic app](/docs/assets/images/code-step-f079f39f5d8f2d4416015296d23555d9.png) #### Code structure[​](#code-structure "Direct link to Code structure") The code component provides a stub function by default. Let's examine the structure of the code: ```javascript module.exports = async ({ logger, configVars }, stepResults) => { return { data: null }; }; ``` You must export an asynchronous function. The default code uses [arrow function notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) to create an `async` function to return. #### Code component parameters[​](#code-component-parameters "Direct link to Code component parameters") This function is provided a few parameters: 1. The first positional parameter is comprised of several properties: * `logger` allows you to write out log lines. * `debug` is an object which you can use when [debug mode](https://prismatic.io/docs/integrations/troubleshooting.md#debug-mode) is enabled to emit additional debug log lines or measure time or memory costs of specific portions of your code. * `configVars` lets you access config variables (including connections). * `instanceState`, `crossFlowState`, `integrationState`, and `executionState` give you access to [persisted state](https://prismatic.io/docs/custom-connectors/actions.md#execution-instance-and-cross-flow-state). * `stepId` is the ID of the current step being executed. * `executionId` is the ID of the current execution. * `webhookUrls` contains the URLs of the running instance's sibling flows. * `webhookApiKeys` contains the API keys of the running instance's sibling flows. * `invokeUrl` was the URL used to invoke the integration. * `customer` is an object containing an `id`, `name`, and `externalId` of the customer the instance is assigned to. * `user` is an object containing an `id`, `name`, `email` (their ID), and `externalId` of the customer user whose user-level configuration was used for this execution. This only applies to instances with [User Level Configuration](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md). * `integration` is an object containing an `id`, `name`, and `versionSequenceId` of the integration the instance was created from. * `instance` is an object containing an `id` and `name` of the running instance. * `flow` is an object containing the `id` and `name` of the running flow. 2. The second positional parameter, `stepResults`, is an object that contains output from previous steps of the integration. ##### Logging[​](#logging "Direct link to Logging") `context.logger` is an object that can be used for logging and debugging. `context.logger` has four functions: `debug`, `info`, `warn`, and `error`. For example: ```javascript module.exports = async (context, stepResults) => { context.logger.info("Things are going great"); context.logger.warn("Now less great..."); }; // or module.exports = async ({ logger }, stepResults) => { logger.info("Hello World"); }; ``` **Note**: Log lines are truncated after 4096 characters. If you need longer log lines, consider [streaming logs](https://prismatic.io/docs/monitor-instances/logging/streaming-logs-externally.md) to an external log service. ##### Config variables[​](#config-variables "Direct link to Config variables") `context.configVars` provides the [Code Component](https://prismatic.io/docs/components/code.md) with access to all [config variables](https://prismatic.io/docs/integrations/config-wizard/config-variables.md), including connections, associated with the integration. If you have a config variable named `Acme ERP Base URL`, for example, you could reference that config variable in a code step with `context.configVars["Config Variable Name"]` syntax: ```javascript module.exports = async ({ configVars }, stepResults) => { const fuelApiUrl = `${configVars["Acme ERP Base URL"]}/fuel`; // ... }; ``` ##### Connections[​](#connections "Direct link to Connections") [Connections](https://prismatic.io/docs/integrations/connections.md) are a special type of config variable. You can access the contents of a connection the same way that you would any other config variable. In this example, suppose you have a connection config variable named `Acme ERP Connection` that contains two fields, `tenantId` and `apiKey`: Destructuring a connection config variable ```javascript module.exports = async ({ logger, configVars }, stepResults) => { const { fields: { tenantId, apiKey }, } = configVars["Acme ERP Connection"]; const result = await doAThing({ tenantId, apiKey }); return { data: result }; }; ``` #### Referencing previous step outputs[​](#referencing-previous-step-outputs "Direct link to Referencing previous step outputs") Most steps of an integration return some sort of value. An **HTTP - GET** action, for example, might return a JSON payload from a REST API. An **Amazon S3 - Get Object** will return a binary file pulled from S3. The code component can reference those outputs through the `stepResults` parameter. `stepResults` is an object that contains results from all previous steps. For example, if you have an **HTTP - GET** step named **Fetch Users List** that pulls down an array of users from , you can generate an array of email addresses with this code: ```javascript module.exports = async (context, stepResults) => { const userArray = stepResults.fetchUsersList.results; const emailArray = userArray.map((user) => user.email); return { data: emailArray }; }; ``` Step results are often objects Many components return objects that have multiple keys. So, you can reference `stepResults.myStepName.results.someKey`. It's rare for a component to return serialized JSON, so there's rarely need to `JSON.parse()` results from a previous step. ##### Previous step names as variables[​](#previous-step-names-as-variables "Direct link to Previous step names as variables") Since names of steps can include spaces and non-JavaScript-friendly characters, alphanumeric characters of step names are converted to camelCase. So, a step named **Download JSON from API** would be converted to **downloadJsonFromApi**. You can test out step name to referenceable name conversions here: Step Name Download JSON from API Step Name Reference Name / Step ID Reference Name / Step ID ##### Referencing integration trigger payload data[​](#referencing-integration-trigger-payload-data "Direct link to Referencing integration trigger payload data") The integration trigger is another step that can have a unique name. Suppose an integration is triggered by a webhook, the trigger is named `My Integration Trigger`, and the webhook is provided a payload `body.data` of `{"exampleKey": "exampleValue"}`. ![Reference integration trigger payload in Prismatic app](/docs/assets/images/trigger-payload-a34699b9d9f798a16a7d6416099f9a12.png) That `exampleKey` would be accessible using `stepResults.myIntegrationTrigger` like so: ```javascript module.exports = async ({ logger }, stepResults) => { const exampleKey = stepResults.myIntegrationTrigger.results.body.data.exampleKey; logger.info(`Received '${exampleKey}'`); }; ``` Using JavaScript destructuring, you can instead write this: ```javascript module.exports = async ( { logger }, { myIntegrationTrigger: { results: { body: { data: { exampleKey }, }, }, }, }, ) => { logger.info(`Received '${exampleKey}'`); }; ``` Notice the logged message in the testing drawer: ![Test runner step results in Prismatic app](/docs/assets/images/using-stepresults-99620a74473761fa61bc32577cecea88.png) #### Persisted data in a code step[​](#persisted-data-in-a-code-step "Direct link to Persisted data in a code step") Like a custom component, a code step can save and reference [persisted state](https://prismatic.io/docs/custom-connectors/actions.md#execution-instance-and-cross-flow-state) at the flow (`instanceState`), cross-flow (`crossFlowState`), integration (`integrationState`), or execution (`executionState`) level. To save a value `bar` as key `foo` at the `executionState` level: ```javascript module.exports = async ({ logger, configVars }, stepResults) => { return { data: null, executionState: { foo: "bar" }, }; }; ``` To load the value of the key `foo` at the execution level, you can reference your function's first parameter's `executionState` property: ```javascript module.exports = async ({ executionState }, stepResults) => { const myvalue = executionState["foo"]; return { data: `My value is ${myvalue}` }; }; ``` #### Code component return values[​](#code-component-return-values "Direct link to Code component return values") The code component can optionally return a value for use by a subsequent step. The return value can be an object, string, integer, etc., and will retain its type as the value is passed to the next step. The return value is specified using the `data` key in the return object. In this example, we return a string with value `"https://ipinfo.io/ip"`: ```javascript module.exports = async (context, stepResults) => { return { data: "https://ipinfo.io/ip" }; }; ``` The output can be used as input for the next step by referencing `codeComponentStepName.results`. ![Use output for prior step for input to new step in Prismatic app](/docs/assets/images/return-values-4804dc7a4da4c9754651d60c6c6dbbbe.png) To see an example of returning more complex data structures, and a good example use case for a code component, see the [Using a Code Component to Transform Data](https://prismatic.io/docs/integrations/low-code-integration-designer/code-step/code-component-to-transform-data.md) quickstart. ##### Returning binary data from a code step[​](#returning-binary-data-from-a-code-step "Direct link to Returning binary data from a code step") Sometimes you'll want your code component to return binary data (like a rendered image or PDF). To do that, return an object with a `data` property of type `Buffer` (a file buffer), and a `contentType` property of type `String` that contains the file's [MIME type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types): ```javascript module.exports = async (context, stepResults) => { // ... const fileBuffer = SomePdfLibrary.generatePdf(); return { data: fileBuffer, contentType: "application/pdf", }; }; ``` To see an example use case for returning binary data from a code component, check out our [Generate a PDF with a Code Component](https://prismatic.io/docs/integrations/low-code-integration-designer/code-step/generating-a-pdf-with-a-code-component.md). **For More Information**: [Using a Code Component to Transform Data](https://prismatic.io/docs/integrations/low-code-integration-designer/code-step/code-component-to-transform-data.md) #### Making HTTP calls from a code step[​](#making-http-calls-from-a-code-step "Direct link to Making HTTP calls from a code step") The [fetch](https://developer.mozilla.org/en-US/docs/Web/API/fetch) API is baked into the code component. To make an HTTP call to an API, you can use the `fetch` function: Make an HTTP POST request from a code step ```javascript module.exports = async (context, stepResults) => { const options = { method: "POST", headers: { Accept: "application/json", "Content-Type": "application/json", Authorization: "Bearer abc-123", }, body: JSON.stringify({ foo: "bar", baz: 123 }), }; const response = await fetch("https://postman-echo.com/post", options); const jsonData = await response.json(); return { data: jsonData }; }; ``` #### Adding dependencies to a code step[​](#adding-dependencies-to-a-code-step "Direct link to Adding dependencies to a code step") If your code component depends on node modules from `npm`, dependencies will be dynamically imported from the [UNPKG](https://unpkg.com/) and [jsDelivr](https://www.jsdelivr.com/) CDNs. For example, if your code component reads: Import lodash as a dependency ```javascript const lodash = require("lodash@4.17.21/lodash.js"); module.exports = async (context, stepResults) => { const mergedData = lodash.merge( { cpp: "12" }, { cpp: "23" }, { java: "23" }, { python: "35" }, ); return { data: mergedData }; }; ``` Then [lodash](https://unpkg.com/browse/lodash@4.17.21/lodash.js) version 4.17.21 will be imported as a dependency. You should specify specific known working versions of `npm` packages for your code component: ```javascript const lodash = require("lodash@2.4.2"); const { PDFDocument } = require("pdf-lib@1.17.1/dist/pdf-lib.js"); ``` You can require any file from `npm` using `package[@version][/file]` syntax. Note that with the `lodash` import above, no file was specified. If no file is specified, the `main` file defined in the `npm` package's `package.json` is imported. An explicit path was called out for the `pdf-lib` import because the `pdf-lib` package defaults to importing an index file that itself requires other files, and `dist/pdf-lib.js` is a completely independent file that can be imported on its own.. Downstream dependencies In order for an external dependency to be compatible with a code step, all JavaScript code must be compiled into a single file. For example, contains all of the code necessary to run in a single file. does not - it has its own `require()` statement and depends on other files. The former would work in the code step, the latter would not. If the external package has its own dependencies that are not compiled in, or if the file you reference has its own `require()` statements, you will see errors. CDN outages can cause downtime When a `require()` line is encountered in a code step, the code step will attempt to download the dependency from the UNPKG CDN. If UNPKG is down or otherwise unavailable, the code step will fall back to downloading the dependency from the jsDelivr CDN. If both CDNs are down, your code step will error. If you need external dependencies, we strongly recommend using a code step for prototyping, but building a [custom component](https://prismatic.io/docs/custom-connectors.md) for production use. Custom components have their dependencies compiled in, and are not dependent on the uptime of an external CDN. ##### Requiring built-in NodeJS modules[​](#requiring-built-in-nodejs-modules "Direct link to Requiring built-in NodeJS modules") You can also require built-in NodeJS modules, like `crypto` or `path`. If the library you specify is built in to NodeJS, the client will *not* reach out to a CDN, and will instead use the built-in module. ```javascript const crypto = require("crypto"); module.exports = async () => { const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", { modulusLength: 4096, publicKeyEncoding: { type: "spki", format: "pem", }, privateKeyEncoding: { type: "pkcs8", format: "pem", cipher: "aes-256-cbc", passphrase: "top secret", }, }); return { data: { publicKey, privateKey, }, }; }; ``` #### Using spectral utility functions in a code step[​](#using-spectral-utility-functions-in-a-code-step "Direct link to Using spectral utility functions in a code step") Prismatic's SDK, [@prismatic-io/spectral](https://www.npmjs.com/package/@prismatic-io/spectral) is automatically imported into each code block as `spectral`. You can reference any utility functions that Spectral declares. For example, if you need to cast a truthy `"NO"` string to a boolean, you can do this: ```javascript module.exports = async ({ configVars }, stepResults) => { const doAThing = spectral.util.types.toBool(configVars["Do a Thing?"]); if (doAThing) { return "Did a thing"; } else { return "Didn't do a thing"; } }; ``` A list of all util type functions is available in the [Spectral SDK](https://github.com/prismatic-io/spectral/blob/main/packages/spectral/src/util.ts). --- ##### Using a Code Component to Transform Data Prismatic's code component lets you incorporate custom JavaScript code anywhere in your integration. The code component, alongside [custom components](https://prismatic.io/docs/custom-connectors.md), allows you to write the product- or industry-specific portions of your integration that aren't readily solved using built-in components from the [component catalog](https://prismatic.io/docs/components.md). Both code and custom components have their use cases. As a reminder: code components are useful to write simple one-off, single integration code snippets. If you need to run the same code for multiple integrations, if your code is complex enough that it would benefit from unit testing, or if you are reliant on lots of external Node.js libraries, consider creating a [custom component](https://prismatic.io/docs/custom-connectors.md) instead. #### Today's problem[​](#todays-problem "Direct link to Today's problem") B2B companies often encounter a problem when working with third-party vendors: data not coming in within an agreed-upon spec. Suppose that your system and a third-party vendor agreed to exchange JSON-formatted data via webhook payload that looked like this: ```json [ { "firstName": "John", "lastName": "Smith", "dob": "1987-05-20", "userid": "123" }, { "firstName": "Jane", "lastName": "Smith", "dob": "1992-07-16", "userid": "172" } ] ``` Your integration works during testing with sample data, but when you turn it on for third-party vendor consumption, errors are generated when your integration tries to parse the data that was sent. Logs indicate that the data the vendor is sending is formatted entirely differently than agreed: ```json { "123": { "name": "John Smith", "dob": "05/20/1987" }, "172": { "name": "Jane Smith", "dob": "07/16/1992" } } ``` The vendor drags its feet and claims a fix is a "long ways off". You don't have time to wait - your customer needs the integration they paid for! You can implement a quick fix by adding a code component to the top of your integration to transform the malformed data into the format you expect. #### Using a code component as a shim[​](#using-a-code-component-as-a-shim "Direct link to Using a code component as a shim") Your integration expects one format of input but receives another. You need to transform the data like this: ```json { "123": { "name": "John Smith", "dob": "05/20/1987" } } ``` into something like this: ```json { "firstName": "John", "lastName": "Smith", "dob": "1987-05-20", "userid": "123" } ``` That should be pretty easy to do. You need to do three things: * Split the name at the space character to form a first and last name * Reformat the date of birth into a more reasonable format * Pull the JSON key ("123") into a value of `userid` If you add a code component to your integration, by default it reads: ```javascript module.exports = async (context, stepResults) => { const results = null; // Result of calculation, API request, etc. return { data: results }; }; ``` First, you'll use JavaScript destructuring to capture the JSON that the third-party vendor sent as part of the integration trigger's webhook payload: ```javascript module.exports = async ( { logger }, { integrationTrigger: { results: { body: { data: userData }, }, }, }, ) => { logger.info(userData); // Verify we're capturing user data properly const result = null; return { data: result }; }; ``` Looking at logs, your destructuring is correct. Next, you'll [map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) over the objects (users) with `Object.entries(userData).map()`. Each iteration of your `map()` will generate an object with `firstName`, `lastName`, `dob`, and `userid`. You'll reformat the date of birth with the `Date()` object, split the name into a first and last name using `split()`, and grab the `userid` from the keys: ```javascript module.exports = async ( { logger }, { integrationTrigger: { results: { body: { data: userData }, }, }, }, ) => { const result = Object.entries(userData).map(([userid, user]) => { const dob = new Date(Date.parse(user.dob)).toISOString().slice(0, 10); const firstName = user.name.split(" ").slice(0, -1).join(" "); const lastName = user.name.split(" ").slice(-1).join(" "); return { firstName, lastName, dob, userid, }; }); return { data: result }; }; ``` That's it! The rest of your integration can now be configured to reference the results of the code component rather than the body of the integration payload, and it'll start working as expected despite the poorly formatted JSON from the third-party vendor. Testing the code component shows that it's reformatting data as expected: ![Step outputs in Prismatic integration designer](/docs/assets/images/results-883fd3c98ddc537b3c630b481f56a514.png) --- ##### Generating a PDF with a Code Step In this tutorial, you'll write a short code step snippet that references some JSON data and outputs a rendered PDF file. This tutorial covers how to import external libraries in a code step and how to output binary data (like a PDF file) from a code step. For this example, assume that our integration receives a list of rocket launches that have occurred recently in JSON format: ```json [ { "rocketName": "Deep Space 9", "launchTime": "2020-10-27T20:29:46.139Z", "launchSupervisor": "Robert Smith", "launchNotes": "Rocket was launched into orbit where it will remain for several months." }, { "rocketName": "Voyager", "launchTime": "2020-10-27T21:34:15.229Z", "launchSupervisor": "Sally Smith", "launchNotes": "Rocket launched without a hitch. Thrusters were retrieved 30 minutes after launch." } ] ``` Our customers would like to generate PDF files from this data with launch information, one launch per page, for their managers to print and read through. #### Should we use a custom component instead?[​](#should-we-use-a-custom-component-instead "Direct link to Should we use a custom component instead?") This use case straddles the line of needing a custom component versus using a code step. This code is only used for a single integration, is relatively short, and after some preliminary testing *probably* doesn't need extensive unit testing. When your code step has external dependencies (like on a PDF library), that external dependency is pulled down from a CDN each execution. That can be slow, and your integration will be dependent on the CDN being available. You may be better off building a custom component, where the external dependency will be compiled into your component. #### Importing external libraries[​](#importing-external-libraries "Direct link to Importing external libraries") This tutorial uses the [PDF-LIB](https://www.npmjs.com/package/pdf-lib) library to render the PDF. To do that, add the following to the top of a new code step in your integration: ```javascript const { PDFDocument, StandardFonts, } = require("pdf-lib@1.17.1/dist/pdf-lib.js"); ``` It's wise to pin requirements to known working versions. Since we're testing our integration with PDF-LIB version 1.17.1, we'll select that specific version. If you omit the version, your integration will import whatever latest version is available. Adding this `require()` line to the top of your code step will cause the code step to [dynamically import](https://prismatic.io/docs/integrations/low-code-integration-designer/code-step.md#adding-dependencies-to-a-code-step) the PDF-LIB library as a dependency. #### Writing the code step snippet[​](#writing-the-code-step-snippet "Direct link to Writing the code step snippet") Next, you'll write the code that generates a PDF. First, test that you can generate a blank PDF file: ```javascript const { PDFDocument } = require("pdf-lib@1.17.1/dist/pdf-lib.js"); module.exports = async (context, stepResults) => { const doc = await PDFDocument.create(); const pdfBytes = await doc.save(); return { data: pdfBytes, contentType: "application/pdf" }; }; ``` If you run this code step and look at step outputs, you'll see that the code step generated a 583 byte binary file. By adding a **Save File** step after the code step to GCP Storage, you can write that blank file out to GCP Storage to verify that it looks as expected. You can choose to write the file out to Amazon S3, Azure, an SFTP share, DropBox, etc. You now have a blank PDF written out. What's left to do is add some text to the PDF based on the data that was presented to our integration's webhook: ```javascript const { PDFDocument, StandardFonts, } = require("pdf-lib@1.17.1/dist/pdf-lib.js"); module.exports = async (context, stepResults) => { // Pull in data from the webhook trigger payload const rocketLaunches = stepResults.integrationTrigger.results.body.data; // Generate a PDF Document const doc = await PDFDocument.create(); // Embed the Times Roman font const timesRomanFont = await doc.embedFont(StandardFonts.TimesRoman); // Loop over each rocket launch, adding a page to our document for each one rocketLaunches.forEach((rocketLaunch) => { const { rocketName, launchTime, launchSupervisor, launchNotes } = rocketLaunch; // Create a new page for each launch const page = doc.addPage(); const { width, height } = page.getSize(); const _launchTime = new Date(launchTime).toLocaleString(); // Print information about the launch page.drawText(`Rocket: ${rocketName}`, { x: 30, y: height - 120, size: 30, font: timesRomanFont, }); page.drawText(`Launch Time: ${_launchTime}`, { x: 30, y: height - 150, size: 12, font: timesRomanFont, }); page.drawText(`${launchSupervisor}: ${launchNotes}`, { x: 30, y: height - 166, size: 12, font: timesRomanFont, }); }); // Get PDF file as a file UInt8Array const pdfBytes = await doc.save(); // Return a PDF file with proper MIME type return { data: Buffer.from(pdfBytes), contentType: "application/pdf" }; }; ``` Note the format of the object that is returned from this code step: ```javascript return { data: Buffer.from(pdfBytes), contentType: "application/pdf" }; ``` The return object specifies both a `data` property that is a file `Buffer` and a `contentType` specifying the MIME type of the file being returned. If you run a test again, you can see that a two-page PDF is being generated with formatted content from the webhook payload: ![Sample PDF output file from webhook payload](/docs/assets/images/final-product-a7f8c604fc2d49c8631397cc8f1df8f4.png) #### Further reading[​](#further-reading "Direct link to Further reading") That's it! With just about 40 lines of code (if you omit comments and blank lines), you have a code step that renders PDFs. For more information on code steps, check out the [code step usage](https://prismatic.io/docs/integrations/low-code-integration-designer/code-step.md) page. --- ##### Low-Code Integration Designer Error Handling Integrations interact with external systems that can experience temporary failures, timeouts, or unexpected responses. Prismatic provides error handling mechanisms at both the flow and step level to gracefully handle these situations and improve integration reliability. #### Handling errors in integrations[​](#handling-errors-in-integrations "Direct link to Handling errors in integrations") Errors happen. An API you integrate with may encounter a temporary outage, or the "eventually" part of an "eventually consistent" database may need a couple more seconds to save a record. When you encounter errors, you have two tools to handle them: 1. Flow-level error handling. 2. Step-level error handling. #### Flow-level error handling[​](#flow-level-error-handling "Direct link to Flow-level error handling") If an execution fails, you can have the runner automatically retry a few minutes later. The webhook payload that you received will be passed back through your flow again, and your flow will start again at its first step. This is useful if your flow is [idempotent](https://en.wikipedia.org/wiki/Idempotence) and you don't know which step might fail. Read more about flow-level error handling on the [automatic retry](https://prismatic.io/docs/monitor-instances/retry-and-replay/automatic-retry.md) article. #### Step-level error handling[​](#step-level-error-handling "Direct link to Step-level error handling") You might not want your entire flow to stop because one step failed, especially if you're looping over hundreds of items and one item has issues. You can configure how the runner should handle errors on each step. To do that, click a step that you would like to configure and then open the **Error Handling** tab in the step configuration drawer. Under **Error Handler Type**, you have three options: * **Fail** - stop the flow and throw an error. * **Ignore** - ignore the error and continue running the rest of the flow. * **Retry** - wait for an amount of time (**Seconds Between Attempts**) and then try the step again, a maximum of **Max Attempts** times. Optionally wait longer and longer (**Exponential Backoff**, twice as long each time) between retries. If the last attempt still fails, either fail the integration or ignore the error depending on whether **Ignore Final Error** is true or false. ![Screenshot of step-level error handling configuration](/docs/assets/images/step-level-error-handling-af28cb1ca04efcaa4921c32d510de061.png) ##### Branching after ignored errors[​](#branching-after-ignored-errors "Direct link to Branching after ignored errors") If a step is configured to **Ignore** errors, or if the step has retried its configured number of times and then ignored the final error, the step returns a result with an `error` property detailing the error that occurred. You can use the [branch](https://prismatic.io/docs/components/branch.md) component to branch based on that error. This is useful if you have some sort of [dead letter queue](https://en.wikipedia.org/wiki/Dead_letter_queue) to write the failed item to, or if you would like to notify someone of the problematic item. You can branch based on whether or not the step's returned `error` **exists** and act accordingly. ![Screenshot of branching on step-level error handling](/docs/assets/images/branch-on-step-error-94d2f2ef580a3b5d87e29ae0d0597210.png) --- ##### Flows An integration can contain multiple flows, each with its own trigger and sequence of steps to handle different events or webhook types. This allows you to organize complex integrations into manageable, logical units that share configuration but execute independently. #### Flows in integrations[​](#flows-in-integrations "Direct link to Flows in integrations") Some integrations contain a single **flow** (one trigger and a series of steps that execute sequentially). For integrations requiring multiple logical flows - such as when integrating with Acme ERP that sends various webhook payload types - you can combine multiple flows into a single integration, with each flow handling a specific webhook type. This approach is more manageable than deploying dozens of distinct instances to each customer to integrate with Acme ERP; instead, you deploy a single integration composed of multiple flows. An integration's [config variables](https://prismatic.io/docs/integrations/config-wizard/config-variables.md) are scoped at the integration level. Therefore, config variables set for an integration are shared and accessible by any of the integration's flows. Each flow has its own unique trigger and its own [webhook URL](https://prismatic.io/docs/integrations/triggers/webhook.md) for invoking that specific flow. ##### Managing integration flows[​](#managing-integration-flows "Direct link to Managing integration flows") To add a new flow to your integration, click the **+ Add new flow** button at the top of the designer. ![Manage integration flows in Prismatic app](/docs/assets/images/manage-flows-c9b31ca571ecf4c72ec980f786934874.png) To edit a flow, click your current flow's name, then click the pencil icon to the right of the flow. Each flow should have a unique name and may include an optional description. To delete a flow from an integration, click the trash icon to the right of the flow's name and description. ##### Cloning a flow[​](#cloning-a-flow "Direct link to Cloning a flow") When you need to add a flow similar to an existing one, you can **clone** (copy) the flow. To clone a flow, open the flow menu by clicking the flow name at the top of the integration designer. Then, select the clone flow button and provide a new name for the copy. ![Clone integration flow in Prismatic app](/docs/assets/images/clone-flow-9395b81e33e85d4a9aed5d6d2fbe285b.png) --- ##### Looping For many integrations, it's useful to be able to loop over an array of items or to loop a certain number of times. If your integration processes files on an SFTP server, for example, you might want to loop over an array of files on the server. If your integration sends alerts to users, you might want to loop over an array of users. Prismatic provides the [loop component](https://prismatic.io/docs/components/loop.md) to allow you to loop over an array of items, or you can loop a predetermined number of times. After adding a **loop** step to your integration, you can then add steps within the loop that will execute over and over again. The **loop** component takes one input: **items**. **Items** is an array - an array of numbers, strings, objects, etc. For example, one step might generate an array of files that your integration needs to process. Its output might look like this: ```json [ "path/to/file1.txt", "path/to/file2.txt", "path/to/file3.txt", "path/to/file4.txt" ] ``` The loop component can then be configured to loop over those files by referencing the `results` of the **list files** step: ![Loop over files by referencing results of list files step in Prismatic app](/docs/assets/images/loop-e6ec0d680fdefd641e528f94ef20da30.png) Subsequent steps can reference the loop step's `currentItem` and `index` parameters to get values like `path/to/file3.txt` and `2` respectively: ![Repeat for Each to get file paths in Prismatic app](/docs/assets/images/loop-current-item-1ea005243551228d1ad54aa26030a967.png) **For More Information**: [The Loop Component](https://prismatic.io/docs/components/loop.md), [Looping Over Files Quickstart](https://prismatic.io/docs/integrations/common-patterns/loop-over-files.md), [Looping Over a Paginated API](https://prismatic.io/docs/integrations/common-patterns/loop-over-paginated-api.md) #### Looping over lists of objects[​](#looping-over-lists-of-objects "Direct link to Looping over lists of objects") The list of objects passed into a loop component can be as simple or complex as you like. In this example, if we have a loop named **Loop Over Users**, and the loop was presented **items** in the form: ```json [ { "name": "Bob Smith", "email": "bob.smith@progix.io" }, { "name": "Sally Smith", "email": "sally.smith@progix.io" } ] ``` Then the loop will iterate twice - once for each object in the list, and we can write a code component that accesses the loop's `currentItem` and `index` values and sub-properties of `currentItem` like this: ```javascript module.exports = async ( { logger }, { loopOverUsers: { currentItem, index } }, ) => { logger.info(`User #${index + 1}: ${currentItem.name} - ${currentItem.email}`); }; ``` That will log lines like `User #1: Bob Smith - bob.smith@prismatic.io`. #### Looping over a paginated API[​](#looping-over-a-paginated-api "Direct link to Looping over a paginated API") Many third-party APIs limit the number of records you can fetch at once and let you load a batch (or "page") of records at a time. You may need to loop over an unknown number of pages of records in an integration. You can accomplish that with a combination of two loops (one to loop over pages and one to loop over records on each page) and a [break loop](https://prismatic.io/docs/components/loop.md#breakloop) action that stops loading pages when there are no more left to load: ![Loop over paginated API in Prismatic app](/docs/assets/images/paginated-loop-bbb917cd04a71d594325e56097c249c1.png) Please reference [this quickstart](https://prismatic.io/docs/integrations/common-patterns/loop-over-paginated-api.md) for an example of how to loop over a paginated API. #### Return values of loops[​](#return-values-of-loops "Direct link to Return values of loops") A loop will collect the results of the **last** step within the loop and will save those results as an array. For example, if the loop is presented the list of JSON-formatted user objects [above](#looping-over-lists-of-objects), and the last step in the loop is a code component reading: ```javascript module.exports = async(context, loopOverUsers: { currentItem }) => { return {data: `Processed ${currentItem.email}`} } ``` Then the `result` of the loop will yield: ```json ["Processed bob.smith@progix.io", "Processed sally.smith@progix.io"] ``` --- ##### Passing Data Between Steps One of the core concepts in building integrations is understanding how data flows from one step to the next. Each step can produce outputs that become available as inputs for subsequent steps, enabling you to build complex data transformation pipelines. #### Step outputs[​](#step-outputs "Direct link to Step outputs") When a step runs, it may output data that subsequent steps can consume as input. For example, an SFTP **List Files** step outputs an array of file names: ![Example of step outputs as a list in Prismatic app](/docs/assets/images/step-output-0-9de17f07c4d88b64782b2bea4e9eb6f2.png) An SFTP **Get File** step outputs the contents of a file retrieved from an SFTP server (in this case, an image): ![Example of step outputs as a file in Prismatic app](/docs/assets/images/step-output-1-877272cd67da17a3fe9156db24d5d135.png) Outputs take one of three forms: * A primitive value, like a **string**, **boolean**, **number**, or **array** of primitives. A subsequent step that references this output will receive the string, boolean, number, or array as input. * An **object**. An output might include multiple key-value pairs: ```json { "key1": "value1", "key2": ["value2.0", "value2.1", "value2.2"] } ``` You might see this after retrieving JSON from an HTTP endpoint. Specific values from an object can be referenced as inputs using familiar JavaScript `.dot` and `["bracket"]` notation. Using the above example, to access `value2.1`, you would reference `results.key2[1]`. * A **binary file**. Binary file outputs contain a combination of a file `Buffer` and content type (like `"image/png"`) in the form: ```json { "data": Buffer, "contentType": String } ``` Note: An action can return a combination of JSON and binary file(s) if properties of the JSON object are objects with `data` and `contentType` properties. **For More Information**: [Custom Component Action Results](https://prismatic.io/docs/custom-connectors/actions.md#perform-function-return-values) #### Configuring step inputs[​](#configuring-step-inputs "Direct link to Configuring step inputs") After adding a step to your integration, you will typically need to configure inputs for that step. Inputs might include a RESTful URL endpoint, an S3 bucket name, a Slack webhook to invoke, or even a binary file such as an image or PDF to upload or process. Some inputs are required and denoted with a `*` symbol, while others are optional. Inputs can take one of four forms: **value**, **reference**, **config variable**, or **template**. **Value** inputs are static strings, **reference** inputs reference the results of a previous step, **config variable** inputs reference customer-specific config variables, and **template** inputs allow you to concatenate static strings, config variables, and step result references. Use the Join Lines action for multi-line input values If you need to enter multiple lines of text for an input value, you can use the [Join Lines](https://prismatic.io/docs/components/text-manipulation.md#joinlines) action to concatenate multiple lines of text into a single string. ![Join lines action in an input](/docs/assets/images/join-lines-input-f3900aee97a9481004adfefdbc87105e.png) ##### Value inputs[​](#value-inputs "Direct link to Value inputs") A **value** is a simple string (perhaps a URL for an HTTP request). When you set a **value** for an input, that static value will be used as input for all your customers: ![Set input value in Prismatic app](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAApIAAACFCAYAAADoxBhsAAAAAXNSR0IArs4c6QAAAGJlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAABJKGAAcAAAASAAAAUKABAAMAAAABAAEAAKACAAQAAAABAAACkqADAAQAAAABAAAAhQAAAABBU0NJSQAAAFNjcmVlbnNob3Rta/LUAAAB1mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4xMzM8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NjU4PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6VXNlckNvbW1lbnQ+U2NyZWVuc2hvdDwvZXhpZjpVc2VyQ29tbWVudD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CjxqCNQAACI8SURBVHgB7Z0HuFTV1YbXvXSpoiJIEaOCRgUbIBoLihoVlVhiSQRjj1jy2xU1UWPDFmNijRrs3VgIERVRLCAqFgQpKoICAQVUOgj/+vbPnv8wztwy9871MvOu55k7Z3Y7e73n8Mw3a+19KFnlZhgEIAABCEAAAhCAAAQqSaC0ku1pDgEIQAACEIAABCAAgUAAIcmNAAEIQAACEIAABCCQEwGEZE7Y6AQBCEAAAhCAAAQggJDkHoAABCAAAQhAAAIQyIkAQjInbHSCAAQgAAEIQAACEEBIcg9AAAIQgAAEIAABCOREACGZEzY6QQACEIAABCAAAQggJLkHIAABCEAAAhCAAARyIoCQzAkbnSAAAQhAAAIQgAAEEJLcAxCAAAQgAAEIQAACORFASOaEjU4QgAAEIAABCEAAAghJ7gEIQAACEIAABCAAgZwIICRzwkYnCEAAAhCAAAQgAAGEJPcABCAAAQhAAAIQgEBOBBCSOWGjEwQgAAEIQAACEIAAQpJ7AAIQgAAEIAABCEAgJwIIyZyw0QkCEIAABCAAAQhAACHJPQABCEAAAhCAAAQgkBMBhGRO2OgEAQhAAAIQgAAEIJBRSE6aONF677WXHXfccWsQuvaaa0L5s888E8r79+sXPqvt3r172+GHHWYXDxxon06ZkuoX2yTLUpUcQAACEIAABCAAAQistQQyCsmVq1bZkiVLbKm/krZs+fJQvmLFilCsNnp16tTJunbtGo5HjBhhAwYMsNmzZ6/RRmNiEIAABCAAAQhAAAKFQyCjkKyse/9z1ll2/Q032DPPPmudO3e2BQsW2BtvvFHZYWgPAQhAAAIQgAAEILAWEagWIRn9LS0ttbZt24aPderUicW8QwACEIAABCAAAQgUIIG61eHT+2PH2qxZs2zC+PE2cuRIq1u3rnXr1q06hmYMCEAAAhCAAAQgAIFaSqBahOTNN9+ccq9JkyZ27aBB1qZNm1QZBxCAAAQgAAEIQAAChUcgY2q7tKQkeLps2bI1PP5h9Sab0rS09Uknn2wXXnhhiEQuXbrUWrduvUY/PkAAAhCAAAQgAAEIFB6BjEKybbt2VuJicubMmTZjxozg9cqVK+09T2HL2nt90nr27GkH9Oljffy13Hd233P33clqjiEAAQhAAAIQgAAECpBAxtR248aNrVevXjZ8+HA7zR/ls5MLxY/HjbO533xjHTt2tG26dMmIol///jZkyBAbOnSoHXX00aFtbHjH7bdb02bN4kfb2cfce599Up85gAAEIAABCEAAAhBYuwhkjEjKhfMvuMD69u1r8+fPNz2AfOrUqabI46DrrrP69etn9LJVq1ahj6KX/7jrrjXajBo1yl4cNiz1mjR58hr1fIAABCAAAQhAAAIQWLsIlKxyK2vKqp47d641b948rIEsqy11EIAABCAAAQhAAALFQ6BcIVk8KPAUAhCAAAQgAAEIQKAyBLKmtiszCG0hAAEIQAACEIAABIqPAEKy+K45HkMAAhCAAAQgAIFqIYCQrBaMDAIBCEAAAhCAAASKjwBCsviuOR5DAAIQgAAEIACBaiGAkKwWjAwCAQhAAAIQgAAEio8AQrL4rjkeQwACEIAABCAAgWohgJCsFowMAgEIQAACEIAABIqPAEKy+K45HkMAAhCAAAQgAIFqIYCQrBaMDAIBCEAAAhCAAASKjwBCsviuOR5DAAIQgAAEIACBaiGAkKwWjAwCAQhAAAIQgAAEio9A3WwuL16yzGbNnW8LFi62latWZWtGOQQgAAEIQAACEIDAWkigtKTEmjRuZK1btrBGDevn5EFGISkROWX6TB+0gVmJj1tgOrJrp445waITBCAAAQhAAAIQKCQCS5Yut8nTZthm7dvkJCYzprYViZSIXLh4ia1cWWAqspCuPr5AAAIQgAAEIACBKhBo2KCebd5ho5CFzmWYjBFJpbNDJDKXEekDAQhAAAIQgAAEILDWEJCYDNovhxlnFJJhTSSByBxw0gUCEIAABCAAAQisfQRy3Q+TMbW99rnPjCEAAQhAAAIQgAAEapoAQrKmiXM+CEAAAhCAAAQgUCAEEJIFciFxAwIQgAAEIAABCNQ0AYRkTRPnfBCAAAQgAAEIQKBACCAkC+RC4gYEIAABCEAAAhCoaQIIyZomzvkgAAEIQAACEIBAgRBASBbIhcQNCEAAAhCAAAQgUNMEEJI1TZzzQQACEIAABCAAgQIhgJAskAuJGxCAAAQgAAEIQKCmCSAka5o454MABCAAAQhAAAIFQgAhWSAXEjcgAAEIQAACEIBATRNASNY0cc4HAQhAAAIQgAAECoQAQrJALiRuQKAQCfTv168Q3cInCEAAAgVDACFZMJcSRyAAAQhAAAIQgEDNEkBI5sh74sSJ9u233+bYm261mcBnn31mX3/9dW2eInODAAQgAAEI1AoCCMkcL8Oga6+1Dz/4IMfeNdPtiy++sAfuv3+Nk7355ps2fPjwNcqq8uGee+6xUW+9VZUhal3f22+7zV4fObLWzasyE3r88cdNP3Z+Chv+8sv2yCOP/BSn5pwQgAAEIFDDBBCSeQL++eef2wXnn79G1DJTWZ5OH4Z9+aWXrG69eqlTrFq1yv719NO2XsuWqbKqHqzTqJHVb9CgqsPQvxoJKJo67IUXrF27dtU4asWHauD3QyO/LzAIQAACECh8AnXz7eJ33y6q8imaNV+nymPkc4AffvjB6tSps8Ypli1bZjNnzjTVRctUFuv0rralpaVWUlKSLE4dZzpPqjLtYMmSJTZ69Gj745/+lKpRhGqpz2ubLl1SZfGgvLElQleuXPkjP4886qg4RJnv6iu/svlWZmevLG9+6p/eprxzZvMp21zKGy+9X3ntdX69dM3Lssr6pWjqttttZ40bN15jWM2nrHNV13x3+cUv1jhv8kO6L8k6HZdXn96ezxCAAAQg8NMSyLuQ/GTcF9Xi4RZbb2y1TVDOnjPHrrrySpsyZYp13GQTO+CAA2yHHXawO++80957993g98UDB9puu+1m333//Y/KJML+dsst1qRJE5s7d659/PHHVr9+feu99952yCGHpETXRx99ZC+++KKN9/q2bdta9+7dbX8/l0SZxOLDDz1kV119ta2zzv8L7jFvvx0iUhtttFGK/2uvvWa/8C/5KCb0pf3UU0/Z66+/bgt8flttvbXtu+++ttVWW4U+L3hU69133gm+vfbqq7Z8+fIgUE44/nhrtPpcf3KhGuaz//6p8yQPFi1aZPcNHmwf+DIACZUdd9zRjvGduA0bNrQnnnjCNM/LLr88fBaDyy67zPq4b3vvs08QFWXN7z//+U/o37lzZ3vllVdMQn2bbbax43x+Dz34oI0dOzaIqZ169rTDDz888KqIT8n567gsH9Lb6vO8efPs/vvus/ETJlgTF3M9d945cNV1li1cuNDu8/pxfl015y222MJ+d9xx1nJ1pDgXv8LA/kfCdKQLSY0XTT9ohjz/vL333nshUqh7tM+BB1qzZs1Ck7L8+9zXi15//fW2j98XQ4cOtR5+7+nafzl9up1z7rnxFPa030cT3N+L/H4X+zn+b+PMP/wh1Os+e8jvUf2b0A8c3S8aT/eyTHPWdX7zjTcCu80228yOP+EE23DDDUM9fyAAAQhAoPYSKDsUUg3zbtt+favqqzaKSKF5ycVd79697fIrrrAN1l8/fIGq/CgXiBIzsrPPPtsOPOigjGWqX7x4cfji38EFlgRVTxc9zz37bBB3qp/vouQvN91k7fxL9xpfl/nL/fYLAuwlT1vL2rdvH4SqhFnSXnXhJwEbTed5Z8wY223XXWNROM8rvl7ylFNOsUHXXWct113X7rn77vDFrkbLXeSEdXb+RX/pH/9ohxx6qE0YP94eeOCB1BiLXBQtW7o09Tl5IAHx15tvtnnz5wfRceqpp9rESZPssUcfDc369OljK1asCCJEBQ+6AGnWtKntuddeoV4cypqfRJhEvEzzO+HEE02i++yzzrIWLVqEsoMPPjiIqMmTJ4d2FfEpNFz9pzwfkm11rDndeMMNtmDBAvvDmWfaCS6IJMbv8h8XMvmr6znjq6/spJNOsiv+/Gdb5eVXX3VVqFObXPxSP9l4vz4S7PHHgMr0Y0Xi9pJLL7UT/ZzvuqAb/M9/qiqI9bKu0Q8+lnyR4D+2f3/b95e/tO082jlu3Lhwb4ZB/M9bvk62y+pI9xK/Hxb5/RZtsH5IvP++HeZifuDFF4fxtMZYolIWReRRRx8drlk9X46hOUlgYhCAAAQgULsJ5D0i2bbDBrWbQBVmp6hh9x49wggHuCi6xL8kv3KBoEhL8+bNQ/m6HmWKKcZMZWrUzSM0u+++e2jfz7+sp06daq+OGGG7uuib7zvDJT70Jb2+i1W9FLWM0UdFHCXwkqYI1LRp06zHTjulikePGmUdO3a0DVu3TpUp/akIYTsXo7KD+/YNkb1PPvnEttxyy1C2rovLo3/zm3Cscyma9h+PTPU/9tgwj1CR5Y/Em6JUipbG6NPMWbPsCd8IEqOSEtw3eMSrhZ9nrEfM/uQRybhMoCLzUxTv10ccEWbQpk0be8OjWrPc/yOOPDJEIHVeRXMVDevUqVNoVxmfyvMhPVUvf8Veol/zkWkuWpuq6zjJlxdMcjEt4bvpppuG+t84X62nlRjTNZfl4pf6KeqsMeK8JMa++eYb27FbtzAfzenUAQNs9uzZam7l+Rca+Z+TTj7ZOnToED5qzPXWW8/GuEDe26Pnik5qPEV+001iUdFs3de77LJLqNbxTTfeaDNnzLD2Pua/hwwJ95juRdnhv/61/dFF76effho+8wcCEIAABGovgbwLydrretVnlowCRpGoRwJF0VTRM8RUc2yvFLOEpGzjjTcOYvU6jxhu7eVKC26//fap1HJolPZnpIuJbi4ckvNThDJG+mJzjT3RReNTTz4ZIo9KccoUvYyWPretPe2tNKnESRRKsW36ux6jo+iShGM0CVGlyOd7lFKCTpEzCZ9HHn7Y+rqQjWJF7XOZn6KqijpGIaVxdG2+99R9tMr4VBEf4rh614Yq+ZVk07VrV9NLNsXFkX4EbOJLIaKpbatWrYIgi2Xpc6yIX2IrwSzhHk0cDvUfGkot6ykD+uGjeyOK2PL8i+MkN89ozJ09Xa8opYSkBKVEun7kpJueHCDhmYyQ6nroB4NMqXMJbEVtleqPpnPMWS12YxnvEIAABCBQ+wjkXUh+NW2Off9d+RtumjZbxwo5elmpS+9fvEpPyvSFOsAjSIpyjfHU9NMe2XrYRddZnjL/2c9+9qNhlYrVmscBp52WqlOUdIZHfyRCk6aUonb3KuWolyKdl15ySbLJj45jsjHO70cNEgUSNhKSP1+95jJWKQKrnb3R4ljxPZbnMr/YtzLvZflUUR/i+SSKkjvlY3l8l8iVwE8XihJbutZVMUU0tb5wgw3WzAJovamWTkisveWR6ccfeyxECPfYY48QYa7INUqf184eXXzuuefCD4J3FJn0JR6ZTD8aZDpHJlu4+sfL5ptvbk18WUM03TOKVmIQgAAEIFC7CeRVSGrH9lfTK/ZgZwlJ7P8IKD0axYAiRtM8qrNHr14hWqd0utJ+2jTy+9///kfIFHVq6I9e0QaUaIpGKs2dFG+q0+aGPffcM0SV9FmiqTz7xOcmwZMp+pTeV6lwRTmVJs/2KJqPPvwwpKOP9vVxj/raSQmejp6Cl+Uyv9Cxkn/K8qkiPiRP18aXDiiSpkfwREZaavC2R+8O9A0uilZrU5HSwXFJgdprc0qmHwbJscs7Vgp5P19DmzStsR3t59YmKwlKvbQZRptjJCQr618cW1FUXafnPTqtpQT6cZDJYmRWG8li2l5rQP/973+HVHesb+oiUpvMMAhAAAIQWLsI5HWzjXZZd99lS9NmmfJehRaNVKpSJiGoL05ZpjKVKx2pzQuLXXQN8fVi6rPb6jWT6nvvvfeaHvKs9WaKLCp93nT1DmAJFm36UURLpjVyyQ01ilBKkCU33oSG/kfpSJ33yy+/DBHP2269NVRpHtGUwpZo1bnf9h3i2hHcw9Oj6aJU7TWvF4cNCxs49FkpVIkp7WDWukClzDVXbUaR6Tx6oLnSo9rEoXG1KUVRPVlF5hcaVvJPZXwqzwdFUeWzxKFMgkrrPR/0DUniOsvXhN7rPmptpKKQWquo++B+f1C81gBKvD/hSwtauwDVjvNcTRFrPUVAQjxp2mH9pO+Of9QfEK7rI2GpeyhG/8rzLzlW+rHWPOp6Km0f1wGnt9FaSi3F0Lpa/WjQEgPN5QXfca8Ut+o1B+0I1xMIdL9rw5TWG2uTDwYBCEAAArWbQF4jktH12vbYnjivfL5rnZzWkWnHrNKA2qGbqUxz0M5rpRu1nkxio5dHCePmGz0a5jh/lMuTLjb0yBg9skVf3HGDjXbpKpKnR8xIgOlLuL9vZoimR+Ao2qOUZ7od5Dua9T/f6EtbG1z0+CKt4ZvhEaZoOt+nvjNaaxglmhRd/O0xx8TqNd61g1pz2c6FgwSkUuV6RMzd//hHeEySIpmKyGnjhkzr9mS/8iirTLt2tenkX//6lx122GFWkfmFjpX8UxmfyvNBwugxv3YSZtpxH9qfc47deccdpkc/iaue23mi7yiX1a1b184977xQf4Xv0le90rriFB+pVEl3QnNFI3t61FnnT5oE//nO9B9+Dc4844xQr7TxyauvQXn+JcdKP1aUW8ssdJ+XZXqUj8T0Tb5bXfeQ7veBzibO9XjfcCVhfcftt4d6/TvRhpv4uKSyxqYOAhCAAAR+WgIlHsmKS8RSM/lg0tTUcSEedO3UscbcUlRFa+IkIKIly64bNMia+6NqJDTjerz4BRvbx3eJFn25pq+lU9RRgkTPUpSw1JrKaBI02tihlGY2U/pZgkNjJO15XwOn/07xRhcAihRpvVu2yFPsF+cSP8d3RTR1qyU3bcS68t6zza+8fpnqc/EpjpPNh2w+a9667tmup8bTtcwU3Y3nrMi7zj/woovsZH+MU3ITT3pfRYDrudBM3ovJNtn8S7apyrHuH91H2e4h+SFm+uETrb8/c3Sw/4DCIAABCEAgvwSk/XLRR/+vbvI7v6IdPVNUJVOZAGX7go3wkl+wsUzvUQD28nWUeiUtRv+SZenH8VFC6eXJzxJD2QRRsl2cS7JMx8kd5Ol15X2uyPzKGyNTfUV9in2z+ZDN5/LmnW28eL6Kvuv8etxQeVZexLO65pNtHtpwk23TjfrIj2z3eLYxKYcABCAAgZ+WQF7XSP60rnF2CEAAAhCAAAQgAIF8EiC1nU+6FRhb6T6lN7OlGyswRN6aKNWoV0UikXmbRDUPXIg+VTOiWjUcqe1adTmYDAQgUMAESG2vpRe3rFTfT+2SUo3Z0rY/9dxyPX8h+pQri7WhH+sj14arxBwhAIFiJkBqu5ivPr5DAAIQgAAEIACBKhBASFYBHl0hAAEIQAACEIBAMRNASBbz1cd3CEAAAhCAAAQgUAUCCMkqwKMrBCAAAQhAAAIQKGYCCMlivvr4DgEIQAACEIAABKpAACFZBXh0hQAEIAABCEAAAsVMACFZzFcf3yEAAQhAAAIQgEAVCCAkqwCPrhCAAAQgAAEIQKCYCSAki/nq4zsEIAABCEAAAhCoAgGEZBXg0RUCEIAABCAAAQgUMwGEZDFffXyHAAQgAAEIQAACVSCAkKwCPLpCAAIQgAAEIACBYiaQUUiWlpRYaWlJMXPBdwhAAAIQgAAEIFA0BKT9crGMQrJJ40bWqEGDXMajDwQgAAEIQAACEIDAWkRgydLlJu2Xi2UUkq1btrDFS5Za40YNiUzmQpU+EIAABCAAAQhAYC0gIBE5edoMk/bLxUpWuWXquHjJMps1d74tWLjYVmZukqkbZRCAAAQgAAEIQAACawEBpbMViZSIbNSwfk4zziokcxqNThCAAAQgAAEIQAACRUMgY2q7aLzHUQhAAAIQgAAEIACBnAkgJHNGR0cIQAACEIAABCBQ3AQQksV9/fEeAhCAAAQgAAEI5EwAIZkzOjpCAAIQgAAEIACB4iaAkCzu64/3EIAABCAAAQhAIGcCCMmc0dERAhCAAAQgAAEIFDcBhGRxX3+8hwAEIAABCEAAAjkTQEjmjI6OEIAABCAAAQhAoLgJ5Cwkx40bZ2+++WaZ9H744Ycy67NVrly50rL8hzvZumQt/++sWfbsM89kraeifAKjR4+2Dz74INVwyPPP21dffZX6zAEEIAABCEAAAsVJoG4mt++84w575513MlWFsltvu80+diE5ffp023nnnTO2e+mll+ylF1+0a669NmN9WYW3/v3v1qRJEzv2d78rq1nGurlz55qE6Prrrx/qZ/33v/aMC8mDDj44Y/vKFC5btsxOGzDAzj7nHOvcuXOq6ywXq5decolddvnl1qZNG3vg/vvttddeC/WlderY5pttZl233dZ22mmn4Jcq1L6ns9tvv/1S42Q7kCCXcNPY9erVy9Ysb+WjR42ydddd17p27RrOMWTIEGu14YbWtm3bvJ2TgSEAAQhAAAIQqP0EMgrJo44+2g459NAw+5ddEL773nt23nnnpbyp4+KoPOvRo4dtsskm5TXLWK9z16ubcWoZ2ycLn3v2Wfvu++/t9NNPTxZX2/HSpUuDUE0OqOipymMUdfmKFcH3E086KbSdPGmSPe9RvLc9snfRwIGhq0TpD96uIrZgwQK75OKL7cqrrrJ27dpVpAttIAABCEAAAhCAQN4JZFRrTZs2Nb1kjT0yWNeFY4zwpc/ovx7xe3/sWNugVasQsYoi89tvvzVFBzfddNNUF0XVPvroI+vQoUOI6MW2qQarD9SvQYMGYUwVKcI4ycXY1KlTbeuttrJ27dundwmfX3/9dZsxY4YtWbLERowYYbvuumuq3aJFi2zM22/bKi/p3r27rbPOOqk6RfwmTJhgUz//3Nr73GLkLdUgh4N69eunmLVyNnU9kqhIq7g0b968wiN++eWXgZk6jBkzxpa6b5t6hFP29ddfh7oVy5fb1ttsEyKWoSLLH0VOtSShRYsWtq1HSOsmxPrChQvt/ffft++++y74v9FGG2UZhWIIQAACEIAABCDwfwRyXiOp7hI5f735Zpvu7w8+8IBd7RGzaBPGj7enn3oqfrQnn3jCbrzhBpvtwvPhhx4KEbYYwUs1Wn0w/OWXbdRbb6WKr7rySnvkkUdM6x2vu/56G/zPf6bqkgeaz/cejZRonD5tWipCKCF67TXX2CeffGIjXnnFzjzjDFu8eHHoKhGped17zz2mNPhtnra/b/Dg5LDVclxaWmoSzpVNTcufuB5x1syZNm/+/DAfCfLzzj03RDnfdoF8sUc6FfHMZqq74Pzz7aMPP7RXhg+3MzxiO2fOnNB8zuzZof+bb7xhs/1YvLUcAIMABCAAAQhAAAJlEcgYkSyrQ7JOwkip2saNG9uXvl7yYk+/Zoq4rfAU7rBhw+z3p54aImFK695044322WefrRGxTI4djydPnmyffvqp3XrrrdbIo4i777FHEKKKOjZs2DA2C+9HHnlkiNgptX1Mv36pOglWrbdUql3Hp592WhBU3T39PuyFF0IU83oXk/Jnzz33tMsvu8z23Xdf27B169QYlT1QlHC+i74QTZ04MQiz7bbffo1IaEXG3HLLLU3RwZG+5vLAgw4KqW3x/Nstt9jhhx9u++2/fxjm+eees8EugLv4OsZ0LuJ955132vHHH2+77rZbaK/PL7jvv/3tb22aXztFabWkQbblFlvYA/7DoE+fPoFJKOQPBCAAAQhAAAIQSCNQJSGpzR8SkbKYbtYGnPTUrVKo7T0d/dSTTwahueOOO9r5F1yQNpXMH5UW1jnuuuuuIIK28RTuhRddlLlxllIJxLhes6SkJMwliCcXkhNd5MmP5A70+p6WVn1VhKSin2efdVaYkYSfBNzvctg8lMmlL774IqTvd+zWLVW9U8+e9vjjj9s0j8R26tQpVa4DLQnQHLbdbrtUeT8X2lp7Kdthhx1sexe5aiceikrqB4HS3c2aNUv14QACEIAABCAAAQgkCVRJSCYHKu/4PE+raiezBJtSxz18B/Oxxx5rEm1lmUTp5VdcYa+++qo9/thjdsftt9vBvgM7RuLK6ltWXUyra42nhOaHicfbaI1ko0aNsnZf5anypCnqKJNIjaY1i+f47m7Z331t5Nxvvqm26N43vjZS3JLrVrXuUYJ94WpxGOeh93nz5gV/4rpXlSlqGSOXn3tk+HpfMrCFRyI7duyYippGRmqPQQACEIAABCAAgXQCNSIktc5PkbLevXuHlzaJKA3eafPNbY9evdLntMbnmb4uUDuif/WrX4XXu+++G9ZlKk3cugqp53gSbRKS/Bvg6e7yTOJNYkzRxp/7pp9oiuJJROoROZnsMN+FfuGFF9qHvj6xS5cumZpUqkyP3lG6Ork0YMqUKSHq2GHjjX80VqsNNgjrRrWGNO76VtRRayS3cj+GDh0aopin+9pRmZYp6NmbSWH8o0EpgAAEIAABCECg6AlUabNNRekt9/WC1w0aZK/6Tur4kPJSF14VEYJKt1755z+HtKsiZNq0orWS2VKuLVzMzfNd3/E85c1x9913DzufteNb6V8JLG0gkvjNZIqkjhw50sb6I5Ek5j7++OPwvMwdPF0fI3zp/ZQi19rOR33DUIxeqs1S76/0cfKVrI/jKLUvERs3x2zsYlE7t59++umwAUliW8JvC19Pud5668VuqfeOvjZUSw/URjvi53uEUmtO40PGdR3ktyKXEvmPeYpcJgFfnikFrt3eGAQgAAEIQAACxUegRiKSLVu2DBE/beDQS8JoN18zKOFTnukh3oqkXXP11UEcas3kMb5BJPn4nuQY3XzdoATriSecYH9xQVieaX2gNuloXtoNrscOHeCbTJJp4OQYRxxxhPmOnZCulvDcwKN9e3mkdQ8XimVZ37597VxPdUuESrzKJOz0StoVLpr1eKSkKWXdyzcB3fLXv4b3Y445xk7zCKoeHK9Ip+rF8rRTTkl2Sx0rsqjnampZwDlnnx1S7N18c40268h67713EOqqk1DfxzcafeDiUAJV/pVlevST1mbe8re/EcEsCxR1EIAABCAAgQIkUOJRPj1ascZMGzz0v9ZU1jRNPdYnbu4pq7/a6jzZxGCmvuqjKGS2SGd6H7XXrmytTaypFLD8V2RSwjGadq9rjWdFHyu02MfQMy6TY8Sx9Egkja/xKmOKokqAYhCAAAQgAAEIFBeBGheSxYUXbyEAAQhAAAIQgEDhEiCMVLjXFs8gAAEIQAACEIBAXgkgJPOKl8EhAAEIQAACEIBA4RJASBbutcUzCEAAAhCAAAQgkFcCCMm84mVwCEAAAhCAAAQgULgEEJKFe23xDAIQgAAEIAABCOSVAEIyr3gZHAIQgAAEIAABCBQuAYRk4V5bPIMABCAAAQhAAAJ5JYCQzCteBocABCAAAQhAAAKFSwAhWbjXFs8gAAEIQAACEIBAXgkgJPOKl8EhAAEIQAACEIBA4RJASBbutcUzCEAAAhCAAAQgkFcCCMm84mVwCEAAAhCAAAQgULgEEJKFe23xDAIQgAAEIAABCOSVAEIyr3gZHAIQgAAEIAABCBQuAYRk4V5bPIMABCAAAQhAAAJ5JYCQzCteBocABCAAAQhAAAKFS+B/AW0GYosaweSxAAAAAElFTkSuQmCC) ##### Reference inputs[​](#reference-inputs "Direct link to Reference inputs") A **reference** is a reference to the output of a previous step. For example, if a previous step retrieves a file from Amazon S3 and the step is named **Fetch my file**, then you can reference **Fetch my file** as input for another step, and that subsequent step will receive the file that **Fetch my file** returned. Outputs from one step can be referenced by a subsequent step by referencing the previous step's **results** field. For instance, if a previous step returned an object - such as when an **HTTP - GET** action retrieved JSON reading `{ "firstKey": "firstvalue", "secondKey": "secondvalue" }` - you can access that `firstvalue` property in a subsequent step's input by selecting the **HTTP - GET** step and choosing `results.firstKey` in your **Reference search**: ![Reference earlier step result as step input in Prismatic app](/docs/assets/images/input-reference-fa93e7600208ae18a0cb68d7786a7dde.png) ##### Config variable inputs[​](#config-variable-inputs "Direct link to Config variable inputs") A **config variable** references one of the integration's config variables. For example, we can select a config variable, `CMS API Endpoint`, as input for one of our steps. Config variables can be distinct for each customer, allowing each customer to be configured with a different `CMS API Endpoint`: ![Config variable inputs in Prismatic app](/docs/assets/images/input-config-var-5934d55889fb0f60dac22801665016fc.png) ##### Template inputs[​](#template-inputs "Direct link to Template inputs") Finally, a **template** is a combination of string values, config variables, or step result references. You can concatenate several strings, config variables, and step results together to serve as a single input. Templates are useful when an input needs to be composed from various config variables and step results. For example, suppose you want to make an HTTP request to an API endpoint that is stored as a config variable and fetch an item whose ID was obtained in a previous step. You could combine the API endpoint, URL path, and product ID like this: ![Add template input in Prismatic app](/docs/assets/images/input-template-194f6543bdea68639003de4c858e7036.png) A static string, like `/product?id=`, can be intermixed with config variables and step result references. You can add references to config variables or step results by clicking the **+** button. ![Template inputs in Prismatic app](/docs/assets/images/input-template-add-reference-260c1ea8c0da4843cca4d33a4463b336.png) **For More Information**: [Custom Component Inputs](https://prismatic.io/docs/custom-connectors/actions.md#input-parameters) --- ##### Raw Request Actions Components contain actions that wrap a large number of API endpoints. But, some APIs are vast with thousands of endpoints (only some of which are relevant to integrations). Not every endpoint that an app offers is represented by an action in the component. That's where an **HTTP Raw Request** action is useful. Raw request actions allow you to send a request to any endpoint that an API offers, using an HTTP client that is already authenticated with the third-party. Most built-in components include raw request actions, and depending on the API they may include an action for generic HTTP requests or an action for GraphQL requests. This document details how to use raw request actions in your integration. #### Determining what endpoint to specify[​](#determining-what-endpoint-to-specify "Direct link to Determining what endpoint to specify") You can determine the endpoint URL to use by looking at the API's documentation. For example, the [Asana API documentation](https://developers.asana.com/reference/rest-api-reference) lists all of the endpoints that they offer. To [Get audit log events](https://developers.asana.com/reference/getauditlogevents) from Asana, you need to send a GET request to `https://app.asana.com/api/1.0/workspaces/{workspace_gid}/audit_log_events`. The Asana component helper text notes that it fills in the base URL `https://app.asana.com/api/1.0` for you. So, you would need to construct the remaining `/workspaces/{workspace_gid}/audit_log_events` portion of the URL. ![Raw request URL input with config variable template](/docs/assets/images/url-input-5e39dd4891ee0d9ca4ec0867fbbdb20c.png) The comments and example you see when you first input the URL are provided by the component developer and let you know what the base URL is and what the rest of the path should look like. Override a raw request base URL You can override a raw request base URL by specifying a fully qualified URL in the URL input. For example, if you wanted to send a request to `https://my-api.example.com/some/endpoint` from the Asana raw request component, you can specify that full URL in the URL input and the component's base URL will be ignored. #### Sending JSON to an API using raw request[​](#sending-json-to-an-api-using-raw-request "Direct link to Sending JSON to an API using raw request") The majority of modern APIs expect JSON data in the request body. You can send JSON data to an API using a raw request action by constructing a JSON string in the `data` input. include a content-type header Most JSON-based APIs require that you specify a `content-type` header of `application/json` when sending JSON data. Otherwise, you may see an error from the API that the request body is not valid JSON. If you reference a JavaScript object in the `data` input, the component will automatically set the `content-type` header to `application/json` for you. If you specify a JSON string, manually include the `content-type` header in the `Headers` input. ![Raw request JSON content type header](/docs/assets/images/json-content-type-98f56660bfb62db30b6bf5f808c860a4.png) Additionally, you can reference a JavaScript object in the `data` input, and the component will automatically convert it to a JSON string for you. This is useful if you have a code step that constructs a JavaScript object that you want to send to the API. ![Raw request data from a JavaScript object](/docs/assets/images/javascript-object-data-input-ce6b3d93a7c909014369f56e742272c1.png) #### Sending non-JSON text to an API using raw request[​](#sending-non-json-text-to-an-api-using-raw-request "Direct link to Sending non-JSON text to an API using raw request") For non-JSON text data, like XML, CSV, etc., you can use the [Change Data Format](https://prismatic.io/docs/components/change-data-format.md) component to serialize data into the appropriate format. Like JSON data, ensure that you specify an appropriate `content-type` header (e.g. `text/csv`, `application/xml`, etc.). #### Sending form data to an API using raw request[​](#sending-form-data-to-an-api-using-raw-request "Direct link to Sending form data to an API using raw request") Form data inputs are useful for sending data to APIs that expect a content type `application/x-www-form-urlencoded`. Form data is largely used when you need to send several types of data in a single request, such as a file along with some metadata. To send form data, first ensure that you have cleared the `data` input - you can't send both data and form data together. Then, specify form data key/value pairs. In the below example, we send both a simple string `userid` and an XML payload `person-xml`. We also send a file `profile-picture` by referencing a picture from a previous step, and we give the file a name using the `File Data File Names` input: ![Raw request form data inputs](/docs/assets/images/form-data-inputs-19d0830e518da1e4f9176bfd98c3884e.png) Serialize JSON before sending Unlike the `data` input, form data inputs cannot accept JavaScript objects. Serialize the JavaScript object into a JSON string (or XML string, etc.) before referencing it. #### Sending custom parameters and headers using raw request[​](#sending-custom-parameters-and-headers-using-raw-request "Direct link to Sending custom parameters and headers using raw request") The `Query Parameters` input allows you to specify custom query parameters to send to the API (that's the `?key=value` portion of the URL). While you could specify query parameters in the URL input through a [template input](https://prismatic.io/docs/integrations/low-code-integration-designer/passing-data-between-steps.md#template-inputs), string concatenation is prone to encoding issues. The `Query Parameters` input ensures that your query parameters are properly URI-encoded. The `Headers` input allows you to specify custom headers to send to the API. In addition to the usual `Content-Type` header, you may need to specify other headers like `Accept` or a custom header like `X-Tenant-ID`. ![Raw request parameters and headers inputs](/docs/assets/images/parameters-and-headers-8513ec999d4a2bc57d26c3a7dedf1800.png) #### Response data types for raw request actions[​](#response-data-types-for-raw-request-actions "Direct link to Response data types for raw request actions") The `Response Type` input allows you to specify how you would like the response data to be formatted. * `json` is the default response type and will return the response data as a JavaScript object. This type assumes that the API returns `application/json` response data. * `text` will return the response data as a string. This type assumes that the API returns `text/plain`, `text/html`, or other text-based response data. * `arraybuffer` is used when you expect a binary file response. Use this if you expect a file, like a PDF, image, etc. #### Debugging raw request actions[​](#debugging-raw-request-actions "Direct link to Debugging raw request actions") If you're having trouble with a raw request action, you can toggle the `Debug` input to see the full request and response data in logs (remember to toggle it back before deploying to production!). This is useful for debugging issues with the request body, headers, etc. It is also useful to use a tool like [Postman Echo](https://learning.postman.com/docs/developer/echo-api/) to echo back the request that you're sending. To use Postman echo, set the URL to `https://postman-echo.com/post` and the `HTTP Method` to `POST`. The raw request step's result will contain the full request data that you sent, which you can compare to the API's documentation to ensure that you're sending the correct data. Another tool that is useful for debugging raw requests is the [mendhak/http-https-echo](https://hub.docker.com/r/mendhak/http-https-echo) Docker image. This Docker container will print and echo any request that it receives. To run the Docker container and expose its port, run the following command: ```bash docker run -p 8080:8080 -p 8443:8443 --rm -t mendhak/http-https-echo:latest ``` In a separate terminal, run [ngrok](https://ngrok.com/) to expose the Docker container to the internet: ```bash ngrok http 8080 ``` `ngrok` will give you a public URL, like `https://73ed-123-45-67-89.ngrok-free.app`, which you can use as the URL in your raw request action. When you run your integration, you can see the full request data in the Docker container's logs. ![Raw request ngrok echo](/docs/assets/images/ngrok-echo-02a0e9eb1fe0ca99e242e074fd0cea2d.png) tip If you can get an HTTP request to work in a tool like `curl` or [Postman](https://www.postman.com/) but cannot get it to work in a raw request action, send both the raw request and Postman request to your `ngrok` / echo endpoint. Comparing the two requests side-by-side can help you identify what is different between the two requests. #### Building an HTTP raw request action in your custom component[​](#building-an-http-raw-request-action-in-your-custom-component "Direct link to Building an HTTP raw request action in your custom component") You can build your own raw request actions for your custom components. That's useful if you have a large API but don't have the development resources to build out actions for every endpoint. A raw request action can be used by your integration builders to send requests to any endpoint that your API offers. For an example raw request action, see our [GitHub examples repo](https://github.com/prismatic-io/examples/blob/main/components/asana/src/actions/rawRequest.ts) for the code that backs our Asana component's [raw request action](https://prismatic.io/docs/components/asana.md#rawrequest). Prismatic components use standard inputs and a `sendRawRequest` function to build built-in raw request actions. You can import the same inputs and function from the custom component SDK, `@prismatic-io/spectral`, so you don't need to write that code yourself. Example raw request action from the Asana component ```ts import { action } from "@prismatic-io/spectral"; import { inputs as httpClientInputs, sendRawRequest, } from "@prismatic-io/spectral/dist/clients/http"; import { connectionInput } from "../inputs"; const rawRequest = action({ display: { label: "Raw Request", description: "Send a raw HTTP request to Asana API", }, inputs: { connection: connectionInput, ...httpClientInputs, url: { // Optional; this adds component-specific instructions to the URL input ...httpClientInputs.url, comments: "Input the path only (/goals), The base URL is already included (https://app.asana.com/api/1.0). For example, to connect to https://app.asana.com/api/1.0/goals, only /goals is entered in this field.", example: "/goals", }, }, perform: async (context, { connection, ...httpClientInputs }) => { const asanaToken = connection?.token?.access_token || connection?.fields?.apiKey; const { data } = await sendRawRequest( "https://app.asana.com/api/1.0", // Change this to your API's base URL httpClientInputs, { Authorization: `Bearer ${asanaToken}` }, // Authorization methods vary by API ); return { data }; }, }); export default rawRequest; ``` You can likely copy and paste the above code, changing the helpful `comments` and `example` for the `URL` input and changing the base URL and authorization header to match your API. #### Sending GraphQL requests using raw request[​](#sending-graphql-requests-using-raw-request "Direct link to Sending GraphQL requests using raw request") Some APIs, like [Fluent Commerce](https://prismatic.io/docs/components/fluent-commerce.md), are GraphQL-based. These built-in components generally have `Generic GraphQL Request` actions that you can use to send GraphQL requests. The generic GraphQL request action has a `Query or Mutation` input, which is the GraphQL query or mutation that you want to send. It's wise to parameterize queries and mutations using variables (to avoid QL-injection issues). Most generic GraphQL request actions have a `Variables` input, which is a key/value input where you can specify variables and their values that your mutation uses. It also generally includes a `Variables Object` input if you would like to provide a key/value object from a previous step. `Variables` and `Variables Object` are merged together and can be used in tandem. For example, suppose we want to send this mutation: ```graphql mutation myMutation( $customerName: String! $customerDescription: String! $labels: [String] ) { createCustomer( input: { name: $customerName description: $customerDescription labels: $labels } ) { id } } ``` You could reference `customerName` from a previous step but also supply `customerDescription` or `labels` from a previous step using the `Variables Object` input: ![GraphQL Raw request variables object input](/docs/assets/images/graphql-variables-object-input-39fb2997895e337e5433063476f4f03f.png) Construct GraphQL queries and mutations first using a GraphQL client GraphQL APIs often offer a web-based GraphQL explorer where you can construct queries and mutations. We recommend using a GraphQL client tool to construct your query or mutation first and then copy/pasting it into the `Query or Mutation` input. #### Building a GraphQL raw request action in a custom component[​](#building-a-graphql-raw-request-action-in-a-custom-component "Direct link to Building a GraphQL raw request action in a custom component") A generic GraphQL raw request action is similar to an HTTP raw request action, but instead of generic data inputs it has inputs for the query/mutation to run and the parameterized variables to use. This example requires three additional dependencies: ```bash npm install graphql graphql-request lodash.merge ``` In the below example, we use the `graphql-request` library to prepare and send the GraphQL request (including the query/mutation and parameterized variables). `lodash.merge` is used to merge the `Variables` and `Variables Object` inputs together, as you may want to specify some variables in the UI and reference other variables as an object from a previous step. The portions of code you will need to change are the GraphQL API URL, and any custom authorization headers that your API requires. Example GraphQL raw request action ```ts import { GraphQLClient } from "graphql-request"; import { Connection, action, component, connection, input, util, } from "@prismatic-io/spectral"; import merge from "lodash.merge"; const createClient = (connection: Connection) => new GraphQLClient( "https://app.prismatic.io/api", // Replace this URL with your API { headers: { Authorization: `Bearer ${connection.fields.apiKey}` }, // Authorization methods vary by API }, ); const genericGraphQLQuery = action({ display: { label: "Generic GraphQL Query", description: "Issue a query or mutation against the GraphQL API", }, inputs: { connection: input({ label: "Connection", type: "connection", required: true, }), query: input({ label: "Query or Mutation", type: "code", required: true, language: "graphql", clean: util.types.toString, }), // Variables are presented in the UI as a key-value pair list, and are handy // if you know ahead of time how many variables your query includes variables: input({ label: "Variables", type: "string", required: false, collection: "keyvaluelist", clean: (val: any) => util.types.keyValPairListToObject(val), }), // Variables Object is presented in the UI as a JSON editor, and is handy // if you don't know ahead of time how many variables your query includes, // or if you want to reference entire key/value objects from previous steps. variablesObject: input({ label: "Variables Object", type: "code", language: "json", required: false, clean: (value) => (value ? util.types.toObject(value) : {}), }), }, perform: async (context, inputs) => { const client = createClient(inputs.connection); const data = await client.request( inputs.query, merge(inputs.variables, inputs.variablesObject), // Merge the two variables inputs together ); return { data }; }, }); ``` --- ##### Steps A flow consists of a series of steps that execute in order, with each step performing a specific action like fetching data, transforming it, or sending it to another system. Understanding how to add, configure, and connect steps is fundamental to building integrations. #### Integration steps[​](#integration-steps "Direct link to Integration steps") Actions, like downloading a file from an [SFTP server](https://prismatic.io/docs/components/sftp.md) or posting a message to [Slack](https://prismatic.io/docs/components/slack.md), are added as **steps** of an integration. Steps are executed in order, and outputs from one step can be used as inputs for subsequent steps. Steps are run in order from top to bottom, and you can add conditional logic to your integration with a [branch](https://prismatic.io/docs/integrations/low-code-integration-designer/branching.md) or run a series of steps on a data set in a [loop](https://prismatic.io/docs/integrations/low-code-integration-designer/looping.md). If one step throws an error, the integration stops running. #### The trigger step[​](#the-trigger-step "Direct link to The trigger step") The first step of your integration is the **trigger** step, which determines when instances of your integration will run. The [integration triggers](https://prismatic.io/docs/integrations/triggers.md) article details how triggers work and how to invoke your integration. #### Adding steps to integrations[​](#adding-steps-to-integrations "Direct link to Adding steps to integrations") To add a step to an integration, click the **+** icon underneath the trigger or another action. Select the component and action you would like to add to your integration. For example, you can choose the **Amazon DynamoDB** component and then select the **Create Item** action. You can begin to type the name of the component or action you would like to add to filter the list of components and actions available. ![Choose component or action to add step in Prismatic app](/docs/assets/images/add-step-0b3d52842bcdb6843d2e86306a2945a2.png) ##### Choosing component versions[​](#choosing-component-versions "Direct link to Choosing component versions") Components are [versioned](https://prismatic.io/docs/custom-connectors/publishing.md#component-versioning). You can choose a version of each component (custom or built-in) that works for your integration. "Pinning" component versions for your integration prevents accidental regressions if a new version of a component is published that contains breaking changes. To choose what version of each component your integration uses, click the **Component Versions** button on the right-hand side of the integration designer. You can choose to run the latest version of a component or any previous version. Components running the latest available version will be marked in grey, while components running a version for which there is a newer version available will be marked in yellow. ![Component version list highlighting latest version in Prismatic app](/docs/assets/images/component-version-drawer-f631af013799bfec554f534f68ca2707.png) To change the version of a component your integration uses, click the **CHANGE VERSION** button to the right of the component you want to change and select a version from the dropdown. #### Cloning steps[​](#cloning-steps "Direct link to Cloning steps") If you would like to make a copy of a step in your integration, click the **...** button next to the step and then select **Duplicate**. ![Clone a step in Prismatic app](/docs/assets/images/clone-step-d35196ce020591bfc2c66558cea5e887.png) This will copy the step, including any inputs you've configured for the action. #### Changing step actions[​](#changing-step-actions "Direct link to Changing step actions") If you would like to change the action that a step uses, click the **...** button next to the step and then select **Change Step Action**. ![Change a step](/docs/assets/images/change-step-action-8609d5c079f227eb286d9092e5fe0bb0.png) You will be prompted to select a different action and then will be prompted to fill in that new action's inputs. #### Changing step names[​](#changing-step-names "Direct link to Changing step names") By default, steps are uniquely named after the action they invoke (so they're named things like **CSV to YAML** or **Delete Object**). To override that default name, click the step and open the **Details** tab in the step configuration drawer. Like using descriptive variable names in a program, renaming steps allows you to give your steps descriptive names. Rather than `HTTP - PUT`, you could give your step a name like **Update Record in Acme**. We recommend giving your steps descriptive names and descriptions so your team members can read through integrations and understand their purpose more readily. ![Rename a step in Prismatic app](/docs/assets/images/rename-step-8855a985c0877027b1b63302d472d879.png) #### Reordering steps[​](#reordering-steps "Direct link to Reordering steps") Steps are executed in serial. To reorder steps, click and drag a step up or down. ![Reorder flow steps in Prismatic app](/docs/assets/images/reorder-steps-9d853c5e025a0dba3f333aa621b2e12f.webp) --- ##### Testing Integrations The integration designer provides a sandbox for testing integrations. From the designer, you can invoke a test instance of your integration, configure test values for config variables, and view test logs in real time. You can test your integration after you set test values for config variables by clicking the green **Run** button. If your integration is made up of multiple [flows](https://prismatic.io/docs/integrations/low-code-integration-designer/flows.md), each flow is tested independently. Click the flow name on the top of the integration designer area, select the flow you would like to test, and then click **Run** for that flow. Note that each flow has a distinct webhook URL, so if you are invoking the integration from a third-party app via webhook, you'll need to note the flow's webhook URL. #### The test runner drawer[​](#the-test-runner-drawer "Direct link to The test runner drawer") The test runner drawer is where you can configure and run tests of your integration. It's located at the bottom of the integration designer screen. ![Test runner drawer in Prismatic app](/docs/assets/images/test-runner-drawer-dfecd0be2d126b7a0e95e875e278fd80.png) If you build a code-native integration, the designer canvas will be hidden (as all of your integration logic exists in code), but the same test configuration experience will be available to you. #### Test instance config variables[​](#test-instance-config-variables "Direct link to Test instance config variables") If your integration uses config variables, you can specify testing values for those variables by clicking **Test Configuration** in the **Test Runner** drawer and then select **Test-instance configuration**. You will be prompted to fill out the same configuration wizard that your customers will see when they deploy an instance of your integration. If you specified default values for your [config variables](https://prismatic.io/docs/integrations/config-wizard/config-variables.md), those will be preset for you. Otherwise, fill in testing values and connection information for the purposes of testing your integration. ![Config wizard for testing integrations in Prismatic app](/docs/assets/images/config-wizard-3cb0e339be5725f3c1619dd50995ede7.png) We recommend that you create testing, non-production sandbox credentials for integration tests. #### Running a test of your flow[​](#running-a-test-of-your-flow "Direct link to Running a test of your flow") To run a test of your flow, click the green **Run** button in the **Test Runner** drawer. If you would like to send data to your integration's trigger as a webhook payload, you can specify that payload in the **Test Configuration** tab by clicking **Trigger payload**. ![Trigger payload dialog for testing integrations in Prismatic app](/docs/assets/images/trigger-payload-0633fda27c80366d4cb391009657acd5.png) Within the trigger payload dialog, you can also specify custom HTTP headers to be sent with the webhook request. If you would like to invoke your integration from an external system (i.e. send a webhook from your app or a third-party system), copy the webhook URL that is displayed in the **Trigger payload** dialog and send HTTP requests to that endpoint. #### Replaying test invocations[​](#replaying-test-invocations "Direct link to Replaying test invocations") Time-saving tip Like [instance replays](https://prismatic.io/docs/monitor-instances/retry-and-replay.md), you can replay a test integration invocation. That comes in handy if you are testing an integration invocation from a third-party app. You don't need to set up your third-party environment every time - you can send a webhook invocation once from your third-party app with a payload and run that same payload through your integration until you're happy with the results. To replay a test integration invocation, open the **Test Runner** drawer and select a test that you'd like to replay. Click the replay button to the right of the test. ![Replay test integration invocation in Prismatic app](/docs/assets/images/test-integration-replay-8841f4992785f992c7b675de118b328f.png) The payload that was sent to trigger this integration test will be fed back into another test of the integration. This allows you to make changes to your integration and iterate quickly, without needing to reconfigure your third-party apps and services to fire new webhook requests over and over. #### Test run results and logs[​](#test-run-results-and-logs "Direct link to Test run results and logs") After running an integration test, the steps that ran are displayed in the **Steps** column of the **Test Runner** drawer. You can toggle the **Logs** option to show logs for each step that ran. Clicking on a step will display the step's outputs and logs in the third column. This is helpful for debugging and verifying the flow of data within your integration. **For More Information**: [Logging](https://prismatic.io/docs/monitor-instances/logging.md), [Log Retention](https://prismatic.io/docs/monitor-instances/logging.md#log-retention) --- #### Triggers ##### Triggers Overview Integration **triggers** define *when* a flow should run. If your integration has multiple [flows](https://prismatic.io/docs/integrations/low-code-integration-designer/flows.md), each flow has its own trigger (and its own webhook URL or schedule). There are several types of triggers: [App Events](https://prismatic.io/docs/integrations/triggers/app-events.md) [Invoke a flow when data changes in a third-party app. Some app event triggers rely on webhook requests sent from the third-party, while others poll the third-party app's API for changes.](https://prismatic.io/docs/integrations/triggers/app-events.md) [**Example:** A new lead is created in Hubspot, and you want to ingest the new lead data in your app.](https://prismatic.io/docs/integrations/triggers/app-events.md) [Universal Webhook](https://prismatic.io/docs/integrations/triggers/webhook.md) [The Universal Webhook trigger allows you to invoke a flow by making an HTTP request to the trigger's URL.](https://prismatic.io/docs/integrations/triggers/webhook.md) [**Example:** You want to invoke a customer's flow when the customer clicks a button in your app.](https://prismatic.io/docs/integrations/triggers/webhook.md) [Schedule](https://prismatic.io/docs/integrations/triggers/schedule.md) [Scheduled triggers allow you to create a regular schedule to dictate how often your integration should run.](https://prismatic.io/docs/integrations/triggers/schedule.md) [**Example:** A customer would like their data synced from your app to Salesforce each weekday at 8:00 AM.](https://prismatic.io/docs/integrations/triggers/schedule.md) [Management](https://prismatic.io/docs/integrations/triggers/management.md) [The management trigger allows you to invoke a flow as part of a setup or management task.](https://prismatic.io/docs/integrations/triggers/management.md) [**Example:** When a customer completes configuration of your Dropbox integration, run a flow that creates a set of directories in their Dropbox account.](https://prismatic.io/docs/integrations/triggers/management.md) [Cross Flow](https://prismatic.io/docs/integrations/triggers/cross-flow.md) [The cross flow trigger allows you to create flows that are designed to be invoked by other flows.](https://prismatic.io/docs/integrations/triggers/cross-flow.md) [**Example:** One instance fetches 10,000 records to process, and splits those records into 10 sets of 1000 records, sending each chunk to a sibling flow to be processed in parallel.](https://prismatic.io/docs/integrations/triggers/cross-flow.md) --- ##### App Event Triggers A flow with an **app event trigger** runs when some data changes in a third-party app. For example, you may want to be notified when an [Asana Project](https://prismatic.io/docs/components/asana.md#workspaceprojectstrigger) is created, updated, or deleted, or when a [PagerDuty Incident](https://prismatic.io/docs/components/pagerduty.md#incidentstrigger) occurs. ![Salesforce app event trigger in flow builder](/docs/assets/images/sfdc-9722442106acc2a1b306373d7b99550b.png) If the connector you're working with does not have a built-in app event trigger, you can leverage the [universal webhook trigger](https://prismatic.io/docs/integrations/triggers/universal-webhook.md) to receive event notifications from a third-party app or the [schedule trigger](https://prismatic.io/docs/integrations/triggers/schedule.md) to periodically check for updates from the third-party. #### What are app events?[​](#what-are-app-events "Direct link to What are app events?") You want your flows to run when data is updated in a third-party app. There are two ways to detect changes to data: 1. The app notifies you of a change via a [webhook](https://prismatic.io/docs/integrations/triggers/webhook.md) request. 2. You [poll](#app-event-triggers-with-polling) the app for changes on a regular cadence, like every few minutes. ##### App event triggers with webhooks[​](#app-event-triggers-with-webhooks "Direct link to App event triggers with webhooks") Some app event triggers receive update notifications from the third-party app via [webhook](https://prismatic.io/docs/integrations/triggers/webhook.md) request. You'll need to configure the third-party app to notify you when changes occur. This can be done in one of three ways: 1. Some triggers are built to configure webhooks in a third-party app when an instance of your integration is deployed and remove webhook configuration if the instance is deleted. Examples of those triggers include [Asana's Workplace Projects Trigger](https://prismatic.io/docs/components/asana.md#workspaceprojectstrigger) or [PagerDuty's Incidents Trigger](https://prismatic.io/docs/components/pagerduty.md#incidentstrigger). 2. Some triggers are built to receive webhook requests, but you'll need to build logic into a [deploy flow](https://prismatic.io/docs/integrations/triggers/management.md) that configures the third-party app to send requests to another flow. You can see an example of that in the [Gmail](https://prismatic.io/docs/components/google-gmail.md#receiving-notifications-from-gmail) connector. 3. Some apps do not allow you to configure webhooks programmatically. You'll need to [display the instance's webhook URLs](https://prismatic.io/docs/integrations/config-wizard/config-pages.md#displaying-webhook-information-in-the-configuration-wizard) in your config wizard and have your customer manually configure webhooks in the third-party app using these URLs. Or, you could use [polling triggers](https://prismatic.io/docs/integrations/triggers/app-events.md#app-event-triggers-with-polling) to poll for changes. If you are integrating with an app that sends all of your customers' webhook requests to a single endpoint, see [Single-Endpoint Webhook Integrations](https://prismatic.io/docs/integrations/triggers/single-endpoint-webhook-integrations.md). To build your own app event trigger in a custom connector that supports webhooks, see [instance deploy and delete events for triggers](https://prismatic.io/docs/custom-connectors/triggers.md#app-event-webhook-triggers). ###### Rate limiting app events[​](#rate-limiting-app-events "Direct link to Rate limiting app events") If you are receiving a high volume of webhook requests, you may need to implement rate limiting to avoid overwhelming your integration or downstream services. This can be done by queuing incoming requests and processing them at a controlled rate using [Flow Concurrency Management](https://prismatic.io/docs/integrations/triggers/fifo-queue.md). ##### App event triggers with polling[​](#app-event-triggers-with-polling "Direct link to App event triggers with polling") Some apps do not support webhooks, or webhook configuration is tedious to configure. Polling triggers are useful when you want to be notified when data changes in those apps. A polling trigger will poll an external API on a schedule that you set (for example, "every 5 minutes"), and if new data is available since the last time it polled, a full execution will run so your flow can process the data. Some sort of cursor is stored internally on the trigger. The cursor might be an ID of a record that was last seen or an "Updated At" timestamp. The cursor acts as a "bookmark" so the trigger knows what records to fetch the next time it runs. Use singleton executions to ensure you don't double-process data Like other [persisted data](https://prismatic.io/docs/integrations/persist-data.md), your trigger's cursor is loaded when an execution starts and is written out when the execution finishes. If your flow takes 10 minutes to run and you configure your trigger to run every 5 minutes, a second execution will fetch and process the same changes that the first fetched, since the first didn't finish before the second began. To prevent double-processing data, enable [singleton executions](https://prismatic.io/docs/integrations/triggers/schedule.md#ensuring-singleton-executions-for-scheduled-flows) on your polling trigger. Note that an execution runs each time a polling trigger looks for new data. If no new data is available, the execution immediately stops, and no additional steps run. If you navigate to an executions screen, you can filter executions that found no new data to process by clicking **Filter** and selecting **Exclude executions without trigger-detected changes**. Examples of connectors that implement polling triggers include [Dropbox](https://prismatic.io/docs/components/dropbox.md#pollchangestrigger) and [Google Drive](https://prismatic.io/docs/components/google-drive.md#pollchangestrigger). To build your own app event trigger in a custom connector that supports polling, see the [Custom Triggers](https://prismatic.io/docs/custom-connectors/triggers.md#app-event-polling-triggers) article. --- ##### Cross-Flow Trigger The cross-flow trigger allows one flow within an integration to invoke another flow in the same integration. This enables parallel processing, modular design, and reusable logic across your integration flows. #### Cross-Flow trigger overview[​](#cross-flow-trigger-overview "Direct link to Cross-Flow trigger overview") Sometimes, you may want to have one flow invoke an execution of another flow. Common examples include: * [Parallel processing](https://prismatic.io/docs/integrations/common-patterns/processing-data-in-parallel.md) of tens or thousands of records * Simplifying large and complex integrations into smaller chunks * Creating reusable logic that can be referenced by other flows For situations like these, we recommend using the [Cross-Flow](https://prismatic.io/docs/components/cross-flow.md) component's trigger and Invoke Flow action. Cross-flow executions are linked in Prismatic's API, and you can readily identify which flows called other flows. #### Adding a cross-flow trigger[​](#adding-a-cross-flow-trigger "Direct link to Adding a cross-flow trigger") To declare that a certain flow should be invoked by others, begin the flow with a [Cross-Flow Trigger](https://prismatic.io/docs/components/cross-flow.md#crossflow). ![Add a cross-flow trigger to an integration](/docs/assets/images/add-trigger-c39b70cbd5c2b883d839643f20627014.png) Note that cross-flow triggers can be invoked like regular [webhook triggers](https://prismatic.io/docs/integrations/triggers/webhook.md) through their webhook URL, but invoking the sibling flow through the [Invoke Flow](https://prismatic.io/docs/components/cross-flow.md#invokeflow) step ensures that the flows' executions are linked. ##### Synchronous and asynchronous cross-flow invocations[​](#synchronous-and-asynchronous-cross-flow-invocations "Direct link to Synchronous and asynchronous cross-flow invocations") When you configure a cross-flow trigger, you can select a **Response Type** of **Synchronous** or **Asynchronous**. * If you select **synchronous**, your sibling flow must complete its execution within 30 seconds. The flow calling its sibling will wait for a response before continuing its execution. * If you select **asynchronous**, your sibling flow can run for up to 15 minutes as usual. The flow calling its sibling will continue its execution without waiting for the sibling to finish. Full documentation on synchronous and asynchronous invocations is available [here](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md). #### Invoking a sibling flow[​](#invoking-a-sibling-flow "Direct link to Invoking a sibling flow") To invoke a sibling flow, add an [Invoke Flow](https://prismatic.io/docs/components/cross-flow.md#invokeflow) step to your integration. Select a sibling flow using the **Flow Name** input. If you would like to send data to the sibling flow, generate the data in one step - [Code](https://prismatic.io/docs/integrations/low-code-integration-designer/code-step.md) or [Create Object](https://prismatic.io/docs/components/collection-tools.md#createobject) actions are great for generating payloads - and then reference that data as the **Data** input. ![Configure an invoke flow step](/docs/assets/images/configure-action-2cec384694af3f9d08244adc9d8ead8c.png) The **data** that you send will be available to the sibling flow via the trigger's payload under `results.body.data`. Flows that invoke themselves Precautions in the Cross-Flow component are in place to prevent a flow from calling itself (as it is easy to create an unintended infinite loop or [fork bomb](https://en.wikipedia.org/wiki/Fork_bomb)). If you need a flow to call itself, please use caution. You can instruct a flow to call itself by adding an [HTTP](https://prismatic.io/docs/components/http.md) step to your integration that references your flow's trigger's `results.webhookUrls.Flow Name`. ##### Dynamically selecting a flow to invoke[​](#dynamically-selecting-a-flow-to-invoke "Direct link to Dynamically selecting a flow to invoke") Each **Invoke Flow** step must select a single flow to invoke. If you'd like to invoke other flows dynamically (i.e. conditionally sometimes invoke "Flow 1" and other times invoke "Flow 2"), add a [Branch](https://prismatic.io/docs/components/branch.md) step and use branching logic to determine which **Invoke Flow** step to run. ![Invoke other steps conditionally](/docs/assets/images/conditional-invocation-5f1c481268b9b0c1d447162d7714ca23.png) #### Tracing executions across flows[​](#tracing-executions-across-flows "Direct link to Tracing executions across flows") When the [Cross-Flow](https://prismatic.io/docs/components/cross-flow.md) component is used, executions that call one another are linked together within the Prismatic API. You can access an execution's `lineage`, which tells you two things: 1. Which execution invoked this execution? 2. Did this execution invoke any others? ![Query for an execution](/docs/assets/images/lineage-query-a004e91c444590cc96c71a5b42318c52.png) Within the integration designer, you can click **View linked executions** beside an execution to see its lineage. In this example, our parent execution invoked three children. One of the children invoked its own child execution. ![Linked executions in the designer](/docs/assets/images/linked-executions-designer-5de1a107fe955dd7a6814429a6194cbd.png) You can see linked executions within an instance's **Executions** tab as well, which can help when debugging an integration that calls sibling flows. ![Linked executions in an instance screen](/docs/assets/images/linked-executions-instance-8250a66c2b3f166a3326ccdfc1fc576f.png) #### Using cross-flow triggers in code-native[​](#using-cross-flow-triggers-in-code-native "Direct link to Using cross-flow triggers in code-native") If you're building a [code-native integration](https://prismatic.io/docs/integrations/code-native.md), you can use the `context.invokeFlow` function that the cross-flow component wraps in your own code. In this example, "Parent Flow" pulls down 100 records from the JSON Placeholder API. It breaks those records into 5 chunks of 20 and sends those chunks to "Capitalize Titles" using `context.invokeFlow`. "Capitalize Titles" is configured to be synchronous, so "Parent Flow" waits for each invocation of its sibling to complete before continuing. It accumulates the results returned from the sibling flow. ```typescript import axios from "axios"; import { flow } from "@prismatic-io/spectral"; interface Post { userId: number; id: number; title: string; body: string; } const CHUNK_SIZE = 20; export const parentFlow = flow({ name: "Parent Flow", stableKey: "parent-flow", description: "Parent flow that invokes its sibling flow", onExecution: async (context) => { // Fetch 100 posts from JSON Placeholder API const { data: posts } = await axios.get( "https://jsonplaceholder.typicode.com/posts", ); const processedTitles = []; // Send posts to sibling flow to be capitalized, 20 at a time for (let i = 0; i < posts.length; i += CHUNK_SIZE) { // Get chunk of 20 posts const chunk = posts.slice(i, i + CHUNK_SIZE); // Invoke sibling flow with chunk of posts const siblingFlowResponse = await context.invokeFlow( "Capitalize Titles", { posts: chunk }, ); // Response was synchronous; append returned capitalized titles // to accumulator processedTitles.push(...siblingFlowResponse.data); } return { data: processedTitles }; }, }); // This flow is contrived, but simulates "work" to be done by a sibling flow export const capitalizeTitles = flow({ name: "Capitalize Titles", stableKey: "capitalize-titles", description: "Capitalize titles of all posts it receives", isSynchronous: true, onExecution: async (context, params) => { const { posts } = params.onTrigger.results.body.data as { posts: Post[] }; const titlesCapitalized = posts.map((post) => post.title.toUpperCase()); // Synchronously return titles of posts, capitalized. return Promise.resolve({ data: titlesCapitalized }); }, }); export default [parentFlow, capitalizeTitles]; ``` --- ##### Endpoint Configuration Webhook triggers can be configured for an integration in one of three ways, depending on your needs: * **Instance and Flow Specific**: Each flow on each instance gets its own unique endpoint. This is the default configuration. * **Instance Specific**: Each instance gets a unique endpoint, and the integration determines which flow to run based on header or payload data. This is useful when each customer can configure their own webhook endpoint, but all webhook events are sent to the same endpoint. You can route a "Create Widget" request to a "Create Widget" flow and an "Update Gadget" request to an "Update Gadget" flow. * **Shared**: All customers' instances of the integration share an endpoint. Data in the header or payload determines which customer and flow should run. This is useful when you are only allowed to configure a single webhook URL in a third-party app for all of your customers' webhook events. When deciding on a webhook endpoint configuration, ask yourself two questions: 1. Do my webhook endpoints need to be the same for each of my customers, or can customers be configured to use different webhook endpoints? 2. If my customers can have unique webhook endpoints, can webhooks be configured to send data to unique endpoints depending on what activity they're responding to? For example, if a third-party app invokes an integration when it has an inventory update **or** when a new order is created, can those two activities be configured to invoke distinct webhook endpoints? Once you have answers to those questions, you can choose the appropriate webhook endpoint configuration. Shared Endpoint Configuration vs Single-Endpoint Webhooks The **shared** endpoint configuration is useful when the third-party app you're integrating with sends webhook requests one at a time (i.e. one request is for customer A, the next request is for customer B, etc.). Your preprocess flow can determine which customer to dispatch the request to. If the third-party app sends requests in bulk (i.e. one request is an array of updates, with a few for Customer A and a few for Customer B), you should reach for a "router" integration to break down and route all requests to their respective destinations. See [Single-Endpoint Webhooks](https://prismatic.io/docs/integrations/triggers/single-endpoint-webhook-integrations.md). #### Selecting endpoint configuration[​](#selecting-endpoint-configuration "Direct link to Selecting endpoint configuration") * Low-Code * Code-Native To configure your integration's endpoint settings, click the **Endpoint Configuration** button on the top of the integration designer. From the **Endpoint Type** tab, select one of the three endpoint types listed above. ![Select endpoint type in Prismatic app](/docs/assets/images/select-endpoint-type-115e0918b0c66c3cd59f66489da82d51.png) Depending on what endpoint type you choose, you will be presented with different configuration options (described next). By default, instances of integrations that you deploy will be assigned unique webhook URLs - one URL for each flow. We call this **Instance and Flow Specific** endpoint configuration. Alternatively, you can choose **Instance Specific** endpoint configuration (each instance gets its own webhook URL and all flows share the single URL) or **Shared** endpoint configuration, where all flows of all instances share one URL. To specify endpoint type, add an `endpointType` property to the `integration()` definition in `src/index.ts`. It can have values `"instance_specific"`, `"flow_specific"`, or `"shared_instance"` and defaults to `"flow_specific"`: ```typescript import { integration } from "@prismatic-io/spectral"; import flows from "./flows"; import { configPages } from "./configPages"; export default integration({ name: "shared-endpoint-example", description: "Shared Endpoint Example", iconPath: "icon.png", flows, configPages, componentRegistry, endpointType: "instance_specific", }); ``` When **Instance Specific** or **Shared** endpoint configuration is selected, you need some logic to determine which flow (and which customer's instance in the case of **Shared**) should be run. This can be done with or without a [preprocess flow](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md#instance-specific-endpoint-with-a-preprocess-flow), and both methods are described below. #### Instance and flow-specific endpoint configuration[​](#instance-and-flow-specific-endpoint-configuration "Direct link to Instance and flow-specific endpoint configuration") This is the *default* configuration. When an instance is deployed to a customer, each flow within the instance is assigned its own webhook endpoint. Customer A's "Update Inventory" flow has a unique endpoint that is different from Customer A's "Process Order" flow endpoint and different from Customer B's "Update Inventory" flow endpoint. Integrations that use this endpoint configuration often set up webhooks with a [deploy trigger](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger) and remove them with an [instance remove](https://prismatic.io/docs/components/management-triggers.md#instanceremove) trigger. #### Instance-specific endpoint configuration[​](#instance-specific-endpoint-configuration "Direct link to Instance-specific endpoint configuration") When an instance is deployed to a customer, that instance is assigned a single webhook endpoint. The flows that comprise the instance all share that endpoint. Each customer's instance has a unique endpoint, so Customer A's instance of the "Acme ERP" integration will have one endpoint, and Customer B's instance of the same integration will have a different endpoint. **If flows share an endpoint, which flow is executed?** Since several flows share an endpoint URL, you need a way to determine which flow should run when data is sent to your endpoint. You can determine which flow to run in two ways: 1. **Without a preprocess flow**. You can send the name of the flow that should run as an HTTP header or as a value in the HTTP payload. 2. **With a preprocess flow**. You can designate one flow of your integration to be a **preprocess** flow - that flow will determine which sibling flow should run. ##### Instance-specific endpoint without a preprocess flow[​](#instance-specific-endpoint-without-a-preprocess-flow "Direct link to Instance-specific endpoint without a preprocess flow") If you do not use a preprocess flow, you can send the name of the flow to run as an HTTP header or as a value in the HTTP payload. ###### Flow name from HTTP payload[​](#flow-name-from-http-payload "Direct link to Flow name from HTTP payload") For example, you could send the name of the flow you'd like to execute as part of your payload like this: Determine flow name from HTTP payload ```bash curl https://hooks.prismatic.io/trigger/EXAMPLE== \ --header "Content-Type: application/json" \ --data '{"myFlowName":"Update Inventory","item":"widgets","quantity":5,"state":"removed"}' ``` Within the **Endpoint Configuration** drawer, you could choose to reference `results.body.data.myFlowName` to determine which flow to run: ![Set flow name from HTTP payload for Endpoint Configuration in Prismatic app](/docs/assets/images/flow-name-from-payload-fd82f641c3501b64218483c87cb37aa9.png) Given the `curl` invocation above, the `Update Inventory` flow would be run with the rest of the payload that was provided. Run a test of endpoint configuration In order to populate the result picker in the screenshot above, click open the testing drawer's **Test Configuration** tab and then select **Endpoint Payload**. Enter a sample payload and click the globe icon to the right of the **Run** button. ###### Flow name from HTTP header[​](#flow-name-from-http-header "Direct link to Flow name from HTTP header") If you'd like to pass flow name as an HTTP header instead, a `curl` invocation could look like this: Determine flow name from an HTTP header ```bash curl https://hooks.prismatic.io/trigger/EXAMPLE== \ --location \ --header "Content-Type: application/json" \ --header "myflowname: Update Inventory" \ --data '{"item":"widgets","quantity":5,"state":"removed"}' ``` In that case, you would reference `results.headers.myflowname` to determine which flow to run: ![Set flow name from HTTP header for Endpoint Configuration in Prismatic app](/docs/assets/images/flow-name-from-header-b20986df09dcabf9eee14f2a2c83610a.png) Use lower-case HTTP header keys Per [HTTP RFC 2616](https://datatracker.ietf.org/doc/html/rfc2616#section-4.2), HTTP headers should be case-insensitive. We've found HTTP clients to be inconsistent about their behavior and implementations, though. [Postman](https://www.postman.com/), for example, will send camel-cased headers, while others will always lowercase header keys. We recommend that you use lowercase HTTP header keys to avoid compatibility issues. ##### Instance-specific endpoint with a preprocess flow[​](#instance-specific-endpoint-with-a-preprocess-flow "Direct link to Instance-specific endpoint with a preprocess flow") If you need additional logic to determine which flow to run (for example, if you need to inspect an XML payload's shape to determine what kind of data was received), you can leverage a **preprocess flow**. This flow executes when data is sent to the instance's endpoint. It can be comprised of any number of steps, and the last step's results determine which sibling flow to execute. To configure a preprocess flow, first build a flow that can inspect an incoming payload and verify that the last step returns the name of the flow that you'd like to run next. Then, open the **Endpoint Configuration** drawer, select your preprocess flow from the **Preprocess Flow** dropdown menu, and under **Flow Name** select the key representing the name of the flow that should run: ![Set flow name from preprocess flow for Endpoint Configuration in Prismatic app](/docs/assets/images/flow-name-from-preprocess-flow-550cd5450f00b7c0e53efe0c6b6d99ec.png) You may need to run a test of your preprocess flow in order to populate the result picker in the **Endpoint Configuration** drawer. #### Shared endpoint configuration[​](#shared-endpoint-configuration "Direct link to Shared endpoint configuration") All customers that have an instance of a particular integration deployed to them share a webhook endpoint, and data is routed to the proper customer and flow based on data contained in the HTTP request. Like [Instance-Specific Endpoint Configuration](#instance-specific-endpoint-configuration), **Shared Endpoint Configuration** can be configured with or without a **preprocess flow**. ##### Shared endpoint without a preprocess flow[​](#shared-endpoint-without-a-preprocess-flow "Direct link to Shared endpoint without a preprocess flow") If you do not use a preprocess flow, the shared endpoint's webhook invocation must include an [external customer ID](https://prismatic.io/docs/customers/managing-customers.md#customer-external-ids) and **flow name** either in the HTTP payload or as HTTP headers. You can mix-and-match if you'd like - provide one value as an HTTP header and the other in the HTTP payload. For example, if "Customer A" had an external ID of `abc-123` and you wanted to invoke their `Update Inventory` flow, you could send this `curl` request with the flow name represented as an HTTP header and customer ID represented in the HTTP payload: Routing a request by header and payload ```bash curl https://hooks.prismatic.io/trigger/EXAMPLE== \ --location \ --header "Content-Type: application/json" \ --header "myflowname: Update Inventory" \ --data '{"myCustomerId":"abc-123","item":"widgets","quantity":5,"state":"removed"}' ``` ![Set flow name from HTTP payload or header for shared endpoint configuration in Prismatic app](/docs/assets/images/flow-name-and-customer-id-1527d8707eae0e119df1f79ffa6abc2e.png) Flow name is not required for single-flow Integrations If your integration is comprised of just a single flow, then you only need to specify an external customer ID and not a flow name. ##### Shared endpoint with a preprocess flow[​](#shared-endpoint-with-a-preprocess-flow "Direct link to Shared endpoint with a preprocess flow") If you need additional logic to determine which flow to run or need to look up a customer's external ID, you should leverage a **preprocess flow**. This flow executes when data is sent to a shared endpoint. It can be comprised of any number of steps, and the last step's results determine which flow to execute for which customer. The final step must return an object containing both an [external customer ID](https://prismatic.io/docs/customers/managing-customers.md#customer-external-ids) and a **flow name**. ![Set flow name with preprocess flow for shared endpoint configuration in Prismatic app](/docs/assets/images/flow-name-and-customer-id-from-preprocess-flow-54098e4f85100d89bfab7374d093a1a9.png) Shared endpoint preprocess flows cannot reference config variables When you use a shared endpoint, the preprocess flow runs without knowing yet what customer the data is destined for. It runs as a "system" instance and is not bound to any particular customer. Because of this, a preprocess flow cannot reference any customer-specific config variables or connections. ##### Shared endpoint config and versioning[​](#shared-endpoint-config-and-versioning "Direct link to Shared endpoint config and versioning") An integration can have [multiple versions](https://prismatic.io/docs/integrations/low-code-integration-designer.md#publishing-an-integration), and customers' instances can be on different versions. The endpoint configuration can change between versions, but a shared endpoint exists outside of a specific instance. So, **which version's endpoint configuration is used?** The answer is *the latest version that is currently deployed to a customer*. If your integration currently has three available published versions: 4, 5 and 6, and some of your customers are on version 4 and some are on version 5, then the endpoint configuration on version 5 is used for the shared endpoint for all customers. If another instance is then deployed using version 6, then the endpoint configuration for version 6 is used for all customers. If that single instance of version 6 is removed, leaving just versions 4 and 5 deployed, the endpoint configuration for version 5 will be used for all customers. #### Testing endpoint configuration[​](#testing-endpoint-configuration "Direct link to Testing endpoint configuration") You can test each of your flows individually (including a **preprocess flow**, if applicable) by clicking the **Run** button on the bottom of the integration designer in the testing drawer. If you would like to test your endpoint configuration, click the globe icon to the right of **Run**. To configure a test payload, open the testing drawer, select **Test Configuration**, and then select **Endpoint payload**. You can enter the payload and any headers that you would like to send to the shared endpoint. ![Test endpoint configuration in Prismatic app](/docs/assets/images/test-endpoint-config-payload-8ddc1f90f2b905d1e2777d647c2766bb.png) Within the **Logs** and **Step Outputs** tabs you will see logs and step results for both the preprocess flow (if you have one), and the flow that the request was routed to. If an error is thrown (for example, the flow name that the preprocess flow generated was not found), that error will appear in the **Logs** tab. Invocations from the integration designer are always dispatched to a test customer The integration designer is a sandbox. No test invocations will go to your customers' instances. Instead, if you use [Shared Endpoint Configuration](#shared-endpoint-configuration) (where all customers' instances share an endpoint), the execution will always be dispatched to a "test customer" within the integration designer. So, you can reference any external customer ID and the endpoint configuration test will be routed to the "test customer." #### Securing endpoints with API keys[​](#securing-endpoints-with-api-keys "Direct link to Securing endpoints with API keys") Endpoints can be configured to only run when an API key is included with the webhook request as an HTTP header. You can elect to use API keys for all flows, or only for specific flows. * Low-Code * Code-Native To configure instances of your integration to use API keys, open the **Endpoint Configuration** drawer in the integration designer, and select the **Security Type** for each of your flows. You have three options: * **No API Keys** indicates that the flow can be invoked without an API key. This option is often paired with a trigger that handles security in another way (like with [HMAC](https://prismatic.io/docs/integrations/triggers/webhook/what-is-hmac.md)). * **Secured by Customer** allows a customer to generate API keys for an endpoint when they deploy an instance of your integration. The API keys can either be generated automatically, or the customer can provide their own. If you select **Required**, your customer will be required to provide one or more API keys when they deploy an instance of your integration. * **Secured by Organization** gives you the option to set an API key that will be used by all customers' instances for that endpoint. ![Endpoint configuration security options in Prismatic app](/docs/assets/images/endpoint-config-drawer-security-6caab4c58c7f318e1de436f63de8192a.png) To configure instances of your integration to use API keys, specify an `endpointSecurityType`. You have four options: * `unsecured` - do not use API keys. Secure webhooks some other way (like with [HMAC](https://prismatic.io/docs/integrations/triggers/webhook/what-is-hmac.md)). * `customer_optional` - customers can choose to secure endpoints with API keys when they deploy an instance of your integration. * `customer_required` - customers are required to supply API keys. * `organization` - you specify the API keys that will be used. If you specify `"organization"`, also provide a string array of API keys for the `organizationApiKeys` property. Secure a flow with API keys ```ts export const flow1 = flow({ name: "Flow 1", stableKey: "9499d1d8-dddd-4d9b-aaff-c054f59d02cc", description: "This is the first flow", isSynchronous: true, endpointSecurityType: "organization", organizationApiKeys: ["my-first-key", "p@s$W0Rd"], onExecution: async (context, params) => { return { data: null }; }, }); ``` ##### Endpoint API keys in the config wizard[​](#endpoint-api-keys-in-the-config-wizard "Direct link to Endpoint API keys in the config wizard") Endpoints marked **Secured by Customer** will appear on the first page of the config wizard when a customer deploys an instance of your integration. ![Endpoint configuration in config wizard in Prismatic app](/docs/assets/images/endpoint-config-in-config-wizard-8dc9d22a2d3583ea28fa62cba8a65961.png) Your customers are required to provide API keys if you selected **Required**, and can optionally provide API keys if you didn't. An endpoint can have multiple API keys. If part of your configuration experience involves showing the customer the endpoint URL and API key, you can add a **Trigger Details** section to your config wizard by clicking **+Text/Image** and then selecting **Trigger Details**. ![Add trigger details to config wizard in Prismatic app](/docs/assets/images/add-trigger-details-1b4f1caa723b7a2c6855f719fda306c0.png) You can add additional headings, helper text, and images to assist your customers as they configure webhooks in third-party apps. ![Trigger details in config wizard in Prismatic app](/docs/assets/images/trigger-details-in-config-wizard-4e88cc110a395f7173801abe98b1340e.png) ##### Sending requests to an endpoint secured with an API key[​](#sending-requests-to-an-endpoint-secured-with-an-api-key "Direct link to Sending requests to an endpoint secured with an API key") If your instance's flow has an API key, pass in an additional `Api-Key` header as part of your POST request: ```bash curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --location \ --header "Content-Type: application/json" \ --header "Api-Key: 5cc74e1546382c52a8e93dce6795a5d4" \ --data '{"examplePayloadKey": "examplePayloadValue"}' ``` #### Troubleshooting shared endpoints in production[​](#troubleshooting-shared-endpoints-in-production "Direct link to Troubleshooting shared endpoints in production") If you have an integration with [Instance-Specific Endpoint Configuration](#instance-specific-endpoint-configuration), then all logs and execution records will appear in the instance's execution results page. Executions that that run through a [preprocess flow](#instance-specific-endpoint-with-a-preprocess-flow) that then trigger a sibling flow are packaged together as one execution in the executions page. If your instance's preprocess flow throws an error or yields the name of a flow that doesn't exist, you can see those errors and step results from that page. If you have an integration with [Shared Endpoint Configuration](#shared-endpoint-configuration), then the preprocess flow runs before it knows what customer it will dispatch the work to and is not tied to a specific instance. --- ##### Flow Concurrency & FIFO By default, Prismatic runs executions concurrently, processing multiple webhook invocations of the same flow at the same time. This is great for performance - hundreds of webhook requests can all be processed in parallel, but it can lead to out-of-order processing or rate limiting issues. To mitigate concurrency problems, you can configure a flow to run only a hand-full of executions at a time, queuing up additional requests until prior executions complete. #### Configuring flow concurrency[​](#configuring-flow-concurrency "Direct link to Configuring flow concurrency") To configure flow concurrency, select your flow's trigger and open the **Flow control** tab. Here, you have three options: 1. **Parallel** (default): All incoming requests are processed concurrently. There is no limit to the number of concurrent executions (except for global organization [limits](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md#webhook-rate-limiting-and-concurrent-executions)). 2. **Throttled**: Incoming requests are processed concurrently, but only up to a specified limit. Additional requests wait in a queue until an execution slot becomes available. 3. **Sequential (FIFO)**: First-In, First-Out processing. Requests are processed one at a time in the order they are received. Additional requests wait in a queue until the current execution completes. This is the same as setting **Throttled** with a concurrency limit of `1`. ![Configuring flow concurrency](/docs/assets/images/flow-concurrency-config-9133179679fd7f0409cb7a07874a6dba.png) **Note**: This feature is available for webhook-based [app event triggers](https://prismatic.io/docs/integrations/triggers/app-events.md) and generic [webhook triggers](https://prismatic.io/docs/integrations/triggers/webhook.md). It is not available for [management](https://prismatic.io/docs/integrations/triggers/management.md) or [pre-process flows](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md#instance-specific-endpoint-with-a-preprocess-flow). Additionally, flows that have FIFO enabled *must* be [asynchronous](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md). For [app event polling](https://prismatic.io/docs/integrations/triggers/app-events.md#app-event-triggers-with-polling), or [scheduled](https://prismatic.io/docs/integrations/triggers/schedule.md) triggers, see [singleton executions](https://prismatic.io/docs/integrations/triggers/schedule.md#ensuring-singleton-executions-for-scheduled-flows). #### First In, First Out (FIFO) queues[​](#first-in-first-out-fifo-queues "Direct link to First In, First Out (FIFO) queues") When **First In, First Out** (FIFO) is enabled, requests are processed one at a time in the order they are received. If your flow is already processing a request when a new request arrives, the new request is placed in a queue until the flow is ready to process it. This is helpful if it's important that requests are processed in the order they are received (e.g., financial transactions). #### Throttled concurrency[​](#throttled-concurrency "Direct link to Throttled concurrency") When **Throttled** concurrency is enabled, you can specify the maximum number of concurrent executions allowed for your flow (between 2 and 15). This works similarly to FIFO queuing, but instead of processing requests one at a time, multiple executions can run concurrently up to your specified limit. Additional requests that arrive while the concurrency limit is reached are placed in a queue and processed as soon as execution slots become available. This is useful when you need to control the load on downstream systems while still maintaining reasonable throughput. This is helpful in scenarios such as: * If your integration is sensitive to the load it places on downstream systems (e.g., third-party rate limits). * If you expect to encounter [execution rate limits](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md#webhook-rate-limiting-and-concurrent-executions) in Prismatic. #### Message deduplication[​](#message-deduplication "Direct link to Message deduplication") Many applications ensure "at least once" delivery of outbound webhook requests, which can result in duplicate events being processed. To prevent processing duplicate requests, you can implement message deduplication strategies in your FIFO-enabled flows. To enable automatic deduplication of messages, specify a **Deduplication ID** in your trigger's **Flow control** configuration. For example, if a third-party sends a header called `x-acme-webhook-id`, you can use that value as the Deduplication ID. If two requests with the same `x-acme-webhook-id` header are received within a 10-minute window, the second request will be considered a duplicate and will be ignored. ![Screenshot of message deduplication configuration](/docs/assets/images/message-deduplication-b0687c981c57e7f7d2db4e63801b4ab1.png) #### Enabling flow concurrency management in code-native integrations[​](#enabling-flow-concurrency-management-in-code-native-integrations "Direct link to Enabling flow concurrency management in code-native integrations") You can add a `queueConfig` property to your flow definition to enable flow concurrency management in code-native integrations. Set throttled concurrency to 5 ```ts export const fiveConcurrentTwo = flow({ queueConfig: { concurrencyLimit: 5, }, }); ``` The `queueConfig` property supports the following options: * `concurrencyLimit`: The maximum number of concurrent executions allowed for the flow. Values of 2 through 15 can be specified. * `usesFifoQueue`: A boolean that, when set to `true`, enables FIFO processing with a concurrency limit of 1. * `dedupeIdField`: An optional string that specifies the field to use for message deduplication. This field should contain a unique identifier for each incoming request. * `singletonExecutions`: A boolean that, when set to `true`, ensures that a [scheduled trigger](https://prismatic.io/docs/integrations/triggers/schedule.md) flow only has one execution running at a time. ##### FIFO queues in code-native integrations[​](#fifo-queues-in-code-native-integrations "Direct link to FIFO queues in code-native integrations") FIFO queues can be enabled in code-native integrations' flows by adding a `queueConfig` property to your flow. `usesFifoQueue` must be set to `true` to enable FIFO. You can optionally specify a `dedupeIdField` to prevent [message duplication](#message-deduplication). ```ts export const listItems = flow({ name: "List Items", stableKey: "abc-123", description: "Fetch items from an API", queueConfig: { usesFifoQueue: true, dedupeIdField: "body.data.webhook-id", }, onTrigger: () => {}, onExecution: () => {}, }); ``` The above example assumes that the body of the incoming webhook request contains a field called `webhook-id` that uniquely identifies the event. To reference a header (for example, one named `x-acme-webhook-id`), you can use the following syntax: ```ts dedupeIdField: "headers.x-acme-webhook-id", ``` #### Managing flow concurrency programmatically[​](#managing-flow-concurrency-programmatically "Direct link to Managing flow concurrency programmatically") You can programmatically monitor and manage FIFO queues using Prismatic's GraphQL API. This is useful for monitoring or clearing queues. ##### Getting flow config IDs[​](#getting-flow-config-ids "Direct link to Getting flow config IDs") To manage a FIFO queue, you first need the flow config ID. Every integration has one or more flows. A deployed instance has one or more corresponding flowConfig. You can query an [instance](https://prismatic.io/docs/api/schema/queries.md#instance) to get its flow configurations: ```graphql query { instance(id: "SW5example-instance-id") { id name flowConfigs { nodes { id flow { name } } } } } ``` This returns all flow configurations for the instance, including their IDs and flow names. ##### Checking queue statistics[​](#checking-queue-statistics "Direct link to Checking queue statistics") Use the [fifoQueueStats](https://prismatic.io/docs/api/schema/queries.md#fifoqueuestats) query to get information about a specific queue: ```graphql query { fifoQueueStats(flowConfigId: "SW5example-flow-config-id") { flowConfigId queueLength workingSetSize workingSetItems } } ``` **Return fields:** | Field | Type | Description | | ----------------- | ------ | ---------------------------------------------- | | `flowConfigId` | `ID` | The flow config global ID | | `queueLength` | `Int` | Number of items waiting in the queue | | `workingSetSize` | `Int` | Number of items currently being processed | | `workingSetItems` | `[ID]` | Execution global IDs currently being processed | The `queueLength` field tells you how many executions are waiting to be processed. This is useful for monitoring queue buildup and identifying potential processing bottlenecks. ##### Clearing queued items[​](#clearing-queued-items "Direct link to Clearing queued items") Before clearing queued items, you must first disable the instance using the [updateInstance](https://prismatic.io/docs/api/schema/mutations.md#updateinstance) mutation: ```graphql mutation { updateInstance(input: { id: "SW5example-instance-id", enabled: false }) { instance { id enabled } errors { field messages } } } ``` Once the instance is disabled, you can clear queued items. After clearing, you can re-enable the instance by setting `enabled: true`. ###### Clearing all queued items[​](#clearing-all-queued-items "Direct link to Clearing all queued items") To clear all queued items for a flow config, use the [clearAllFifoData](https://prismatic.io/docs/api/schema/mutations.md#clearallfifodata) mutation: ```graphql mutation { clearAllFifoData(input: { id: "SW5example-flow-config-id" }) { result { message } errors { field messages } } } ``` This removes all pending executions from the queue. Any executions currently being processed will continue to completion. **Important**: This operation cannot be undone. Cleared executions are permanently removed and will not be processed. ###### Removing a specific number of items[​](#removing-a-specific-number-of-items "Direct link to Removing a specific number of items") To remove a specific number of items from the front of the queue, use the [removeFifoQueueItems](https://prismatic.io/docs/api/schema/mutations.md#removefifoqueueitems) mutation: ```graphql mutation { removeFifoQueueItems( input: { id: "SW5example-flow-config-id", itemCount: 10 } ) { result { message } errors { field messages } } } ``` The `itemCount` parameter specifies how many items to remove (between 1 and 100). Items are removed from the front of the queue in FIFO order. This is useful when you need to: * Reduce queue backlog without clearing everything * Remove a known set of problematic requests * Gradually drain the queue while monitoring system health ###### Clearing the working set[​](#clearing-the-working-set "Direct link to Clearing the working set") To remove actively running executions from the queue, use the [clearFifoWorkingSet](https://prismatic.io/docs/api/schema/mutations.md#clearfifoworkingset) mutation: ```graphql mutation { clearFifoWorkingSet(input: { id: "SW5example-flow-config-id" }) { result { message } errors { field messages } } } ``` This removes currently running executions from the working set, allowing other queued items to begin processing. The running executions themselves will still complete, but they are no longer blocking the queue. This is useful when you have a long-running execution that is preventing other queued items from processing and you want to allow the queue to continue without waiting for the current execution to finish. #### Flow concurrency management FAQ[​](#flow-concurrency-management-faq "Direct link to Flow concurrency management FAQ") ##### Where can I see my queued requests?[​](#where-can-i-see-my-queued-requests "Direct link to Where can I see my queued requests?") Queued requests will appear alongside your other executions. In the integration designer if you observe a queued execution's logs, you will see a message like `Queuing Execution for Instance 'Salesforce'. Total Queued Executions: 4`. ![Queued logs](/docs/assets/images/queued-logs-e3668e77938e2161392a7121fcfad910.png) Make sure that you toggle **Logs** on. When the queued execution is processed, you will see a message like `Resuming Queued Execution for Instance Salesforce'. Total Queued Executions: 3. Total Concurrent Executions: 15` in the logs. The `15` there represents all concurrent executions of all flows. ![Resumed logs ](/docs/assets/images/resumed-logs-b5630da456ad84a8c62d3aeded26fba8.png) In an instance's **Executions** tab, queued executions will also appear alongside running executions. ![Queued instance executions](/docs/assets/images/queued-instance-executions-5dca02633244989cad4412750ddbb814.png) ##### Why can't flow concurrency be enabled for a synchronous flow?[​](#why-cant-flow-concurrency-be-enabled-for-a-synchronous-flow "Direct link to Why can't flow concurrency be enabled for a synchronous flow?") The goal of a synchronous flow is to process requests in real time, providing immediate feedback to the caller. Queueing the request and processing it later would defeat that purpose. ##### What happens to my flow concurrency queue if my instance is paused?[​](#what-happens-to-my-flow-concurrency-queue-if-my-instance-is-paused "Direct link to What happens to my flow concurrency queue if my instance is paused?") No new executions will queue, but any existing executions that were queued will resume after the instance is enabled again. ##### What happens to my flow concurrency queue if my instance is deleted?[​](#what-happens-to-my-flow-concurrency-queue-if-my-instance-is-deleted "Direct link to What happens to my flow concurrency queue if my instance is deleted?") All queued executions will be permanently removed and cannot be recovered. ##### What happens if I enable flow retry with flow concurrency?[​](#what-happens-if-i-enable-flow-retry-with-flow-concurrency "Direct link to What happens if I enable flow retry with flow concurrency?") If you enable [flow retry](https://prismatic.io/docs/monitor-instances/retry-and-replay/automatic-retry.md), failed executions will be retried in the order they were received, preserving the FIFO semantics. For example, if an execution fails and your flow is configured to retry up to 3 times, waiting 2 minutes between failures, the failed execution will be retried after 2 minutes, then again after 4 minutes, and finally after 6 minutes (assuming it continues to fail). During that time, no other executions will be processed from the queue. If an execution ultimately fails after all retries, it will be marked as failed and the next execution in the queue will be processed. ##### How many executions can be queued?[​](#how-many-executions-can-be-queued "Direct link to How many executions can be queued?") There is no current hard limit on the number of executions that can be queued, but keep in mind that excessive queuing will result in delayed processing of new requests. --- ##### Listening Mode **Listening mode** allows you to test webhook-based triggers in the integration designer (or [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md)) by temporarily setting up webhooks and capturing incoming webhook requests. When you enter listening mode, webhooks are created in the third-party application your integration interacts with, and when you leave listening mode, those webhooks are removed. #### Entering listening mode[​](#entering-listening-mode "Direct link to Entering listening mode") After configuring a webhook-based [app trigger](https://prismatic.io/docs/integrations/triggers/app-events.md) or [generic webhook trigger](https://prismatic.io/docs/integrations/triggers/webhook.md), you can enter **listening mode** by opening the trigger's configuration drawer in the integration designer, selecting the **Test** tab, and clicking **Enter Listening Mode**. ![Trigger test tab in integration designer](/docs/assets/images/trigger-test-tab-ec5bd4d9486b535eda65eb781457cb27.png) Opening listening mode causes the trigger's `webhookLifecycleHandlers.create` function to run (if defined - see [instance lifecycle](https://prismatic.io/docs/integrations/lifecycle.md)). This function can be used to set up webhooks in third-party applications that your integration interacts with, and sets up webhooks temporarily for testing purposes. If your trigger does not define a `webhookLifecycleHandlers.create` function, you will need to manually configure the third-party application to send webhook requests to your flow's test URL. You can view the **Test URL** in the listening mode interface. Once in listening mode, the trigger will listen for incoming webhook requests. From the third-party application, you can perform actions that cause webhook requests to be sent to your flow's test URL. In the example here, we marked a task as complete in Asana, which caused Asana to send a webhook request to our flow's test URL. We can see the captured webhook request in the listening mode interface and save the payload for later use in testing our flow. ![Listening mode interface in integration designer](/docs/assets/images/listening-mode-3dda18d67edf4820b37af8d1c7932882.png) When you are finished testing, close the listening mode interface. This will cause the trigger's `webhookLifecycleHandlers.delete` function to run (if defined), which can be used to clean up any webhooks that were created when entering listening mode. **Note:** While in listening mode, executions triggered by incoming webhook requests will not run the rest of your flow. This allows you to focus on testing and capturing webhook requests without triggering any actions in your flow. Testing webhooks outside of listening mode If you would like to keep the webhooks active after exiting listening mode (for example, to see how your integration handles a stream of live data), open the **Advanced Testing** section in the **Test** drawer and select **Create Webhook**. That will cause your trigger's `webhookLifecycleHandlers.create` function to run without entering listening mode, allowing you to keep the webhooks active. All webhook requests your flow receives will cause a normal execution. You can select **Teardown Webhook** in the same section to disable the webhook when you are finished testing. #### Using captured webhook data for testing[​](#using-captured-webhook-data-for-testing "Direct link to Using captured webhook data for testing") Any webhook requests captured while in listening mode can be saved and used later for testing your flow. From your trigger's configuration drawer, navigate to the **Test** tab and select one of your **Saved Payloads**. You can click the play button to immediately run your flow using the saved payload, or you can copy the `curl` command to send the payload to your flow's test URL from your terminal. ![Saved payloads in trigger test tab](/docs/assets/images/saved-payloads-140619581f40132eab65dd3300f6fd72.png) Alternatively, when you click the green **Run** button in the integration designer (or embedded workflow builder), you can select one of your saved payloads to use as the trigger data for that test run. ![Run integration with saved payload](/docs/assets/images/run-with-saved-payload-935da1f4ca1c9167391b4f814a02f4e2.png) #### Listening mode for code-native integrations[​](#listening-mode-for-code-native-integrations "Direct link to Listening mode for code-native integrations") If you are building a code-native integration, you can enter listening mode using the [Prism CLI](https://prismatic.io/docs/integrations/code-native/testing.md#listening-for-webhook-invocations-from-the-cli). --- ##### Management Triggers Management triggers allow you to run specific flows when lifecycle events occur for your instances. You can execute setup tasks when an instance is deployed and cleanup tasks when it's removed. #### Instance deploy trigger[​](#instance-deploy-trigger "Direct link to Instance deploy trigger") An integration flow can be configured to run when an instance of the integration is [deployed](https://prismatic.io/docs/components/management-triggers.md#instancedeploy). This is useful when your integration needs to complete a series of tasks when it's deployed. For example, you might need to configure a third-party app to send data to the other flows' webhooks. Or, you might need to enable features in a third-party app or create a series of directories in a file share before the integration is invoked. If you need to run tasks when an instance is deployed, set up those tasks as a flow and configure the trigger to run at deploy time. #### Instance remove trigger[​](#instance-remove-trigger "Direct link to Instance remove trigger") The opposite of an instance deploy trigger is an [instance remove](https://prismatic.io/docs/components/management-triggers.md#instanceremove) trigger. Flows with this trigger run when an instance is removed (deleted) and can be used to clean up webhooks and other configuration that an instance deploy trigger created. #### User-level config deploy and remove triggers[​](#user-level-config-deploy-and-remove-triggers "Direct link to User-level config deploy and remove triggers") If your integration supports [user-level configuration](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md), you can use the [User Level Config Deploy](https://prismatic.io/docs/components/management-triggers.md#userlevelconfigdeploy) and [User Level Config Remove](https://prismatic.io/docs/components/management-triggers.md#userlevelconfigremove) triggers to execute when a user-level config is completed or removed respectively. --- ##### Schedule Triggers Scheduled triggers allow you to create a regular schedule to specify how often your integration should run. This is useful when you have an integration that should be triggered consistently at a specific time. You can set up your integration to run at the same time for all customers, or you can set up schedules on a per-customer basis. To set up the same schedule for all customers, click the integration's trigger, open the **Schedule** input, and enter the schedule you would like your integration to follow. You can configure your integration to run every X minutes, hours, days, or weeks: ![Set static integration trigger in Prismatic app](/docs/assets/images/static-schedule-77346254f672d18de935bb8065a14428.png) You can alternatively select **Custom** and provide a [cron string](https://en.wikipedia.org/wiki/Cron) (interpreted in UTC time). For example, a trigger of `*/5 8-16 * * 1-5` would cause your integration to run every five minutes during business hours (8:00-16:55), Monday through Friday. For help computing a cron schedule, see this [Cron Calculator](https://crontab.guru/). #### Ensuring singleton executions for scheduled flows[​](#ensuring-singleton-executions-for-scheduled-flows "Direct link to Ensuring singleton executions for scheduled flows") By default, if a scheduled trigger is set to run every 5 minutes, and one execution takes longer than 5 minutes, a second execution will start while the first execution is still running. This can lead to multiple executions running concurrently, which may not be desirable for your integration. To ensure that only one execution of your integration runs at a time, select your scheduled trigger and open its **Flow control** tab. Toggle **Enable Singleton Executions**. ![Enable singleton executions for integration trigger in Prismatic app](/docs/assets/images/singleton-executions-2fbbb3a543b6df139ca2e2fb338ec717.png) When this option is enabled, if an execution is still running when the next scheduled time occurs, the new execution will be skipped. To enable singleton executions in a code-native integration, see [Code Native Flows](https://prismatic.io/docs/integrations/code-native/flows.md#enabling-singleton-executions-for-code-native-flows). #### Setting trigger schedules from a config variable[​](#setting-trigger-schedules-from-a-config-variable "Direct link to Setting trigger schedules from a config variable") To configure schedules on a per-customer basis, first create a config variable of type **Schedule** within the config wizard designer. You can give your config variable any name you choose: ![Configure integration trigger to use config variable in Prismatic app](/docs/assets/images/schedule-config-variable-254858b76e40b13eec5c982bc9f32e25.png) Then, click your integration trigger and reference the **Config Variable** you created: ![Set config variable for integration trigger in Prismatic app](/docs/assets/images/config-driven-schedule-a57a41a4f727bbd319bc9993edd6fe62.png) When your integration deployment team later deploys an instance of your integration, they can configure a custom schedule for that instance. #### Staggering schedules for customers[​](#staggering-schedules-for-customers "Direct link to Staggering schedules for customers") If you have many customers, you may want to stagger the schedules to avoid [concurrent execution constraints](https://prismatic.io/docs/integrations/integration-runner-environment-limits.md#webhook-rate-limiting-and-concurrent-executions) or prevent overwhelming your integration's target system. To stagger schedules, you can use a string type [config variable](https://prismatic.io/docs/integrations/triggers/schedule.md#setting-trigger-schedules-from-a-config-variable) to set a schedule for each customer. If you use a [code](https://prismatic.io/docs/components/code.md#runcodestring) data source and mark your config variable as [embedded](https://prismatic.io/docs/integrations/config-wizard/config-variables.md#config-variable-visibility), you can automatically generate a schedule for each customer without their intervention. This allows you to set different schedules for each customer without manually configuring each instance. This [Code Block String](https://prismatic.io/docs/components/code.md#runcodestring) example shows how to set a schedule that runs every 5 minutes, but with a random offset of up to 5 minutes for each customer: Configure each customer to run every 5 minutes with a random offset ```javascript module.exports = async () => { // Random integer between 0 and 4 const randomNumber = Math.floor(Math.random() * 5); // Create a cron string using the random number // "0-59/5 * * * *" means "At every 5th minute from 0 through 59." (i.e. :00, :05, :10, :15, :20...) // "3-59/5 * * * *" means "At every 5th minute from 3 through 59." (i.e. :03, :08, :13, :18, :23...) const cronString = `${randomNumber}-59/5 * * * *`; return { result: cronString }; }; ``` One customer may have a schedule of [`0-59/5 * * * *`](https://crontab.guru/#0-59/5_*_*_*_*), while another customer may have a schedule of [`3-59/5 * * * *`](https://crontab.guru/#3-59/5_*_*_*_*). An example integration that uses the above code is available [here](https://prismatic.io/docs/samples/random-schedule.yml). Similar code snippets can be used to generate other random schedules. See [Cron Guru](https://crontab.guru/) for help with cron syntax. #### Disabling scheduled triggers[​](#disabling-scheduled-triggers "Direct link to Disabling scheduled triggers") To disable a scheduled trigger that is driven by a config variable, you can select **Never** as your schedule type. When **Never** is selected, the flow will not run on a schedule and will only run when manually triggered. ![Set integration trigger to never run in Prismatic app](/docs/assets/images/never-726b080b1ee4823615f51bd82fdeb1b4.png) --- ##### Single-Endpoint Webhook Integrations Some apps, notably Slack, Dropbox and Hubspot, do not have customer-specific webhooks. Instead, all events are sent to a single endpoint, and these apps expect the receiving server to route the events to the correct customer. If you would like to build an event-driven integration with Slack, Dropbox, Hubspot, or another app that offers single-endpoint webhooks, you can do so by building out a "router" integration that receives all events and routes them to the correct customer's instance. This video explains the concept: #### Example Dropbox router integration[​](#example-dropbox-router-integration "Direct link to Example Dropbox router integration") The router integration and Dropbox integration demonstrated in the video above is available in our GitHub examples repo. You can [import](https://prismatic.io/docs/configure-prismatic/integrations-multiple-regions.md#importing-an-integrations-yaml-definition) the integrations for yourself and extend them however you see fit. [Router Integration](https://github.com/prismatic-io/examples/blob/main/integrations/dropbox/example-dropbox-router.yml)
[Example Dropbox Integration](https://github.com/prismatic-io/examples/blob/main/integrations/dropbox/example-dropbox-integration.yml) ##### How the Dropbox router integration works[​](#how-the-dropbox-router-integration-works "Direct link to How the Dropbox router integration works") When an instance of the Dropbox integration is deployed, its `Register Instance` flow looks up the authenticated Dropbox connection's Dropbox account ID and sends that account ID along with the webhook URL for the `Handle Change Notifications` flow to the router integration. The router integration stores the Dropbox account ID and webhook URL in a map. When the router integration receives a webhook event from Dropbox, it looks up the Dropbox account ID associated with the event and sends the event to the correct instance of the Dropbox integration using the corresponding webhook URL. --- ##### Universal Webhook Trigger The universal webhook trigger can be used to invoke a flow from any service that can [send an HTTP request](https://prismatic.io/docs/integrations/triggers/webhook/sending-data.md) to a custom URL. Each flow that begins with a universal webhook trigger has its own uniquely generated webhook URL, and sending HTTP requests to that webhook URL causes the flow to execute. Each of your customers' instances' flows have their own webhook URL by default (though you can change that with [Endpoint Configuration](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md)). The universal webhook trigger is useful in many situations. To name a few: * A third-party API may support webhooks, but a connector does not have a dedicated trigger for the webhook events you're interested in. A flow that starts with the universal trigger can receive requests from any third-party app for any event. * You can implement webhooks in your own application. Your webhooks can invoke a universal webhook trigger, allowing you to send data from your app to your customers' third-party apps in real time. * Your frontend app can make [synchronous](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md) requests to a flow with a universal webhook trigger in order to fetch data from a third-party in real time. #### When should I build a custom webhook trigger?[​](#when-should-i-build-a-custom-webhook-trigger "Direct link to When should I build a custom webhook trigger?") The [universal webhook trigger](https://prismatic.io/docs/components/webhook-triggers.md) is useful if the party calling the trigger sends a JSON or form data payload and expects a generic HTTP 200 response. It's also useful if you're building [MCP-compatible agentic flows](https://prismatic.io/docs/ai/flow-invocation-schema.md). If the third-party you're working with requires a custom response (like a challenge code), if they send atypical payloads (like XML or YAML that needs to be parsed first), or if they secure requests by signing payloads with [HMAC](https://prismatic.io/docs/integrations/triggers/webhook/what-is-hmac.md), you may need to wrap custom third-party logic in a [custom trigger](https://prismatic.io/docs/custom-connectors/triggers.md). --- ##### Using Shared Webhooks and Preprocess Flows In this tutorial you'll learn how to create a single webhook endpoint that can be invoked by multiple customers. For our scenario, imagine we want to integrate with a third-party ERP "Acme ERP". The Acme ERP tracks things like inventory and orders. Configuration of the ERP is limited and only allows you to specify a single webhook URL for all of your customers to post inventory and order updates. We need to create a single webhook URL that can accept the webhook payload that Acme ERP sends. That endpoint will need to inspect the payload, determine what customer it pertains to and what sort of payload it is (an "inventory update" payload or an "order creation" payload), and then it'll need to route that information to the correct flow in an instance deployed to the correct customer in Prismatic. #### Our Acme ERP Integration[​](#our-acme-erp-integration "Direct link to Our Acme ERP Integration") The Acme ERP integration that we deploy to customers has two flows. The first flow takes a collection of inventory updates ("remove 5 bowler hats", "add 20 pencils", etc.) and ensures that those updates are reflected in Progix: ![Acme ERP Update Inventory flow in Prismatic integration designer](/docs/assets/images/inventory-flow-7b34feb2ed77ecf3dcee4f814cd52c55.png) The second flow takes an order created in Acme ERP, processes the data in the order, and submits an order request to Progix: ![Acme ERP Create Order flow in Prismatic integration designer](/docs/assets/images/order-flow-7f0652fccc7103cf0262a922fd01349a.png) All of our customers are going to use the same webhook endpoint, so we'll need to configure our integration to create one URL for all instances of this integration that are deployed. To do that, let's open up the **Endpoint Configuration** drawer using the button on the right-hand side of the integration designer. We'll toggle **Endpoint Type** to **Shared**. ![AcmeERP Endpoint Configuration for shared endpoint in Prismatic integration designer](/docs/assets/images/endpoint-type-shared-instance-d64a5a3938f7e2d7383a1e1eeac21aa2.png) Leave **Preprocess Flow**, **Flow Name**, and **External Customer Id** alone for now and come back to them in a moment. Read more about webhook endpoint configuration on the [Integration Triggers](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md) article. #### The webhook payloads[​](#the-webhook-payloads "Direct link to The webhook payloads") When a customer in Acme ERP updates their inventory, Acme ERP sends a payload in this format to the shared webhook: ```json { "customerId": "A06DFFAC", "type": "inventory_update", "data": [ { "txid": "E865298B-A3EC-4D17-B410-5FDFC8861BA7", "item": "bowler hat", "quantity": 5, "state": "removed" }, { "txid": "4A38DE96-EA6B-4AB9-B9E2-3B033CA997CC", "item": "pencils", "quantity": 20, "state": "added" } ] } ``` When a customer in Acme ERP creates a new order, Acme ERP sends a payload in this format to the shared webhook: ```json { "customerId": "C3E72B0C", "type": "create_order", "data": { "orderid": "75F5AEC5-D482-4386-8878-219F92185DEC", "date": "2021-09-08", "shipped": false, "addr_1": "177A Bleecker St.", "addr_2": "New York, NY 10012", "total": 122.57, "paid": true } } ``` These two webhook payloads share some similarities: they both contain a `customerId` for your customers in Acme ERP, and they both have a `type` indicating the type of the webhook payload. We're going to use those two values to determine which customer in Prismatic to send the webhook to and which flow to invoke for that customer. To determine which customer to dispatch the webhook request to, we'll need a way to map an Acme ERP `customerId` to the [external ID](https://prismatic.io/docs/customers/managing-customers.md#customer-external-ids) that we use in Prismatic. We'll also need to map `type` to a flow name. To accomplish those tasks, we'll create another flow that will execute when a webhook is first received but before it's been dispatched to a customer's instance. #### The preprocess flow[​](#the-preprocess-flow "Direct link to The preprocess flow") A "preprocess" flow allows us to process data that comes in to a shared webhook URL and dispatch the data to a specific customer's instance and flow accordingly. The preprocess is called synchronously and returns whatever the last step of the flow returns. Our preprocess flow will contain two steps: one step will look up the customer's Progix external ID given the Acme ERP customer ID, and the second step will map `type` to a flow name and return both customer ID and flow name: ![AcmeERP preprocess flow in Prismatic integration designer](/docs/assets/images/preprocess-flow-41b37cd162ad4f2d0a7462e7a978f399.png) The `customerId` from the Acme ERP webhook request (accessible from `integrationTrigger.results.body.data.customerId`) can be passed in to a Progix external ID lookup action. The code component can then grab the external ID that is returned and can map `type` to a flow name: ```javascript const flowNamesMap = { inventory_update: "Update Inventory", create_order: "Create Order", }; module.exports = async ({ logger, configVars }, stepResults) => { const customerExternalId = stepResults.getCustomerByAcmeId.results.id; const webhookType = stepResults.trigger.results.body.data.type; const flowName = flowNamesMap[webhookType]; return { data: { customerExternalId, flowName }, }; }; ``` If we run our flow with a sample payload from above using the **Run** button, we can see that our last step returns an `customerExternalId` and `flowName`. ![AcmeERP Update Inventory test runner step results in Prismatic integration designer](/docs/assets/images/preprocess-step-results-d0bd34294cab22b0c94f0241608ca646.png) With a working flow, now we need to indicate in Prismatic that this flow is a preprocess flow. To do that, let's open up the **Endpoint Configuration** once again and select our "Preprocess" flow as the **Preprocess Flow** (you can name your preprocess flow whatever you want). ![Set preprocess flow for Acme ERP in Prismatic integration designer](/docs/assets/images/set-preprocess-flow-e23c13096ac005e07cd2397f29288611.png) #### Routing the webhook to the correct customer and flow[​](#routing-the-webhook-to-the-correct-customer-and-flow "Direct link to Routing the webhook to the correct customer and flow") Now that our preprocess flow has yielded a customer's `customerExternalId` and `flowName`, we now need to configure our integration to route the webhook invocation to the proper customer and flow. All we need to do is instruct our integration where to look to get the `customerExternalId` and `flowName` to key off of. Let's open up the **Endpoint Configuration** drawer once more. This time, we'll open the **Flow Name** input, which works a lot like a step input. The results of the preprocess flow test run are available here as an object named `results`. We can select `results.flowName` that our preprocess flow returned from the result picker. We'll do the same for the **External Customer ID** input - this time we'll select `results.customerExternalId`: ![Reference preprocess flow results in Prismatic integration designer](/docs/assets/images/reference-preprocess-results-0573ba0f9acdc4528587ec8593ef9c49.png) That's it. All instances we deploy of this integration will share a single webhook endpoint, and webhook invocations will be routed to the proper customer and flow based on the information contained in the body of the webhook request. #### Follow-up questions[​](#follow-up-questions "Direct link to Follow-up questions") ##### Can I test endpoint config in the designer?[​](#can-i-test-endpoint-config-in-the-designer "Direct link to Can I test endpoint config in the designer?") Yes! Your requests will always be routed to your integration designer (and not actual instances), but you can open the test runner drawer and open **Test Configuration**. Then, select **Endpoint payload** and paste in a sample payload. ![Endpoint Test Payload](/docs/assets/images/endpoint-test-payload-5f8be5437f6be32b4b7e8c5db21797cf.png) Once you've done that, click the globe icon to the right of **Run** to run a test of endpoint configuration. ##### Was the preprocess flow necessary here?[​](#was-the-preprocess-flow-necessary-here "Direct link to Was the preprocess flow necessary here?") Possibly. If the webhook trigger had contained the flow name and customer external ID that we needed, we could have omitted the preprocess flow. When no preprocess flow is assigned, the `results` object that **External Customer ID** and **Flow Name** can reference contains the header and payload information from the webhook invocation. We could have mapped the Prismatic customers' external ID from the webhook's `results.body.data.externalId`. That would require Acme ERP to be cognizant of the external IDs we assign to customers in Prismatic, which may or may not be possible in the third-party system. For **Flow Name**, we could have named our flows `inventory_update` rather than `Update Inventory` and `create_order` rather than `Create Order` and could have configured **Flow Name** to reference the webhook request's `results.body.data.type` instead. That would look less attractive in the integration designer, so again, there are trade-offs. ##### Could the customer ID have been passed in as a header instead of in the body?[​](#could-the-customer-id-have-been-passed-in-as-a-header-instead-of-in-the-body "Direct link to Could the customer ID have been passed in as a header instead of in the body?") Yes. The customer ID could have as easily been a header as a value in the body. In that case, the lookup action could have referenced `stepResults.integrationTrigger.results.headers.customerId` rather than `stepResults.integrationTrigger.results.body.customerId`. ##### Do the same concepts apply to an instance-specific webhook?[​](#do-the-same-concepts-apply-to-an-instance-specific-webhook "Direct link to Do the same concepts apply to an instance-specific webhook?") Yes. Instance-specific webhooks provide you with a unique webhook per instance you deploy (so each customer gets its own webhook endpoint). In that situation, you wouldn't need to worry about **Customer External ID**, but you would still want a way to route to a particular flow, so the flow-name-mapping portion of this tutorial still applies. --- ##### What is a Webhook? [What are webhooks?](https://www.youtube.com/embed/CriaG014ocM?rel=0) A **webhook** is an automated message that is sent from one application to another application when certain events occur. Webhooks let applications notify one another in real-time when something has changed in one system and can be used to trigger an instance's flow so the change is reflected in the other system. A webhook consists of two main parts: * The **event** that causes the webhook to fire. The event is usually a change to a record in an application. For example, you may have a `contact.changed` or `report.created` event. * The **endpoint** where information about the event is sent. The endpoint is a URL that you provide to the application that will receive the webhook. #### Webhook request payloads[​](#webhook-request-payloads "Direct link to Webhook request payloads") When a webhook fires, the application where the event occurred will send a POST request to the endpoint you provided. Most applications will send a JSON payload in the request body that contains information about the event that occurred (though some, notably Salesforce, send XML payloads). Some payloads contain the entire record that changed, while others contain only the record's ID and you are expected to fetch the record yourself. #### Webhook security[​](#webhook-security "Direct link to Webhook security") Webhooks are often secured using Hashed Message Authentication Codes (or HMAC) to ensure that the request is coming from the application that you expect. **For More Information**: [Secure Webhook Endpoints with HMAC](https://prismatic.io/docs/integrations/triggers/webhook/what-is-hmac.md). #### Responses to webhooks[​](#responses-to-webhooks "Direct link to Responses to webhooks") Some applications expect a particular response to the webhook request to ensure that your application understood the request. Most applications expect an HTTP 200 response with a special message in the response body that is usually derived from the webhook's payload or request header. Other applications expect an HTTP 200 response. #### Webhooks in Prismatic[​](#webhooks-in-prismatic "Direct link to Webhooks in Prismatic") In Prismatic, the **endpoint** of a webhook is generally an instance's flow's trigger URL. Your integration configures webhooks in the third-party app when the instance is deployed either using an [webhookLifecycleHandlers](https://prismatic.io/docs/custom-connectors/triggers.md#app-event-webhook-triggers) function in a custom trigger, or by creating a "setup" flow that is [triggered on instance deploy](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger). Some built-in components will subscribe to webhooks automatically when the instance is deployed. If you configure webhooks on instance deploy, you should also remove webhooks on instance delete using a similar process. If the third-party app expects a specific response to the webhook request, you can build a [custom trigger](https://prismatic.io/docs/custom-connectors/triggers.md) that validates HMAC signatures and responds to the webhook request appropriately. Many built-in components already have triggers that handle this for you. #### Ensuring webhook ordering with a FIFO queue[​](#ensuring-webhook-ordering-with-a-fifo-queue "Direct link to Ensuring webhook ordering with a FIFO queue") Prismatic's integration runner is designed to process requests in parallel. If you invoke an integration with multiple requests in quick succession, the runner will scale and process all of the requests simultaneously. If you have a flow that requires requests to be processed in a specific order, or a flow that requires you to process only one record at a time, you can leverage [Flow Concurrency Management](https://prismatic.io/docs/integrations/triggers/fifo-queue.md) to create a "first in, first out" (FIFO) queue. --- ##### Custom HMAC Trigger **Hash-Based Message Authentication Code** (or HMAC) is an authentication mechanism used frequently by webhooks to verify that a webhook message is legitimate. It helps ensure that messages sent to a webhook endpoint originated from a particular third-party and not from a malicious actor on the internet. If you compute an HMAC hash from the webhook's request body, your secret key is a string, and the hash is included as a header, you can use the built-in [HMAC Webhook Trigger](https://prismatic.io/docs/components/hash.md#hmacwebhooktrigger) to validate webhook requests. Most (but not all) HMAC implementations follow this pattern. In this tutorial, we'll examine the code behind the [Slack Webhook Trigger](https://prismatic.io/docs/components/slack.md#slashcommandwebhook), which differs from the generic HMAC trigger in two ways: * Slack concatenates the request body with a timestamp before computing the HMAC hash (see [Slack documentation](https://api.slack.com/authentication/verifying-requests-from-slack#verifying-requests-from-slack-using-signing-secrets__a-recipe-for-security__step-by-step-walk-through-for-validating-a-request)). * When a webhook is first configured in Slack, Slack sends a URL verification ["challenge" request](https://api.slack.com/apis/connections/events-api#the-events-api__subscribing-to-event-types__events-api-request-urls__request-url-configuration--verification__url-verification-handshake) to the webhook endpoint and expects the endpoint to respond with the same challenge string. The Slack trigger responds with that dynamic response and branches into "URL Verify" or "Notification" branches, depending on whether it received a "challenge" confirmation request or an actual Slack event. Full source code for the Slack trigger can be found in our [examples repository on GitHub](https://github.com/prismatic-io/examples/blob/main/components/slack/src/triggers.ts). #### Computing the Slack HMAC hash[​](#computing-the-slack-hmac-hash "Direct link to Computing the Slack HMAC hash") Rather than hashing only the request's body, Slack hashes a timestamp as well, which is passed in as a header. The string that is hashed follows this format: ```text v0:{TIMESTAMP}:{REQUEST BODY} ``` Then, an HMAC hash is computed and appended to the end of a string that starts with `v0=`. So, we built a function that takes a request body, timestamp, and signing secret, and returns a string in the form Slack sends: Helper function to compute Slack hashes ```typescript const computeSignature = ( requestBody: string, signingSecret: string, timestamp: number, ) => { const signatureBaseString = `v0:${timestamp}:${requestBody}`; const signature = crypto .createHmac("sha256", signingSecret) .update(signatureBaseString, "utf8") .digest("hex"); return `v0=${signature}`; }; ``` #### Verifying the incoming hash signature[​](#verifying-the-incoming-hash-signature "Direct link to Verifying the incoming hash signature") Now that we have a helper function, let's examine the trigger's `perform` function that is invoked when a webhook request is received. The trigger first gathers the necessary pieces of information: * We fetch the raw, unprocessed request **body** from `payload.rawBody.data` and convert it to a string. * We get the **timestamp** from an HTTP header through `payload.headers["X-Slack-Request-Timestamp"]`. The timestamp is an integer (Unix epoch seconds). * We get the **signing secret** from the Slack connection. A connection contains OAuth 2.0 information in addition to a signing secret and is passed in as an input, `inputs.slackConnection.fields.signingSecret`. Then, we'll run our `computeSignature` function on the data we gathered. If the hash we generate is not the same as the hash that Slack provided (through `payload.headers["X-Slack-Signature"]`), we'll throw an error and the integration will stop running: Check Slack HMAC hash ```typescript const signingSecret = util.types.toString( inputs.slackConnection.fields.signingSecret, ); const requestBody = util.types.toString(payload.rawBody.data); const timestamp = util.types.toInt( payload.headers["X-Slack-Request-Timestamp"], ); const computedSignature = computeSignature( requestBody, signingSecret, timestamp, ); const payloadSignature = util.types.toString( payload.headers["X-Slack-Signature"], ); if (payloadSignature !== computedSignature) { throw new Error( "Error validating message signature. Check your signing secret and verify that this message came from Slack.", ); } ``` Assuming the signature Slack generated, `payloadSignature`, matches the hash we computed, `computedSignature`, our trigger continues. #### Returning a Slack challenge and branching[​](#returning-a-slack-challenge-and-branching "Direct link to Returning a Slack challenge and branching") Once we've verified that the webhook we received is, indeed, from Slack, we can return a proper challenge response to Slack if it needs one and then branch appropriately. We'll get the challenge that Slack sent from the request's body (if we got a URL verification request), and then we'll generate a response that contains exactly that challenge string: ```typescript const challenge = (payload.body.data as Request)?.challenge; const response: HttpResponse = { statusCode: 200, contentType: "text/plain", body: challenge, }; ``` Note: if we didn't receive a `challenge` request, the response body will be blank. That's fine. Slack doesn't expect a special response for other webhook events. Finally, we'll return the `payload` that we received (so it can be used by the integration), the `response` that our webhook should send back to Slack, and the `branch` that the integration should follow (depending on whether we received a URL verify challenge request): ```typescript return Promise.resolve({ payload, response, branch: challenge ? "URL Verify" : "Notification", }); ``` #### Conclusion[​](#conclusion "Direct link to Conclusion") You can build your own trigger that validates webhooks by following similar patterns, and you can make your HTTP responses as static/simple or dynamic/complex as you'd like. For more information on building custom components and custom triggers, check out the [Writing Custom Components](https://prismatic.io/docs/custom-connectors.md) article. For full source code of the Slack trigger, check out our [examples repo on GitHub](https://github.com/prismatic-io/examples/blob/main/components/slack/src/triggers.ts). --- ##### Sending data to webhook triggers Webhook triggers allow you to run a particular instance or [flow](https://prismatic.io/docs/integrations/low-code-integration-designer/flows.md) of an instance by making an HTTP POST, PUT, PATCH, DELETE, or GET request to the webhook's URL. This is useful when you would like an outside application to invoke an integration when something within the outside application occurs. The outside application can assemble data and send that data to a Prismatic webhook URL via an HTTP request. For example, third-party software could invoke an instance with a JSON payload whenever a job in the third-party application is complete, like this: ```bash curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --data '{"renderId":51266,"s3Bucket":"test-customer-renders","status":"complete"}' \ --header "Content-Type: application/json" ``` Note that the payload of the request is available by referencing the integration's trigger, shown in the screenshot below. Steps can then reference data from the webhook payload through the trigger's results. Headers are available through `results.headers` and body data is available through `results.body.data`: ![Set webhook trigger via POST request in Prismatic app](/docs/assets/images/webhook-trigger-payload-83219d66a78b38c88ca0d17e6fb40d39.png) The GET verb is supported You *can* use the GET verb to invoke an instance, but note that the GET verb does not allow you to send data with your request. If you need to send data with your request, use the POST, PUT, DELETE or PATCH verbs. The GET verb was introduced because some applications send a GET request when a webhook is configured to verify that the webhook endpoint is ready to receive requests. #### Adding a webhook trigger to a flow[​](#adding-a-webhook-trigger-to-a-flow "Direct link to Adding a webhook trigger to a flow") * Low-Code * Code-Native When you [add a flow](https://prismatic.io/docs/integrations/low-code-integration-designer/flows.md) to your integration, you are prompted to add a trigger to the flow. Select the **Webhook Trigger** option to add a webhook trigger to the flow. If you would like to switch the trigger to a different type, click the three dots to the left of the trigger in the flow and select **Change step action**. The generic webhook trigger is used by default if you omit an `onTrigger` property from your `flow` definition. If you need to parse the payload that the trigger receives or send a custom response to the caller, you can define a custom trigger in your `flow` definition. See [Code-native flow triggers](https://prismatic.io/docs/integrations/code-native/flows.md#code-native-flow-triggers). #### Webhook trigger responses[​](#webhook-trigger-responses "Direct link to Webhook trigger responses") By default, webhook triggers provide an HTTP code 200 ("OK") response to callers of the webhook. The response body contains an execution ID, which can be used later to retrieve logs and step results from the Prismatic API. The response looks like this: ```text curl \ --data '{}' \ --header "Content-Type: application/json" \ 'https://hooks.prismatic.io/trigger/EXAMPLE==' {"executionId":"SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6OTdiNWQxYmEtZGUyZi00ZDY4LWIyMTgtMDFlZGMwMTQxNTM5"} ``` * Low-Code * Code-Native You can customize the response by clicking the integration webhook trigger and selecting a different HTTP code, response body, or response content type: ![Customize webhook trigger response code in Prismatic app](/docs/assets/images/webhook-trigger-response-d56d22feb45d1be965ff0a3ae078003c.png) Custom webhook responses can be defined in the `onTrigger` block of your `flow` definition. Create an `HttpResponse` object with a `statusCode`, `contentType` and `body` to be returned to the caller: ```ts import { HttpResponse, flow, util } from "@prismatic-io/spectral"; import { XMLParser } from "fast-xml-parser"; flow({ // ... onTrigger: async (context, payload) => { // Parse the raw XML from the webhook request const parser = new XMLParser(); const parsedBody = parser.parse(util.types.toString(payload.rawBody.data)); // Respond to the request with a plaintext response that includes the challenge key const response: HttpResponse = { statusCode: 200, contentType: "text/plain", body: parsedBody.notification.challenge, }; // Ensure that the payload is updated with the parsed body return Promise.resolve({ payload: { ...payload, body: { data: parsedBody } }, response, }); }, }); ``` #### Other webhook trigger responses[​](#other-webhook-trigger-responses "Direct link to Other webhook trigger responses") You may encounter other responses to your webhook trigger request. ##### HTTP 303 See Other / Redirect to S3 results bucket[​](#http-303-see-other--redirect-to-s3-results-bucket "Direct link to HTTP 303 See Other / Redirect to S3 results bucket") When your webhook trigger is invoked [synchronously](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md), your HTTP client sends a request and waits for the integration to finish running before closing the request. Prismatic responds to the request with an HTTP 303 redirect, redirecting your HTTP client to an object in an Amazon S3 bucket that contains the results of the last step of your integration. Ensure that your HTTP client follows redirects (for `curl`, you add a `--location` flag). Synchronous request redirected ```txt $ curl 'https://hooks.prismatic.io/trigger/example==' --location -v ... < HTTP/2 303 < content-type: application/json < content-length: 0 < location: https://example.s3.us-east-2.amazonaws.com/example < date: Wed, 28 Sep 2022 19:42:24 GMT < x-amzn-requestid: 4c2a5179-89a2-4351-afe0-336df2cdef11 < access-control-allow-headers: Accept,CloudFront-Forwarded-Proto,CloudFront-Is-Desktop-Viewer,CloudFront-Is-Mobile-Viewer,CloudFront-Is-SmartTV-Viewer,CloudFront-Is-Tablet-Viewer,CloudFront-Viewer-ASN,CloudFront-Viewer-Country,Host,User-Agent,Via,X-Amz-Cf-Id,X-Amzn-Trace-Id,X-Forwarded-For,X-Forwarded-Port,X-Forwarded-Proto < x-amz-apigw-id: ZL6A7GegCYcF5Ew= < prismatic-executionid: SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6NGVjMTJiNWMtODk2ZC00ZGJiLThjZDgtZWUwYzNlMDE4OTBh < access-control-allow-methods: GET < x-amzn-trace-id: Root=1-6334a39f-0f975a9a7bb343c768645c36;Sampled=1 < x-cache: Miss from cloudfront < via: 1.1 d67353af1bc95b93fa6102d888271954.cloudfront.net (CloudFront) < x-amz-cf-pop: ORD58-P7 < x-amz-cf-id: xraa6hPBPc_texazIJXdKgavfwVRMW2oV85GqwCx6xUkUck7MxGKPg== < ... * Connection #0 to host hooks.prismatic.io left intact * Issue another request to this URL: 'https://example.s3.us-east-2.amazonaws.com/example' ... < HTTP/1.1 200 OK < x-amz-id-2: zx2ZHsGlC/Szu3HCp7xjRAVDALsEdQ/TJ5x/MkcbFkAB8DjLmRJOWCyTGmpI93UhdpqeYxt7hVo= < x-amz-request-id: SE5KV3CJ5AXCYGZA < Date: Wed, 28 Sep 2022 19:42:25 GMT < Last-Modified: Wed, 28 Sep 2022 19:42:25 GMT < x-amz-expiration: expiry-date="Sat, 29 Oct 2022 00:00:00 GMT", rule-id="expire-old-step-results" < ETag: "083be81885a78809b54f4deead0e6c24" < x-amz-server-side-encryption: AES256 < x-amz-version-id: 7bc_zpEQGmpc_stsaC.9vcF1DqxzXWx. < Accept-Ranges: bytes < Content-Type: application/octet-stream < Server: AmazonS3 < Content-Length: 11 < * Connection #1 to host payload-bucket20200616192411543900000009.s3.us-east-2.amazonaws.com left intact {"item":"Widgets","quantity":5} ``` Avoid combining --location and --request POST Combining `--location` and an explicit `--request POST` (or `-X POST`) flag in the same `curl` command can have unintended consequences. `curl` will be redirected to an S3 bucket but will attempt to make a `POST` request (rather than a `GET` request) to the S3 bucket. This results in S3 responding with a `SignatureDoesNotMatch` error. You will see the S3 error within [Postman](https://www.postman.com/) if you enable **Follow original HTTP method**. Keep that option unchecked. ![Avoid specifying HTTP method with redirects](/docs/assets/images/postman-redirect-warning-351b0761ef09a7830169e2d3a5d26b93.png) ##### HTTP 400 Bad Request[​](#http-400-bad-request "Direct link to HTTP 400 Bad Request") You'll see an HTTP 400 response for one of two reasons: * If your request was malformed (for example, you have a header `content-type: application/json`, but the data you sent wasn't valid JSON). Malformed payload ```txt $ curl 'https://hooks.treece.prismatic-dev.io/trigger/example==' -v \ --data "{bad-data" \ --header "content-type: application/json" ... < HTTP/2 400 < content-type: application/json < content-length: 44 < date: Wed, 28 Sep 2022 19:47:09 GMT < x-amzn-requestid: 64a06065-c2d5-4d1b-8188-1513f072cb8e < access-control-allow-headers: Accept,CloudFront-Forwarded-Proto,CloudFront-Is-Desktop-Viewer,CloudFront-Is-Mobile-Viewer,CloudFront-Is-SmartTV-Viewer,CloudFront-Is-Tablet-Viewer,CloudFront-Viewer-ASN,CloudFront-Viewer-Country,content-type,Host,User-Agent,Via,X-Amz-Cf-Id,X-Amzn-Trace-Id,X-Forwarded-For,X-Forwarded-Port,X-Forwarded-Proto < x-amz-apigw-id: ZL6tYEjPCYcFmMA= < prismatic-executionid: SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6YmIwZmQwMjgtYmRlOS00NDFhLTg4ZTYtNjcwZDlkMDY2NjZm < access-control-allow-methods: POST < x-amzn-trace-id: Root=1-6334a4bb-5a02f67725c134b505472c11;Sampled=1 < x-cache: Error from cloudfront < via: 1.1 ee57d6770700357db4b696b4c5250b82.cloudfront.net (CloudFront) < x-amz-cf-pop: ORD58-P7 < x-amz-cf-id: qSGjLnZJNDjUkUGU2Wl297s6eezPSFs5UEF58H_hceHj9JokJkfB3A== < * Connection #0 to host hooks.treece.prismatic-dev.io left intact {"error":"Received malformed JSON payload."} ``` * When your webhook trigger is invoked [synchronously](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md) and stops because an error is thrown, your request will receive an HTTP 400 response with the error that was thrown. Error thrown in synchronous execution ```txt curl 'https://hooks.treece.prismatic-dev.io/trigger/example==' -v ... * We are completely uploaded and fine * Connection state changed (MAX_CONCURRENT_STREAMS == 128)! < HTTP/2 400 < content-type: application/json < content-length: 55 < date: Wed, 28 Sep 2022 19:53:33 GMT < x-amzn-requestid: 8e46051e-2783-4e25-b95d-35ee323ce523 < access-control-allow-headers: Accept,CloudFront-Forwarded-Proto,CloudFront-Is-Desktop-Viewer,CloudFront-Is-Mobile-Viewer,CloudFront-Is-SmartTV-Viewer,CloudFront-Is-Tablet-Viewer,CloudFront-Viewer-ASN,CloudFront-Viewer-Country,content-type,Host,User-Agent,Via,X-Amz-Cf-Id,X-Amzn-Trace-Id,X-Forwarded-For,X-Forwarded-Port,X-Forwarded-Proto < x-amz-apigw-id: ZL7paEZjiYcFU4A= < prismatic-executionid: SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6NTM5ZGE0YjItNDViMC00MDQxLTg3MTgtMzFhZDkwMDg1Y2Iw < access-control-allow-methods: POST < x-amzn-trace-id: Root=1-6334a63c-0d58198b7c2e26d11f4331f3;Sampled=1 < x-cache: Error from cloudfront < via: 1.1 26c731836eb716e46fe9852a7aaeb508.cloudfront.net (CloudFront) < x-amz-cf-pop: ORD58-P7 < x-amz-cf-id: ZKCbTC07GI90gLLyeRNmGaHub0gpULRK45_GZ7d7uGM3njGFYKaPkw== < * Connection #0 to host hooks.treece.prismatic-dev.io left intact {"error":"The widget requested is not in the database"} ``` ##### HTTP 429 Too Many Requests / Rate Limiting[​](#http-429-too-many-requests--rate-limiting "Direct link to HTTP 429 Too Many Requests / Rate Limiting") A webhook endpoint URL can be invoked up to 50 times per second. If a request is received for an endpoint URL that has already received 50 requests in the last second, the request will receive a 429 "too many requests" response. Request rate limited ```txt $ curl 'https://hooks.prismatic.io/trigger/example==' -v ... < HTTP/2 429 < content-type: application/json < content-length: 200 < date: Wed, 28 Sep 2022 19:36:55 GMT < x-amzn-requestid: f53fbf8c-9ce6-4c4a-80a6-84edbc29fdeb < x-amz-apigw-id: ZL5NtH82CYcFbFg= < x-amzn-trace-id: Root=1-6334a257-1ca7ad23574a8c3255b24776;Sampled=1 < x-cache: Error from cloudfront < via: 1.1 a044221a7cde0fa9b5dc69d5ceb4439a.cloudfront.net (CloudFront) < x-amz-cf-pop: ORD58-P7 < x-amz-cf-id: BoOs_sbk7gB-t16ZCR4uVtD8NcPnmD40rGUPwgaouVe2hiHu60Rcjw== < * Connection #0 to host hooks.prismatic.io left intact {"error":"Endpoint with id: example== has exceeded maximum allowed throughput of 50 requests/second. Please throttle your requests."} ``` #### Webhook endpoint configuration[​](#webhook-endpoint-configuration "Direct link to Webhook endpoint configuration") Webhook triggers can be configured for an integration in one of three ways: * **Instance and Flow Specific**: Each flow on each instance gets its own unique endpoint. This is the default configuration. * **Instance Specific**: Each instance gets a unique endpoint, and the integration determines which flow to run based on header or payload data. * **Shared**: All customers' instances of the integration share an endpoint. Data in the header or payload determines which customer and flow should run. For information on configuring and troubleshooting webhook endpoints, see our [Endpoint Configuration](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md) article. #### Sending data to webhook triggers[​](#sending-data-to-webhook-triggers "Direct link to Sending data to webhook triggers") A webhook parses data from the following sources: * The **request body** - the JSON (or other) data that is sent to the webhook as an [HTTP request body](https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages#body). * The **request headers** - the [HTTP headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Messages#headers). * The **URL path** - The [path to resource](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_URL#path_to_resource) that follows the integration webhook URL. * The **URL parameters** - The [parameters](https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_is_a_URL#parameters) that follow the `?` in a URL.. Take, for example, this `curl` invocation: ```bash curl \ 'https://hooks.prismatic.io/trigger/EXAMPLE==/my/custom/path?param-one=ParamValueOne¶m-two=ParamValueTwo' \ --header "header-one: First header value" \ --header "header-two: Second header value" \ --header "Content-Type: application/json" \ --data '{"Payload Key 1":"Payload Value 1","Do Thing?":true,"quantity":123}' ``` * The request body - `{"Payload Key 1":"Payload Value 1","Do Thing?":true,"quantity":123}` - is parsed (if JSON) and is accessible to the integration by referencing the trigger's `results.body.data.KEY-NAME`. Non-JSON payloads (like XML, images, etc) are accessible through `results.rawBody` and can be parsed in subsequent steps that handle that type of data. * The request headers are accessible through the trigger's `results.headers.HEADER-NAME`. * The url path - `my/custom/path` - is accessible through the trigger's `results.pathFragment`. You can pass that data into the built-in [split string](https://prismatic.io/docs/components/text-manipulation.md#split) action and split on the `/` character to split the URL path into an array `['my','custom','path']`. * The url parameters - `?param-one=ParamValueOne¶m-two=ParamValueTwo` are parsed and accessible through the trigger's `results.queryParameters.PARAMETER-NAME`. ![Screenshot of Sending Data to Webhook Triggers](/docs/assets/images/sending-data-to-webhook-triggers-27ed3fe675c8268226f53f1bb0cd74fa.png) #### Posting binary data with webhook triggers[​](#posting-binary-data-with-webhook-triggers "Direct link to Posting binary data with webhook triggers") If you have binary data (like an image or PDF) that you would like to post as part of your webhook invocation, you can pass that binary data in as part of your request. For example, if you have an image, `my-image.png`, you could invoke a test of an integration with: ```bash curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --request POST \ --header 'Content-Type: image/png' \ --data-binary '@/path/to/my-image.png' ``` The binary file can be accessed by subsequent steps by referencing `integrationTrigger.results.body.data`. #### Posting multipart data with webhook triggers[​](#posting-multipart-data-with-webhook-triggers "Direct link to Posting multipart data with webhook triggers") It's useful to be able to post a combination of binary and text data to a Prismatic webhook. For example, you might want to post information about a person, as well as an avatar image of the person, to be processed by an integration. To do that, use a content type of `multipart/form-data` with your webhook invocation: ```bash curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --request POST \ --header "Content-Type: multipart/form-data" \ --form person='{"firstname":"Taylor","lastname":"Reece"};type=application/json' \ --form photo=@taylor.png ``` The first name in this example is accessible by referencing the trigger's `results.body.data.person.data.firstname`, and the avatar image is accessible by referencing `results.body.data.photo`: ![Post multipart data with webhook triggers in Prismatic app](/docs/assets/images/webhook-multipart-payload-7289ae4b2a0a6c04a9b7267ad8083914.png) #### Accessing webhook URLs in an integration[​](#accessing-webhook-urls-in-an-integration "Direct link to Accessing webhook URLs in an integration") An integration is aware of its own webhook URLs, which is handy for setting up webhooks in third-party apps on deploy, or when you need one flow to call a sibling flow by webhook URL. * Low-Code * Code-Native Those URLs are accessible by referencing the trigger's `results.webhookUrls` object: ![Access webhook URLS for integration in Prismatic app](/docs/assets/images/webhook-urls-payload-01b815beb07d3e588f1c0fafad2b87e2.png) This comes in handy if you need to configure a third-party service to send data to your webhooks. A common pattern is for one [flow](https://prismatic.io/docs/integrations/low-code-integration-designer/flows.md) of your integration to be run when an instance is deployed using a [deploy trigger](https://prismatic.io/docs/integrations/triggers/management.md#instance-deploy-trigger). That deploy-time flow can set up webhooks in a third-party app by referencing its trigger's `results.webhookUrls` values. Then, the third-party app will invoke the other flows of the integration when it needs to. If you use [shared endpoint configuration](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md), the shared endpoint URL is accessible from `results.invokeUrl`. If you set up [API keys](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md#securing-endpoints-with-api-keys) for your deployed instances, you can access `results.webhookApiKeys` similarly. An instance's flow can have multiple API keys assigned to it, so each `results.webhookApiKeys.MY FLOW NAME` is an array. You will likely reference the first API key, `results.webhookApiKeys.MY FLOW NAME.0`. `webhookUrls` in a code-native integration is available in the `context` object passed to the `onTrigger`, `onInstanceDeploy`, `onInstanceRemove`, `onExecution` and `webhookLifecycleHandlers` functions. For example, if you want to establish a webhook in a third-party app when an instance is deployed, you can do so in the `webhookLifecycleHandlers.create` function: ```ts const myFlow = flow({ // ... webhookLifecycleHandlers: { create: async (context, params) => { // Get the current flow's webhook URL const flowWebhookUrl = context.webhookUrls[context.flow.name]; // Create a webhook in Acme const { data } = await axios.post( "https://api.acme.com/webhooks", { endpoint: flowWebhookUrl, events: ["message.created", "message.updated"], }, { headers: { Authorization: `Bearer ${context.configVars["My Connection"].fields.apiKey}`, }, }, ); // Store the webhook ID in cross-flow persistent state so it can be // deleted on instance delete return { crossFlowState: { [`${context.flow.stableId}-webhook-id`]: data.id }, }; }, }, }); ``` --- ##### Synchronous and Asynchronous Invocations Integrations are configured by default to run **asynchronously**. This means that whenever an integration is invoked by trigger webhook URL, the integration begins to run and the system that invoked the integration can proceed to complete other work. This is the most common case for integrations - you want to start up an instance when a certain event occurs, but you don't want to wait while the instance runs. Sometimes, however, it's useful for an application to get information back from the instance that was invoked. For example, you might want your proprietary software to wait until an instance runs to completion before completing other work. In that case, you can choose to run your integration **synchronously**. Then, when your software makes a call to the instance's webhook trigger URL, the HTTP request is held open until the instance run is complete. When you choose to run your integrations **synchronously**, the HTTP request that invokes an instance returns a *redirect* to a URL containing the output results of the final step of the integration. For example, if the final step of your integration pulls down JSON from , you will see this when you invoke the integration synchronously: ```text curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --data '{}' \ --header "Content-Type: application/json" \ --header "prismatic-synchronous: true" \ --location {"id":1,"name":"Leanne Graham","username":"Bret","email":"Sincere@april.biz","address":{"street":"Kulas Light","suite":"Apt. 556","city":"Gwenborough","zipcode":"92998-3874","geo":{"lat":"-37.3159","lng":"81.1496"}},"phone":"1-770-736-8031 x56442","website":"hildegard.org","company":{"name":"Romaguera-Crona","catchPhrase":"Multi-layered client-server neural-net","bs":"harness real-time e-markets"}} ``` You can toggle whether your integration is synchronous or asynchronous by clicking the trigger and selecting a **Response Type**. You can also pass in a header, `prismatic-synchronous` with a webhook invocation to instruct your instance to run synchronously or asynchronously: ```bash curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --header "prismatic-synchronous: false" \ --request POST ``` ##### Synchronous invocations and redirects[​](#synchronous-invocations-and-redirects "Direct link to Synchronous invocations and redirects") When you invoke an instance synchronously, the HTTP response you receive contains the result of the final step of your flow. If the result is over 5MB in size, the result is written to Amazon S3, and you receive an HTTP 303 redirect to an object in S3. Because of this possible redirect, you should ensure that your HTTP client is configured to follow HTTP status code 303 redirects. For `curl`, for example, include a `-L / --location` flag so it follows redirects. Ensure redirects are followed ```bash curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --header "prismatic-synchronous: true" \ --location \ --request POST \ --data "{}" ``` If you would like Prismatic's runner to always return 303 redirects, you can include an optional header, `prismatic-prefer-redirect-sync-response: true` and the runner will return an HTTP 303 response to an S3 object, regardless of size. Response content type You can control the `content-type` of the response by adding a `contentType` property to your last step's result. For example, ```typescript const returnData = ` Tove Jani Reminder Don't forget me this weekend! `; return { data: returnData, contentType: "application/xml", }; ``` #### HTTP status codes for synchronous integrations[​](#http-status-codes-for-synchronous-integrations "Direct link to HTTP status codes for synchronous integrations") When an instance is configured to run synchronously or is invoked synchronously with the `prismatic-synchronous` header, the HTTP response returns a status code `200 - OK` by default. It's sometimes useful, however, to return other HTTP status codes. For example, if a client submits incorrectly formatted data to be processed by an instance, it might be helpful to return a `406 - Not Acceptable` or `415 - Unsupported Media Type`. * Low-Code * Code-Native To accomplish this in a low-code integration, you can configure the final step of your integration to return a different status code. Most commonly, you can add a [Stop Execution](https://prismatic.io/docs/components/stop-execution.md) step to the end of your integration and specify an HTTP response that it should return. ![Configure HTTP status codes for synchronous integrations in Prismatic app](/docs/assets/images/stop-execution-status-code-fdb7a35b761b9e33c74df39481b4b83c.png) If you would like to return HTTP status codes from a custom component at the end of your integration instead, return an object with a `statusCode` attribute instead of a `data` attribute: ```javascript return { statusCode: 415 }; ``` To accomplish this in a code-native integration, mark your flow as `isSynchronous: true,` and return an object with a `statusCode` attribute: Return a 415 status code from a code-native integration ```typescript export const flow1 = flow({ name: "Flow 1", stableKey: "9499d1d8-dddd-4d9b-aaff-c054f59d02cc", description: "This is the first flow", isSynchronous: true, onExecution: async (context, params) => { return { data: { customError: "Invalid file type. You must provide an SVG.", }, statusCode: 415, headers: { "X-Custom-Header": "foo" }, contentType: "application/json", }; }, }); ``` ```bash $ curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --verbose \ --location \ --header "prismatic-synchronous: true" * TCP_NODELAY set * Connected to hooks.prismatic.io (13.227.37.2) port 443 (#0) ... < HTTP/2 415 ``` #### Response headers for synchronous integrations[​](#response-headers-for-synchronous-integrations "Direct link to Response headers for synchronous integrations") In addition to HTTP status codes (above), you can also yield custom response headers from your synchronous integrations. This is useful if you would like to redirect the client to a different URL once the flow is complete. Your code step, for example, can read: ```javascript module.exports = async ({ logger, configVars }, stepResults) => { return { data: "Redirecting you...", statusCode: 303, headers: { Location: "https://example.com" }, }; }; ``` When a client invokes the integration synchronously, they will receive a `303 - See Other` status code and be redirected to `https://example.com`. #### Synchronous call limitations[​](#synchronous-call-limitations "Direct link to Synchronous call limitations") ##### Response body and status code limitations[​](#response-body-and-status-code-limitations "Direct link to Response body and status code limitations") When an integration is invoked synchronously, the integration redirects the caller to a URL containing the output results of the final step of the integration. If the final step of the integration is a [Stop Execution](https://prismatic.io/docs/components/stop-execution.md) action, or any custom component action that returns a `statusCode`, the redirect does not occur and the caller receives a `null` response body instead. ##### API gateway size and time limitations[​](#api-gateway-size-and-time-limitations "Direct link to API gateway size and time limitations") AWS API Gateway times out requests after 29 seconds, and our maximum response size is 500MB. So, to get a response from an instance that is invoked synchronously, please ensure that your integration runs in under 29 seconds and produces a final step payload of less than 500MB. If your integration regularly takes over 29 seconds to run, or produces large responses, we recommend that you run your integrations asynchronously instead. When you invoke an integration asynchronously, you receive an `executionId`: ```text curl 'https://hooks.prismatic.io/trigger/EXAMPLE==' \ --data '{}' \ --header "Content-Type: application/json" {"executionId":"SW5zdGFuY2VFeGVjdXRpb25SZXN1bHQ6OTdiNWQxYmEtZGUyZi00ZDY4LWIyMTgtMDFlZGMwMTQxNTM5"} ``` That execution ID can be exchanged later with the Prismatic API for logs and step results using the [executionResult](https://prismatic.io/docs/api/schema/queries.md#executionresult)) GraphQL mutation. --- ##### What is HMAC? When it comes to transferring data via integrations, security is a top concern. To secure data being passed via webhooks ([for event-driven integrations](https://prismatic.io/docs/intro/guidelines/event-driven-guidelines.md)), you have a few options, including API keys, your own authorization headers, or HMAC. Prismatic recommends using HMAC because it's the simplest (and strongest) approach you could use. It doesn't require that your team learn a new language or gain an advanced understanding of encryption, but it does an excellent job of protecting the integrity of your data at the point of transfer. #### What is HMAC?[​](#what-is-hmac "Direct link to What is HMAC?") HMAC, or hashed message authentication code, is an authentication method that generates a hash from a message and a cryptographic key. When you implement HMAC for your webhook, you'll use an algorithm such as MD5, SHA-256, or RipeMD-128 for the hash to ensure the HTTP request that shows up at your webhook endpoint is legitimate. #### How does HMAC work?[​](#how-does-hmac-work "Direct link to How does HMAC work?") Before the source app sends an HTTP request via the webhook, it hashes or serializes the payload (request body) with HMAC using the secret key. The resulting hash is then bundled into the HTTP request as a header, and the entire request (header and body) is sent to the webhook endpoint. The Secret Key is never sent in the payload! It's important to be aware that the secret key is and should never be sent in the payload -- It is only used to generate and validate the HMAC signature. Upon receiving the HTTP request, the destination app hashes the body with the secret key and then compares the result to the hash provided in the header. If the values match, the destination app knows the data is legitimate and processes it. If the values do not match, the destination app rejects the data and executes whatever code was written for that scenario - perhaps creating a log entry or sending a notification. If someone tries to spoof the payload, they won't be able to generate a valid hash since they don't have the secret key. Door closed. Key Rotation can reduce security risks Adding key rotation to rotate the secret key can decrease the risk of key exposure, leakage, long-term compromise, orphaned integration compromise, insider threats, and compliance violations. Consider this approach when setting up HMAC Validation #### HMAC language support[​](#hmac-language-support "Direct link to HMAC language support") Here are links to popular languages with HMAC capabilities: * [NodeJS](https://nodejs.org/api/crypto.html) * [Python](https://docs.python.org/3/library/hmac.html) * [PHP](https://www.php.net/manual/en/function.hash-hmac.php) * [.NET C#](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.hmac?view=net-6.0) #### Example code for HMAC[​](#example-code-for-hmac "Direct link to Example code for HMAC") Here is an example of how HMAC might be set up in NodeJS using the built-in crypto module: ```tsx const crypto = require("crypto"); const SECRET_KEY = "secret-FA782CF7-060E-484E-B3DC-055CF2C9ED99"; const payload = JSON.stringify({ event: "REFUND_REQUEST", user: "realcustomer@notabaddie.com", amount: "50.25", }); const hash = crypto .createHmac("sha256", SECRET_KEY) .update(payload, "utf-8") .digest("hex"); console.log(hash); // Prints d12f95e3f98240cff00b2743160455fdf70cb8d431db2981a9af8414fc4ad5f8 ``` The corresponding HTTP request using HMAC might look like this: ```text curl https://my.webhook.endpoint.com/callback \ --request POST \ --header "x-hmac-hash: d12f95e3f98240cff00b2743160455fdf70cb8d431db2981a9af8414fc4ad5f8" \ --data '{"event":"REFUND_REQUEST","user":"realcustomer@notabaddie.com","amount":"50.25"}' ``` #### Example code for HMAC multipart form data[​](#example-code-for-hmac-multipart-form-data "Direct link to Example code for HMAC multipart form data") If you're planning to use HMAC to send data as multipart form data, you'll need to hash the entire request body and use buffers to convert the data into the appropriate format. Here's an example of how you might do that in NodeJS: ```tsx const crypto = require("crypto"); const formData = require("form-data"); const fs = require("fs"); const SECRET_KEY = "secret-FA782CF7-060E-484E-B3DC-055CF2C9ED99"; // Create the Forms Data object const data = new formData(); data.append("foo", "bar"); data.append("baz", JSON.stringify({ buz: "biz" }), { contentType: "application/json", }); data.append("my-buffer", Buffer.from(fs.readFileSync("/Path/To/File/")), { filename: "name_of_file.ext", contentType: "content/type", }); // Create the HMAC hash for the fetch request const hash = crypto .createHmac("sha256", SECRET_KEY) .update(data.getBuffer().toString()) .digest("hex"); console.log(hash); ``` #### Sending HMAC through Axios or fetch[​](#sending-hmac-through-axios-or-fetch "Direct link to Sending HMAC through Axios or fetch") If you're using Axios or fetch to send the HTTP request, you can include the HMAC hash in the headers like this: With fetch, you'll need to ensure you provide the body as a buffer object and include the boundary from the form data object in the content-type header. ```tsx const response = await fetch("https://my.webhook.endpoint.com/callback", { method: "POST", body: data.getBuffer(), headers: { "x-hmac-hash": hash, "content-type": `multipart/form-data; boundary=${data.getBoundary()}`, }, }); console.log(response.json()); ``` With Axios, you can include the HMAC hash in the headers like this: ```tsx axios.post("https://my.webhook.endpoint.com/callback", data, { headers: { "x-hmac-hash": hash, }, }); ``` #### Further resources[​](#further-resources "Direct link to Further resources") Setting up HMAC for one of your integrations should be straightforward. To make things even simpler, we've added an [HMAC Webhook trigger](https://prismatic.io/docs/components/hash.md#hmacwebhooktrigger) to our built-in Hash component. In many cases, this will address your needs, but for times it doesn't, here is a quickstart tutorial for [Writing a Custom Webhook Trigger with HMAC Validation](https://prismatic.io/docs/integrations/triggers/webhook/custom-hmac-trigger.md). --- ### Custom Connectors #### Custom Connectors Overview #### Overview[​](#overview "Direct link to Overview") Prismatic is extensible and allows developer users to develop their own custom connectors. Connectors that Prismatic users develop are proprietary to their organization and are private. Connectors are Node.js/TypeScript projects that accomplish specific tasks or connect to an outside service. Connectors comprise: * [Connections](https://prismatic.io/docs/custom-connectors/connections.md) which contain information such as passwords, API keys, or OAuth 2.0 connection information. Your customers fill in their authentication information for connections when they deploy your integration. * [Actions](https://prismatic.io/docs/custom-connectors/actions.md) which are purpose-built functions that you can use in your integration. You might build "Create Widget" or "List Gadgets" actions in a custom connector, and each action can be used as a step within an [integration](https://prismatic.io/docs/integrations.md). * [Triggers](https://prismatic.io/docs/custom-connectors/triggers.md) determine when an integration should run and how it should handle requests. You might create a custom trigger that configures webhooks in a third-party application when a customer configures an instance of your integration and responds to webhook requests from the third-party appropriately. * [Data Sources](https://prismatic.io/docs/custom-connectors/data-sources.md) fetch data dynamically during the [config wizard](https://prismatic.io/docs/integrations/config-wizard.md) experience. For example, you might create a data source that fetches a list of projects in a CMS and presents the projects as a dropdown menu in the config wizard. [Sample connector code](https://github.com/prismatic-io/examples/tree/main/components) is referenced throughout these docs. For a sample connector that wraps an HTTP-based API, see our getting started guide on [Wrapping an API in a Custom Connector](https://prismatic.io/docs/get-started/build-integrations/wrap-api-custom-connector.md). #### Custom connector library[​](#custom-connector-library "Direct link to Custom connector library") [![Spectral NPM version](https://badge.fury.io/js/@prismatic-io%2Fspectral.svg)](https://www.npmjs.com/package/@prismatic-io/spectral) Prismatic provides a Node.js package, [@prismatic-io/spectral](https://www.npmjs.com/package/@prismatic-io/spectral), which provides TypeScript typing and some utility functions. Source code for Spectral is available on [GitHub](https://github.com/prismatic-io/spectral). **Node.js Version Support**: While many versions of Node.js may work for connector development, we recommend using the latest LTS (long-term support) version of Node.js. You can find the latest LTS version on the [Node.js download page](https://nodejs.org/en/download/). Connector vs Component The terms **connector** and **component** are used interchangeably in the Prismatic platform. Generally, if a component connects to a third-party API, we call it a **connector**. But, not all components connect to external APIs. The [Change Data Format](https://prismatic.io/docs/components/change-data-format.md) component, for example, converts data between common formats (JSON, XML, etc), and the [Math](https://prismatic.io/docs/components/math.md) component provides a variety of math utility functions, but neither component makes a network request to a third-party app. #### Customer users and custom components[​](#customer-users-and-custom-components "Direct link to Customer users and custom components") If you use the [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md), your customers may want to build their own custom components and use them in their workflows. These private components are scoped to the customer and are not visible to other customers. This is beneficial when customers need to build custom components specific to their business (for example, to wrap their own API). The development of customer-scoped custom components follows the same process as organization-wide custom components. Customer users must be able to log in to Prismatic For a customer user to publish a custom component, you must [create a customer user account](https://prismatic.io/docs/customers/customer-users.md) and assign them the "Admin" role. The customer user must be able to log into Prismatic to authenticate the Prism CLI tool for publishing custom components. A customer user cannot use a signed JWT to publish custom components. Once a customer user has logged into Prismatic, they can authenticate the Prism CLI tool by running `prism login` and then publish a component scoped to their customer with `prism components:publish`, as an organization team member would. To publish a component as an organization team member for a specific customer, run `prism components:publish --customer ` using an ID that you can fetch by running `prism customers:list --columns "id,name"`. --- #### Custom Actions #### Overview[​](#overview "Direct link to Overview") A component is comprised of zero, one or many actions. For example, the [HTTP component](https://prismatic.io/docs/components/http.md) contains actions to [GET](https://prismatic.io/docs/components/http.md#httpget) (`httpGet`), [POST](https://prismatic.io/docs/components/http.md#httppost) (`httpPost`), etc. An action can be added as a step of an integration. An `action` has three required properties: 1. `display` which affects how the action renders within the Prismatic web application 2. A series of [`input` fields](https://prismatic.io/docs/custom-connectors/inputs.md) 3. A function to `perform` when the action is encountered in a flow. An action may return some `data` that can be used in a subsequent step. ```typescript import { action, input } from "@prismatic-io/spectral"; const myAction = action({ display: { label: "Say Hello", description: "Concatenate the first and last name of a person", }, inputs: { firstName: input({ label: "First Name", type: "string", required: true }), lastName: input({ label: "Last Name", type: "string", required: true }), }, perform: async (context, inputs) => { const myMessage = `Hello, ${inputs.firstName} ${inputs.lastName}`; return Promise.resolve({ data: myMessage }); }, }); ``` #### The perform function[​](#the-perform-function "Direct link to The perform function") Each action contains one `perform` function, which is an async function with two parameters that may or may not have a return value. In this example `firstName`, `middleName`, and `lastName`, are input fields for this `perform` function: ```typescript export const properFormatName = action({ display: { label: "Properly Format Name", description: "Properly format a person's name (Last, First M.)", }, perform: async (context, inputs) => { if (inputs.middleName) { return { data: `${inputs.lastName}, ${inputs.firstName} ${inputs.middleName[0]}.`, }; } else { return { data: `${inputs.lastName}, ${inputs.firstName}` }; } }, inputs: { firstName, middleName, lastName }, }); ``` ##### `perform` Function Parameters[​](#perform-function-parameters "Direct link to perform-function-parameters") The `perform` function takes two positional parameters, `context` and `inputs`, that can be destructured into their respective properties: ```typescript perform: async (context, inputs) => {}, // or perform: async ( { logger }, { paramName1, paramName2, ... } ) => {}, ``` ##### The `context` parameter[​](#the-context-parameter "Direct link to the-context-parameter") The `context` parameter is an object that contains the following attributes: * `logger` allows you to write out log lines. * `debug` is an object which you can use when [debug mode](https://prismatic.io/docs/integrations/troubleshooting.md#debug-mode) is enabled to emit additional debug log lines or measure time or memory costs of specific portions of your code. * `instanceState`, `crossFlowState`, `integrationState` and `executionState` gives you access to [persisted state](#execution-instance-and-cross-flow-state). * `stepId` is the ID of the current step being executed. * `executionId` is the ID of the current execution. * `webhookUrls` contains the URLs of the running instance's sibling flows. * `webhookApiKeys` contains the API keys of the running instance's sibling flows. * `invokeUrl` was the URL used to invoke the integration. * `customer` is an object containing an `id`, `name`, and `externalId` of the customer the instance is assigned to. * `user` is an object containing an `id`, `name`, `email` (their ID) and `externalId` of the customer user whose user-level config was used for this execution. This only applies to instances with [User Level Configuration](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md). * `integration` is an object containing an `id`, `name`, and `versionSequenceId` of the integration the instance was created from. * `instance` is an object containing an `id` and `name` of the running instance. * `flow` is an object containing the `id` and `name` of the running flow. * `invokeFlow` is a function that lets you invoke another flow by name. Generally, you'll want to use the [Invoke Flow](https://prismatic.io/docs/components/cross-flow.md#invokeflow) action which wraps this function. ###### Step ID[​](#step-id "Direct link to Step ID") `context.stepId` contains the unique identifier (UUID) of the step. It is used by the [Process Data - DeDuplicate](https://prismatic.io/docs/components/process-data.md#deduplicate) action to track what items in a array have or have not been previously seen. You can use it similarly in a custom component to persist step-specific data. ###### Webhook URLs[​](#webhook-urls "Direct link to Webhook URLs") You can reference an instance's webhook URLs through the `context.webhookUrls` object. This is useful when writing actions to configure and delete webhooks in a third-party app. ```typescript perform: async (context, inputs) => { const inventoryUrl = context.webhookUrls["My Inventory Flow"]; }; ``` You can reference `context.flow.name` to fetch the current flow's webhook URL: ```typescript perform: async (context, inputs) => { const myCurrentUrl = context.webhookUrls[context.flow.name]; }; ``` ##### Logger object[​](#logger-object "Direct link to Logger object") `context.logger` is a logging object and can be helpful to debug components. ```typescript perform: async ({ logger }, inputs) => { logger.info("Things are going great"); logger.warn("Now less great..."); }; ``` Available log functions in increasing order of severity include `logger.debug`, `logger.info`, `logger.warn` and `logger.error`. You can also execute `logger.metric` on an object, which helps when [streaming logs and metrics](https://prismatic.io/docs/monitor-instances/logging/streaming-logs-externally.md) to an external logging service. **Note**: Log lines are truncated after 4096 characters. If you need longer log lines, consider [streaming logs](https://prismatic.io/docs/monitor-instances/logging/streaming-logs-externally.md) to an external log service. ##### Execution, instance, and cross-flow state[​](#execution-instance-and-cross-flow-state "Direct link to Execution, instance, and cross-flow state") `context.executionState`, `context.instanceState`, `context.integrationState` and `context.crossFlowState` are key/value stores that may be used to store small amounts of data for future use: * `context.executionState` stores state for the duration of the execution, and is often used as an accumulator for loops. * `context.instanceState` stores state that is persisted between executions. This state is scoped to a specific flow. The flow may persist data, and reference it in a subsequent execution. Shouldn't `instanceState` be called `flowState`? Great question! We developed state storage prior to multi-flow, and the name `instanceState` was retained for historical reasons. * `context.crossFlowState` also stores state that is persisted between executions. This state is scoped to the instance, and flows may reference one another's stored state. * `context.integrationState` stores state between flows in instances of the same integration. Customer A's flow 1 can share data with Customer B's flow 2. State is most notably used by the [Persist Data](https://prismatic.io/docs/components/persist-data.md) and [Process Data](https://prismatic.io/docs/components/process-data.md) components, but you can use it in your custom components, too. If, for example, a previous flow's run saved a state key of `sampleKey`, you can reference `context.instanceState['sampleKey']` to access that key's value. To do the reverse, and save data to a flow's state storage for subsequent runs, add an `instanceState` property to your perform function's return value: ```typescript return { data: "Some Data", instanceState: { exampleKey: "example value", anotherKey: [1, 2, 3] }, }; ``` **Note**: To remove a key from persisted state, set it to `null`: Remove a key from crossFlowState ```typescript return { data: "Some Data", crossFlowState: { exampleKey: null }, }; ``` ##### Input parameters[​](#input-parameters "Direct link to Input parameters") The `inputs` parameter is an object that has attributes for each input field the action supports. For example, for the perform action [defined above](#the-perform-function), `inputs` has `inputs.firstName`, `inputs.middleName`, and `inputs.lastName`. `firstName`, `middleName`, and `lastName` are based off of the input objects that are provided to the action as `inputs`. Shorthand property names You can use [shorthand property names](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer) for inputs. If your input object variables have different names - say you have a `const myFirstNameInput = input ({...})`, you can structure your action's input property like this: ```typescript inputs: { firstName: myFirstNameInput, middleName: myMiddleNameInput, lastName: myLastNameInput, } ``` and the `inputs` object passed into `perform` will have keys `firstName`, `middleName`, and `lastName`. Using non-shorthand property names is preferable to some developers to avoid [variable shadowing](https://en.wikipedia.org/wiki/Variable_shadowing). The function is written with a destructured `inputs` parameter. It could be rewritten without being destructured. ```typescript perform: async (context, inputs) => { if (inputs.middleName == "") { return { data: `${inputs.lastName}, ${inputs.firstName}` }; } else { return { data: `${inputs.lastName}, ${inputs.firstName} ${inputs.middleName[0]}.`, }; } }, ``` ##### Coercing input types[​](#coercing-input-types "Direct link to Coercing input types") TypeScript-based Node libraries often have strict rules about the type of variables that are passed into their functions, but inputs to `perform` functions are of type `unknown` since it's not known ahead of time what types of values users of components are going to pass in. For example, you might expect one of your inputs to be a `number`, but a user might pass in a `string` instead. That's obviously a problem since `"2" + 3` is `"23"`, while `2 + 3` is `5` in JavaScript. The Spectral package includes several utility functions for coercing input to be the type of variable that you need. Looking at the number/string example, suppose you have some input - `quantity` - that you need turned into a number (even if someone passes in `"123.45"` as a string), and you have another input - `itemName` - that you'd like to be a string. You can use `util.types.toNumber()` and `util.types.toString()` to ensure that the input has been converted to a number and string respectively: ```typescript import { action, util } from "@prismatic-io/spectral"; import { someThirdPartyApiCall } from "some-example-third-party-library"; action({ /*...*/ perform: async (context, { quantity, itemName }) => { const response = await someThirdPartyApiCall({ orderQuantity: util.types.toNumber(quantity), // Guaranteed to be a number orderItemName: util.types.toString(itemName), // Guaranteed to be a string }); return { data: response }; }, }); ``` If an input cannot be coerced into the type you've chosen - for example, suppose you pass `"Hello World"` into `util.toNumber()` - an error will be thrown indicating that `"Hello World"` cannot be coerced into a number. ###### Writing your own type checking functions[​](#writing-your-own-type-checking-functions "Direct link to Writing your own type checking functions") Prismatic provides a variety of type check and type coercion functions for common types (number, integer, string, boolean, etc). If you require a uniquely shaped object, you can create your own type check and coercion functions to ensure that inputs your custom component receives have the proper shape that the libraries you rely on expect. You can import an `interface` or `type` (or write one yourself) and write a function that converts inputs into an expected shape. For example, the SendGrid SDK expects an object that has this form: ```javascript { "to": [string], "from": string, "subject": string, "text": string, "html": string } ``` We can pull in that defined type, `MailDataRequired`, from the SendGrid SDK, and write a function that takes inputs and converts them to an object containing a series of strings: ```typescript import { MailDataRequired } from "@sendgrid/mail"; import { util } from "@prismatic-io/spectral"; export const createEmailPayload = ({ to, from, subject, text, html, }): MailDataRequired => ({ to: util.types .toString(to) .split(",") .map((recipient: string) => recipient.trim()), from: util.types.toString(from), subject: util.types.toString(subject), text: util.types.toString(text), html: util.types.toString(html), }); ``` #### Perform function return values[​](#perform-function-return-values "Direct link to Perform function return values") An action's perform function can return a variety of data, outlined in [step outputs](https://prismatic.io/docs/custom-connectors/step-outputs.md) ##### Setting synchronous HTTP status codes[​](#setting-synchronous-http-status-codes "Direct link to Setting synchronous HTTP status codes") If you invoke your instances [synchronously](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md#http-status-codes-for-synchronous-integrations) and would like to return an HTTP status code other than `200 - OK`, you can configure the final step of your integration to be a custom component that returns any HTTP status code you want. To return an HTTP status code other than 200, return a `statusCode` attribute in the object you return from your custom component instead of a `data` attribute: ```typescript return { statusCode: 415, }; ``` If this custom component is the last step of an integration, then the integration will return an HTTP status code of 415 if invoked synchronously. Note: When an integration is invoked synchronously, by default the integration redirects the caller to a URL containing the output results of the final step of the integration. If the final step of the integration is a [Stop Execution](https://prismatic.io/docs/components/stop-execution.md) action, or any custom component action that returns a `statusCode`, the redirect does not occur. Instead, the caller receives an HTTP response with the `statusCode` specified. Read more about [HTTP status codes for synchronous integrations](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md#http-status-codes-for-synchronous-integrations). --- #### Handling Binary Files Integrations in Prismatic generally process serialized JSON, XML or other simple strings and pass deserialized JavaScript objects between steps. However, there are situations when you may want to process and pass binary data between steps. By "binary data", we mean files that are not plain text - PDF files, images, MP3 audio, etc. Within an integration, a binary file is represented as an object with two properties: * `data` which is a Node.js `Buffer` that contains the file's data * `contentType` which is a string representing the file's MIME type. See Mozilla's documentation for a list of common file [MIME types](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types). ```ts { data: Buffer.from("..."), contentType: "application/pdf" }; ``` ##### Processing binary data as an input[​](#processing-binary-data-as-an-input "Direct link to Processing binary data as an input") Inputs for binary files are similar to any other input you might create, though you can use the `util.types.toBufferDataPayload` function to ensure that the input has the form `{ data: Buffer, contentType: string }`: ```ts { inputs: { myFile: input({ label: "My File", type: "data", required: true, clean: util.types.toBufferDataPayload, }), } } ``` The `myFile` property that comes in to your `perform` function will have the form of a binary file, with `data` and `contentType` properties that you can reference. ```ts { perform: async (context, inputs) => { axios.post("http://my-endpoint", inputs.myFile.data, { headers: { "Content-Type": inputs.myFile.contentType }, }); }; } ``` ##### Returning binary data from an action[​](#returning-binary-data-from-an-action "Direct link to Returning binary data from an action") To return a binary file from your action, return the data as a `Buffer` and optionally include a `contentType` property alongside `data` that indicates its MIME type. For example, if your custom component returns a rendered PDF file and the PDF contents are saved in a `Buffer` variable named `pdfContents`, the return block might look like this: ```typescript return { data: pdfContents, contentType: "application/pdf", }; ``` You can return multiple files, or binary files in a nested object with a similar structure: ```typescript return { myKey: "myValue", myPdf: { data: pdfBuffer, contentType: "application/pdf", }, myPng: { data: pngBuffer, contentType: "image/png", }, }; ``` ##### Fetching binary data with the Spectral HTTP client[​](#fetching-binary-data-with-the-spectral-http-client "Direct link to Fetching binary data with the Spectral HTTP client") When fetching binary data from an API, you must configure your HTTP client to expect binary data and to write the data to a `Buffer`. For the HTTP client (which is Axios-based), use the `responseType: "arraybuffer"` configuration option to ensure the `data` property returned is a `Buffer`: ```typescript { perform: async (context, inputs) => { const client = createBambooClient(inputs.connection); const { data, headers } = await client.get(`/v1/files/${inputs.fileId}`, { responseType: "arraybuffer", }); return { data, contentType: headers["content-type"] }; }; } ``` --- #### Branching in Custom Actions and Triggers Similar to the [branch](https://prismatic.io/docs/components/branch.md) component, your custom actions and triggers can make use of logical branches. To support branches, give your `action()` or `trigger()` two additional properties, `allowsBranching: true` and `staticBranchNames: ["List", "Of", "Branches"]`, and ensure that the object that your `perform` function returns includes a `branch` property: Example action with branching ```typescript export const branchExample = action({ display: { label: "Branch Example", description: "An example action that branches", }, inputs: { myValue: input({ label: "My Value", type: "string", required: true }), }, allowsBranching: true, staticBranchNames: ["First", "Second", "Other"], perform: async (context, inputs) => { let branchName = "Other"; if (inputs.myValue === "One") { branchName = "First"; } else if (inputs.myValue === "Two") { branchName = "Second"; } return await Promise.resolve({ branch: branchName, data: null }); }, }); ``` Similar code can be used in a custom trigger. `allowsBranching: true` indicates to the integration designer that it should render branches beneath your action or trigger. `staticBranchNames` is an array of strings representing names of possible branches that can be followed. The branch name that matches the `branch` return value will be followed. ![Logs showing which branch was followed in a custom branch step](/docs/assets/images/custom-branch-logs-965d22d90453676cf8d015e10f9b2fbc.png) --- #### Custom Connections #### Overview[​](#overview "Direct link to Overview") A **connection** is a special type of input for an action that contains information about how to connect to an external application or service. A connection can contain one or more inputs representing API endpoints, keys, passwords, OAuth 2.0 fields, and other authentication details. The inputs within a connection use the same structure as other inputs, described [here](https://prismatic.io/docs/custom-connectors/inputs.md). For example, suppose you're writing a component for an API that accepts either a username/password combination or an API key. You would create two connections - one for username/password authentication and one for API key authentication. You also want to allow your customers to point to either a sandbox or production environment - each connection should include an input for the endpoint. Your connections might look like this: ```typescript import { connection } from "@prismatic-io/spectral"; // Define this once to avoid repetition in the two connections const acmeEnvironment = input({ label: "Acme Inc Environment to Use", placeholder: "ACME Environment", type: "string", required: true, model: [ { label: "Production", value: "https://api.acme.com/", }, { label: "Sandbox", value: "https://sandbox.acme.com/api", }, ], }); const basicAuth = connection({ key: "basicAuth", display: { label: "Acme username and password", description: "Acme basic auth", }, inputs: { username: { label: "Acme Username", placeholder: "Username", type: "string", required: true, }, password: { label: "Acme Password", placeholder: "Password", type: "string", required: true, }, acmeEnvironment, }, }); const apiKey = connection({ key: "apiKey", display: { label: "Acme API Key", description: "Acme API key auth", }, inputs: { username: { label: "Acme API Key", placeholder: "API Key", type: "string", required: true, }, acmeEnvironment, }, }); ``` After defining connections, include them in your `component` definition. This allows users to enter connection information once and reuse it in actions that require that connection. It also makes connections available to all inputs of type "connection" in your component: ```typescript import { component } from "@prismatic-io/spectral"; // ... export default component({ key: "acme", public: false, display: { label: "Acme Inc", description: "Interact with Acme Inc's API", iconPath: "icon.png", }, actions: { myAction1, myAction2 }, triggers: { myTrigger1 }, connections: [basicAuth, apiKey], }); ``` Connection ordering The **first** connection listed in the `connections:` array becomes the default connection. In this example, `basicAuth` would be the default connection for this component. The default connection is recommended to users when they add an action from your component to their integration, but they can select other connection types as well. #### Referencing connections as inputs in actions[​](#referencing-connections-as-inputs-in-actions "Direct link to Referencing connections as inputs in actions") Actions can reference connections like any other input. To allow users to assign a connection to an action, create an `input` of type `connection` and add it as an input to your action: Reference a connection input from an action ```typescript import { action, input } from "@prismatic-io/spectral"; const connectionInput = input({ label: "Connection", type: "connection" }); export const getAcmeData = action({ display: { label: "Get Item", description: "Get an Item from Acme", }, inputs: { itemId: itemIdInput, myConnection: connectionInput }, perform: async (context, { itemId, myConnection }) => { const response = axios({ method: "get", url: `${myConnection.fields.acmeEnvironment}/item/${itemId}`, headers: { Authorization: `Bearer ${myConnection.fields.apiKey}`, }, }); return { data: response.data }; }, }); ``` #### Throwing connection errors[​](#throwing-connection-errors "Direct link to Throwing connection errors") It's important to know whether a connection is valid and to track any connection failures. Within your custom component, you can throw a `ConnectionError` to signal to Prismatic that there's a problem with the connection (such as inability to connect to an endpoint or invalid credentials). For example, if you know the API returns a 401 "Unauthorized" response for invalid credentials, you could throw a `ConnectionError` when your HTTP client receives a status code 401: Throw a connection error ```typescript import { action, ConnectionError, util } from "@prismatic-io/spectral"; const getItem = action({ display: { label: "Get Item", description: "Get an item from Acme", }, perform: async (context, { myConnection, itemId }) => { const apiKey = util.types.toString(myConnection.fields.apiKey); const response = await axios.get(`https://api.acme.com/items/${itemId}`, { headers: { Authorization: apiKey }, }); if (response.status === 401) { throw new ConnectionError( myConnection, "Invalid Acme credentials have been configured.", ); } return { data: response.data, }; }, inputs: { myConnection: input({ label: "Connection", type: "connection" }), itemId: itemIdInput, }, }); ``` The thrown error will be indicated by a red mark next to customers' connections on an instance, and messages will appear in logs. #### Writing OAuth 2.0 connections[​](#writing-oauth-20-connections "Direct link to Writing OAuth 2.0 connections") An OAuth 2.0 authorization code connection follows the [OAuth 2.0](https://oauth.net/2/) protocol and requires five inputs: * `authorizeUrl` - The URL where users authorize an OAuth 2.0 connection * `tokenUrl` - The URL for exchanging authorization codes for API keys and optional refresh tokens, and for refreshing API keys using refresh tokens * `scopes` - A space-delimited list of required permissions (scopes) for your application * `clientId` - Your OAuth 2.0 application's client ID * `clientSecret` - Your OAuth 2.0 application's client secret The first three fields are typically found in the API documentation of the service you're integrating with. Client ID and secret are created when you register an application with the third-party service. You can allow integration builders to edit any of these fields. Alternatively, you can mark fields as `shown: false`, in which case the default value will always be used and integration developers won't see the value. For example, when writing an OAuth 2.0 connection to Google Drive, the `authorizeUrl` and `tokenUrl` are constant. These can have default values and be marked as `shown: false`. Integration developers typically need to adjust scopes, client ID, and client secret (though you may already know which scopes you need). Here's an example connection: Example OAuth 2.0 Connection with Google Drive ```typescript import { oauth2Connection, OAuth2Type } from "@prismatic-io/spectral"; export const oauth2 = oauth2Connection({ key: "googleDriveOauth", display: { label: "OAuth2", description: "OAuth2 Connection", icons: { oauth2ConnectionIconPath: "oauth-icon.png", }, }, required: true, oauth2Type: OAuth2Type.AuthorizationCode, inputs: { authorizeUrl: { label: "Authorize URL", placeholder: "Authorization URL", type: "string", required: true, shown: false, comments: "The Authorization URL for Google Drive.", default: "https://accounts.google.com/o/oauth2/v2/auth", }, tokenUrl: { label: "Token URL", placeholder: "Token URL", type: "string", required: true, shown: false, comments: "The Token URL for Google Drive.", default: "https://oauth2.googleapis.com/token", }, scopes: { label: "Scopes", placeholder: "Scopes", type: "string", required: true, comments: "Space delimited listing of scopes. https://developers.google.com/identity/protocols/oauth2/scopes#drive", default: "https://www.googleapis.com/auth/drive", }, clientId: { label: "Client ID", placeholder: "Client Identifier", type: "password", required: true, comments: "The Google Drive app's Client Identifier.", }, clientSecret: { label: "Client Secret", placeholder: "Client Secret", type: "password", required: true, comments: "The Google Drive app's Client Secret.", }, }, }); ``` Use `oauth2Connection` for OAuth Connections Note that we used `oauth2Connection()` rather than `connection()` to define this OAuth connection. That's because the `oauth2Connection` helper function gives us additional TypeScript hinting about what fields are required. An `oauth2Connection` can be assigned to a component and referenced as an input just like a `connection`. The input that is received by a `perform` function will have the form: ```json { "token": { "access_token": "EXAMPLE-TOKEN", "token_type": "bearer", "expires_in": 14400, "refresh_token": "EXAMPLE-REFRESH-TOKEN", "scope": "account_info.read account_info.write file_requests.read file_requests.write files.content.read files.content.write files.metadata.read files.metadata.write", "uid": "123456789", "account_id": "dbid:EXAMPLEIRNhsZ3wECJZ3aXK3Gm47Di74", "expires_at": "2021-12-07T01:54:38.096Z" }, "context": { "code": "EXAMPLEqMEAAAAAAAAON5iBXhk_yOxjkfDeWy_vSE0", "state": "EXAMPLE2VDb25maWdWYXJpYWJsZTpmMDZlMDVkNy1kMjY0LTQ0YTgtYWI0Ni01MDhiOTNmZjU5ZjI=" }, "instanceConfigVarId": "EXAMPLE2VDb25maWdWYXJpYWJsZTpmMDZlMDVkNy1kMjY0LTQ0YTgtYWI0Ni01MDhiOTNmZjU5ZjI=", "key": "oauth", "fields": { "scopes": "", "clientId": "example-client-id", "tokenUrl": "https://api.dropboxapi.com/oauth2/token", "authorizeUrl": "https://www.dropbox.com/oauth2/authorize?token_access_type=offline", "clientSecret": "example-client-secret" } } ``` You will likely want to reference `myConnection.token.access_token`. Add a custom button to your OAuth 2.0 Connection You can specify what the OAuth 2.0 button looks like in the instance configuration page by specifying an optional `display.icons.oauth2ConnectionIconPath` (see the above example). An icon must be a PNG file, and we recommend that it be wider than it is tall with text indicating what it does: ![OAuth buttons including Dropbox, Facebook, GitHub, Google, Tumblr, and Twitter](/docs/assets/images/oauth-buttons-eab496c1878db45ceae27ce3aa08ec03.png) Without an `oauth2ConnectionIconPath`, a simple button that says **CONNECT** will be placed in the configuration page. ##### Supporting PKCE with OAuth 2.0[​](#supporting-pkce-with-oauth-20 "Direct link to Supporting PKCE with OAuth 2.0") If the application that you are integrating with supports [Proof Key for Code Exchange](https://oauth.net/2/pkce/) (PKCE), you can add PKCE to your OAuth 2.0 connection by adding a `oauth2PkceMethod` property. You can specify either the `plain` or `S256` method, or omit the property to specify "no PKCE". Example PKCE declaration ```ts export const oauth = oauth2Connection({ oauth2Type: OAuth2Type.AuthorizationCode, oauth2PkceMethod: OAuth2PkceMethod.S256, key: "oauth", display: { label: "Airtable OAuth 2.0", description: "Airtable OAuth 2.0 Auth Code", }, inputs: { // ... }, }); ``` ##### Overriding OAuth 2.0 token refresh URL[​](#overriding-oauth-20-token-refresh-url "Direct link to Overriding OAuth 2.0 token refresh URL") The OAuth 2.0 standard specifies that the refresh endpoint URL is the same as the token endpoint URL. It is generally something like `https://example.com/oauth2/token`. However, some OAuth 2.0 providers use a different URL for refreshing tokens. For example, they may use `/oauth2/token` for the initial auth code exchange, but `/oauth2/refresh` for refreshing tokens. To override the refresh endpoint URL, add a `refreshUrl` property to your OAuth 2.0 connection: Example refresh URL override ```ts export const oauth = oauth2Connection({ oauth2Type: OAuth2Type.AuthorizationCode, key: "oauth", display: { label: "Acme OAuth 2.0", description: "Acme OAuth 2.0 Auth Code", }, inputs: { // ... refreshUrl: { label: "Refresh URL", placeholder: "Refresh URL", type: "string", required: true, shown: false, comments: "The Refresh URL for Acme Inc.", default: "https://example.com/oauth2/refresh", }, }, }); ``` #### Templating connection inputs[​](#templating-connection-inputs "Direct link to Templating connection inputs") Some apps provide their customers with unique OAuth 2.0 authorization and token URLs. When authenticating two of your customers, you may need to send one to `https://hooli.acme.com/oauth/authorize` and another to `https://pied-piper.acme.com/oauth/authorize`. Shopify is an example of an app that provisions customers with [unique OAuth endpoints](https://shopify.dev/docs/apps/build/authentication-authorization/access-tokens/token-exchange#step-2-get-an-access-token). Asking a customer to add their custom domain to several inputs (authorize URL, token URL, optional refresh URL, etc) is error-prone and not a good user experience. That's where templated connection inputs are helpful (not to be confused with [connection templates!](https://prismatic.io/docs/integrations/connections/integration-specific.md#connection-templates)). You can prompt a user for their custom domain (or other information) once, and generate other inputs' values automatically. So, a user can enter `pied-piper` once, and a connection could derive an authorization URL `https://pied-piper.acme.com/oauth/authorize` and token URL `https://pied-piper.acme.com/oauth/token` automatically. To add templated connection inputs to your custom connector, import `templateConnectionInputs`. Provide user-specified or global inputs as the function's first parameter, and templated inputs as the second parameter. In this example, we prompt a user for their Acme `domain`, and `authorizeUrl` and `tokenURL` are derived using `domain`. Example OAuth 2.0 auth code connection with templated inputs ```ts import { OAuth2Type, oauth2Connection, templateConnectionInputs, } from "@prismatic-io/spectral"; export const acmeOAuth = oauth2Connection({ key: "acmeOauth", display: { label: "Acme OAuth 2.0", description: "Connect to Acme with OAuth 2.0 auth code flow", }, oauth2Type: OAuth2Type.AuthorizationCode, inputs: templateConnectionInputs( { domain: { label: "Acme Subdomain", example: "pied-piper", type: "string", required: true, shown: true, comments: "Your acme subdomain. The **pied-piper** portion of **pied-piper**.acme.com.", }, clientId: { label: "Client ID", type: "string", required: true, shown: true, comments: "Obtain by creating an OAuth app [here](https://partners.acme.com/)", }, clientSecret: { label: "Client Secret", type: "password", required: true, shown: true, comments: "Obtain by creating an OAuth app [here](https://partners.acme.com/)", }, scopes: { label: "Scopes", example: "widgets.read widgets.write offline_access", default: "widgets.read widgets.write offline_access", type: "string", required: false, shown: true, comments: "A space-delimited set scopes (permissions) to request from your user. Read more [here](https://acme.dev/api/usage/access-scopes#authenticated-access-scopes)", }, }, { authorizeUrl: { label: "Authorize URL", placeholder: "Authorize URL", type: "template", comments: "The OAuth 2.0 Authorization URL for Acme", templateValue: "https://{{#domain}}.acme.com/oauth/authorize/", }, tokenUrl: { label: "Token URL", placeholder: "Token URL", type: "template", comments: "The OAuth 2.0 Token URL for Acme", templateValue: "https://{{#domain}}.acme.com/oauth/token/", }, }, OAuth2Type.AuthorizationCode, ), }); ``` ![Screenshot of templating connection inputs](/docs/assets/images/templating-connection-inputs-80d173992b5c6fbdf862b383526cd84a.png) use templated connection inputs for non-OAuth connections Templated connection inputs are not only for OAuth 2.0 authorize and token URLs. You can use templated connection inputs for any connection type. You can also use multiple inputs within a template. Suppose, for example, you need to build a URL using several inputs. You could prompt a user for their `username`, `password`, `host`, and `serviceName` and template a value of `https://{{#username}}:{{#password}}@{{#host}}/api/{{#serviceName}}`. #### Using connections with HTTP clients[​](#using-connections-with-http-clients "Direct link to Using connections with HTTP clients") While the majority of APIs you'll interact with are HTTP based, and most present a RESTful interface, not all are the same. Some APIs (like Prismatic's!) use GraphQL. Others use remote procedure calls (RPCs), like gRPC, XML RPC, or SOAP. Luckily, there is an [NPM](https://www.npmjs.com/) package for almost any protocol. * If you are working with an HTTP-based **REST API**, we recommend using Spectral's built-in `createClient` function, which creates an [Axios](https://axios-http.com/docs/intro) HTTP client behind the scenes with some useful settings pre-configured (see [example](#using-the-built-in-createclient-http-client) below). If your team is more comfortable with vanilla Axios or [node-fetch](https://www.npmjs.com/package/node-fetch), you can certainly use those, too. * For **GraphQL APIs**, we recommend using [graphql-request](https://www.npmjs.com/package/graphql-request). You can use a generic HTTP client, but `graphql-request` provides a handy `gql` string literal tag. * For **XML RPC APIs**, you can import [xmlrpc](https://www.npmjs.com/package/xmlrpc) into your component project, or you can reach for [soap](https://www.npmjs.com/package/soap) if it's a **SOAP API**. * It's far less common for HTTP API integrations, but [@grpc/grpc-js](https://www.npmjs.com/package/@grpc/grpc-js) can be used for **gRPC APIs**. Regardless of which client you use, you will likely need to set some HTTP headers for authentication, content type, etc. #### Using the built-in createClient HTTP client[​](#using-the-built-in-createclient-http-client "Direct link to Using the built-in createClient HTTP client") Spectral comes with a built-in HTTP client for integrating with REST APIs. Behind the scenes, `createClient` creates an Axios-based HTTP client with some timeout, retry, and debug logic built on top of it. You can see the source code for `createClient` in Spectral's [GitHub repo](https://github.com/prismatic-io/spectral/blob/main/packages/spectral/src/clients/http/index.ts). To create an HTTP client, feed the client a base URL for your API along with the header information you need for authentication. You can fetch authentication values from a connection. It may look something like this: Example createClient usage ```typescript import { createClient } from "@prismatic-io/spectral/dist/clients/http"; action({ perform: async (context, inputs) => { // Create the authenticated HTTP client const myClient = createClient({ baseUrl: "https://example.com/api", debug: false, headers: { "X-API-Key": inputs.connection.fields.apiKey, Accept: "application/json", }, responseType: "json", }); // Use the HTTP client to POST data to the API const response = await myClient.post("/items", { sku: "12345", quantity: 3, price: 20.25, }); // Return the response as the action's result return { data: response.data }; }, }); ``` Debugging an HTTP Connection If you would like to see the full contents of the HTTP request and response, set `debug: true`. You will see all endpoints, headers, response codes, etc. in the integration logs. Just remember to turn off debugging for production! ##### Adding retry logic to your HTTP client[​](#adding-retry-logic-to-your-http-client "Direct link to Adding retry logic to your HTTP client") APIs can be unreliable. Servers go down, networks hiccup, and sometimes requests time out. An API may also return a 429 "Too Many Requests" response if your integration is sending too many requests in a short period of time. The built-in `createClient` function supports automatic retries for failed requests. You can configure retry behavior by passing a `retryConfig` object with the following options: * `retries` - The number of times to retry a failed request * `retryDelay` - The number of milliseconds to wait between retry attempts * `useExponentialBackoff` - When `true`, doubles the retry delay after each attempt (e.g., 2000ms, 4000ms, 8000ms, 16000ms) * `retryAllErrors` - When `true`, retries all errors. When `false`, uses the `retryCondition` function * `retryCondition` - A custom function that determines whether to retry based on the error. The function receives the error object and should return `true` to retry or `false` to fail immediately. Here's a basic example that retries failed requests after 2 seconds with exponential backoff: Basic retry with exponential backoff ```typescript import { createClient } from "@prismatic-io/spectral/dist/clients/http"; action({ perform: async (context, inputs) => { const myClient = createClient({ baseUrl: "https://example.com/api", headers: { "X-API-Key": inputs.connection.fields.apiKey, Accept: "application/json", }, responseType: "json", retryConfig: { retries: 3, // Retry up to 3 times retryDelay: 2000, // Start with 2 second delay useExponentialBackoff: true, // 2s, 4s, 8s delays }, }); const response = await myClient.post("/items", { sku: "12345", quantity: 3, }); return { data: response.data }; }, }); ``` You can also configure retries to occur only for specific error conditions. For example, if you want to retry only when the API returns a 429 "Too Many Requests" status code: Retry only on HTTP 429 responses ```typescript import { createClient } from "@prismatic-io/spectral/dist/clients/http"; action({ perform: async (context, inputs) => { const myClient = createClient({ baseUrl: "https://example.com/api", headers: { "X-API-Key": inputs.connection.fields.apiKey, Accept: "application/json", }, responseType: "json", retryConfig: { retries: 5, // Retry up to 5 times for rate limiting retryDelay: 1000, // Wait 1 second between retries retryCondition: (error) => { // Only retry if we get a 429 status code return error.response?.status === 429; }, }, }); const response = await myClient.post("/items", { sku: "12345", quantity: 3, }); return { data: response.data }; }, }); ``` Combining retry strategies You can combine `useExponentialBackoff` with `retryCondition` to implement sophisticated retry logic. For example, you could use exponential backoff specifically for rate limiting errors while letting other errors fail immediately. #### Using existing component connections in data sources[​](#using-existing-component-connections-in-data-sources "Direct link to Using existing component connections in data sources") You may want to extend an existing component to populate a config variable. For example, you may want to fetch and filter specific information from a CRM or ERP and present the data to your user as a picklist menu. Your data source can reference any existing connection config variable - including those from built-in components. To use an existing component's connection, reference its connection's key names. The [AWS Glue component](https://prismatic.io/docs/components/aws-glue.md) , for example, has an `accessKeyId` and `secretAccessKey`. Your data source can reference those with: ```ts { perform: async (context, inputs) => { const { accessKeyId, secretAccessKey } = inputs.myConnection.fields; }; } ``` The field that you likely need to use for OAuth 2.0 connections is the connection's `access_token`, which is nested under `token` like this: ```ts { perform: async (context, inputs) => { const myAccessToken = inputs.myConnection.token.access_token; }; } ``` An example of reusing existing connections is available in the [Building a Field Mapper Data Source](https://prismatic.io/docs/integrations/data-sources/field-mapping/salesforce-field-mapper.md) tutorial which covers pulling down custom fields from Salesforce. --- #### Writing Custom Data Sources A **Data Source** fetches data from a third-party API that will be used to dynamically generate a config variable. When your customer deploys an instance, they use a [connection](https://prismatic.io/docs/custom-connectors/connections.md) to authenticate with a third-party API. A data source can generate a variety of [types of data](https://github.com/prismatic-io/spectral/blob/main/packages/spectral/src/types/DataSourceResult.ts) including a `string`, `date`, `picklist` (which is a `string[]`), complex `objectSelection` objects, and more. Here's a simple data source that fetches an array of customers, each with a `name` and `id`. It maps them to a `label`/`key` object so the customer's names show in a `picklist`, and the customer's `id` is saved: Fetch a string from an external API ```typescript import { dataSource, Element } from "@prismatic-io/spectral"; interface Customer { name: string; id: string; } const companyName = dataSource({ display: { label: "Fetch Customers", description: "Fetch an array of customers' names", }, inputs: { connection: input({ label: "Connection", type: "connection", required: true, }), }, perform: async (context, inputs) => { const client = createAcmeClient(inputs.connection); const response = await client.get<{ customers: Customer[] }>("/customers"); const customers = response.data.customers?.map((customer) => ({ label: customer.name, key: customer.id, })); return { result: customers }; }, dataSourceType: "picklist", examplePayload: { result: [ { label: "Smith Rocket Company", key: "abc-123" }, { label: "Mars Rocket Corp", key: "def-456" }, ], }, }); ``` In this example, we fetch several items from an API, including metadata about each item, so that a user can select one or more of the items and get that metadata of each: Fetch a string from an external API ```typescript const companyName = dataSource({ display: { label: "Fetch Items", description: "Fetch all available items", }, inputs: { connection: input({ label: "Connection", type: "connection", required: true, }), }, perform: async (context, inputs) => { const client = createAcmeClient(inputs.connection); const response = await client.get("/items"); const objects: ObjectSelection = response.data.items.map((item) => ({ object: { key: item.id, label: item.name }, fields: [ { key: item.quantity, label: "Quantity" }, { key: item.sku, label: "SKU" }, ], })); return { result: objects }; }, dataSourceType: "objectSelection", examplePayload: { result: [ { object: { key: "abc-123", label: "widgets" }, fields: [ { key: "5", label: "Quantity" }, { key: "0000000000", label: "SKU" }, ], }, ], }, }); ``` An example of a data source that generates a picklist is available in the [Slack component](https://github.com/prismatic-io/examples/blob/main/components/slack/src/dataSources/index.ts). #### JSON Forms data sources[​](#json-forms-data-sources "Direct link to JSON Forms data sources") [JSON Forms](https://jsonforms.io/) is a form-generating framework that allows you to create forms through JSON schema that you generate. A JSON Form can contain any number of string, boolean, number, date, time, datetime or enum (dropdown menu) inputs, and you have some control over how the input elements are rendered (in tabs, grouped, vertical or horizontal layout, etc). Full documentation on JSON Forms is available on their [documentation page](https://jsonforms.io/examples), including several examples. Prismatic offers a [JSON Forms playground](https://prismatic.io/docs/jsonforms/playground) where you can create new forms and see how they would be rendered in Prismatic. A JSON Form config data source must return two properties (and one optional property): * `schema` defines the types of inputs your form contains (its `properties`), and some optional validators, like which properties are required. * `uiSchema` defines how those inputs should be rendered, like whether the inputs should be vertically or horizontally aligned. * `data` (optional) allows you to specify some default values for your form inputs. This simple example's `schema` declares two inputs - `name` (a string) and `age` (an integer between 0 and 150), and the `uiSchema` labels the first input "First Name" and places the input fields horizontally next to one another. A simple JSON Form ```typescript return { result: { schema: { type: "object", properties: { name: { type: "string", }, age: { type: "integer", minimum: 0, maximum: 150, }, }, }, uiSchema: { type: "HorizontalLayout", elements: [ { type: "Control", scope: "#/properties/name", }, { type: "Control", scope: "#/properties/age", }, ], }, data: { name: "Bob", age: 20 }, }, }; ``` The resulting JSON Form looks like this: ![Simple JSON form config variable](/docs/assets/images/simple-example-result-7f7a01fad848d0ee5d15df556fd7b7b8.png) ##### Data mapping with JSON Forms[​](#data-mapping-with-json-forms "Direct link to Data mapping with JSON Forms") A common use-case for JSON Forms is presenting a data-mapping UI to a user. For the sake of illustration, we'll pull down [users](https://jsonplaceholder.typicode.com/users) and [to-do tasks](https://jsonplaceholder.typicode.com/todos?_limit=10) from [JSON Placeholder](https://jsonplaceholder.typicode.com/), and give our users the ability to map any number of users to tasks. In order to provide any number of mappings of user-to-task, our JSON schema will need to contain an [`array`](https://jsonforms.io/examples/array) of `object`. Each `object` will contain a `user` property and a `task` property. The `user` and `task` property will each have a [`oneOf`](https://jsonforms.io/docs/multiple-choice#one-of) property, which represents a dropdown menu. For the sake of illustration, we also provide a `data` value containing some defaults that our UI should show. This property can be omitted. Data mapping with JSON forms ```typescript import axios from "axios"; import { dataSource, util } from "@prismatic-io/spectral"; interface User { id: number; name: string; email: string; } interface Task { id: number; title: string; } const userTaskExample = dataSource({ dataSourceType: "jsonForm", display: { label: "User/Task Mapper", description: "Map users to to-do tasks", }, inputs: {}, perform: async (context, inputs) => { const { data: users } = await axios.get( "https://jsonplaceholder.typicode.com/users", ); const { data: tasks } = await axios.get( "https://jsonplaceholder.typicode.com/todos?_limit=10", ); const schema = { type: "object", properties: { mymappings: { // Arrays allow users to make one or more mappings type: "array", items: { // Each object in the array should contain a user and task type: "object", properties: { user: { type: "string", // Have users select "one of" a dropdown of items oneOf: users.map((user) => ({ // JSON Forms expects a string value: const: util.types.toString(user.id), title: user.name, })), }, task: { type: "string", oneOf: tasks.map((task) => ({ const: util.types.toString(task.id), title: task.title, })), }, }, }, }, }, }; const uiSchema = { type: "VerticalLayout", elements: [ { type: "Control", scope: "#/properties/mymappings", label: "User <> Task Mapper", }, ], }; // Provide a default value, mapping of the first user to the first task const defaultValues = { mymappings: [ { user: util.types.toString(users[0].id), task: util.types.toString(tasks[0].id), }, ], }; return { result: { schema, uiSchema, data: defaultValues }, }; }, }); ``` The resulting JSON Form looks like this: ![Data mapping JSON form result](/docs/assets/images/data-mapping-json-form-result-8637c4495d93588f47d4ef7df9b357ff.png) ##### JSON Forms data validation[​](#json-forms-data-validation "Direct link to JSON Forms data validation") You can validate the data returned by a JSON Form by passing the form data into a subsequent data source. See [JSON Form Validation](https://prismatic.io/docs/integrations/data-sources/json-forms/form-validation.md) --- #### Data Sources in Embedded Workflow Builder In the [low-code integration designer](https://prismatic.io/docs/integrations/low-code-integration-designer.md), data sources populate [config wizard](https://prismatic.io/docs/integrations/config-wizard.md) variables with dynamic data from third-party APIs. However, the [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) doesn't have config wizards. Instead, you can use data sources to provide dynamic options directly for action inputs. #### How data sources work in embedded workflow builder[​](#how-data-sources-work-in-embedded-workflow-builder "Direct link to How data sources work in embedded workflow builder") In the embedded workflow builder, data sources provide picklist options for action inputs. When you reference a data source in an action input, the workflow builder fetches the data and displays it as a dropdown menu. This allows your customers to select from dynamic lists like: * Slack channels from their workspace * Salesforce objects from their org * Users from their system * Any other data fetched from external APIs #### Creating a data source for action inputs[​](#creating-a-data-source-for-action-inputs "Direct link to Creating a data source for action inputs") To use a data source with an action input in the embedded workflow builder, you create both a data source and an action that references it. Here's an example that fetches a list of people from an API and lets users select one: * index.ts * connections.ts * dataSources.ts * actions.ts index.ts ```typescript /** * Main component file that exports the component definition. * This file imports and combines the actions, data sources, and connections * defined in separate files. */ import { component } from "@prismatic-io/spectral"; import { getPerson, listPeople } from "./actions"; import { peopleApiKey } from "./connections"; import { selectPerson } from "./dataSources"; export default component({ key: "inlineDataSources", public: false, display: { label: "inline-data-sources", description: "Component demonstrating inline data sources for EWB", iconPath: "icon.png", }, // Export the action that uses the data source actions: { getPerson, listPeople }, // Export the data source so it can be referenced by actions dataSources: { selectPerson }, // Export the connection for authenticating with the API connections: [peopleApiKey], }); ``` connections.ts ```typescript /** * Connection definitions and helpers for authenticating with the People API. * This file defines the connection configuration and provides a helper function * to create an authenticated HTTP client. */ import { connection, input, type Connection } from "@prismatic-io/spectral"; import { createClient } from "@prismatic-io/spectral/dist/clients/http"; // Define the connection that users will configure export const peopleApiKey = connection({ key: "peopleApiKey", display: { label: "People API Key", description: "API Key for accessing the People API", }, inputs: { apiKey: input({ label: "API Key", type: "string", required: true }), }, }); // Connection input that can be used by actions and data sources export const connectionInput = input({ label: "Connection", type: "connection", required: true, }); // Helper function to create an authenticated HTTP client export const getPeopleClient = (connection: Connection) => { const apiKey = connection.fields.apiKey as string; return createClient({ baseUrl: "https://jsonplaceholder.typicode.com", headers: { Authorization: `Bearer ${apiKey}`, }, }); }; ``` dataSources.ts ```typescript /** * Data source that fetches a list of people from the API. * In the embedded workflow builder, this data source provides dropdown options * for action inputs that reference it. */ import { dataSource } from "@prismatic-io/spectral"; import { connectionInput, getPeopleClient } from "./connections"; interface Person { id: number; name: string; } export const selectPerson = dataSource({ display: { label: "Select Person", description: "Select a person from the list", }, // Use "picklist" type to display options in a dropdown menu dataSourceType: "picklist", inputs: { connection: connectionInput }, perform: async (context, inputs) => { // Fetch people from the API const client = getPeopleClient(inputs.connection); const response = await client.get("/users"); const people = response.data; // Return data in key/label format for the picklist return { result: people.map((person) => ({ key: person.id.toString(), // The value that will be saved label: person.name, // The text that users will see })), }; }, }); ``` actions.ts ```typescript /** * Action that retrieves a specific person by ID. * The personId input references the selectPerson data source, * which provides a dropdown menu of people in the embedded workflow builder. */ import { action, input, util } from "@prismatic-io/spectral"; import { connectionInput, getPeopleClient } from "./connections"; interface Person { id: number; name: string; username: string; email: string; address: { street: string; suite: string; city: string; zipcode: string; geo: { lat: string; lng: string; }; }; phone: string; website: string; company: { name: string; catchPhrase: string; bs: string; }; } export const listPeople = action({ display: { label: "List People", description: "List the ID and name of all people stored in the Acme People API", }, inputs: { connection: connectionInput, }, perform: async (_context, { connection }) => { // Fetch people from the API const client = getPeopleClient(connection); const response = await client.get[]>("/users"); return { data: response.data.map((person) => ({ id: person.id, name: person.name, })), }; }, examplePayload: { data: [ { id: 1, name: "Leanne Graham" }, { id: 2, name: "Ervin Howell" }, { id: 3, name: "Clementine Bauch" }, ], }, }); export const getPerson = action({ display: { label: "Get Person", description: "Get a person stored in the Acme People API by their ID", }, inputs: { connection: connectionInput, personId: input({ label: "Person ID", comments: "A numerical ID of a person stored in the Acme People API" type: "string", required: true, clean: util.types.toString, // Reference the data source by its key - this creates a dropdown in EWB dataSource: "selectPerson", }), }, perform: async (_context, { connection, personId }) => { // Fetch the specific person using the selected ID const client = getPeopleClient(connection); const response = await client.get(`/users/${personId}`); return { data: response.data }; }, examplePayload: { data: { id: 1, name: "Leanne Graham", username: "Bret", email: "Sincere@april.biz", address: { street: "Kulas Light", suite: "Apt. 556", city: "Gwenborough", zipcode: "92998-3874", geo: { lat: "-37.3159", lng: "81.1496", }, }, phone: "1-770-736-8031 x56442", website: "hildegard.org", company: { name: "Romaguera-Crona", catchPhrase: "Multi-layered client-server neural-net", bs: "harness real-time e-markets", }, }, }, }); ``` Given the code above, a user configuring the `Get Person` action in the embedded workflow builder will see a dropdown menu for the `Person ID` input populated with names fetched from the API. ![Embedded Workflow Builder Data Source Dropdown](/docs/assets/images/data-source-dropdown-f046a0bd9c0dd0f7ea2d4d64b272ae62.png) ##### Key points[​](#key-points "Direct link to Key points") When creating data sources for the embedded workflow builder: 1. **Reference by key**: In the action input, set `dataSource` to the key of your data source (in the example above, `"selectPerson"`). 2. **Export the data source**: Include the data source in your component's `dataSources` export. 3. **Use picklist type**: The `dataSourceType` should be `"picklist"` for dropdown menus. 4. **Return key/label pairs**: The data source should return an array of objects with `key` and `label` properties. The embedded workflow builder will automatically fetch the data source when users configure the action and display the results as a dropdown menu. #### Data source types in embedded workflow builder[​](#data-source-types-in-embedded-workflow-builder "Direct link to Data source types in embedded workflow builder") Only **picklist** data source types work as action inputs in the embedded workflow builder For complete information on creating data sources, see [Config Wizard Data Sources](https://prismatic.io/docs/custom-connectors/data-sources.md). --- #### Custom Connector Error Handling #### Global error handlers[​](#global-error-handlers "Direct link to Global error handlers") The actions in your component might all wrap API endpoints using an HTTP client, and that client might throw certain errors. You could handle those errors within each action, but you would end up writing the same error handlers over and over. You can now specify an error handler function to run whenever any of your actions throws an error. To specify an error handler, add a `hooks` block to your `component({})` function definition: ```typescript component({ // ... hooks: { error: (error) => doSomething(error), }, }); ``` For example, the popular HTTP client [axios](https://www.npmjs.com/package/axios) throws an error whenever it receives a status code that's [*not* between 200-299](https://github.com/axios/axios/blob/1f13dd7e26124a27c373c83eff0a8614acc1a04f/lib/defaults/index.js#L127-L129). If your HTTP client receives a status code in the 4xx or 5xx range, an error is thrown with a minimal message. If you would like additional information, like the status code or full response to the HTTP request, you can inspect the error being thrown and return a more detailed error message, as illustrated in Spectral [here](https://github.com/prismatic-io/spectral/blob/v6.5.0/packages/spectral/src/clients/http/index.ts#L53-L62). #### Measuring performance of a custom connector[​](#measuring-performance-of-a-custom-connector "Direct link to Measuring performance of a custom connector") When in [debug mode](https://prismatic.io/docs/integrations/troubleshooting.md#debug-mode), you can leverage functions of `context.debug` to measure [how long](https://prismatic.io/docs/integrations/troubleshooting.md#measuring-time-performance-in-a-code-block-or-custom-connector) specific portions of your actions take to run, and [how much memory](https://prismatic.io/docs/integrations/troubleshooting.md#measuring-memory-performance-in-a-code-block-or-custom-connector) they consume (see links for examples). --- #### Fork an Built-in Connector All built-in connectors are open source and available on [GitHub](https://github.com/prismatic-io/components). The actions, triggers and data sources in these connectors address common integration needs, but APIs are often vast - an API may have 1000's of unique endpoints, and the connector may only implement actions for a subset of them. If you need to access an endpoint that isn't implemented in the connector, you can use the [raw request](https://prismatic.io/docs/integrations/low-code-integration-designer/raw-request-actions.md) action, or you can fork the connector and add the functionality you need. To fork a connector, you can either copy a single connector's code into your private repo, or you can [fork the entire components repo](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/fork-a-repo). After forking the connector code, update `src/index.ts` so that the `public` property is set to `false`. You can update the `key` or `display.label` properties or `src/assets/icon.png` file to customize the connector's name and icon. Alternatively, you can create a net new connector from scratch and use the existing connector as a reference for how to interact with the third-party app's API. --- #### Prismatic Skills for Claude Code [Prismatic Skills](https://github.com/prismatic-io/prismatic-skills) is a Claude Code plugin that gives your AI assistant deep knowledge of Prismatic's platform and the tools to interact with it. Instead of working from generic code suggestions, Claude uses Prismatic-specific agents and knowledge bases to guide you through building custom connectors, code-native integrations, and embedded marketplace experiences from requirements through deployment. #### Prerequisites[​](#prerequisites "Direct link to Prerequisites") Before installing Prismatic Skills, ensure you have: * [Claude Code](https://claude.ai/code) installed * The [Prism CLI](https://prismatic.io/docs/custom-connectors/get-started/setup.md#install-the-prismatic-cli-tool) installed globally and authenticated (`npm install -g @prismatic-io/prism`) * An active Prismatic account with integration and component permissions #### Installation[​](#installation "Direct link to Installation") Install Prismatic Skills using the Claude Code CLI: ```bash claude plugin marketplace add prismatic-io/prismatic-skills claude plugin install prismatic-skills@prismatic-skills ``` Or, from within the Claude Code client: ```text /plugin marketplace add prismatic-io/prismatic-skills /plugin install prismatic-skills@prismatic-skills ``` #### Available commands[​](#available-commands "Direct link to Available commands") Prismatic Skills adds six slash commands to Claude Code. ##### `/prismatic-skills:build-component`[​](#prismatic-skillsbuild-component "Direct link to prismatic-skillsbuild-component") Guides you through building and deploying a Prismatic custom connector. Claude walks through setup, requirements gathering, external API research, project scaffolding, code generation, and then builds and publishes the component. ##### `/prismatic-skills:build-integration`[​](#prismatic-skillsbuild-integration "Direct link to prismatic-skillsbuild-integration") Guides you through building and deploying a code-native integration (CNI). Claude handles setup, requirements gathering, credential collection, scaffolding, code generation, and then deploys, tests, and iterates until the integration works. ##### `/prismatic-skills:modify-integration`[​](#prismatic-skillsmodify-integration "Direct link to prismatic-skillsmodify-integration") Guides you through modifying an existing code-native integration. Claude extracts the current state of your integration, captures what you want to change - such as adding flows, changing error handling, adding components, or fixing bugs - and then applies targeted edits, rebuilds, deploys, and tests. ##### `/prismatic-skills:migrate-integration`[​](#prismatic-skillsmigrate-integration "Direct link to prismatic-skillsmigrate-integration") Guides you through migrating an integration from another platform to a Prismatic code-native integration. Claude detects the source platform (like Boomi or Cyclr), parses the export, builds a migration plan with confidence scores, and then drives the standard build flow with pre-populated requirements derived from the original integration. ##### `/prismatic-skills:embedded`[​](#prismatic-skillsembedded "Direct link to prismatic-skillsembedded") Guides you through embedding Prismatic's marketplace and workflow builder in your web application. Claude walks through signing key setup, JWT backend generation, frontend SDK integration, theming, and internationalization across React, Next.js, Vue, Svelte, and vanilla JS. ##### `/prismatic-skills:orby`[​](#prismatic-skillsorby "Direct link to prismatic-skillsorby") Launches Orby, an interactive Prismatic platform guide. Orby can explore your Prismatic environment, perform platform operations, construct and run GraphQL queries against the Prismatic API, search Prismatic documentation, and troubleshoot issues with your integrations. #### How it works[​](#how-it-works "Direct link to How it works") Prismatic Skills is built on specialized agents and knowledge bases that Claude loads progressively as needed, keeping your context window focused on the task at hand. ![Claude Code loading Prismatic integration patterns and exploring a code-native integration project structure](/docs/assets/images/agent-skills-dc34a7264705f6bfe8ccfc9438b021a1.png) ##### Agents[​](#agents "Direct link to Agents") | Agent | Description | | ------------------------- | ---------------------------------------------------------------------------------------- | | `component-builder` | Builds custom connectors from requirements to deployment | | `cni-builder` | Builds and modifies code-native integrations from requirements to deployment | | `embed-advisor` | Guides embedding of Prismatic's marketplace and workflow builder in your app | | `external-api-researcher` | Researches external APIs and extracts structured specifications | | `migration-analyzer` | Analyzes integration exports from other platforms and produces migration plans | | `migration-reviewer` | Reviews generated code against the original export for field and transformation accuracy | | `orby` | Interactive Prismatic platform guide | ##### Knowledge bases[​](#knowledge-bases "Direct link to Knowledge bases") | Skill | Contents | | ---------------------- | --------------------------------------------------------------------------------------------- | | `component-patterns` | Reference docs and code generation patterns for custom connectors | | `embedded` | JWT authentication, SDK setup, marketplace and workflow builder embedding, theming, i18n | | `integration-patterns` | CNI pattern library covering webhooks, state management, error handling, OAuth, and more | | `migration-framework` | Standard integration schema, confidence scoring, and migration-aware code generation patterns | | `boomi-migration` | Boomi Component XML export parsing and concept mapping to Prismatic equivalents | | `cyclr-migration` | Cyclr cycle JSON export parsing and concept mapping to Prismatic equivalents | | `prismatic-api` | GraphQL query reference and API access patterns | | `prismatic-docs` | Documentation search strategies | #### Related tools[​](#related-tools "Direct link to Related tools") Prismatic Skills works well alongside other development tools in the [Development Environment](https://prismatic.io/docs/custom-connectors/get-started/setup.md) section: * The [Prism MCP dev server](https://prismatic.io/docs/dev-tools/prism-mcp.md) gives your AI assistant direct API access to your Prismatic tenant - useful for listing components, publishing integrations, and generating manifests from any AI coding assistant. * The [Prismatic VS Code extension](https://prismatic.io/docs/dev-tools/vscode-extension.md) provides test runners, config wizards, and execution result views directly in your IDE. --- #### Set Up Your Dev Environment Code-native integrations and custom connectors are built with TypeScript using the [@prismatic-io/spectral](https://www.npmjs.com/package/@prismatic-io/spectral) SDK. This article outlines recommendations on how to set up your development environment. #### Operating system considerations[​](#operating-system-considerations "Direct link to Operating system considerations") You can develop Prismatic connectors and integrations on any operating system that supports Node.js (Windows, Linux, MacOS, etc). If you use Windows, you may find it helpful to work in a Windows Subsystem for Linux [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) environment. #### Install Node.js[​](#install-nodejs "Direct link to Install Node.js") Custom connectors and integrations are written in Node.js (with TypeScript layered on top). While many versions of Node.js can be used to build custom connectors and integrations, we recommend using the latest LTS version available on [NodeJS.org](https://nodejs.org/en/about/previous-releases). Once you install Node.js, ensure that you can run both `node --version` and `npm --version`. #### Node package managers[​](#node-package-managers "Direct link to Node package managers") While `npm` is the default package manager for Node.js, you can also use `yarn`, `pnpm` or `bun` if you prefer. #### Install an IDE[​](#install-an-ide "Direct link to Install an IDE") If you have a favorite code editor, use that. We've found that [VS Code](https://code.visualstudio.com/) works great for custom connector development, but some of our developers prefer [Sublime](https://www.sublimetext.com/) or [neovim](https://neovim.io/). If you use VS Code, scaffolded projects include a `.vscode/` directory with recommended extensions and settings, and VS Code prompts you to install them when you open the project. You may also want to install the [Prismatic VS Code extension](https://prismatic.io/docs/dev-tools/vscode-extension.md) to enhance your development workflow with test runners, config wizards, and execution result views directly in your editor. #### Install the Prismatic CLI tool[​](#install-the-prismatic-cli-tool "Direct link to Install the Prismatic CLI tool") The Prismatic CLI tool, `prism` is used for initializing and publishing custom connectors and integrations. You can install it after installing Node.js and `npm` with this command: Install prism ```bash npm install --global @prismatic-io/prism ``` Scaffolding toolchain `prism components:init` and `prism integrations:init` scaffold new projects with our recommended toolchain. If you run into issues with the recommended toolchain, you can scaffold with the previous toolchain by passing `--toolchain legacy` - and please [contact support](mailto:support@prismatic.io) so we can address it. Once you've installed `prism`, log in by typing `prism login`. You'll be prompted to enter your Prismatic credentials, and then to authorize `prism` to access your Prismatic account. Click **Accept**. Once you've logged in, run `prism me` to verify that you are logged in. ```bash $ prism me Name: John Doe Email: john.doe@example.com Organization: Example Corp - US Region Endpoint URL: https://app.prismatic.io ``` #### AI-assisted development[​](#ai-assisted-development "Direct link to AI-assisted development") To accelerate your development workflow, Prismatic provides two AI tools: * The [Prism MCP dev server](https://prismatic.io/docs/dev-tools/prism-mcp.md) connects any AI coding assistant (Cursor, GitHub Copilot, etc.) to your Prismatic tenant, giving it tools to list components, publish integrations, generate manifests, and more. * [Prismatic Skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) is a Claude Code plugin with specialized agents and knowledge bases for building custom connectors and code-native integrations end-to-end. --- #### Handling Large Files in Custom Components If you need to process large files in your integration, it's easy to exhaust your available memory and encounter out-of-memory (OOM) problems. For example, if you pull down a 100 MB file from an SFTP server, deserialize the CSV to a JavaScript object, map each row to a new format, serialize each row, etc., you can end up with a dozen copies of the data in memory and can overflow the 1GB of memory that the integration runner has by default. Rather than loading an entire large file at once, you can load and process smaller portions of the file at a time. That way, you can load a few kilobytes of a file, or a few rows of a CSV, process those, and then move to the next set of bytes or rows. If done correctly, a step can process very large files with only a few megabytes of memory. You can process large files a small portion at a time in Node.js using [streams](https://nodejs.org/api/stream.html). Let's look at a couple of examples. #### Streaming a large file from HTTP to SFTP[​](#streaming-a-large-file-from-http-to-sftp "Direct link to Streaming a large file from HTTP to SFTP") Suppose that your integration needs to pull down a file from an HTTP endpoint, and save that file to an SFTP server. If your file is large (say, 200MB in size), and you use several steps to accomplish your goal, you can end up using well over 1 GB of memory: * The HTTP step will use 200MB when downloading the file * The HTTP step will use 200MB+ when serializing and persisting the step result * Depending on the output format, the HTTP step may use another 200MB+ to deserialize the file to JSON, etc. * The SFTP step will use 200MB when converting the file's contents to a communication format that SFTP understands If you download the file from the HTTP endpoint a few KB at a time, and stream those bytes directly to the SFTP server, your step will only use a few MB of memory at a time - the entire file will never be loaded into memory at once. In the example below, the `axios.get` function takes a parameter, `{ responseType: "stream" }`. That will cause `response.data` to be of type `stream.Readable`. That stream can be passed to an SFTP client's `.put` function. When it detects a readable stream, [ssh2-sft-client](https://www.npmjs.com/package/ssh2-sftp-client) will pipe that stream to the SFTP server as chunks are received. * actions.ts * inputs.ts * connections.ts Stream a file from the internet to an SFTP server ```ts import { action, util, ConnectionError } from "@prismatic-io/spectral"; import { connectionInput, sftpPathInput, sourceUrlInput } from "./inputs"; import axios from "axios"; import SFTPClient from "ssh2-sftp-client"; const uploadFileFromUrl = action({ display: { label: "Upload file from URL", description: "Upload a file from a URL to an SFTP server", }, inputs: { connection: connectionInput, sourceUrl: sourceUrlInput, sftpPath: sftpPathInput, }, perform: async (context, inputs) => { const sftpClient = new SFTPClient(); const { username, password, host, port, timeout } = inputs.connection.fields; try { await sftpClient.connect({ username: util.types.toString(username), password: util.types.toString(password), host: util.types.toString(host), port: util.types.toInt(port), readyTimeout: util.types.toInt(timeout) || 3000, }); } catch (err) { throw new ConnectionError( inputs.connection, `Unable to connect to SFTP server. ${err}`, ); } const response = await axios.get(inputs.sourceUrl, { responseType: "stream", }); try { const result = await sftpClient.put(response.data, inputs.sftpPath); return { data: result }; } finally { await sftpClient.end(); } }, }); export default { uploadFileFromUrl }; ``` ```ts import { input, util } from "@prismatic-io/spectral"; export const connectionInput = input({ label: "Connection", type: "connection", required: true, }); export const sourceUrlInput = input({ label: "Source File URL", type: "string", clean: util.types.toString, required: true, example: "https://files.example.com/my-file.pdf", }); export const sftpPathInput = input({ label: "Destination File Path", type: "string", clean: util.types.toString, required: true, example: "/path/to/my-file.pdf", }); ``` ```ts import { connection } from "@prismatic-io/spectral"; export const basic = connection({ key: "basic", display: { label: "Basic Username/Password", description: "Basic Username and Password connection", }, inputs: { username: { label: "Username", type: "string", required: true }, password: { label: "Password", type: "password", required: true }, host: { label: "Host", type: "string", required: true }, port: { label: "Port", type: "string", default: "22", required: true }, }, }); export default [basic]; ``` You can extend the HTTP call to be authenticated, have search parameters, etc. As long as you specify `{ responseType: "stream" }`, your response will be a readable stream. Similar concepts can be applied to stream a file from HTTP to Dropbox, Google Drive, Azure Files, or most other file storage systems - most Node.js file storage libraries accept streams as inputs or have writeable stream functions. #### Streaming and processing a large CSV from Amazon S3[​](#streaming-and-processing-a-large-csv-from-amazon-s3 "Direct link to Streaming and processing a large CSV from Amazon S3") In this example, suppose you host large CSV files in Amazon S3 that represent transactions. These files are formatted like this: transactions.csv ```csv id,product,quantity,price 1,widgets,5,100 2,gadgets,10,3.5 3,whatsits,1,200 ``` However, there are thousands of records and the file is hundreds of MB in size and cannot be loaded into memory all at once. You want to find the total price of the transactions (sum of `quantity x price`) and return just the total price. 1. First, we'll use the AWS SDK to fetch an object from Amazon S3. The resulting object's `.Body` property is an instance of `stream.Readable`. 2. Then, we'll stream the readable file into a popular CSV parser, [PapaParse](https://www.papaparse.com/). PapaParse accepts streams and provides a callback function, `step`, which is run whenever a line of a CSV stream is processed. As we process each record, we'll add `quantity x price` to the total price. 3. Finally, we'll return the total price as the step's result. Because we're not returning the entire file that was read, the runner does not spend time and memory serializing the file as a step result. ```ts import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3"; import { action, input, util } from "@prismatic-io/spectral"; import { parse } from "papaparse"; import { Readable } from "stream"; interface CsvRecord { data: { id: string; product: string; quantity: number; price: number; }; } export const processLargeCsvFromS3 = action({ display: { label: "Process Large CSV from S3", description: "Find the total price of many transactions in a CSV file", }, inputs: { connection: input({ label: "Connection", type: "connection", required: true, }), bucket: input({ label: "Bucket Name", type: "string", required: true, clean: util.types.toString, }), objectKey: input({ label: "Object Key", type: "string", required: true, clean: util.types.toString, }), }, perform: async (context, inputs) => { // Initialize an Amazon S3 client const s3 = new S3Client({ region: "us-east-2", credentials: { accessKeyId: util.types.toString(inputs.connection.fields.accessKeyId), secretAccessKey: util.types.toString( inputs.connection.fields.secretAccessKey, ), }, }); // Initialize an accumulator let total = 0; // Fetch an object from Amazon S3. // The returned item.Body is stream.Readable const command = new GetObjectCommand({ Bucket: inputs.bucket, Key: inputs.objectKey, }); const item = await s3.send(command); // Parse the stream as it is read from S3, running "step" for each record read await new Promise((resolve) => { parse(item.Body as Readable, { header: true, dynamicTyping: true, // As each line in the CSV is read, function "step" is called step: (record: CsvRecord, parser) => { parser.pause(); // Pause the parser while work is done total += record.data.quantity * record.data.price; parser.resume(); // Re-enable the CSV parser after work is complete }, // When the stream ends, run complete complete: () => { resolve(null); }, }); }); return { data: total }; }, }); export default { processLargeCsvFromS3 }; ``` The `parser.pause()` and `parser.resume()` above are not necessary for our example, but if you need to do work on each record that you read (for example, transform the data and send it to an API), pausing the CSV parser while that work is done can help you to avoid overwhelming the API you're sending data to. Some File Formats stream better than others A CSV file is able to be processed readily as a stream because it can be read line-by-line. Other formats, like JSON or XML, have beginning and ending brackets or tags that may require you to load the file in its entirety. If you are dealing with JSON, consider [JSONL](https://jsonlines.org/) format, which can be read line-by-line. If you are parsing XML, you can parse the XML file by streaming data into [node-xml-stream-parser](https://www.npmjs.com/package/node-xml-stream-parser) and looking for specific XML tags. --- #### Initializing a New Component #### Initializing a new connector[​](#initializing-a-new-connector "Direct link to Initializing a new connector") To initialize a new project, run `prism components:init {{ CONNECTOR NAME }}`. If you do not have Prismatic's CLI tool, `prism`, installed, please take a moment to look through the [Prism overview page](https://prismatic.io/docs/cli.md). ```bash prism components:init acme-erp ``` Your component name must be comprised of alphanumeric characters, hyphens, and underscores, and start and end with alphanumeric characters. You will be prompted with a couple of questions - to give a description for your component and to determine what connection authorization type should be templated (you can edit those later). This will create a directory structure that looks like this: ```text acme-erp ├── .env.testing ├── .vscode │ ├── extensions.json │ └── settings.json ├── assets │ └── icon.png ├── biome.json ├── package.json ├── src │ ├── actions.test.ts │ ├── actions.ts │ ├── client.ts │ ├── connections.ts │ ├── dataSources.test.ts │ ├── dataSources.ts │ ├── index.ts │ ├── triggers.test.ts │ └── triggers.ts ├── tsconfig.json ├── tsdown.config.mts └── vitest.config.ts ``` * `.env.testing` contains environment variables that are loaded when you run your component's tests locally. * `.vscode/` contains recommended settings and extensions for the project. If you use VS Code, it prompts you to install the recommended extensions when you open the project. * `assets/icon.png` is the icon that will be displayed next to your component. Square transparent PNGs at least 128 x 128 pixels in size look best, and will be scaled by the web application appropriately. * `biome.json` contains linting and formatting configuration. * `package.json` is a standard node package definition file. * `src/actions.ts` contains your component's actions. This can be broken out into distinct files as your code grows. * `src/actions.test.ts` contains tests for your component's actions. See [Unit Testing Custom Components](https://prismatic.io/docs/custom-connectors/unit-testing.md). * `src/client.ts` contains a shared "client". This is handy for actions that share a mechanism for connecting to an API. The "client" will probably be an authenticated HTTP client that's configured to make requests of a particular endpoint. * `src/connections.ts` contains the connections that your component uses to authenticate with third-party APIs. * `src/dataSources.ts` contains data sources, which surface configuration data to integration builders. * `src/dataSources.test.ts` contains tests for your component's data sources. * `src/index.ts` contains your component definition. * `src/triggers.ts` contains custom triggers. * `src/triggers.test.ts` contains tests for your component's triggers. * `tsconfig.json` contains configuration for [TypeScript](https://www.typescriptlang.org/). * `tsdown.config.mts` contains build configuration. * `vitest.config.ts` contains test configuration. #### Custom connectors from WSDLs or OpenAPI specs[​](#custom-connectors-from-wsdls-or-openapi-specs "Direct link to Custom connectors from WSDLs or OpenAPI specs") Third-party applications and services often provide APIs with hundreds of RESTful endpoints. It would be tedious to manually write actions for each individual endpoint. Luckily, many companies also provide an API specification - commonly a [Web Service Definition Language (WSDL)](https://www.w3.org/TR/2001/NOTE-wsdl-20010315) file, or an [OpenAPI (Swagger)](https://swagger.io/specification/) specification. You can generate a custom component from a WSDL file with `prism` by passing the `--wsdl-path` flag to the `components:init` subcommand: ```bash prism components:init myThirdPartyComponent --wsdl-path ./thirdPartySpec.wsdl ``` You can generate a custom component from an OpenAPI definition (you can use a YAML or JSON file - both work fine) with `prism` by passing the `--open-api-path` flag to the `components:init` subcommand: ```bash prism components:init myThirdPartyComponent --open-api-path ./third-party-openapi-spec.json ``` The custom component code that is generated may require some tweaking - some APIs have undocumented required headers, or irregular authentication schemes (so you may need to touch up `src/client.ts` or `src/connections.ts`). But, this does give you a great jumping-off point when building a custom component for a third-party app. --- #### Step Inputs [Actions](https://prismatic.io/docs/custom-connectors/actions.md), [Triggers](https://prismatic.io/docs/custom-connectors/triggers.md) and [Data Sources](https://prismatic.io/docs/custom-connectors/data-sources.md) are configured via `inputs`. Each `input` is comprised of a required `label` and `type`, and optional `placeholder`, `default`, `comments`, `required` and `model`. Consider this example input: ```typescript const middleName = input({ label: "Middle Name", placeholder: "Middle name of a person", type: "string", required: false, default: "", comments: "Leave blank if the user has no middle name", clean: (value) => util.types.toString(value), }); ``` This contributes to an input prompt that looks like this: ![Step Config - Properly Format Name in Prismatic app](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAhQAAACCCAYAAAAe2lGSAAAAAXNSR0IArs4c6QAAAFZlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA5KGAAcAAAASAAAARKACAAQAAAABAAACFKADAAQAAAABAAAAggAAAABBU0NJSQAAAFNjcmVlbnNob3Spc8vtAAAB1mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4xMzA8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NTMyPC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6VXNlckNvbW1lbnQ+U2NyZWVuc2hvdDwvZXhpZjpVc2VyQ29tbWVudD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+ClFvdBcAACFVSURBVHgB7Z0HfFRV9sdPeoeERQggJZSgEJTQDEgAgVBlFVDEsruuK6iUoICFXbCA69JUCE2kSAfpXVqoAiH0DiEJIC0Qekhv/3tueM/JMJNpIfOY/+9+PmTee7d/73Pu751z3uiUnpGZT0ggAAIgAAIgAAIgYAMBZxvqoioIgAAIgAAIgAAISAIQFLgRQAAEQAAEQAAEbCYAQWEzQjQAAiAAAiAAAiAAQYF7AARAAARAAARAwGYCEBQ2I0QDIAACIAACIAACEBS4B0AABEAABEAABGwmAEFhM0I0AAIgAAIgAAIgAEGBewAEQAAEQAAEQMBmAhAUNiNEAyAAAiAAAiAAAhAUuAdAAARAAARAAARsJgBBYTNCNAACIAACIAACIABBgXsABEAABEAABEDAZgIQFDYjRAMgAAIgAAIgAAIQFLgHQAAEQAAEQAAEbCYAQWEzQjQAAiAAAiAAAiAAQYF7AARAAARAAARAwGYCJSoo0tJS6cSJE5Sfn2dw4Hv27KbZs2ZRYmKCWflLly6R5dPT0w2Wx0UQAAEQAAEQAIGSIWCToHj/X+9R504d6bXu3SgrK6vQiI8fOybzOD9q/DiZN37ceBrQvz/t2L6jUFnlZM/u3TRn9mxKiDciKPTyly5eIstnZGQoTZj9qYz9118XFarTv19fOe7Tp04Vuo4TEAABEAABEAAB4wRsEhSZmZmUkZ5Bd27fob179hTqZcuWLTKP87OysmVe+/btqUPHDhRSr16hsvY4UcbOAubGjevqELIys+S4c/MMW1HUgjgAARAAARAAARBQCbiqRzYcuLm5UnR0NLVs1Uq2kpOTQzt37iA3dzfKfigmOMPFxYXq1KlL2dl/WjOOHjlCJ0+eoKCg6rKu/h9T+frlr1y+TAcOHqDcnFxq1LgxValSRb9IoXMWPBMnTKThI0YUuq57EhcXRydPHCcvL29q2qwZlS5dWmbHnztHZ8+epefrP09Xr1yl+PhzVKtWMDVu0kReP3LkMNWtG0IhISG6zZGlYyxUGScgAAIgAAIgoEECxSIoGjRsSLH7YiglJYX8/PzowP79dP/efXoh7AXaF7NPnfb69etpy+bNNOzLL6lChYq0atVKihJuECV5eXkph/LTVH6hwuJk86ZNNGrk/0SMRkEOC53/DB1G4S1a6BdVzwPKBNDu33+nmJgYCgsLU68rB/PmzqVZv8wkJ2dnysvNI2cXZ+rTpw917dad9sbspVkzf6HAwEBKSkpSqtBLL71EO4Sg4vKcXnn1FYoc8LE8tmaMsiL+gAAIgAAIgICGCdjk8lDm1bx5uLA6CKvEjoLYiOjoLdIa0bRpM6WIwc/169bJ658MHEjfDB9O3j7ehcqZytctnJycTGPHjCZfX1/6Ydw4GjlqFOXl5dOECVFGg0C5fq9evaVImBg1ntgNopvY0sKBorWCg2nxkqU0fsIEKRIWLSocd+Hr50tREydQzzfflNVZnIwZM5bG/vA9OTkR7RaxH5ysHaOsjD8gAAIgAAIgoGECxSIo6oeGyo2c3R4cIMkbKJv9PTw9jE79zp07IvgynipWqkgvd+lCzcPDqYmooyRT+Uo55ZPdJjnCzfH005Xp0h9/0PXr16nsU2Xp1s1bdDP5plLskc8aNWtS167d6Nq1JFowf36hfFdXV5o85ScaPnyEtLrE7tsnBYJ+ey1atJSujVYvtZL1q1arSswkNLQBPVWunOz/1q1b0rVjzRgLDQonIAACIAACIKBBAsXi8sjNzSXeTNmisHLFCsrMyKSIdhF0//59o1O+eOGCdE0Elg80WMZUvn6lq1evykscxzB16kU1m60e9+7dkxu7elHv4N1//pO2b99G/MaHj/efVpI8EZg5bOhQitm7l4KqB1H16objPJTmnJ1dlEP100W4SjhxW7aMUW0QByAAAiAAAiCgQQLFIij4ldG2bSNo7Zq1Mt6AN/FmzV6k1atXGZ0yP7lzik+IF8IiXzz5O5Hu70mYytdvuFLFSvJScO3aFCWCLDllZ2fLAFBvbx95buyPtxARffr0pRHC7XI3655aLDEhoUBMBAXRtOkz5Bi3bdtG+Q9jI9SCZh7YMkYzu0AxEAABEAABELALgWIRFCwI+FVQJTixbUQEubu7kxodaWBqlSpVouDawRR3No4GRPYnHx8fEdgZq5Y0la8WfHgQ1jSMKlSsIN7GOEmDBn5CderWFQGhMeKNDH8aM3asfvFHzluJQMp1wsJy6OBBNa+8CLb09PIU7pMkWiysF2dOn1EDLa357Qtbx6gODAcgAAIgAAIgoDECxRJDwXNiC0Obtm3l9CIi2pk1zS+GDKHKVSpLEZCYmCjexggvVM9Uvm5hDw9PGYhZP7Q+HTt6lBbMmy+DLN98GCipW9bY8YCPBxC/GaIkfmPlo4/6kF+pUjRnzhwq7e8vRRPnnz9/Xin2yCcLLEOpOMZoqF1cAwEQAAEQAAF7E3BKz8g0vPuV4Mg4xqGU2LRZlBhKpvL167ALhv/xGx/FkVgg8Bsgnp6exdGcbKO4x1hsA0NDIAACIAACIGAFAU0ICivGjSogAAIgAAIgAAIaIlBsLg8NzQlDAQEQAAEQAAEQKGECEBQlDBzdgQAIgAAIgIAjEoCgcMRVxZxAAARAAARAoIQJQFCUMHB0BwIgAAIgAAKOSACCwhFXFXMCARAAARAAgRImAEFRwsDRHQiAAAiAAAg4IgEICkdcVcwJBEAABEAABEqYAARFCQNHdyAAAiAAAiDgiAQgKBxxVTEnEAABEAABEChhAhAUJQwc3YEACIAACICAIxL48/+EZcXsUtLSKfHKDbp5N8WK2qgCAiAAAiAAAiBgbwJl/f2oeqVy5OftZdNQrBYULCZiTyaQt6eHTQPQauXw0Ge1OjSMCwRAAARAAASKjUBqRqbcz5vUrWGTqLDa5cGWCRYTaWIgSCAAAiAAAiAAAk8mAR+xlzd4trr0ONgyA6stFHBz2IIddUEABEAABEBAOwRYVNi6r1ttodAOBowEBEAABEAABEDA3gQgKOy9AugfBEAABEAABByAAASFAywipgACIAACIAAC9iYAQWHvFUD/IAACIAACIOAABCAoHGARMQUQAAEQAAEQsDcBCAp7rwD6BwEQAAEQAAEHIABB4QCLiCmAAAiAAAiAgL0JQFDYewXQPwiAAAiAAAg4AAEICgdYREwBBEAABEAABOxNAILC3iuA/kEABEAABEDAAQhAUDjAImIKIAACIAACIGBvAhAU9l4B9A8CIAACIAACDkAAgsIBFhFTAAEQAAEQAAF7E4CgsPcKoH8QAAEQAAEQcAACEBQOsIiYAgiAAAiAAAjYmwAEhb1XwMb+jx87Rndu37axFePV7969S8ePHTVeADkgAAIgAAIgIAhAUDzG22DH9u303xHDaX/sPqO9TJk8SZZ58CBFllm7ZjVN/3mq0fL6Gf/+/DPavz9W/3KxnR86eJA+HTSw2NpDQyAAAiAAAo5JAILiMa7rxQvnadfOHbRw/nyDvSQkxNOqFctlmaysLFnG09OTvL29DZbHRRAAARAAARDQKgFXLQzsfHyczcMIqhlscxuPowE/Pz86deokXRDiolq1oEJdrF+7lkqVKkX3799Xr7eNaKce6x/k5uaSi4uL/mX13FR+Tk4OuboaX3JT+WpHOAABEAABEAABPQLGdxe9go/zdEbU2GJp/l+Rg0lrwqJCxYpUIZ/ot3Xr6KO+/dR5ZmRk0LboaIpo355WrVyhXp86ZTIlJSXRV98Ml9dYJPC1vXv2UHp6GoW3aEmvdu1GVatVU+skXbtGgwd+TKdPnaLg4Nr0+hs9qdmLL6r158yeRVs2bZTCJbRBQ+rarTuFNmig1k+Ij6cZ034WsRLHyMfXh157vQd1F/+cnJzUMjgAARAAARAAgaIIaEJQtO7YpagxmpUXVKu25sQEDzxbuDJe7dqdpv38E/3r/V7k7uEh57N921ZycXWhsKbNCgmK9PR0Sk19oM55wvhxdOTwYXr3vfeoRs2aNHf2bBoi4iZm/DKLvB66RlavWkmRH39C/SIH0CLhXmEBogiKhQvm07o1a2jYV18Ti5tF4nzcD9/TrLnzpGBITk6mYf/5NzVt1ow+6tdPioopEydS+cBAKV7UgeAABEAABEAABIog4DCCoog52jUrOzubWrVuTT9PnUI7d+ygtu0KXBrr162liHYdihwbi4tNGzZQ38hIatM2Qpbt1z+Svho2lC5dukTBtWvLa3/7+7vUomUredyj55vU58PedPHCBWnFeCEsjJo3D6dqQQXulrff+Rtx38eOHqXn69enjRt+E8KCqE+//tKdUrlyFYqNiaEN69dDUEii+AMCIAACIGAOAU0ICnMG+qSW4WBLD2GVYEGwbt0aKSjYxRB39ix99vkQunr1itGpxZ87R3n5eYXcEwFlylDUpMmF6nh5e6nnAQEB8vjO3TtUlapRzZq1pNWB3R4nhEvjwYMC60daWqosF3fmjBAUzvTt8G/UNq5eu0o52TnqOQ5AAARAAARAwBQBTQiKrb+tIXMCMzk+ojjcI6agFGd+Xl6ebK7Tyy/Th73el8GZbCFg68DTlSvT5cuXjHbH1g1Obm7uRsuYypgrhMTK5cvpH8Jlwv88PTyp70cfqNVSxOuqZYRIqR8aql7jY37bBAkEQAAEQAAEzCVgd0HBQoIFhTlJawGX5oxZKcNveNStG0LLliyh3bt20ceDBilZRj9ZcHA6cviQcI+0l8dZmZm0ZPFiahMRQYEizsFUit6yhTp36UKvvNpVFlV+70KpV6VKVYrdF0Nd/voKOTvjLWKFCz5BAARAAAQsI2B3QcEi4duon822UFg2PW2V7vxyFxo96n/k7+8vgiabmxxcuXLlRLDki1KEBASUoVrBtWjenDm0bWs0vdajh8n6XCAkJIT4x6naRJwnfmNk5vTpsl5qaoHLg4XGZvEGyMSo8fLtEHc3N5o7Z7YQKxWo51tvmdUHCoEACIAACICA3QWFsgRPsvVBmYOpz/CWLemnKZOofYeORf4ehG47nwwaTON//EEEYv6H8vPyZXDl2B/GybgM3XLGjnu+/Tb9NGkS9fmgt+zz9R5v0JnTp2RQJ9epXqMGfT18BE2aEEW/rV8ny4SGNqD3e/c21iSugwAIgAAIgMAjBJzSMzLFryRYnqL3n7C80hNUIzz0WU2NloM7M4W7g38oy5rEwZgcF1HUD1vdu3eXfHx8iyxjTd+oAwIgAAIgoH0Cuw6fpjaNQ6weqGYsFFbP4P9JRXd3d+J/1iZfX1+TVUuX9jdZBgVAAARAAARAwBABROEZooJrIAACIAACIAACFhGAoLAIFwqDAAiAAAiAAAgYIgBBYYgKroEACIAACIAACFhEAILCIlwoDAIgAAIgAAIgYIgABIUhKrgGAiAAAiAAAiBgEQEICotwoTAIgAAIgAAIgIAhAhAUhqjgGgiAAAiAAAiAgEUEICgswoXCIAACIAACIAAChghAUBiigmsgAAIgAAIgAAIWEYCgsAgXCoMACIAACIAACBgiAEFhiAqugQAIgAAIgAAIWEQAgsIiXCgMAiAAAiAAAiBgiAAEhSEquAYCIAACIAACIGARAQgKi3ChMAiAAAiAAAiAgCECEBSGqOAaCIAACIAACICARQQgKCzChcIgAAIgAAIgAAKGCFgtKMr6+5G3p4ehNnENBEAABEAABEDgCSKQnplFvK/bkqwWFNUrlaO0jEyIClvooy4IgAAIgAAI2JlAqtjLD5xKIN7XbUlO6RmZ+dY2kJKWTolXbtDNuynWNoF6IAACIAACIAACdiTAlgkWE37eXjaNwiZBYVPPqAwCIAACIAACIOAwBKx2eTgMAUwEBEAABEAABEDAZgIQFDYjRAMgAAIgAAIgAAIQFLgHQAAEQAAEQAAEbCYAQWEzQjQAAiAAAiAAAiAAQYF7AARAAARAAARAwGYCDi8oTp08STF79xoFlZiYSNu2bTWab0lGXl4erV61im7fvm1Wtdzc3ELluP7JEydowfx5dPHihUJ5hk64Pvd3584dQ9lP1DVHmos54PXXXr/OkcOH6dDBg/qX1XPd/Pv378v7ICsrS83HAQiAAAiUNAGbBMXChQto4oQJJT1mi/o7deoUxcQYFxTnWVBs3WZRm8YK5+Tk0OrVYoM3U1AMHjSQYmP3qc1t3rSJZsyYTl7e3uTh/uivkKalpdGVK1fU8nITtqA/taIGDxxpLubg1V97/TqHWVAcOqR/WT3XzZeCQtwHEBQqHhyAAAjYgYBNgiInO4eys/FUZO269enbj+rVe06tHhsbS+3bd6CuXbtRYIUK6nXlgK0XY0aNUk7x+QQT0F/7J3gqGDoIgAAISAKuj5vDrVu36NjRo0RORKGhDcjf31/tMjU1lY4dO0op91Oo3nPPUYWHm+jly5fpelISNWzUSC0bH3+OMsXPg9YNCZHX+Gn9xPHjdFuY++vUqUNVqlRRyxo64Da5vI+PDzVq3Ji8vIz/ItiNGzfo5MkT5OLsQvVDQ6lUqVKySXZJ/P77LmrYsBGdOX2aeG4NGjaksmXLGuqSuDy7W8oHBlKNGjUeKXMzOZkCAgLIxcVFWlGSkq7R9evXae/ePdS0abNC5dl1c1r0mZ2TTTt37qAQwcHXt+B31/Py8+jggQNGx1PUGuh2clCY2MuXL09PP/20vHz37l25Pi1atFSL3bt3j9jc7i04PifWzMOjsCXl4sWLdFpYhUqXLi05u7m5ybqHDx+ip54qR9euXqXMrExq3jxcbVP3oKi5ME++l65du0Y1a9akmrVqkZOTuLEeJs4/d+6cdBfVqVNXnYeSr3yySyru7Fl67vnnJbd8yqdGjRqTt7AM6SZ2O505fYZ8fH2ofv1QwdtXN1s9ZrdZVmYm+fr50Qkh+nxEO3xfuLu709GjR4TF6g6FNmhQ6D5R1l65D3nsB/bvp+SbyRQcXFttWzkwla+UUz7Z4nPmzBm6eOECVa5cWf73peTpfprDoiju1syd+zf3ntQdK45BAAS0TcBl6NBhX1s7RP5y541df/NT2uMNcOR334kN05kSEhJo7Zo14ou5vvziTRab6Xf//ZbuiU2LxcbSJYuJLR7BtWvLmIAff/yBXmrdWn4pc3sToibITaqG2Eh4oxv+zdfEG3BGejotW7qUypUrR5UqFWyESv/8yS6PE8dPiC/64+Ts7ExbtmwWm8hBatmqldyM2OXBmyD3xenIkSMUNW4cubm5003x5T5v7lyxcdWUm0F2drYY83+JN3fOu3XrJi1auJBqVK9BT4n++Ut83bq1FB4eLoXCLzNn0sGDB6TVgTcX/TR29GiqVq2a2Gifon0xMXIc3uKnT53EOHlD1E3MMiEhXvR7kwKEKKso5urj4yP7S0w8L+I2bj0yHq5f1Brots/HP0+dKoWWIn4unD9PU6ZMpr++8ooselLMe/SokeQpxFi82Ljnz5tHL4SFqRsxr+/cObPJr5Qf7d79u5zTi82bS+4zp0+nHdu3U1xcnCjvIwWRbv8KO2Nz4U3t+7FjpMDx8ysl15FdAs1efFFtZtTIkXRSiEY3N1dauXIF3bhxXYoGtcDDAx779OnTZLxKphACvJ7Lli2l1m3aiLoFAmiVqD9zxkwpnPbti6GtW6PlpuwnRIN+2rx5k3B1rZYihfUNC74jRw5LgXBVuKiSk2/Sr4sWUtUqVaW45PrK2lesWFE2NyEqijZu3CDW1Jf2C0tV4vlEcU+Xl8KECxSVzy6P7du3UYeOHeV/L8wqavw42rN7N3mK+2nt2jVS1LAA1E+mWJjibs3cLbkn9ceLcxAAAe0SeGwWCt4gpkyeRD169qRWYvPmNH7cj+LLbS2936sXXbp0ST4V9njjDZlXu/YztHDBfOrYqRNVrSq+eMWT8qFDB4mfjll8XLlymZq88IIsO2f2bPGEWos++PBDeb7ht9+EIFlCTZoU5MuLOn/cPdzpiyH/lpvlgwcP6LNPBxMLCRYn+on7ee311ym8RQuZNW/eXIreEk08PiWxgIpo106eTp40iQ6Jp+86dQsLgAXz59M5sXl+PmSI0SdbpT1PT0966+13RDxFrHxyb9ykiZKlfrZo2VKO/+KFi7IsZyg+cxYwhsZjag3Uxs082Lhhg+TSo0fBmrGg4CdyXt8L4kl41cqVNPzbb6WlieNJBn7ysbC27JUCi7uoIta1f2Rkkb0Zmwtbjfz9A4hdBSyk2FLy6eBBxJYntqgkxMdTYmICjRfCk5/6w8V9s/jXRcSCQd+KwgPg8b3zt79LQZefny/HynNpLKxXLH7XCHE0dNiXMp83Vd7QF4mYoU8GDjI4fhaCn3/xBbm6uopxJEqx3K59e1JYzRCCiteXrSL6iV1ZbKn7+pvhci7c32jp2ir43+yYytdvj2NxrgpL0KjRY6SYa9XqJfpOrEvbiAj535V++aJYmOLObVky9+K+J/XngnMQAAH7EXhsgoK/6NPT0ilPCIvd4kmJEz+Z/vHHH/K4gTABhwp3ApuVz8WdE0+TN4iftNKEG8RPuBh402YzPgsKjnZ/5plnpLuEv/x5o27arJnaLn8hsumWxYIhs3SgcDkopmXOZ3M5bx6GBEXnzi/LjZotG2wuvn3rthA0N+SYlT/KEzyfs6uFrR+6af369fLa8BEjpKVCN+9xHBsbj6k1sHQsQdWDaNfOneQnXC2NhDvq7XfeUZuIiztL/gH+cjPlDZUTW14u/XFRHBW4N5i7qWRsLryGvXr3luKS74d79++Rq4urdH+woCgr+mKXxcwZM6h5eHOqWzeEPv3sc6PdsZupmrAOcWK3SSVh8bksRC4LCp5LmTJ/UfPZssXXWcjyZs/n+ondXiwmOAUFBcnPkJB68pP/VK5SWVgM9qjnugcsYLg/xdXE7bOg5s2Xk6l83bb4mK1A7D5kMackFtU8P25XPxXFwhR3bsuSuRf3Pak/F5yDAAjYj8BjExQsEJycneSXm+70lE2FzenjfvxRujiqVqsqNwMup/yvT8OahslX4VgksKWiZctWspkHKSmUnpEuNxIWIEpiHzi7JMxJAQFlZKyCobLbt22T/XJMB3/Be4gvYnVQhiqIayxydBM/LXt5egkhdEhaXHTzSuJYGY+pNbB0LBwsymb7/ftjxRP8aimm3u/VW24o3BdvtsePHVOb5ZgJNtvbkpS5pKWl0hjhImLXEVuLypQpI+8vAV82zzEbX371Nf2+a5d0gU3/eRq93KULte/Qwezulb7Yv6+4IpTKLFhycnOkxUMRp0qe/qduXIeS58R+PSMpVcwtMNA4J1P5+s2yq8dFiC3dteDgX1Pj1m1HYWGKu24dPjY19+K+J/X7xzkIgID9CDw2QcFPp7zBdOzUUbgwqj0yw40bN8qguj59+8o8fnJhX6/ytctPbDVq1hCvdG6VZm0OcuPEgW9swq5Xrx6xSdmcpHw5KmUTEuKpXTvDdfm1z06dO0vzMJePjt4i+1fqmvP57nvvyUDLiROiqFZwLemeMadecZcxtQb6/bHrJSMjQ/+yPOe1ZN/3M88+K/36XG68iDVZsXyZsBx8QE+VFRuusBSxO0t5UjfYkJUXY2P3SwvWyFGj1TiHFSuW8w4mW0wSQbyZmRky3oNjPjgIdNLEiSKotr54Kg+0qFeOx9m3N0ZaCPjpndOZM6elcLJkUza307J/KUt7ft9t3PphIl+/HxZyjOXDjz7Sz7L43BR3Sxu09J60tH2UBwEQsB+BR223Fo6FzbL8tobuP7YUsCuA/61YvpySRFQ+bza8OW/atFH2wE9k7ErgH2XiJ8Lly5bJ65k6P84TJtwe60TMxXPPPU+82XHiJ6DmImaAf4yK37RgscAb3Uzx+w36wkFWEH842p03GI452BodLd4guU4hQpAYSrz5cJBmugj25LgA9kdnZlr2aqyfcKsogmfqTz9JV4yhviy9xm/I8EbOVhtzkqk10G8jpF6IDEpNEZYftv5sEnPnxFzZDM8/uLVk8a/yKZ2vF5jmCzZrjvvgNV4uBAaPj4N1586Z84iFiutZkwKFqT71QSpdEcKT2+agSb7n+O0KTuw6G/W/kXLtlPHy5s8BnJamsLCmsgoHavITOru/OMBRNwDU0jaLKs9vHfHbOyyQ+L5jlxEHdSqmMVP5+m3zfx8ciMxj5jVJFtajyZMmUoqw7lmaTHG3tD1L70lL20d5EAAB+xGw2ULBm/WAyP6FZtCz55vyCZ8D6H6ZOYOGDRsqTdX8+lrvDwoCKVu3biM27Is05PPP5cbEAWMcmMavBCqvYbKfnjexpk0LvuCVTrp3f01sJFnSZcKbWmn/0vSPf7xr0NzKdWrVCpbCYMrkybKvrt26SZO50p7uZ/fXuss3NyL79ZPt8lspO7bvUAMgdcuaOmYXAUfRs1+fgxENmYNNtaGbzzEf7B7iYEe2CjxvIMBPtzyzKWoNdMvyMW+k7K75dPBgyUmxCinl+or1nDZtGn08IJLcxVswtUVcC68bJ3ZB9O8fKeMMtoogVhdXF2rQoCFVr15dqW7TJ1tGmrcIp++/HyvXngVMOX4NVbzpw4kDcq9cviLfnsjNy5XxG2+99bbqSrOkc35NOHLAAPrll5kiIHeLdF9xzE6XLn+1pBmzy/Krw2zZmT1rFnHgK8c56HIzla/fEccnvf5GD+IfnuO3lDh+olOnzkJc+ekXNXluirvJBvQKWHpP6lXHKQiAgIYJOKVnZBYOAHgMg+VIe06Gou35iYyv8xeNpYmtI/zEzlH/5iR+smUfvDkmeX4y5SBSLSa2APCcLREoRa2B/hyZE1uEjK0JrxlzVNwB+vV5fBwgaay+fnlLzvmJm9fd0L3E7bB1gsdv7j1hqm9rWJtq01g+j52tLoYCi7mOqXz9drk8xxxxkLOtyRR3a9q35J60pn3UAQEQKFkCJSIoSnZK6A0EQAAEQAAEQKCkCVhuFijpEaI/EAABEAABEAABzROAoND8EmGAIAACIAACIKB9AhAU2l8jjBAEQAAEQAAENE8AgkLzS4QBggAIgAAIgID2CUBQaH+NMEIQAAEQAAEQ0DwBCArNLxEGCAIgAAIgAALaJwBBof01wghBAARAAARAQPMEICg0v0QYIAiAAAiAAAhonwAEhfbXCCMEARAAARAAAc0TgKDQ/BJhgCAAAiAAAiCgfQIQFNpfI4wQBEAABEAABDRPAIJC80uEAYIACIAACICA9glAUGh/jTBCEAABEAABENA8AQgKzS8RBggCIAACIAAC2icAQaH9NcIIQQAEQAAEQEDzBCAoNL9EGCAIgAAIgAAIaJ8ABIX21wgjBAEQAAEQAAHNE4Cg0PwSYYAgAAIgAAIgoH0CEBTaXyOMEARAAARAAAQ0T+D/ALYsqQtcev01AAAAAElFTkSuQmCC) Note where the `label` and `placeholder` text appeared in the web application, and note that First Name and Last Name are required - indicated with a `*`, but Middle Name is not. #### Input types[​](#input-types "Direct link to Input types") An input can take a number of types, which affects how the input renders in the Prismatic web application: * **string** will allow users to input or reference a string of characters. ![String input in Prismatic app](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAokAAABhCAYAAAC6XzvnAAAAAXNSR0IArs4c6QAAAGJlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAABJKGAAcAAAASAAAAUKABAAMAAAABAAEAAKACAAQAAAABAAACiaADAAQAAAABAAAAYQAAAABBU0NJSQAAAFNjcmVlbnNob3Rv4K/YAAAB1WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj45NzwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj42NDk8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpVc2VyQ29tbWVudD5TY3JlZW5zaG90PC9leGlmOlVzZXJDb21tZW50PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KXM8UOQAAE95JREFUeAHt3XlwlFW6x/EnCZCEgEAGkCUssiNLQMFSGSUgm8qMpTKOluKCAmOV/uU4DnWn7nhFRL3iNojDBREEFAT/sEB2VBwZNh1IQGQJCAmbbBMhMQmB5J7nSPd0ujvp7qTpftP9PVUx3e9y3vN+XqrmN2d5k1BhilAQQAABBBBAAAEEEPAQSPT4zEcEEEAAAQQQQAABBKwAIZF/CAgggAACCCCAAAI+AoREHxI2IIAAAggggAACCBAS+TeAAAIIIIAAAggg4CNASPQhYQMCCCCAAAIIIIAAIZF/AwgggAACCCCAAAI+AoREHxI2IIAAAggggAACCBAS+TeAAAIIIIAAAggg4CNASPQhYQMCCCCAAAIIIICAY0Oi/iGYCxcu1PgJFRUVyc6cHCkvL69xHZyIAAIIIIAAAgjEq0DIIXHdunUy7Lbb7M/ChQt93J555hn3/lOnTvnsD7Th4MGD8vRTT8mI4cNtPQ+PHSurV61yn/bTTz/J7t275dixY+5t/j68Pm2aPPnkk/LFF1/42802BBBAAAEEEEAAgWoEQg6Jly5dkpKSEvuzcsWKSlWfPn1atm3d6t4f6p+FPnPmjIx/4gnZsWOH9OnTR4YMGSInTpyQyZMnu4Oi1j9h/HiZ+/77la7t/eX222+XO++8U/r27eu9i+8IIIAAAggggAACAQTqBdhf5e769evLoUOHJDc3V7p06WKP015GHd7VfWVlZXbbt99+K0ePHpVe114rnS8fpz2DpWYoefDgwdKkSRP3NTZv3iylpaUycOBAef2NN+z25cuXy6uvvCLr16+Xbt27i9anJT8/X9auWSPDR4yQZcuWSUpysnTv0UM2bNggo0ePlqR69aRX7962Hdomradxo0bS24RPPaZZs2Zy66232rbaCs1/tIfyX6b+5s2b23O3b98uPXv2lK5du7oO4TcCCCCAAAIIIBAXAjUOiQMGDJBNmzbJmtWr3SFRQ1t6erq0aNFC9u7dawHzDh+WaWbod8jQobZH8Pjx4/a3hsM77rijEnKHDh3s9127dsnXX38tN998sw18Gvq0LFmyxAZC/bxz50758ccfbUj831dflQYNGkiyCYo6HK3nLTfBcbVp2/+88IINfRo0GzZsaI8rKCjQKqRjx46y4PKQ+WeffSavvPyyew5jIxMoCwsLZfyECYREq8V/EEAAAQQQQCCeBEIebnbhZGRkSKdOnUR7D3VYWXv2NBjeNmyYuxdRj9VwmJSUJFu3bJGLFy/Kxo0bbRVZZii5nunt8yy9evWS+x94QIqLi+XPzz0nv7/vPpk3d64Na3rc3XffLZMmTbKnjDA9iB9+9JH7dB0C1xCo8xC1J9Bf0WMeMPXPnTdPWrdubXtCj5h26wKZGe+8YwOinj99+nS7318dbEMAAQQQQAABBOJBoMYhUYPcyFGj5OTJk5Jt5hBqL6KWUWab7nOVpk2bygAzfKyrjbOzs+Wfl0PicBMmvUtCQoI8ZRatvPX225KVlWXrnjVrlujiFZ2vqKFSewu16OeUlBR3FYmJifLRokXy4EMPVRrCdh9gPqSmptr9OjzeNzPT7tpjgm1+Xp7tgUz/1a/s/n79+8stZiiaggACCCCAAAIIxKtAjUOi9soNNyuQNZytMQFxzdq1dvi2u5k3qPs8iysQrjPH6Dw/HY52hTTP4zQI6nC09ii+OGWKfGyGl2+88UYbFj/99FPPQ30+a8D07pn0OchjQ5Jpt5YKM1/xvBlW1pLRtq39zX8QQAABBBBAAIF4F6hxSNSFJy1btpT+ptdt5cqVosO2I0aOtJ7e7ze81SxQ0TmDOu9PF7TcZl6ho+HSu8yZM0d+N2aMzDVDzFquvvpqGf2b39jPm838R89Sboa4w1V06FnLvn373EPll8zQOAUBBBBAAAEEEIhXgcqTAkNRuBzSdMhZVxxrT57OE9Ti/eobnSs4aNAg9zsLh5keSH9lpAmZK0yQ/HjxYjn0ww/Suk0b2fDll/ZQV89jB7PYRMtW8yqc2bNnyxPmlTm1LRpG+/XrZ1+988Tjj9uFOGtNrycFAQQQQAABBBCIVwHf7rwQJbKysuw8wUwTslq1alXl2a5gmNGunfQwr6rxV/Sdhu/MmGFXE2sI/GTpUrsIZdy4cTJx4kR7ir6OZqhZDHPOrGJe5LFwxV99oWz7r7/8xb5i58CBA3LYrMjOvDxn0V+PZyj1ciwCCCCAAAIIIFAXBRJMr1/4xm2rEdD5iM8//7w8+thjQfX+6Uu7dQGMvorGX9GFMJ4LWfwdE8o2rU/nROorfPSaT/7hD/Y1O6+Z1/fovEgKAggggAACCCAQTwI1H24OQWmyeVehvvdQVyN7vxuxqmr0tTlVBUQ9Jy0trapTa7R95syZdqj7uuuukzwzv1LnWLZv394OQ9eoQk5CAAEEEEAAAQTqsEBEQmJOTo5d5DLBvJi6jZln6MTyuJmLqCudta1pZg7l3ffcI+PNn//zfM2OE9tNmxBAAAEEEEAAgSshELHh5ivReOpEAAEEEEAAAQQQuDICtV64cmWaRa0IIIAAAggggAAC0RQgJEZTn2sjgAACCCCAAAIOFSAkOvTB0CwEEEAAAQQQQCCaAoTEaOpzbQQQQAABBBBAwKEChESHPhiahQACCCCAAAIIRFOAkBhNfa6NAAIIIIAAAgg4VICQ6NAHQ7MQQAABBBBAAIFoChASo6nPtRFAAAEEEEAAAYcKEBId+mBoFgIIIIAAAgggEE2BGv1ZvuKSC3LibIEUFhVLeUVFNNvPtRFAAAEEEEAAAQSCEEhMSJBGaanSKr2ppKY0CHhGyCFRA2Ju/nFTebJIgqk/xjJiZreOAdE4AAEEEEAAAQQQqIsCJaVlsj/vmHRp1zpgUAx5uFl7EDUgFhWXSHl5jCXEuvi0aTMCCCCAAAIIIBCkQEpyfenavo0dEQ50Ssg9iTrEbHsQA9XMfgQQQAABBBBAAAHHCWhQtHkuQMtCDol2DiIdiAFY2Y0AAggggAACCDhXIJg1JSEPNzv3dmkZAggggAACCCCAQLgECInhkqQeBBBAAAEEEEAghgQIiTH0MLkVBBBAAAEEEEAgXAKExHBJUg8CCCCAAAIIIBBDAoTEGHqY3AoCCCCAAAIIIBAuAUJiuCSpBwEEEEAAAQQQiCEBQmIMPUxuBQEEEEAAAQQQCJcAITFcktSDAAIIIIAAAgjEkAAhMYYeJreCAAIIIIAAAgiES4CQGC5J6kEAAQQQQAABBGJIgJAYQw+TW0EAAQQQQAABBMIlQEgMlyT1IIAAAggggAACMSRASIyhh8mtIIAAAggggAAC4RIgJIZLknoQQAABBBBAAIEYEiAkOvhhXrx4UfZ8/70U//yzbeWJEyckLy/PwS3237Rz587Jnj17/O+8vDU/P1+OHz9e7THsRAABBBBAAIHICRASw2xdUVEh70yfLrt37/apWUOQ7jt9+rTPPn8bzp8/L1OnTpXDl4PhihUrZOHChf4ODWmbtnHm3/8uOTk5lc7L3rFD3ps9u9K28vJyeXfGDPnuu+8qbQ/ly3e7dsnUl16q9pTFixbJsmXL7DF6zaoMq62EnQgggAACCCAQNgFCYtgof6lIA9jWrVvl1MmTPjUXFhbafcXFxT77IrkhISFBTp06JVu3bKl02a+++kr059ixY+7t2sO3efNmadiwoXtbJD6o4ZkzZyJxKa6BAAIIIIAAAn4E6vnZFrFN5376ZRi1Nhe8qklkw0tt2lrdudp7puFNf2pSLl26JElJSUGf2r17d9n2zTfu4/V87S1MS0uTnTt3Sps2bey+/fv3S0pKinTo0MF9rH7QMKw/iYlV//8MHS6vV6/qf2J6vt63d7tddbp+V7owXxBAAAEEEEAgIgJV/y94BC6/Z9fhsFylR+8OUlfD4s9mvuEH8+ZJdna2DUwDBgyQsQ8/bINZIBwNWUuXLpUtpkfwjBnCzsjIkIfGjhUNgIFK9x49ZPny5aJD2o0bN5bc3FzRUDdixAjZZULiyJEjbRW5JiR27dbNHQaLiorkgw8+sMdcuHBBeph6Hhs3TtLT0+3x0//2N2nQoIEdUt+3b5+8+eabPk1xtXv9+vVSZuro37+/qMNVTZq4j9WAmFRNAHUfyAcEEEAAAQQQuCICUQ2Jbds1r/VNNW6S5siAWFJSYgOY5w1qEPIs2nv39ltvSYXZ+Mdnn5VCE9jmL1ggHy9eLA8/8ojnoX4/63EbN26U3951lw1an3/+ubzy8ssyxcz/a926td9zXBu7du1qey01yF1//fWSY0Jqz5497ee1a9fawKi9gNqTOGTIEHuahsg333hD9N4mTJggV7dqJQtMe3W+4VRzXT1eh9K3bdsmo0ePtu1qZAKod9FwuGrlShk1apTcPGiQDcjfmF7NVqY+V9GQSE+iS4PfCCCAAAIIRF4guiGxfYvI33GErvjhhx+K/lRXNIB9b1Yvv2QWp7Rt29YeetysYF66ZIntTazu3LKyMlm9erXce++9MmzYMHvomDFj5Ot//EOWmwUg402Iq66kpqZK+/btbQi0IdEsYhmclSWdOneW+vXry969e22bdJGN9jpq2We2aaj877/+VTqb47Q8+OCD8ufnnpNNmzbJLbfcYrdpfb+77z772d9/1pkQ2qdPH/cxeu87vRbREBL9ybENAQQQQACByAlENSRG7jYjfyUNT4N+/etKFz544IC89tpr7m0HDx60gUxDoavocK4GwIKCAtcmv78PHTok2hPZq3dv936dz5iZmSmHDwc3jK/hb78JfXotXaDSr18/27uoAU7nJeqrdzQwXnPNNfYauab9uoDF9V03ao9ly5Yt5Yg531VSTACtqugcxJNmUc/gwYMrHWLnJXrMx7QhMYQ5lpUq4wsCCCCAAAII1FogqiHxaN4pOX+u8hCsvztqfFVDaVvHeh2Tk5PtIhDP+/EOTxoINYRd26uX52Ey8IYbRM8vLS2ttN3zi84H1KI9gp6l3MxTDLbo3MXPzdDv9u3b7UKV5s1/Gf7v07evHQ42K1Okc5cu7sUnOn9QF7F4DwPrHMNQF9x4W3i3WUMjcxK9VfiOAAIIIIBA5ASiFhJ1ZfPR/ODeF6ghMRaLriDWeYo6F1AXnXiX6kKinqvBTN91OHz4cPep+k7CfmYhSDBFQ6LOM9ywYYPtgXSdoz2Js2fNsgG2rwmMrqLDwmfPnrW9hhnt2tnN+qoffZ1Op06dXIdV+1sDZosWLex7JF1zHfUE7WH0LDPefdfzK58RQAABBBBAIMICVb+/5Ao3RFcj3zCop+jK5EA/da0XMVi6gQMHivbezTerhXWuny760Pl6r0+bFrCKZs2ayU033WR7/PR9h9qzuMbMUdR3MbqGcnXY+f05c6qsS1c1a9j8wQx7Z5qhZldpYlYZ63xFHQ73XCk9wLRXh5bnz58vB8zQs/aELv3kE7vgRINlsGXI0KGiL+5etWqV6F9j2fDll7+8fNyjF1QXvxT8+9/BVslxCCCAAAIIIBBmgaj1JLruo66+usbV/tr81lfF6Kpm/SsnL02ZYnsGtbduwsSJQVX7yKOPygIT2GbOnGnnJ7YzvXtPPf20dOzY0Z6vf+FFVz/rK2qqKhoCdU6irnb2LH3N3MYjR45IF4/tunr52T/9Sf7PXG/yCy/Y9xvqeXoPqSG8bFt7Pn80C3R0dfZHZnFPD9OTqr2prqJzLee89578/v77JcsspqEggAACCCCAQOQFEsx8suAnsZn2Ze87FPlWRvCKmd06RvBq/7mUvlZGH4X3HMP/HFH1Jw1Ver6+CNuz6J/eO2t64yZNmuS5OSyf9Xo63K1zJ2tatPdTh7v9/TUXvSfvl2zX9DqchwACCCCAAAKVBTTPBco8Ue9JrNzk+P2mC0JqWjRMeQdErUuHm6vrRazp9fS82rTXdV3tSdUff4WA6E+FbQgggAACCEROgJAYOeuIX2nyiy/SGxdxdS6IAAIIIIBAbAhEbeFKbPA5+y7ojXP286F1CCCAAAIIOFmAkOjkp0PbEEAAAQQQQACBKAkQEqMEz2URQAABBBBAAAEnCxASnfx0aBsCCCCAAAIIIBAlAUJilOC5LAIIIIAAAggg4GQBQqKTnw5tQwABBBBAAAEEoiRASIwSPJdFAAEEEEAAAQScLEBIdPLToW0IIIAAAggggECUBAiJUYLnsggggAACCCCAgJMFCIlOfjq0DQEEEEAAAQQQiJIAITFK8FwWAQQQQAABBBBwsgAh0clPh7YhgAACCCCAAAJREiAkRgmeyyKAAAIIIIAAAk4WICQ6+enQNgQQQAABBBBAIEoCIYfExIQESUxMiFJzuSwCCCCAAAIIIIBAbQU0zwUqIYfERmmpkpqcHKhe9iOAAAIIIIAAAgg4UKCktEw0zwUqIYfEVulNpbikVNJSU+hRDKTLfgQQQAABBBBAwEECGhD35x0TzXOBSkKFKYEO8t5fXHJBTpwtkMKiYikP/XTv6viOAAIIIIAAAgggcIUFdIhZexA1IKamNAh4tRqFxIC1cgACCCCAAAIIIIBAnRYIebi5Tt8tjUcAAQQQQAABBBAISoCQGBQTByGAAAIIIIAAAvElQEiMr+fN3SKAAAIIIIAAAkEJEBKDYuIgBBBAAAEEEEAgvgQIifH1vLlbBBBAAAEEEEAgKAFCYlBMHIQAAggggAACCMSXACExvp43d4sAAggggAACCAQlQEgMiomDEEAAAQQQQACB+BIgJMbX8+ZuEUAAAQQQQACBoAQIiUExcRACCCCAAAIIIBBfAv8PHp9LFQ264r4AAAAASUVORK5CYII=) * **password** will allow users to input or reference a string of characters, and the string will be obfuscated in the UI. ![Password input in Prismatic app](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAoQAAABsCAYAAADzP6OJAAAAAXNSR0IArs4c6QAAAGJlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAABJKGAAcAAAASAAAAUKABAAMAAAABAAEAAKACAAQAAAABAAAChKADAAQAAAABAAAAbAAAAABBU0NJSQAAAFNjcmVlbnNob3R5N0LWAAAB1mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4xMDg8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NjQ0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6VXNlckNvbW1lbnQ+U2NyZWVuc2hvdDwvZXhpZjpVc2VyQ29tbWVudD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+ChKmSVgAABPlSURBVHgB7d17cFXV3cbxX8It4SZGUUBBFIKibQOorYBTsN7v/UNn2lGn4lir1fq+1bE3Xy0j76tWq1OtlzqttrbOiI6XdsQCilolClFBwdYLoGIQoiMy3BNAwrueVVe6k9nn5OScnJOdne+aOUnOvq712WeGx9/a+1i2xzWjIYAAAggggAACCPRYgfIeO3IGjgACCCCAAAIIIOAFCIR8EBBAAAEEEEAAgR4uQCDs4R8Aho8AAggggAACCBAI+QwggAACCCCAAAI9XIBA2MM/AAwfAQQQQAABBBAgEPIZQAABBBBAAAEEergAgbCHfwAYPgIIIIAAAgggQCDkM4AAAggggAACCPRwAQJhD/8AMHwEEEAAAQQQQIBAyGcAAQQQQAABBBDo4QIEwh7+AWD4CCCAAAIIIIAAgZDPAAIIIIAAAggg0MMFCIQl/ADU1tbaA/ffb++vWlXCs3IqBBBAAAEEEEAgu0Dv7Kv/vXbBggV28003+TczLrrIzjvvvFa7XX311bbszTf9sodnz7ahQ4e2Wp/tTfTY2q5v3742ZswYmzJlin3nu9+1srKybLt3q3ULFy60p+fMsQMOOMDGjB3brfpOZxFAAAEEEEAgvQI5VQh3795tTU1N/jX3739vpbF+/Xp77dVXW9bv2bOn1fr23oRj9+/f3yZOnGhjXVB666237O6777Zbb7mlvd1ZjwACCCCAAAIIIFCgQE4VwnCOPn362OrVq22Vm/JUcFNTha+5udm0bteuXX7ZkiVLbO3atXbE4Ye3VMLmz5tnO3butGnTptlee+3lt4v+OOKII+ymm2/2iz766CM731Uhn376abviRz8yhcVt27ZZXV2drXPHneCCo7aPVg+3bNliixYtssbt222yqy7ut99+LYdXv5YuXWoffvihHXXUUS19f+Xll23955/b8ccfbwMGDPDbKOCedNJJft8Fzz5rjS4In3766VZeXm4b3Lavu7F9+umnNv6ww+yoo49uOcdTTz1lFf362aFu+YsvvmhnnHGG7b333vbGG2/4gKuqJw0BBBBAAAEEEEiiQIcCocKUQtcz8+e3hKpnn3nGqqqq/DTxe++958dY7wLdbbfdZsd961s2a9Ysa2ho8L8VBE877bR2HUaNGuXD1IYNG6y+vt6GDBli/3XllT5kKpgpgI4cOdJ+/4c/2MCBA23e3Ll2k5vSVkDU69Zbb7WzzjrLfvLTn/oQ+MPLLjMFxsrKSrvrt7+1cYce6iuQi13AfOLxx21/Fx6/ccwxdtuvf20Ko5MmTbJBgwbZDTfc4M9z5pln2ttvv21XunCqSmloJ5x4os2cOdO/VTVT0939XCjctGmTn/L+xwsveIewvc5PQwABBBBAAAEEkiaQ05Rx6PSBBx5ohxxyiK8Kamp4zZo1phB4/AkntFQHta2CYK9evexVF7i++OILe9lV4tSmH3ec9e4dn0G3u8qeKnjvvvuu3XfffaYwWFFRYQcddJA/joLWDy691BY895wPWzr3Cy5wqT366KOmqec//ulPNsdVFceNG+crc1u3brU5rnKnMHjVVVfZfBdeTz7lFNO5NC2t4Kemcyq0KgyqLV682FasWOGD56Qjj/TLfvHzn/sw+N8//rHdc++9Nnr0aFMF8YXnn/fr9UNhUdXMy1wA3XfffU1VQ7VrrrnGbrzxRl+F9Av4gQACCCCAAAIIJEggPp1l6GBjY6MPVPfec49/iETTsGqnuJBV6x6YCE0VPU2n1rlgtWzZMtPUrNqJLjhmappmvuD881tWKwz+zIUwVdXOOvtsO9VVFlWle/SRR2y764faZ5995n+rcqkA9ys35azp3v9z4Wv48OH/Xuf68Yjb5+GHH7ZNmzfbOeecY9ddd51fp0qeKorvvPOODR482E8L64GYxV9OPWsjhUZNf2sqediwYX5/LdeU8F133WWvuvsnFYDVVL3UQzUKvQq0K1eu9A+QnP3tb/v1L7/yin+oxL/hBwIIIIAAAgggkBCBDgVCVcBOdNOk9/3ud/aMq7YtdffHqVJ2qJuCjU6lamwKfwqEqqLpPjoFra/V1GQctiqBmoJVmNp///39vX6ailZT8PyJq7Kp6ljjjrHVVfyi7XsXXmgD3NTx3/76V7v99tut/De/sYvc09AXzphhkydPtl9ce609/thjdr+bYtbr2GOPtRvcVLamsHVvnyqEmoYeP368D4BPPvmkP5fCogKh7plU09PBoQ398h7FzS5khqbtQwVU91qqijrsy2AatuE3AggggAACCCCQNIEOTRnroRA9rKGngee6+/Y+dtO2J518sh/TTrcu2r7pHh7RPXV6MEQPdejBDVXQMjXdNzjDBbgLLrjAV/lCGNT2qgoqcKpieIu7P3D69Okth9GUtCqQ1dXV9pi7H/COO+/0U7MPPPCAv5dPobSPC5n3uhD757/8xQ53D7ro+wD1gIqapoRV/VOlb6oLinppqvmll17y0+MKjZoqV9P0uIKj2tv/+pf/faC7lzGuhYdaVrkqYXjyWg+80BBAAAEEEEAAgaQJdKhC6JKN77/uw9MUrypi4YncEHrCAHUv3dSpU1vu81P1L9928MEH+xCnr7xZt26dPeaqfWqawlZF7sEHH/T3/12qe/f22ceHNlUT9eRzrQuLTz7xhJ322mu+WqgAqaYpaTVVABU4tVz91bk05b1x40YfFrWNKpb6XsRX3JTvxRdfbNXuCWtVSHX8M93UcVxTiFTlVCFSD7XoKWbdm0hDAAEEEEAAAQSSJpC5ZJelp6rQ6SGPmgkT/H11mTYNIVBVtMPc17Hk284991wfyFTFe859zY2qeGoffPCB/62q4Ve++lU/lX2zu49Q59J9hAqll19+ub/v8Xn38Mf111/vq3VXXHGFHf3lV8ZMcGNQ5VL3HGr6WH8r/KmFh070t6adVfXU/2VEVU89NKJzZaoQap/r3PlU+dQDLO+//77/yh0tpyGAAAIIIIAAAkkSKHOVvY59k3QHeq/7B2fOnOnv5VNlrdCmimC2r27ZsWOHD3yh+hc9n6Z69V2G+jqZQpqmv/WUctx3KWY6rh5e0UMrqqjSEEAAAQQQQACBpAkULRDOct/hp3v19HUwundvxIgRSRs7/UEAAQQQQAABBBBwAh27h7ADZMuXL/cPoFxyySWEwQ64sSkCCCCAAAIIIFBqgaJVCEs9EM6HAAIIIIAAAgggkJ9AXg+V5Hcq9kIAAQQQQAABBBBIogCBMIlXhT4hgAACCCCAAAIlFCAQlhCbUyGAAAIIIIAAAkkUIBAm8arQJwQQQAABBBBAoIQCBMISYnMqBBBAAAEEEEAgiQIEwiReFfqEAAIIIIAAAgiUUIBAWEJsToUAAggggAACCCRRgECYxKtCnxBAAAEEEEAAgRIKEAhLiM2pEEAAAQQQQACBJArk/L+ua2zaaZ9s2GhbtzVa8549SRwLfUIAAQQQQAABBBCICJSXldnAAZU2rGqIVVb0jaxp/WdOgVBhcNWaBnegfmZl7gApy4M140a3VuEdAggggAACCCCQEoGmHbtsZf06GztyeMZQmNOUsSqDCoPbGpusuTllaTAlF5thIIAAAggggAACcQIV/fpY9agRfqY3br2W5VQh1DSxrwxmOgrLEUAAAQQQQAABBBIroFDo81yGHuYUCP09gxQGMxCyGAEEEEAAAQQQSL5AtmdAcpoyTv4Q6SECCCCAAAIIIIBAvgIEwnzl2A8BBBBAAAEEEEiJAIEwJReSYSCAAAIIIIAAAvkKEAjzlWM/BBBAAAEEEEAgJQIEwpRcSIaBAAIIIIAAAgjkK0AgzFeO/RBAAAEEEEAAgZQIEAhTciEZBgIIIIAAAgggkK8AgTBfOfZDAAEEEEAAAQRSIkAgTMmFZBgIIIAAAggggEC+AgTCfOXYDwEEEEAAAQQQSIkAgTAlF5JhIIAAAggggAAC+QoQCPOVYz8EEEAAAQQQQCAlAgTCBF3IOXPr7Pd/nJugHtEVBBBAAAEEEOgJAgTChFxlhcGGTzbY8GFVCekR3UAAAQQQQACBYgro3329ktB6J6ETPb0PIQzKgUDY0z8NjB8BBBBAIM0CS95Y6UNgXBCcNGGsHTmxukuGTyDsEvb/nDSEwRAEl765qss+DP/pFX8hgAACCCCAQGcKKACGMKjjhn/39TtUCpUB9Drj1G+0rO/MPmQ7FoEwm06R14UwqNPo4qvF/ReDX8EPBBBAAAEEEOiWAvq3Xf/mqykAqgoYAmF0QAqMCoTattShsOSBcPOm7dGx5/X34L3657VfknaKhkH1Sx+CTB+QJPWbviCAAAIIIIBA7gLRMBidEtZy/dsfgqEyQJgu7opQWPJA+O4/P8pdMcuWh33lIOuuwTCEwfBfCXqvi68WPgxZht7uql27dlmfPn1abdfZy3bv3u2P36tXr6KeJ9d+x/Unbpk6m+sxWw2MNwgggAACCOQhoH/j1aJhMFQCtTw6M9g2FGq7MIOobYvZes10rb0TfPr5xvY26dB6BblCXgeMGtqpYXDYPkM61P9CNo6GQV3kQQMrbcTwfWzFqrX+vxL0d76tubnZZv7ylzZ79mwbOnSojRw50h/qoYcesjvvuMO2bNliNTU1GZctXrTI/nfWLFvy+us2bdo0Kysrs7hlDQ0N9j/XXmvz582zCRMn2qBBgyzu3HHLdPJS9KfQPnokfiCAAAIIIFCAgAKdAl+mMKgcMNDlAG2jlzJAyAVhmU5fSDaIdl95LlPmKfnXzijMFfpKQ2UwmvhVKfz+jFMLrg5+/PHHtnr1atu5c6fVLV7c8hmora31ga124cKsy+rq6vy+OkZ9fb3fNm7Z8uXLbdOmTf61bNkyv13cueOWaeNS9KfQPvpB8QMBBBBAAIECBLLN/ikkhpnCkAkUIEMLM4bhGGF5sX6XPBAWayBJOq4uqCqB+pJp/Q7vlfZ18cOF7+w+qyJYXV1tFRUVdszkyS2Hnz59uvXu3dtX/cLCuGWTp0zx+44ZO9ZGjRrlN41bNsFVGauqqvwrVBzjzh23TAeNO3fcsrhzxy2L60/csrj+xC0LRvxGAAEEEECgMwT0739o4e9o1W9dw+d+tTJCaNG/w7Ji/i7b41p7J1i2YnV7m+S8fm39Z7Zlc/sPlgwa3N9XEnM+cAEb1owbXcDerXcNU8JaqosZLnx4X6wwGO2F7p1re29fZy8LHxtNK0dbZ58n1+PF9Sdumfqa6zGj4+JvBBBAAAEEOiqggpAqfNFikHKBsoKaqoQKhtH3oTIY9o1ON3f0/G23V57LlHlK+lCJnjBeu2Z92/7Fvlcg7G5NF08XOnrhNYYQEkuV9tuGQfWhs5e1DYI6h1pnnyfX48X1J25ZR/roB8QPBBBAAAEE8hRQuFMu0EsZQe+VBRTyFBTDS4ePBr8QBrU8BET9XcxW0kCoe/++PnW85fLVM93xPsEwz9+2CqiLGUJhMS8mx0YAAQQQQACBZAmEDBAygt5HQ57Cot6HolE0DLbNE8UcWUkDYRhIdwx7oe+ZfuuCqoULGt1Oy/QK20TX8TcCCCCAAAIIpFdA//4r2KkwlCkUavShihiygvaJyxTFkuqSQFiswXTlcdu7aOECd2UfOTcCCCCAAAIIlF6gbShUMNQyvUI+CL+1LFoxLFVvCYSdKB0ubLhPIBxa79V0fwANAQQQQAABBHqegDKCvmIuTAkrAIYQKA2tD2GwK3RK/pRxVwyyvXNmeuKmvf3arteFDU8KaZ0urJqW6+9S3gvgT8wPBBBAAAEEEEisQAiEIS8Uu6PZnjLmewg7UV8XVOk/VAJ1oQmDnQjMoRBAAAEEEEiRgHJDqcJge2xMGbcnlMf68ARRCIN5HIJdEEAAAQQQQACBkglQISwidVJSfxGHyKERQAABBBBAIAUCBMIUXESGgAACCCCAAAIIFCJAICxEj30RQAABBBBAAIEUCBAIU3ARGQICCCCAAAIIIFCIAIGwED32RQABBBBAAAEEUiBAIEzBRWQICCCAAAIIIIBAIQIEwkL02BcBBBBAAAEEEEiBAIEwBReRISCAAAIIIIAAAoUIEAgL0WNfBBBAAAEEEEAgBQIEwhRcRIaAAAIIIIAAAggUIkAgLESPfRFAAAEEEEAAgRQIEAhTcBEZAgIIIIAAAgggUIhAToGwvKzMysvLCjkP+yKAAAIIIIAAAgh0oYDyXKaWUyAcOKDSKvv1y3QMliOAAAIIIIAAAggkWKBpxy5TnsvUcgqEw6qGWGPTDhtQWUGlMJMkyxFAAAEEEEAAgQQKKAyurF9nynOZWtke1zKtjC5vbNppn2zYaFu3NVpzbrtEd+dvBBBAAAEEEEAAgRILaJpYlUGFwcqKvhnPnnMgzHgEViCAAAIIIIAAAgh0a4Gcpoy79QjpPAIIIIAAAggggEBWAQJhVh5WIoAAAggggAAC6RcgEKb/GjNCBBBAAAEEEEAgqwCBMCsPKxFAAAEEEEAAgfQLEAjTf40ZIQIIIIAAAgggkFWAQJiVh5UIIIAAAggggED6BQiE6b/GjBABBBBAAAEEEMgqQCDMysNKBBBAAAEEEEAg/QIEwvRfY0aIAAIIIIAAAghkFSAQZuVhJQIIIIAAAgggkH4BAmH6rzEjRAABBBBAAAEEsgoQCLPysBIBBBBAAAEEEEi/AIEw/deYESKAAAIIIIAAAlkFCIRZeViJAAIIIIAAAgikX4BAmP5rzAgRQAABBBBAAIGsAgTCrDysRAABBBBAAAEE0i9AIEz/NWaECCCAAAIIIIBAVgECYVYeViKAAAIIIIAAAukX+H9bML66DnmZwwAAAABJRU5ErkJggg==) * **boolean** allows users to enter one of two values: true or false. ![Boolean input in Prismatic app](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAo4AAABvCAYAAABiiUHuAAAAAXNSR0IArs4c6QAAAGJlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAABJKGAAcAAAASAAAAUKABAAMAAAABAAEAAKACAAQAAAABAAACjqADAAQAAAABAAAAbwAAAABBU0NJSQAAAFNjcmVlbnNob3SB2vM/AAAB1mlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4xMTE8L2V4aWY6UGl4ZWxZRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpQaXhlbFhEaW1lbnNpb24+NjU0PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6VXNlckNvbW1lbnQ+U2NyZWVuc2hvdDwvZXhpZjpVc2VyQ29tbWVudD4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CoP12o8AABSrSURBVHgB7d1pbF3lncfxv53FTryFLM4OJqFZMCFkJu1UTcABEjaVuhqBRIcXUxrgBS0dEBJipKlYUppKBVVIJUBZGlpINZ1QNRRI2WuxhplMEpo4JGSCs+EsTuI6Nl4S7Hn+j/0czj25m7nHl3vu/T7i+uzPOedz8uLHc85zTlGfKUJBAAEEEEAAAQQQQCCBQFtHp11SnGA5sxFAAAEEEEAAAQQQiBEgOMZwMIEAAggggAACCCCQSIDgmEiG+QgggAACCCCAAAIxAgTHGA4mEEAAAQQQQAABBBIJEBwTyTAfAQQQQAABBBBAIEaA4BjDwQQCCCCAAAIIIIBAIgGCYyIZ5iOAAAIIIIAAAgjECBAcYziYQAABBBBAAAEEEEgkQHBMJMN8BBBAAAEEEEAAgRgBgmMMBxMIIIAAAggggAACiQQIjolkmI8AAggggAACCCAQI0BwjOFgAgEEEEAAAQQQQCCRAMExkQzzEUAAAQQQQAABBGIE8jo47ty5U5568kl59513Yk6aCQQQQAABBBBAAIHBCyQNjq+99posvfRS+3v22WdPq/2OO+7wlh85cuS05clm+OvWfVz97W/LTTfeKH987jnp6+tLtmnay2xwfOopeffdd9PehhURQAABBBBAAAEE4gsMjz+7f+7nn38uXV1ddmL9Sy/J9ddf763e0tIi//3BB9Lb22vnDTbsubrHjh0rs2fPlmPHjsmOHTtk+/bt0nPypFx33XXevhhBAAEEEEAAAQQQ+OoFkgZHd3gjRoyQpqYm2bVrl5xzzjl2trYYamjUZSdN0NOyceNGOXDggNSee67MHFjv5b/8Rbp7eqSurk6qqqrsev4/tbW1svLnP7ez/usPf5CHHnrI3lp2wVH3sW3bNvnwww+loqJCFi9eLBo2XUm13K3nht3d3bJ50ybZYW5jT5s2zR7XsGHD3GIbXnVfo0ePtvtyx9zY2GjPf+HChdZCPS668EKZNn26ty0jCCCAAAIIIIBAPgukFRw1LL333nvyyssve8Hx1VdesQFuwoQJNmwp0t49e+TBBx+Uiy+5RFasWCHNzc12qOHrqquuSunYOdC66cKYhsJ77r5b3njjDW/bR1atkgfMPjRwplrubTQw8tlnn8mPfvhD0VvYrpx33nnyq4cfluHDh8vTq1fLE088IUVFRbbu4uJiufXHP5Zrr71WGhoa5NlnnpGpU6facKzbrzLb3WfO8xJzvhQEEEAAAQQQQCDfBZI+4+hOXlvmZsyYIdrKqLek9+3bZ8PipUuXeq2Nuq4GRm29+2DDBjl16pS8M9ApZcnFF9tg5urzDzdv3iw333STXHvNNfLrxx6TuXPnyvLly+0qL/z5zzY0akj89eOPyw033CAnTpyQlT/7WVrL/fvRcQ16GhqvvvpqecY8s6mtoFu3bpUXX3jBHu/bb78ts2bNkj+tWyePPPKIDY9rAs92VlZWyqPmOJeb5zG1vPXWW3bIHwQQQAABBBBAIN8F0gqOnZ2dcvkVV8jhw4dliwl62tqo5QozT5e5MmbMGFn49a9LR0eHbNmyxevNvMwEzESlpKREJk6aZIOp3h7eu3ev3VbX1zq01H/3u3Kuuf19ww9+IOXl5fZWsXbGSbXcbuz7oyFVyySzPz2P8ePH2+lG81yltjg+blob9ba5Prv5/vvv25bHYKefpcuWibZSXn755XbbHR99ZIf8QQABBBBAAAEE8l0grVvV2kFmmQlMjz36qLxiQuP/mmcEa2pqbKcW13nGQWlI3GBC12uvviqbzHp6K/v8+fPd4tOG2sKot7W1fGRC2I2mtVFbHvX278GDB+18vT2sRW8djxs3Ttrb26WtrS3lcruR748+f6llzZo13tyysjLRW9h62/uuu+6yYXfmzJkyw/ySFT0WLb0h9QBPti+WIYAAAggggAACuSCQVoujdm6prq6WBQsWyPr162W/uVV92UCLW49Z5i8Xmdu/I0eOlBdffNHexr7UvGrHhSz/evHGXc/so0eP2lvieotcS6PpHKNFA6O2SGp9GiZTLbcb+f64APrT+++Xl00A1t9/mg45Gly144++71Fvya9++mm52zxbqc86UhBAAAEEEEAAAQT6BdJqcTQpzq6tt6u157QGqssuu8zOc2HPgert5kWLFsmbb75pZ+mt3WRFX8Fz7733Sqdp9dMWSi2LTM9p3Yfeon7JvAboSfMS733798uH5ta17k872pSWlqZcHtzvNaaTywO/+IXtcKMtqNpa+qppGdXONtpbXOvUVk5tkdxuelFrK6SWYKtqsF6mEUAAAQQQQACBQhBIq8XRQSxZskT0mcT5F1xgnxN084NDFxa1d/ScOXOCi2Om9blJfWbyA/Nc4bx58+S222+XO++8066jzzXefc89MtrcTn7edFjRW81XXnml/Nttt6W1PGZHZqK+vl5+dOutNhCuXbtWXn/9ddEWUd2PvurnVrNMO7/8xrw0XJ/XnDx5sq1i9+7dwaqYRgABBBBAAAEECk6gyLTghfOZFh+dPt94jwl83ze9oG8c6H3sW/ylRltbW23HGO3EEq+kWh7c5vjx4/a9ksHb6Mqh73rU1kcKAggggAACCCCAgEhbR39n6NCD44r77hN9rY1+Gea3v/udTJkyBW8EEEAAAQQQQACBCAu44Bi/+S6DE9OvrmhHmptvvpnQmIEjmyKAAAIIIIAAArkmEHqLY66dIMeDAAIIIIAAAgggkJmAa3EcVOeYzHbJ1ggggAACCCCAAAJRFiA4RvnqcewIIIAAAggggEAWBQiOWcRmVwgggAACCCCAQJQFCI5RvnocOwIIIIAAAgggkEUBgmMWsdkVAggggAACCCAQZQGCY5SvHseOAAIIIIAAAghkUYDgmEVsdoUAAggggAACCERZgOAY5avHsSOAAAIIIIAAAlkUIDhmEZtdIYAAAggggAACURZI+cnBzq4eOXisVdrNx617+/qifK4cOwIIIIAAAgggUBACxUVFUl42SiaNHSOjSkeGds5Jg6OGxl37ms0OS0SKzD7zLDfOn1UTGiQVIYAAAggggAACuSTQ1X1SPt77qZwzfXJo4THprWptadTQ2NHZJb29eZYac+nKciwIIIAAAggggEDIAqUlI+RrZ06xd47Dqjppi6PenrYtjWHtjXoQQAABBBBAAAEEsiag4dHmuZD2mDQ42mcaaWgMiZpqEEAAAQQQQACB7AuE2Ucl6a3q7J8ae0QAAQQQQAABBBDIVQGCY65eGY4LAQQQQAABBBDIMQGCY45dEA4HAQQQQAABBBDIVQGCY65eGY4LAQQQQAABBBDIMYGknWNy7Fg5nEEK9CV4YXuReSkoBQEEEEAAAQQQGKwAwXGwYjm8fqKgGDzk4HoEyaAQ0wgggAACCCAQT4DgGE8lYvOCQXCwh++2J0AOVo71EUAAAQQQKCwBgmOEr7cLfIlOIdHyRAHRrZ9oeaL9MB8BBBBAAAEECkOA4PgVXOe/dRySv7Y1yeb2Zvmkq1WOn+qU3sF+CNx7Mbs3EnMmxX1FcsbwUVJTOkYuKJ8kdZU1Mq9sol3HBUS3QTAouuXB+W59hggggAACCCBQmAIExyxe97fa9sjqQ5vkf9o/zWyvXlb0Rvrr801qED16qkOOtnfIxvYD8uTBjfKP5VPkX6sXyOLKM8UfChMFRZ3vXy+zg2ZrBBBAAAEEEIi6AMExS1dwxb4GWduyLfO9eeHQG5HYxkoz37fIv8ONJz4V/f3z+HPlP6bXeYtcOIwXFOPN8zZkBAEEEEAAAQQKSoDgOMSXu+3zbrl99/rMWxn1OG0gDKTCgcnvjJsjdVU1UltWLdUjyu1ZHT7ZLts6DktD6yfyfMt270z/eKRR9nQelwdmXCFVw0vF3+LoH/c2YAQBBBBAAAEEEDACRSYoBJLIFy5bdjZ9MZGHY/Nn1Qz5WS3/+E8hhkY93IHL5QXG2XLLlH+SySMrkp5Lc88JWbV/g6zTAGle46hvcvwHc+v68a/V2+1cq6Mb6kw37oZ2Rf4ggAACCCCAQKQENM9lmnnaOjrtOfPlmCG89Hp7OuPnGfX4vGgfGxrvmn6hrKhZmjI0ahUaLFfMWCr/ftZFpr4+/c/etr5/b4NtcdT/f3A/XV9Lkv+n6F+BvwgggAACCCBQUAIExyG63NoRJpRnGr3j89KjnaOh8XvV53tL0x353sT5JjzW2fCo6fG5lkZ52xyrC4nB8JhuvayHAAIIIIAAAvkvQHAcomusvadDKTYv+kKjGf3OuNlfKjS649HwWD9+bv+kCY+/Pbg5prXRHx5doHTbMkQAAQQQQACBwhUgOA7Btdf3NIZyizp4bAP5UZ9pzLTcMu2bIr2mQhMcN5rXA201x+wPjFo/oTFTZbZHAAEEEEAgvwSy1qu67e+fZSxXWTU64zqyUYG+3DuUYoPiQFocqFB7T6fqCJPOvieXVEj9hLmy7oj2tu6Thr83Se3o6phN6RQTw8EEAggggAACBS+QteD40dY9oWDPOe8syfUAqV+ECb0M5Ed95U5Ype6Ms2Xd4UZTXZFsSXDMtDqGpU09CCCAAAIIRF8ga8Fx6vTxGWtVVJXlfGjUk9TPCA5N6bPvaQyr7lrzKUK9Va2lqbM17q1pWh3D0qYeBBBAAAEEoi+QveB45oToa6V5Bvrt6YxLnNvU+loe93LvjOs3FVSPLPOqaTXH7FoXCYseCyMIIIAAAggg4BOgc4wPI2dHB25TD8nxaYuj/X3RGcYFSB268SHZN5UigAACCCCAQKQEstbieGDvETnRlrqDTEXlaJka8dbJM4aPkqOnUp/r4P6l9KdH/YxgGJ1jdN+He9q9QxhjPj2oRYOitji6obcCIwgggAACCCBQ8AJZCY7ao/rAvpa0sDU4Rr2cXTpGjraHHRz7VfTb02EFx20nDpmk2F/vWSVVUWfn+BFAAAEEEEBgiAWyEhy1F/Q3Fs2VdF7Jk+s9ptO5HheUTx6a9zianTe0fiJLz5iZzmGkXOevR//PW+f8skm0MnoajCCAAAIIIIBAPIGsPuOooTDVL95BRm3eksqaITvk51u2S3PPiYzrb+5uk3WHtnn1LK440xtnBAEEEEAAAQQQiCeQ1eAY7wDycd68somysHzKkJ3aqv0bMq774aZ3vDoWmNfy1JZV22cbvZmMIIAAAggggAACAQGCYwAkrMnvT1wQVlWn1bPOtDr+/tCW0+anO2PNgU2y7mCjee23KebPv1TPT3dT1kMAAQQQQACBAhYgOA7Rxb+w8iy5ZnxtiLXbmGfqM0Pz38o9DV8qPGpoXLnrDVuHaWKU+nFz5VsV073WRvcORzcM8QSoCgEEEEAAAQQiLpCVzjERN/rSh/+T6XXS1HU8844ymhnduxw1N+qrF016XNnUII3th+WWad8U/fZ0sqLPNOrtaW1ptNnThEbtxHPntMU2NGpQdGHRP3TjyepmGQIIIIAAAggUhgDBcYiv8y9nXCm3714/+PBow6I/MZoDDUzqoetta/3edP2EuaLfntbPCLovwuh7GvWVO9p72nWEMfnQ1NMfGleevcwLjVqXPzzqNAUBBBBAAAEEEPALFJkXPbu2LP98O75lZ9Np8/JpxvxZNVk7nRX7GmRtyxe9mNPasb0yA5fHXSV3uexXXUwtOm1/vnGt3FtPJ3zFBEe9PR1sadTQWFxc7IVHf4gcNmyYrwJGEUAAAQQQQCBKAprnMs08bR39n1OmxTFLV15vWy+pqpHVhzYNvvVRj9G1Nrqhm2eP383UYeKivae1I4x7ptGFQzeMt6UuoyCAAAIIIIAAAipAcMzivwPtMKO/v3WY28dtTbK5vVk+6WqV46c6pdd7iNF3QDYPulDo5g9Mm0Bnmovts472ocdAsiyWYtHPCOoXYfTl3vqeRn3ljhZ/UHTj8YZujwwRQAABBBBAAAEVIDh+Bf8O9D2P+kun+J8kcOM6DI4nmtZ9uGX+1kN/UNR1gtNung4pCCCAAAIIIICAChAcI/TvQMOdhkAXAP3jwdNw6+p8t75bx03rMJ1xtx1DBBBAAAEEEChsAYJjjl9/DXauxdB/qC7wxQuPbhu3jn87N+6WxRu6eW5dhggggAACCCCAgAoQHCP270BDnQuSyQKef73gKfq3c+PBoW7j5gW3ZxoBBBBAAAEEClOA4BiB664BzoVFPVz/tBsPhrx4LZH+U3Xru6Gr163jn+/mMUQAAQQQQACBwhYgOEbk+muQC4ZHPXR/QPSPpzotfzBMNJ6qDpYjgAACCCCAQGEJEBwjdL39Ac8ddrx5bhlDBBBAAAEEEEAgTIHiMCujLgQQQAABBBBAAIH8FSA45u+15cwQQAABBBBAAIFQBZIGx2LzXF1xMZ+cC1WcyhBAAAEEEEAAgSwKaJ4LqyQNjuVlo2RUSUlY+6IeBBBAAAEEEEAAgSwKdHWfFM1zYZWkwXHS2DHS2dUtZaNKaXkMS5x6EEAAAQQQQACBLAhoaPx476eieS6sUmRe4dKXrLLOrh45eKxV2js6pTf5qsmqYRkCCCCAAAIIIIBAlgT09rS2NGpoHFU6MuO9tpkcqCVlcMx4T1SAAAIIIIAAAgggEGkBFxyT3qqO9Bly8AgggAACCCCAAAKhChAcQ+WkMgQQQAABBBBAIH8FCI75e205MwQQQAABBBBAIFQBgmOonFSGAAIIIIAAAgjkrwDBMX+vLWeGAAIIIIAAAgiEKkBwDJWTyhBAAAEEEEAAgfwVIDjm77XlzBBAAAEEEEAAgVAFCI6hclIZAggggAACCCCQvwIEx/y9tpwZAggggAACCCAQqgDBMVROKkMAAQQQQAABBPJXgOCYv9eWM0MAAQQQQAABBEIVIDiGykllCCCAAAIIIIBA/goQHPP32nJmCCCAAAIIIIBAqAIEx1A5qQwBBBBAAAEEEMhfAYJj/l5bzgwBBBBAAAEEEAhVgOAYKieVIYAAAggggAAC+StAcMzfa8uZIYAAAggggAACoQoQHEPlpDIEEEAAAQQQQCB/BQiO+XttOTMEEEAAAQQQQCBUAYJjqJxUhgACCCCAAAII5K/A/wOjuLPeBNhMKQAAAABJRU5ErkJggg==) * **code** opens a code editor so users can enter XML, HTML, JSON, etc. Syntax highlighting can be added to a **code** input's definition and can reference any language supported by [PrismJS](https://prismjs.com/#supported-languages). (e.g. `input({ label: "My Code", type: "code", language: "json" })`) ![Code editor in Prismatic app](/docs/assets/images/code-48c2a44bd46c420ae4f7c50ebdbda038.png) * **conditional** allows users to enter a series of logical conditionals. This is most notably used in the [branch](https://prismatic.io/docs/components/branch.md) component. ![Conditional input in Prismatic app](/docs/assets/images/conditional-e506c09b0bdf739f65ded9ba7246e84f.png) You can also create **connection** inputs for actions. Read more about [connections](https://prismatic.io/docs/custom-connectors/connections.md). #### Dropdown menu inputs[​](#dropdown-menu-inputs "Direct link to Dropdown menu inputs") Rather than allowing integration builders to enter values for an input, you might want to have users choose a value from a list of possible values. You can do that by making your input into a dropdown menu. ![Dropdown menu in Prismatic app](/docs/assets/images/dropdown-input-c0e2e392f8c46f919f81d1c09d98438e.png) To create an input with a dropdown menu, add a `model` property to your input: ```ts export const acmeEnvironment = input({ label: "Acme Inc Environment to Use", placeholder: "ACME Environment", type: "string", required: true, model: [ { label: "Production", value: "https://api.acme.com/", }, { label: "Staging", value: "https://staging.acme.com/api", }, { label: "Sandbox", value: "https://sandbox.acme.com/api", }, ], }); ``` The `model` property should be an array of objects, with each object containing a `label` and a `value`. The `label` is shown in the dropdown menu. The `value` is passed in as the input's value to the custom component. #### Collection inputs[​](#collection-inputs "Direct link to Collection inputs") Most inputs represent single strings. A **collection** input, on the other hand, represents an array of values or key-value pairs. Collections are handy when you don't know how many items a component user might need. ##### Value list collection[​](#value-list-collection "Direct link to Value list collection") For example, your component might require an array of record to query, but you might not know how many record IDs a component user will enter. You can create a `valuelist` collection in code like this: Value List Collection Example ```typescript const assetIdsInputField = input({ label: "Asset ID(s)", type: "string", collection: "valuelist", required: true, }); ``` The corresponding UI in the integration designer would then prompt a user for any number of record IDs that they would like to enter: ![Value List collection in Prismatic app](/docs/assets/images/value-list-collection-fbd0abb1b26e205407cced17d6212571.png) When the input is received by an action's [perform function](https://prismatic.io/docs/custom-connectors/actions.md#the-perform-function), the input is a `string[]`. ##### Key value list collection[​](#key-value-list-collection "Direct link to Key value list collection") If you would like users to enter a number of key-value pairs as an input, you can use a `keyvaluelist` collection. The *Header* input on the [HTTP component](https://prismatic.io/docs/components/http.md#httpget) actions is an example of a `keyvaluelist` collection, and is defined in code like this: Key Value List Input ```typescript export const headersInputField = input({ label: "Header", type: "string", collection: "keyvaluelist", required: false, comments: "A list of headers to send with the request.", example: "User-Agent: curl/7.64.1", }); ``` The "Header" input, then, appears like this in the integration designer: ![Key Value List Collection in Prismatic app](/docs/assets/images/key-value-list-collection-93c770337fa131209be8f4003012a14f.png) When the input is received by an action's [perform function](https://prismatic.io/docs/custom-connectors/actions.md#the-perform-function), the input is an array of objects of the form: ```typescript [ { key: "foo", value: "bar", }, { key: "baz", value: 5, }, ]; ``` If you would like to convert the input to a key-value pair object, you can use the built-in Spectral function, `keyValPairListToObject`: ```typescript import { util } from "@prismatic-io/spectral"; const myObject = util.types.keyValPairListToObject(myInput); // { foo: "bar", baz: 5 } ``` #### Structured object inputs[​](#structured-object-inputs "Direct link to Structured object inputs") A structured object input groups related sub-inputs into a single named object. Use `structuredObjectInput` when an action needs to accept a complex type with named fields - such as a mailing address, a person's name, or any nested record. The integration designer presents each sub-field as its own input row under a collapsible group. Import `structuredObjectInput` from `@prismatic-io/spectral` and pass it a map of `inputs`: Structured object input ```typescript import { action, input, structuredObjectInput } from "@prismatic-io/spectral"; const createContactAction = action({ display: { label: "Create Contact", description: "Create a new CRM contact", }, inputs: { connection: connectionInput, name: structuredObjectInput({ label: "Name", inputs: { prefix: input({ label: "Prefix", type: "string" }), firstName: input({ label: "First Name", type: "string", required: true, }), lastName: input({ label: "Last Name", type: "string", required: true }), }, }), address: structuredObjectInput({ label: "Mailing Address", inputs: { street: input({ label: "Street", type: "string" }), city: input({ label: "City", type: "string" }), state: input({ label: "State", type: "string" }), zip: input({ label: "Zip Code", type: "string" }), }, }), }, perform: async (context, inputs) => { // Structured inputs are plain objects - access sub-fields with dot notation const fullName = `${inputs.name.prefix ?? ""} ${inputs.name.firstName} ${inputs.name.lastName}`.trim(); const { street, city, state, zip } = inputs.address; const client = createCrmClient(inputs.connection); return { data: await client.contacts.create({ name: fullName, street, city, state, zip, }), }; }, }); ``` ![Structured object input in Prismatic app](/docs/assets/images/structured-object-input-1429c427dc2e1b27a6db141f086ecdae.png) #### Dynamic object inputs[​](#dynamic-object-inputs "Direct link to Dynamic object inputs") A dynamic object input shows a different set of sub-inputs depending on which **configuration** the integration builder selects. This is useful when a single action must handle multiple distinct record types - for example, creating an Account, a Lead, or a Contact in a CRM - where each type has its own fields. Import `dynamicObjectInput` from `@prismatic-io/spectral` and define a `configurations` map where each key names a configuration: Dynamic object input - CRM record types ```typescript import { action, input, dynamicObjectInput } from "@prismatic-io/spectral"; const createRecordAction = action({ display: { label: "Create Record", description: "Create an Account, Lead, or Contact in your CRM", }, inputs: { connection: connectionInput, record: dynamicObjectInput({ label: "Record", configurations: { account: { label: "Account", inputs: { companyName: input({ label: "Company Name", type: "string", required: true, }), industry: input({ label: "Industry", type: "string", model: [ { label: "Technology", value: "tech" }, { label: "Finance", value: "finance" }, { label: "Healthcare", value: "healthcare" }, ], }), annualRevenue: input({ label: "Annual Revenue", type: "string" }), }, }, lead: { label: "Lead", inputs: { firstName: input({ label: "First Name", type: "string", required: true, }), lastName: input({ label: "Last Name", type: "string", required: true, }), company: input({ label: "Company", type: "string" }), leadSource: input({ label: "Lead Source", type: "string", model: [ { label: "Web", value: "web" }, { label: "Referral", value: "referral" }, { label: "Event", value: "event" }, ], }), }, }, contact: { label: "Contact", inputs: { firstName: input({ label: "First Name", type: "string", required: true, }), lastName: input({ label: "Last Name", type: "string", required: true, }), email: input({ label: "Email", type: "string", required: true }), phone: input({ label: "Phone", type: "string" }), }, }, }, }), }, perform: async (context, inputs) => { const client = createCrmClient(inputs.connection); // inputs.record.configuration holds the key the builder selected. // inputs.record.values holds the input values for that configuration. if (inputs.record.configuration === "account") { return { data: await client.accounts.create({ companyName: inputs.record.values.companyName, industry: inputs.record.values.industry, annualRevenue: inputs.record.values.annualRevenue, }), }; } if (inputs.record.configuration === "lead") { return { data: await client.leads.create({ firstName: inputs.record.values.firstName, lastName: inputs.record.values.lastName, company: inputs.record.values.company, leadSource: inputs.record.values.leadSource, }), }; } if (inputs.record.configuration === "contact") { return { data: await client.contacts.create({ firstName: inputs.record.values.firstName, lastName: inputs.record.values.lastName, email: inputs.record.values.email, phone: inputs.record.values.phone, }), }; } }, }); ``` Note that [discriminated unions](https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-func.html#discriminated-unions) help in identifying the shape of `inputs.record.values` based on the value of `inputs.record.configuration`. The integration designer shows a dropdown of configuration labels - **Account**, **Lead**, or **Contact** - and displays only the sub-inputs for the selected type. In the `perform` function, `inputs.record.configuration` holds the selected configuration key, and `inputs.record.values` holds the input values the builder filled in for that configuration. ![Dynamic object input in Prismatic app](/docs/assets/images/dynamic-object-input-f5d7c525e10c5d5710e367fc70ee71a0.png) #### Cleaning inputs[​](#cleaning-inputs "Direct link to Cleaning inputs") An input of an action can be anything - a number, string, boolean, JavaScript Buffer, a complex object with lots of properties, etc. If you reuse an input for multiple actions, it's handy to do some preprocessing and type conversion on the input. That's where a `clean` function on an input comes in. For example, suppose you expect an input to be a number. But, inputs by default are presented to `perform` functions as strings. You can leverage the `util.types.toNumber()` utility function and `clean` property to ensure that the input is presented to the `perform` function as a number: Ensure input is a number ```typescript const serverPortInput = input({ label: "Server Port", placeholder: "The port of the API server", comments: "Look for the number after the colon (my-server.com:3000)" type: "string", default: "3000", required: true, clean: (value) => util.types.toNumber(value), }); ``` You can also add validation to the input. For example, if you want to validate that the input is an IPv4 IP address, you can build a more complex `clean` function: Validate that an input is an IP address ```typescript const validateIpAddress = (value: unknown) => { const ipAddressRegex = /^(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))$/; const inputValue = util.types.toString(value); if (!ipAddressRegex.test(inputValue)) { throw new Error(`The value "${inputValue}" is not a valid IP address`); } return inputValue; }; const ipAddressInput = input({ label: "IP Address", placeholder: "Server IP Address", type: "string", default: "192.168.1.1", required: true, clean: validateIpAddress, }); ``` #### Handle complex inputs in a custom action[​](#handle-complex-inputs-in-a-custom-action "Direct link to Handle complex inputs in a custom action") When an API endpoint that you're wrapping in a custom action expects a simple payload, like ```text POST /widgets { "name": "string", "color": "string", "quantity": "number" } ``` it's easy to map each value in the POST request to an input. Here, we'd create a "name" input, a "color" input, and a "quantity" input. Then, we'd apply a `clean: util.types.toNumber` clean function to the "quantity" input to ensure it is cast to a number. But, some endpoints expect complex payloads that may contain arrays of objects with optional properties, etc. ```text POST /widgets { "externalId": "abc-123", "variants": [ { "name": "Variant 1", "color": "red", "price": { "usd": 5, "ca": 5.5 } }, { "name": "Variant 2", "color": "blue", "price": { "usd": 6 } } ] } ``` In this case, it's likely that an integration builder will want to construct a property like `variants` programmatically, and it's probably best to present two inputs, "External ID" which is `type: "string"` and "Variants" which is `type: "code"`. To accommodate both JSON and JavaScript object inputs, use the `util.types.toObject` function to ensure that what is entered becomes a JavaScript object. For example, Convert a complex input to an object ```ts const createWidgets = action({ display: { label: "Create Widgets", description: "Create widgets and their variants", }, inputs: { connection: connectionInput, externalId: input({ label: "External ID", type: "string", comments: "The ID stored in Acme for this Widget type", clean: util.types.toString, }), variants: input({ label: "Variants", comments: "Variant types of the widget. Ensure you provide an array of variant objects.", type: "code", language: "json", clean: util.types.toObject, example: JSON.stringify( [ { name: "Variant 1", color: "red", price: { usd: 5, ca: 5.5, }, }, { name: "Variant 2", color: "blue", price: { usd: 6, }, }, ], null, 2, ), }), }, perform: async (context, inputs) => { const client = createAcmeClient(inputs.connection); const { data } = await client.post("/widgets", { externalId: inputs.externalId, variants: inputs.variants, }); return { data }; }, }); ``` --- #### Handling API Pagination Pagination is a common pattern in APIs that return large sets of data. Instead of returning all records at once, APIs often return a subset of records (a "page") along with information on how to retrieve the next page. This helps reduce the amount of data transferred in a single request and improves performance. #### Pagination in custom components[​](#pagination-in-custom-components "Direct link to Pagination in custom components") Every application implements pagination differently. Some applications require you to pass page number and number of records to return as URL search parameters (i.e. `?page=5&page_size=20`). In that case, it's your job to keep track of which page you're on. Others return a "cursor" with the response (either in the body or as a response header). You can include that cursor with your next request to get another page of results. As you build an action for an API that paginates, ask this question: *is it reasonable to pull down all records at one time?* If the API you're interacting with returns 100 records at a time, for example, and you know that customers never have more than a few hundred records of a particular type, it probably makes sense to pack pagination logic into your custom action. That way, your customers don't need to keep track of page numbers or cursors - your action simply returns all results. In this example, Airtable returns a JSON payload with an `offset` property and array of `records` that we accumulate in a `do`/`while` loop: Handling pagination within an action ```ts export interface AirtableRecord { id: string; createdTime: string; fields: Record; } export interface AirtableRecordResponse { offset: string; records: AirtableRecord[]; } const listRecords = action({ display: { label: "List Records", description: "List all records inside of the given table", }, inputs: { airtableConnection: connectionInput, baseId: baseIdInput, tableName: tableNameInput, view: viewInput, }, perform: async (context, inputs) => { const client = createAirtableClient(inputs.airtableConnection); const records: AirtableRecord[] = []; let offset = ""; do { const { data } = await client.get( `/v0/${inputs.baseId}/${inputs.tableName}`, { params: { view: inputs.view, offset, }, }, ); records.push(...data.records); offset = data.offset; } while (offset); return { data: records }; }, }); ``` On the other hand, if you know that your customers have a significant number of records stored in a third-party application (e.g. they have millions of records in their Airtable base), it's more memory-efficient to fetch a page of records at a time, processing each page before fetching the next page. In that case, we recommend adding `offset`, `cursor`, `page_number`, etc., as inputs of your action, and ensure that your action returns those values for the next iteration. ##### Handling an API that uses link headers for pagination[​](#handling-an-api-that-uses-link-headers-for-pagination "Direct link to Handling an API that uses link headers for pagination") If the API you're working with returns [link headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Link), we recommend the [parse-link-header](https://www.npmjs.com/package/parse-link-header) package, which can be used to extract the next URL to use when paginating. --- #### Publishing a Custom Component #### Publishing a custom component[​](#publishing-a-custom-component "Direct link to Publishing a custom component") Build your component by running `npm run build`: ```bash npm run build ``` This creates a `dist/` directory containing your compiled JavaScript and icon image. Now use `prism` to publish your component. If you do not have Prismatic's CLI tool, `prism`, installed, please take a moment to look through the [Prism overview page](https://prismatic.io/docs/cli.md). ```bash $ prism components:publish Format Name - Format a person's name given a first, middle, and last name Would you like to publish Format Name? (y/N): y Successfully submitted Format Name (v6)! The publish should finish processing shortly. ``` #### Component versioning[​](#component-versioning "Direct link to Component versioning") Components are versioned. The first time a component is published it is given version "1". Thereafter, each time the component is republished its version number increments by one. Within the integration designer, you can choose which version of a component to use. Components marked in grey are using the latest version, and components marked with yellow have newer versions available. ![Component version drawer in Prismatic app](/docs/assets/images/component-version-drawer-f631af013799bfec554f534f68ca2707.png) In most cases, you'll want to use the latest version of each component. For the sake of stability and consistency, though, versions of components used in integrations are not bumped to the latest version automatically. This prevents unintended issues in your integration if team members publish a broken custom component. You can keep an integration pinned to component "version 6" for example, while your team members experiment with a newer "version 7" of a custom component for another integration. When your team publishes a new version of a custom component, or when Prismatic publishes a new version of a built-in component, you will see a notification in the integration designer on the **Component Versions** button indicating that some components are outdated. To upgrade those components, click the **Component Versions** button and select **CHANGE VERSION** for the component whose version you want to change. #### Publishing components in a CI/CD pipeline[​](#publishing-components-in-a-cicd-pipeline "Direct link to Publishing components in a CI/CD pipeline") If you have multiple tenants, or if you want to automate the publishing of your custom components, you can incorporate component publishing into your CI/CD pipeline. At a high level, the steps to publish a component in a CI/CD pipeline are: 1. Install the [`prism` CLI tool](https://prismatic.io/docs/cli.md) 2. [Authenticate](https://prismatic.io/docs/api/ci-cd-system.md) the `prism` CLI tool with your Prismatic tenant 3. Build your component 4. Use the `prism components:publish` command to publish your component If you use GitHub, you can use Prismatic's [GitHub Actions](https://prismatic.io/docs/api/github-actions.md) to publish your component as part of your GitHub Actions workflow. [This example repo](https://github.com/prismatic-io/example-project-structure) contains an example project structure and GitHub Actions workflow that builds and publishes multiple custom components to Prismatic whenever changes are pushed to the `main` branch. ##### Including git commit information[​](#including-git-commit-information "Direct link to Including git commit information") When publishing components in a CI/CD pipeline, you may want to include git commit information with your component publish. This information can help you track which version of your component code is associated with each published component version. In this example, we derive the commit hash, commit URL, and repository URL from the git repository and include that information when publishing the component: Example: Publishing with git commit info ```bash prism components:publish \ --comment "Refactored the 'Get Widgets' action"\ --commitHash $(git rev-parse HEAD) \ --commitUrl $(git config --get remote.origin.url)/commit/$(git rev-parse HEAD) \ --repoUrl $(git config --get remote.origin.url) ``` This information is available in the web app when viewing the component's details. ![Component commit information in Prismatic app](/docs/assets/images/component-details-3c12eea59c22a8c708775a9dda0574eb.png) **Note**: If you use Prismatic's [GitHub Actions](https://prismatic.io/docs/api/github-actions.md), the action automatically includes git commit information when publishing your component. --- #### Step Outputs [Actions](https://prismatic.io/docs/custom-connectors/actions.md) and [Triggers](https://prismatic.io/docs/custom-connectors/triggers.md) can return a variety of data types. To return a simple string, number, boolean, array, or object your return block can read: ```typescript // return a string: return { data: "some string", }; // return a number: return { data: 123.45, }; // return a boolean: return { data: true, }; // return an array: return { data: [1, 2, 3, 4, "a", "b"], }; // return an object: return { data: { key1: "value1", key2: ["value2", 123], }, }; ``` Those values can be used as inputs in subsequent steps by referencing this step's `results`: ![Step results from an action in Prismatic app](/docs/assets/images/step-results-de709fdd89c75df85d74a5fca27694f8.png) If you'd like to return binary data (for example, a PDF file), see [Returning binary data from an action](https://prismatic.io/docs/custom-connectors/binary-files.md#returning-binary-data-from-an-action). #### Example payloads[​](#example-payloads "Direct link to Example payloads") Your custom trigger or action's results can be used as [inputs](https://prismatic.io/docs/custom-connectors/inputs.md) for subsequent steps. But, until the action is executed, the actual results are not available. As an integration builder or as one of your customers' [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) users, you may want to add several steps to your workflow without having to run your entire workflow after adding each step. Your action can provide an example payload in a number of ways that allow your end users to build their workflows faster. ##### Selecting an example payload in the integration designer[​](#selecting-an-example-payload-in-the-integration-designer "Direct link to Selecting an example payload in the integration designer") As an organization user in the integration designer, you can select which step result type you'd like to reference as you map step results to inputs. Depending on what are defined for the action, you can select from: * A recent test run * A stand-alone step result from an out-of-band `perform` or `examplePerform` invocation * Sample data from output schema or example payload ![Selecting an example payload in the integration designer](/docs/assets/images/example-reference-selector-92aded6e006564619cd670e3b488b1b9.png) ##### Example payloads in the embedded workflow builder[​](#example-payloads-in-the-embedded-workflow-builder "Direct link to Example payloads in the embedded workflow builder") Your customers using the embedded workflow builder will be presented the best example possible based on recent executions and what example data is available. The embedded workflow builder will present a step result based on this logic: ##### Safely running the perform function[​](#safely-running-the-perform-function "Direct link to Safely running the perform function") **When to use:** If your action is non-destructive (does not modify data), and you set `performSafety` to `'safe'`, the integration designer and embedded workflow builder will invoke your `perform` function outside of an execution to provide example data. The best data your component can yield is real data. With real data, your customer can be guaranteed to be presented with an accurate shape of the step's return value. For example, if they have custom fields associated with a certain record type in a CRM, the real data will include those custom fields. Note: your reference step must be fully configured with a valid connection. Run perform function safely ```typescript action({ display: { label: "Fetch a Record", description: "Fetch a record from Acme", }, inputs: { connection: input({ label: "Connection", type: "connection", required: true, }), recordId: input({ label: "Record ID", type: "string", required: true, }), }, perform: async (context, inputs) => { const record = await fetchRecord({ recordId: inputs.recordId, apiKey: inputs.connection.fields.apiKey, }); return { data: record }; }, performSafety: "safe", }); ``` ##### `examplePerform`[​](#exampleperform "Direct link to exampleperform") **When to use:** If your action mutates data (creates, updates, or deletes data), but you still want to provide real data for example purposes, you can define an `examplePerform` function. Similar to a `perform` function, the `examplePerform` function is defined on the action and is invoked outside of execution when the step is referenced if it is defined, the step is fully configured (including any connections). Run examplePerform function ```typescript action({ display: { label: "Update a Record", description: "Update a record in Acme", }, inputs: { connection: input({ label: "Connection", type: "connection", required: true, }), recordId: input({ label: "Record ID", type: "string", required: true, }), name: input({ label: "Name", type: "string" }), email: input({ label: "Email", type: "string" }), companyId: input({ label: "Company ID", type: "string" }), }, perform: async (context, inputs) => { const record = await updateRecord({ recordId: inputs.recordId, apiKey: inputs.connection.fields.apiKey, newData: { name: inputs.name, email: inputs.email, companyId: inputs.companyId, }, }); return { data: record }; }, examplePerform: async (context, inputs) => { const record = await fetchRecord({ recordId: inputs.recordId, apiKey: inputs.connection.fields.apiKey, }); // Return the record, simulating an update to fields that changed return { data: { ...record, ...(inputs.name ? { name: inputs.name } : {}), ...(inputs.email ? { email: inputs.email } : {}), ...(inputs.companyId ? { companyId: inputs.companyId } : {}), }, }; }, }); ``` ##### Static output schema[​](#static-output-schema "Direct link to Static output schema") **When to use:** you know the shape of the data your action will return, and would like the workflow builder to yield lorem ipsum data based on that shape. Output schema is particularly useful when consumed by the embedded workflow builder [AI Copilot](https://prismatic.io/docs/embed/workflow-builder/ai-copilot.md) since it provides clear guidance on the structure of the data that will flow through the workflow, including which return fields to expect all the time and which return fields are optional. The step does not need to be fully configured to yield an example based on output schema. Static output schema ```typescript action({ display: { label: "Fetch a Record", description: "Fetch a record from Acme", }, inputs: { connection: input({ label: "Connection", type: "connection", required: true, }), recordId: input({ label: "Record ID", type: "string", required: true, }), }, perform: async (context, inputs) => { const record = await fetchRecord({ recordId: inputs.recordId, apiKey: inputs.connection.fields.apiKey, }); return { data: record }; }, outputSchema: { type: "actionOutput", schema: { type: "object", properties: { person: { type: "object", properties: { first: { type: "string" }, last: { type: "string" }, }, required: ["last"], // Only last name is guaranteed }, age: { type: "number", minimum: 0, maximum: 123 }, }, required: ["person", "age"], }, }, }); ``` ##### Static example payload[​](#static-example-payload "Direct link to Static example payload") **When to use:** you know the shape of the result of your action and would like to provide your own example data. Include an example payload in addition to other example sources We recommend that you include an `examplePayload` in addition to other example strategies (like setting `performSafety` to `'safe'` or defining an `examplePerform` function). That way, if the step is not fully configured and cannot be run outside of an execution, the reference picker has something to fall back on. Static example payload ```typescript action({ display: { label: "Fetch a Record", description: "Fetch a record from Acme", }, inputs: { connection: input({ label: "Connection", type: "connection", required: true, }), recordId: input({ label: "Record ID", type: "string", required: true, }), }, perform: async (context, inputs) => { const record = await fetchRecord({ recordId: inputs.recordId, apiKey: inputs.connection.fields.apiKey, }); return { data: record }; }, examplePayload: { data: { person: { first: "John", last: "Doe", }, age: 20, }, }, }); ``` **Note:** your `examplePayload` must match the exact TypeScript type of the return value of your `perform` function. If your `perform` function's return value does not match the type of the example payload, TypeScript will generate a helpful error message: ![Example Result Data Type Mismatch in Typescript](/docs/assets/images/example-result-data-type-mismatch-452b765b9db0b37daa9a6d365e260e6b.png) --- #### Custom Triggers #### Writing custom triggers[​](#writing-custom-triggers "Direct link to Writing custom triggers") Integrations are usually triggered [on a schedule](https://prismatic.io/docs/integrations/triggers/schedule.md) (meaning instances of the integration run every X minutes, or at a particular time of day) or [via webhook](https://prismatic.io/docs/integrations/triggers/webhook.md) (meaning some outside system sends JSON data to a unique URL and an instance processes the data that was sent). The vast majority of integrations built in Prismatic start with a schedule trigger or webhook trigger. There are situations, though, where neither the schedule nor the standard webhook trigger are suitable for one reason or another. That's where writing your own triggers come in handy. Triggers are custom bits of code that are similar to [actions](https://prismatic.io/docs/custom-connectors/actions.md). They give you fine-grained control over how a webhook's payload is presented to the rest of the steps of an integration and what HTTP response is returned to whatever invoked the trigger's webhook URL. Similar to an action, a trigger is comprised of `display` information, a `perform` function and `inputs`. Additionally, you specify if your trigger can be invoked [synchronously](https://prismatic.io/docs/integrations/triggers/webhook/synchronous-and-asynchronous.md) (`synchronousResponseSupport`) and if your trigger supports scheduling (`scheduleSupport`). Suppose, for example, a third-party app can be configured to send CSV data via webhook and requires that the webhook echo a header, `x-confirmation-code`, back in plaintext to confirm it got the payload. The default webhook trigger accepts JSON, and responds with an execution ID, so it's not suitable for integrating with this third-party app. This trigger will return an HTTP 200 and echo a particular header back to the system invoking the webhook, and then it'll parse the CSV payload into an object so that subsequent steps can reference through the trigger's `results.body.data`: ```typescript import { input, trigger, TriggerPayload, HttpResponse, util, } from "@prismatic-io/spectral"; import papaparse from "papaparse"; // CSV Library export const csvTrigger = trigger({ display: { label: "CSV Webhook", description: "Accepts and parses CSV data into a referenceable object and returns a plaintext ACK to the webhook caller.", }, perform: async (context, payload, { hasHeader }) => { // Create a custom HTTP response that echos a header, // x-confirmation-code, that was received as part of // the webhook invocation const response: HttpResponse = { statusCode: 200, contentType: "text/plain; charset=utf-8", body: payload.headers["x-confirmation-code"], }; // Create a copy of the webhook payload, deserialize // the CSV raw body, and add the deserialized object // to the object to the trigger's outputs const finalPayload: TriggerPayload = { ...payload }; const parseResult = papaparse.parse( util.types.toString(payload.rawBody.data), { header: util.types.toBool(hasHeader), }, ); finalPayload.body.data = parseResult.data; // Return the modified trigger payload and custom HTTP response return Promise.resolve({ payload: finalPayload, response, }); }, inputs: { // Declare if the incoming CSV has header information hasHeader: input({ label: "CSV Has Header", type: "boolean", default: "false", }), }, synchronousResponseSupport: "invalid", // Do not allow synchronous invocations scheduleSupport: "invalid", // Do not allow scheduled invocations }); export default { csvTrigger }; ``` Notice a few things about this example: * The `trigger`'s form is very similar to that of an `action` definition. * The `response` contains an HTTP `statusCode`, `body`, and `contentType` to be returned to the webhook caller. * The second argument to the `perform` function - `payload` - contains the same information that a standard webhook trigger returns. The `rawBody.data` presumably contains some CSV text - the `body.data` key of the payload is replaced by the deserialized version of the CSV data. * `inputs` work the same way that they work for actions - you define a series of `input`s, and they're passed in as the third parameter of the `perform` function. ##### Instance lifecycle functions[​](#instance-lifecycle-functions "Direct link to Instance lifecycle functions") Similar to a `perform` function, a trigger can also define several [lifecycle functions](https://prismatic.io/docs/integrations/lifecycle.md) that run when an instance is created, updated or deleted. These include `onInstanceDeploy` and `onInstanceDelete` functions that are called when an instance is deployed or deleted, respectively. They are handy for creating or deleting resources in a third-party system that are associated with an instance (like custom record types, file directories, etc). Additionally, if your trigger is a webhook trigger, you can define `webhookLifecycleHandlers` that contain `create` and `delete` functions that run when an instance is deployed or deleted, respectively. These functions also run in the integration designer when you enter [Listening Mode](https://prismatic.io/docs/integrations/triggers/listening-mode.md). ##### Adding a trigger to your component[​](#adding-a-trigger-to-your-component "Direct link to Adding a trigger to your component") Once you've written a trigger, you can add it to an existing component the same way you add an action to your component, but using the `triggers` key: ```typescript import { csvTrigger } from "./csvTrigger"; export default component({ key: "format-name", public: false, display: { label: "Format Name", description: "Format a person's name given a first, middle, and last name", iconPath: "icon.png", }, actions: { improperFormatName, properFormatName, }, triggers: { csvTrigger }, }); ``` #### App event triggers[​](#app-event-triggers "Direct link to App event triggers") It's common for users to want to know when records are created or updated in a third-party app. There are a couple of ways you can achieve this: 1. An event-based system uses [webhooks](https://prismatic.io/docs/integrations/triggers/webhook.md) to notify your flow whenever something happens. 2. A trigger polls the third-party API for changes on a time interval. Generally speaking, webhook triggers are preferable over polling triggers as they provide near real-time updates. ##### App event webhook triggers[​](#app-event-webhook-triggers "Direct link to App event webhook triggers") An app event webhook trigger takes advantage of `webhookLifecycleHandlers.create` and `webhookLifecycleHandlers.delete` functions (described [above](https://prismatic.io/docs/custom-connectors/triggers.md#instance-lifecycle-functions)). When a customer configures and deploys an instance of your integration, `webhookLifecycleHandlers.create` configures a webhook. When the instance is removed, the `webhookLifecycleHandlers.delete` trigger removes the webhook. Additionally, in the integration designer if you enter [Listening Mode](https://prismatic.io/docs/integrations/triggers/listening-mode.md), the `webhookLifecycleHandlers.create` function will run to create a temporary webhook for testing purposes, and when you exit Listening Mode, the `webhookLifecycleHandlers.delete` function will run to clean up the temporary webhook. ###### Example app event trigger using webhooks[​](#example-app-event-trigger-using-webhooks "Direct link to Example app event trigger using webhooks") This example trigger will create a webhook in a third-party app when an instance is deployed, storing the webhook ID in persistent data, and delete the webhook when the instance is deleted: ```typescript const acmeWebhookTrigger = trigger({ display: { label: "Acme Webhook Trigger", description: "Acme will notify your app when certain events occur in Acme", }, scheduleSupport: "invalid", synchronousResponseSupport: "invalid", inputs: { connection: input({ label: "Acme Connection", type: "connection", required: true, }), events: input({ type: "string", label: "Events", comments: "The events that would cause an Acme webhook request to be sent to this flow", collection: "valuelist", model: [ { label: "Lead Created", value: "lead_created" }, { label: "Lead Updated", value: "lead_updated" }, { label: "Lead Deleted", value: "lead_deleted" }, ], }), }, /** Run when a trigger is invoked. This function could contain additional logic for verifying HMAC signatures, etc. */ perform: async (_context, payload, _inputs) => { return Promise.resolve({ payload }); }, /** Run when an instance with this trigger is deployed */ webhookLifecycleHandlers: { create: async (context, inputs) => { // Get the current flow's webhook URL const flowWebhookUrl = context.webhookUrls[context.flow.name]; // Create a webhook in Acme const { data } = await axios.post( "https://api.acme.com/webhooks", { endpoint: flowWebhookUrl, events: inputs.events, }, { headers: { Authorization: `Bearer ${inputs.connection.token?.access_token}`, }, }, ); // Store the webhook ID in persisted state for deletion later // Use stableId for consistency (id changes each version, and name may change) return { crossFlowState: { [`${context.flow.stableId}-webhook-id`]: data.id }, }; }, /** Run when an instance with this trigger is removed */ delete: async (context, inputs) => { // Get the webhook ID from the persisted state const webhookId = context.crossFlowState[`${context.flow.stableId}-webhook-id`]; // Delete the webhook from Acme await axios.delete(`https://api.acme.com/webhooks/${webhookId}`, { headers: { Authorization: `Bearer ${inputs.connection.token?.access_token}`, }, }); }, }, }); ``` Ensure your webhookLifecycleHandlers.create function is idempotent Either the external third-party API, or your trigger, should be designed to be idempotent - meaning that if the `webhookLifecycleHandlers.create` is created twice, it won't cause any problems. To test your trigger's `webhookLifecycleHandlers.create` and `webhookLifecycleHandlers.delete` functions in the integration designer, open the **Test Runner** drawer and click **Test Deploy** or **Test Delete** within the **Trigger** tab. warning Note that `webhookLifecycleHandlers.create` and `webhookLifecycleHandlers.delete` functions do not have access to flow-specific persisted data. Both functions should read and write data at the `crossFlowState` level. You can store unique data for each flow using key names that include the flow name in order to generate unique persisted data keys, like `${context.flow.stableId}-webhook-id` in the example above. ##### App event polling triggers[​](#app-event-polling-triggers "Direct link to App event polling triggers") Polling triggers are used when you want to be notified of changes in an external app, but the app does not support webhooks. The trigger's job is to fetch any new data since the last time it ran. A `pollingTrigger` is similar to a standard trigger that supports running on a schedule. Its `perform` function receives an additional parameter, `context.polling`, which has a few functions: * `context.polling.getState()` will fetch existing poll state. * `context.polling.invokeAction()` can invoke an existing component's action (if one exists) to fetch data from the external app. This is handy if you don't want to duplicate logic in your trigger and an action. * `context.polling.setState()` sets state for the next execution to load. Generally, a polling trigger's `perform` function will look like this: 1. **Get current poll state** from `context.polling.getState()`. This state will represent a cursor of some kind, depending on the API you're working with. If the API is paginated with pages that are numbers, your state may represent the number of the last page you fetched. If records in the API have "updated at" timestamps, this state may represent the most recent timestamp you've processed. 2. **Fetch new records.** Using the cursor you loaded, fetch records that you have not yet processed. You can either use `context.polling.invokeAction()` to run an action that fetches new data, or you can implement the logic yourself. If the API uses numbered pagination, fetch `lastPage + 1`. If the API uses "updated at" timestamps, query for records where `updated_at > ${previous_updated_at}`. Implementations will be different depending on the service you're integrating with. 3. **Update poll state** using `context.polling.setState()`. Save the newest page number of "updated at" timestamp that you fetched. 4. **Return new records** for the flow to process. If no new records were found, return `polledNoChanges: true` which will cause the execution to stop immediately. ###### Example PostgreSQL polling trigger[​](#example-postgresql-polling-trigger "Direct link to Example PostgreSQL polling trigger") This example polling trigger connects to a [PostgreSQL](https://prismatic.io/docs/components/postgres.md) database and queries a table called `people` which has columns `firstname TEXT`, `lastname TEXT` and `updated_at TIMESTAMP`. While PostgreSQL can trigger a webhook request when data changes through a combination of a postgresql [TRIGGER](https://www.postgresql.org/docs/current/sql-createtrigger.html) function and HTTP plugin, implementing webhooks in your database can cause the database to slow down considerably, since every INSERT or UPDATE waits for an HTTP request. Polling makes more sense when looking for updates in a PostgreSQL database. The first time this polling trigger runs, it finds `MAX(updated_at)::TEXT`. We cast the timestamp to `TEXT` so that it can be stored in persisted state readily, and so that PostgreSQL returns a timestamp with microseconds (it normally returns just milliseconds). On subsequent runs, we load the `cursor` (previous timestamp) that was found, and execute `"SELECT firstname, lastname FROM people WHERE updated_at > ${cursor}"`, polling any record that has an `updated_at` timestamp greater than the previous timestamp. Example polling trigger that invokes an existing action ```typescript import { pollingTrigger } from "@prismatic-io/spectral"; import { connectionInput } from "./inputs"; import { createDB } from "./client"; export const pollPeople = pollingTrigger({ display: { label: "Poll people table for changes", description: "Fetch any updated records in the Acme people table", }, inputs: { postgresConnection: connectionInput, }, perform: async (context, payload, inputs) => { const db = createDB(inputs.postgresConnection); const state = context.polling.getState(); const cursorQuery = "SELECT MAX(updated_at)::TEXT AS cursor FROM people"; try { if (!state?.cursor) { // No previous cursor was found. This is the first time this // trigger has run, so fetch an initial cursor and then exit const { cursor: newCursor } = await db.one(cursorQuery); context.polling.setState({ cursor: newCursor }); context.logger.log( `First time running. Next time records with "updated_at" greater than "${newCursor}" will be fetched.`, ); return { payload, polledNoChanges: true, }; } // The trigger has run previously. Fetch results since it last ran const result = await db.tx(async (task) => { return { recordsQuery: await task.manyOrNone( "SELECT firstname, lastname FROM people WHERE updated_at > ${cursor}", { cursor: state.cursor }, ), cursorQuery: await task.one(cursorQuery), // Also fetch new cursor in the same transaction }; }); const newCursor = result.cursorQuery.cursor; const records = result.recordsQuery; context.polling.setState({ cursor: newCursor }); if (records.length > 0) { // If any new records were found, return them return { payload: { ...payload, body: { data: records } }, polledNoChanges: false, }; } else { // If no results were found, return nothing and exit return { payload, polledNoChanges: true }; } } finally { await db.$pool.end(); } }, }); ``` Note that if you return `polledNoChanges: true`, the runner will immediately stop and your flow will not continue to run. Use this property if you checked for new changes, but found none. ###### Example polling trigger using existing action[​](#example-polling-trigger-using-existing-action "Direct link to Example polling trigger using existing action") In this example, imagine you already have a custom component with an action `listProducts` that returns a result like this: List Products action return value ```json { "products": [ {"id": 123, "color": "red", "name": "Widget"}, {"id": 456, "color": "red", "name": "Gadget"} ] "page_info": { "limit": 100, "page": 20 } } ``` You can leverage this already-existing action in a polling trigger using the `pollAction` property, and `context.polling.invokeAction()` function: Invoking an action in a polling trigger ```typescript import { pollingTrigger } from "@prismatic-io/spectral"; import { listProducts } from "./actions"; import { connectionInput } from "./inputs"; interface MyPollingState { limit?: number; page?: number; } interface Product { id: number; color: string; name: string; } interface ListProductsResult { products: Product[]; page_info: { limit: number; page: number; }; } const myPollingTrigger = pollingTrigger({ display: { label: "Poll products API for changes", description: "Fetch new products from Acme", }, pollAction: listProducts, inputs: { connection: connectionInput }, perform: async (context, payload, inputs) => { const { limit, page: oldPage }: MyPollingState = context.polling.getState(); const { data } = (await context.polling.invokeAction({ connection: inputs.connection, limit, page: oldPage + 1, // Fetch the next page of results })) as ListProductsResult; const { page: newPage } = data.page_info; const { products } = data; if (products.length) { // Some products were found return { payload: { ...payload, body: { data: products } }, polledNoChanges: false, }; } else { return { payload, polledNoChanges: true, }; } }, }); ``` --- #### Unit Testing for Custom Connectors #### Overview[​](#overview "Direct link to Overview") It's important to have good unit tests for software - custom components are no exception. You want to catch errors or breaking changes before they wreak havoc on your customers' integrations. Prismatic's Spectral library provides some utility functions to make writing unit tests easier. In the examples below, we use the testing tooling that's installed by default when you run `prism components:init`. The examples use a standard `describe`/`test`/`expect` style, so you can run them as written with the default tooling. You can swap in another testing framework if you prefer. ##### Test file naming conventions[​](#test-file-naming-conventions "Direct link to Test file naming conventions") To create a unit test file, create a new file alongside your code that has the extension `test.ts` (rather than `.ts`). For example, if your code lives in `index.ts`, create a file named `index.test.ts`. If you separate out your component actions into `actions.ts`, create a corresponding `actions.test.ts`. ##### Testing component actions[​](#testing-component-actions "Direct link to Testing component actions") A component action's `perform` function takes two positional arguments: * `context` is an object that contains a `logger`, `executionId`, `instanceState`, and `stepId`. * `inputs` is an object that contains input parameters as key-value pairs. Test `context` parameters are described [here](https://prismatic.io/docs/custom-connectors/actions.md#the-context-parameter). Let's ignore them for now and look at the `inputs` object. Consider the example "Format Proper Name" action described previously: ```typescript export const properFormatName = action({ display: { label: "Properly Format Name", description: "Properly format a person's name (Last, First M.)", }, perform: async (context, inputs) => { if (inputs.middleName) { return { data: `${inputs.lastName}, ${inputs.firstName} ${inputs.middleName[0]}.`, }; } else { return { data: `${inputs.lastName}, ${inputs.firstName}` }; } }, inputs: { firstName, middleName, lastName }, }); ``` You can use the `ComponentTestHarness` class and `createHarness` helper function to test your actions. The test harness's `action` function takes two required and one optional parameters: * The action's key (i.e. `properFormatName`) * An object containing input parameters * An optional `context` object containing `logger`, `executionId`, `instanceState`, and `stepId` A test file, then, could look like this: ```typescript import component from "."; import { createHarness } from "@prismatic-io/spectral/dist/testing"; const harness = createHarness(component); describe("Test the Proper Name formatter", () => { test("Verify first, middle, and last name", async () => { const result = await harness.action("properFormatName", { firstName: "John", middleName: "James", lastName: "Doe", }); expect(result.data).toStrictEqual("Doe, John J."); }); test("Verify first and last name without middle", async () => { const result = await harness.action("properFormatName", { firstName: "John", middleName: null, lastName: "Doe", }); expect(result.data).toStrictEqual("Doe, John"); }); }); ``` You can then run `npm run test`, which runs each test and returns an error code if a test failed. ##### Verifying correct logging in action tests[​](#verifying-correct-logging-in-action-tests "Direct link to Verifying correct logging in action tests") You may want to verify that your action generates some logs of particular severities in certain situations. In addition to step results, the test utility's `invoke` function returns an object, `loggerMock`, with information on what was logged during the action invocation. You can verify that certain lines were logged like this: ```typescript import { myExampleAction } from "./actions"; import { invoke } from "@prismatic-io/spectral/dist/testing"; test("Ensure that an error is logged", async () => { const level = "error"; const message = "Error code 42 occurred."; const { loggerMock } = await invoke(myExampleAction, { exampleInput1: "exampleValue1", exampleInput2: "exampleValue2", }); expect(loggerMock[level]).toHaveBeenCalledWith(message); }); ``` In the above example, the test would pass if an `error` log line of `Error code 42 occurred.` were generated, and would fail otherwise. ##### Providing test connection inputs to an action test[​](#providing-test-connection-inputs-to-an-action-test "Direct link to Providing test connection inputs to an action test") Many actions require a connection to interact with third-party services. You can create a connection object with the `createConnection` function from `@prismatic-io/spectral/dist/testing`: ```typescript import { createConnection, createHarness, } from "@prismatic-io/spectral/dist/testing"; import component from "."; import { myConnection } from "./connections"; const harness = createHarness(component); const myBasicAuthTestConnection = createConnection(myConnection, { username: "myUsername", password: "myPassword", }); describe("test my action", () => { test("verify the return value of my action", async () => { const result = await harness.action("myAction", { someInput: "abc-123", connection: myBasicAuthTestConnection, someOtherInput: "def-456", }); }); }); ``` It's not good practice to hard-code authorization secrets. Please use best practices, like setting environment variables to store secrets in your CI/CD environment: ```typescript import { createConnection } from "@prismatic-io/spectral/dist/testing"; import { myConnection } from "./connections"; const myBasicAuthTestConnection = createConnection(myConnection, { username: process.env.ACME_ERP_USERNAME, password: process.env.ACME_ERP_PASSWORD, }); ``` Use an Existing Integration's Connections for Testing If you would like to fetch an access key from an existing OAuth 2.0 connection in an integration (or username / password, API key, etc.), leverage the `prism components:dev:run` command to fetch the connection's fields and tokens. You can then reference the `PRISMATIC_CONNECTION_VALUE` environment variable in your tests. More info is in our [prism docs](https://prismatic.io/docs/cli/prism.md#componentsdevrun). ##### Testing a trigger[​](#testing-a-trigger "Direct link to Testing a trigger") Testing a trigger is similar to [testing an action](#testing-component-actions), except you use the `harness.trigger` function instead. For example, if you want to test the `csvTrigger` outlined above, your test could look like this: ```typescript import component from "."; import { createHarness, defaultTriggerPayload, } from "@prismatic-io/spectral/dist/testing"; const harness = createHarness(component); describe("test csv webhook trigger", () => { test("verify the return value of the csv webhook trigger", async () => { const payload = defaultTriggerPayload(); // The payload you can expect a generic trigger to receive payload.rawBody.data = "first,last,age\nJohn,Doe,30\nJane,Doe,31"; payload.headers.contentType = "text/csv"; payload.headers["x-confirmation-code"] = "some-confirmation-code-123"; const expectedData = [ { first: "John", last: "Doe", age: "30" }, { first: "Jane", last: "Doe", age: "31" }, ]; const expectedResponse = { statusCode: 200, contentType: "text/plain; charset=utf-8", body: payload.headers["x-confirmation-code"], }; const { payload: { body: { data }, }, response, } = await harness.trigger("csvTrigger", null, payload, { hasHeader: true, }); expect(data).toStrictEqual(expectedData); expect(response).toStrictEqual(expectedResponse); }); }); ``` #### Testing components from the CLI[​](#testing-components-from-the-cli "Direct link to Testing components from the CLI") The [`prism` CLI tool](https://prismatic.io/docs/cli.md) provides two commands for testing custom components: * `prism components:dev:run` fetches an integration's active connection and saves the fields as an environment variable so you can run unit tests and other commands locally. This is helpful, since many unit tests require an access token from a validated OAuth 2.0 connection - this provides a way of fetching the token from a connection you've already authenticated in the Prismatic integration designer. * `prism components:dev:test` publishes your component under a temporary name, and runs a single-action test integration for you that tests the action. This is helpful for quickly testing an action in the real Prismatic integration runner environment. ##### Access connections for local testing[​](#access-connections-for-local-testing "Direct link to Access connections for local testing") The `prism components:dev:run` command fetches an active connection from the Prismatic integration designer, so you can use the connection's fields for unit testing. The connection's values are set to an environment variable named `PRISMATIC_CONNECTION_VALUE`, which can be used by a subsequent command. In this example, we use `printenv` to print the environment variable, and pipe the result into [jq](https://stedolan.github.io/jq/) for pretty printing: ```bash prism components:dev:run \ --integrationId SW50ZEXAMPLE \ --connectionKey "Dropbox Connection" -- printenv PRISMATIC_CONNECTION_VALUE | jq { "token": { "access_token": "sl.EXAMPLE", "token_type": "bearer", "expires_in": 14400, "expires_at": "2022-10-13T20:09:53.739Z", "refresh_token": "EXAMPLE" }, "context": { "code": "sU4pEXAMPLE", "state": "SW5zdEXAMPLE" }, "fields": { "clientId": "EXAMPLE", "clientSecret": "EXAMPLE" } } ``` Note that the command you want to run with the environment variable should follow a `--`. Within your unit test code, you can use `harness.connectionValue()`, which pulls in the `PRISMATIC_CONNECTION_VALUE` environment variable into a connection that you can use for tests: Use PRISMATIC\_CONNECTION\_VALUE for a test ```typescript import { createHarness } from "@prismatic-io/spectral/dist/testing"; import { oauthConnection } from "./connections"; import component from "."; // Initialize a testing harness const harness = createHarness(component); // Parse the OAuth 2.0 connection from the PRISMATIC_CONNECTION_VALUE environment variable const parsedConnection = harness.connectionValue(oauthConnection); describe("listFolder", () => { test("listRootFolder", async () => { const result = await harness.action("listFolder", { dropboxConnection: parsedConnection, // Pass in our connection path: "/", }); const files = result["data"]["result"]["entries"]; // Verify a folder named "Public" exists in the response expect(files).toEqual( expect.arrayContaining([expect.objectContaining({ name: "Public" })]), ); }); }); ``` From your component, you can then run: ```bash prism components:dev:run \ --integrationId SW50ZEXAMPLE \ --connectionKey "Dropbox Connection" -- npm run test ``` ##### Run a test of an action from the command line[​](#run-a-test-of-an-action-from-the-command-line "Direct link to Run a test of an action from the command line") The `prism components:dev:test` command allows you to test an action quickly from the command line in the real integration runner environment. * Run `prism components:dev:test` from your component's root directory. * You will be prompted to select an action to test. Select one. * For each input of the action, supply a value * If your action requires a connection, you will be prompted for values for that connection (username, password, client\_id, etc). * If your action requires an OAuth 2.0 connection, a web browser will open to handle the OAuth flow. Once all inputs are entered, your action will run in the integration runner, and you will see logs from your action. ###### Test run environment files[​](#test-run-environment-files "Direct link to Test run environment files") You do not need to enter the same inputs each time you want to run a test of your action. To set some values for your test inputs, create a new file called `.env` in the same directory where you're invoking `prism` and enter your inputs and values as key/value pairs. For example, if you plan to leave `cursor` and `limit` inputs blank, set `path` to `/`, and you have an OAuth client ID and secret that you want to use each time, your `.env` file can look like this: ```text CURSOR= LIMIT= PATH=/ CLIENT_ID=xlexample CLIENT_SECRET=4yexample ``` --- #### Webhooks #### Handle webhooks in custom components[​](#handle-webhooks-in-custom-components "Direct link to Handle webhooks in custom components") If the API you're building a custom component for supports event-driven notifications ([webhooks](https://prismatic.io/docs/integrations/triggers/webhook.md)), it's helpful to include actions that subscribe an instance's flow to a webhook. Generally, we've found that four webhook-related actions are helpful to have: 1. **List Webhooks**. This action lists all webhooks that are configured in the third-party app. We recommend only displaying webhooks that are pointed at the current instance's flows (otherwise, you'll see webhooks that are configured for other applications). Check out the example in our [GitHub examples repo](https://github.com/prismatic-io/examples/blob/2e45a9aa2af25ddb511df364cb29df057367afd5/components/asana/src/actions/webhooks.ts#L55-L73) which demonstrates how to reference `context.webhookUrls` to filter webhooks down to only ones that match your current instance. 2. **Create Webhook** This action takes an event (or list of events, like `contact.create`) and a URL, which can be a webhook URL of a sibling flow. If an ID of a webhook is returned, this action can return that ID. See our [examples repo](https://github.com/prismatic-io/examples/blob/2e45a9aa2af25ddb511df364cb29df057367afd5/components/asana/src/actions/webhooks.ts#L115-L143) for an example of creating a webhook. 3. **Delete Webhook by ID** This action can take an ID of a webhook (fetched by the **List Webhooks** action), and delete that webhook by ID. 4. **Delete Instance Webhooks** This is a handy action to include, as it fetches a list of all webhooks in the third-party application, filters them down to only webhooks pointed at the current instance, and removes just those webhooks. You can leverage `context.webhookUrls` to determine which webhooks to delete. Having this logic baked in to a single action can reduce complexity on your component's users. Check out our [examples repo](https://github.com/prismatic-io/examples/blob/2e45a9aa2af25ddb511df364cb29df057367afd5/components/asana/src/actions/webhooks.ts#L176-L204) for an example of a **Delete Instance Webhooks** action. The above actions assume that an integration builder will create two flows: one that creates webhooks on instance deploy and one that removes webhooks on instance delete. If you'd like to simplify that process for users of your component, you can bake webhook logic into a custom trigger's [lifecycle functions](https://prismatic.io/docs/integrations/lifecycle.md). See our [examples repository](https://github.com/prismatic-io/examples/blob/ddda586/components/asana/src/triggers/eventTriggers.ts#L116-L156) for an example of how to create and delete webhooks automatically when an instance is created or deleted. --- ### Embed Prismatic #### Prismatic's Embedded SDK You can embed Prismatic's [marketplace](https://prismatic.io/docs/embed/marketplace.md) and [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) directly into your frontend application, allowing users to deploy and manage integrations for themselves. ![Example of embedded integration marketplace](/docs/assets/images/acme-saas-example-f3c74aa7fa39bbe6367853d290ad7418.png) Embedding involves three key steps: 1. [Install](https://prismatic.io/docs/embed/get-started/install-embedded-sdk.md) the embedded SDK into your application. 2. [Authenticate](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md) embedded users through your existing authentication system (eliminating the need for separate Prismatic credentials). 3. Customize the [marketplace](https://prismatic.io/docs/embed/theming.md) or [workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) interface to match your application's design system. Your application displays Prismatic as an iframe, enabling authenticated users to interact with the marketplace and workflow builder as if they were part of your application. #### Testing the embedded experience[​](#testing-the-embedded-experience "Direct link to Testing the embedded experience") The fastest way to test the embedded experience is to use our embedded playground, which is a React application that you can run from within Prismatic. Open your organization settings and then select the **Embedded** tab. Under **Embedded Preview** select **Launch**. From there, you will be able to test the embedded marketplace and (if enabled) the embedded workflow builder. ![Launch embedded playground ](/docs/assets/images/launch-preview-462796fff8fa0ce6fd2a6cbd9135f1bd.png) ![Embedded playground ](/docs/assets/images/preview-9c8f32498005ffb00db4fbd8bd28cae3.png) --- #### Additional Screens Overview In addition to the [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder.md) and [marketplace](https://prismatic.io/docs/embed/marketplace.md), you can embed additional Prismatic screens in your application: * **[Customer dashboard](https://prismatic.io/docs/embed/additional-screens/show-dashboard.md)** - Gives customers a comprehensive view for managing integrations, instances, connections, and monitoring executions * **[Connections screen](https://prismatic.io/docs/embed/additional-screens/show-connections.md)** - Allows customers to manage their connections in one place * **[Component screens](https://prismatic.io/docs/embed/additional-screens/show-components.md)** - Enables customers to view all components or specific component details * **[Logs screen](https://prismatic.io/docs/embed/additional-screens/show-logs.md)** - Provides customers with access to logs for all instances and workflows #### Common configuration options[​](#common-configuration-options "Direct link to Common configuration options") All embedded screen functions share the following configuration options: ##### selector[​](#selector "Direct link to selector") **Type:** `string` (required when `usePopover` is `false`) A CSS selector for the HTML element where the screen should be embedded. The screen will be rendered inside this element as an iframe. ```tsx prismatic.showDashboard({ selector: "#my-container", }); ``` ##### usePopover[​](#usepopover "Direct link to usePopover") **Type:** `boolean` **Default:** `true` When set to `true`, the screen opens in a popover/modal window. When `false`, you must provide a `selector` to specify where to embed the screen inline. ```tsx // Open in a popover (default) prismatic.showDashboard({ usePopover: true }); // Embed inline in a specific element prismatic.showDashboard({ selector: "#dashboard-div", usePopover: false, }); ``` ##### theme[​](#theme "Direct link to theme") **Type:** `"LIGHT" | "DARK"` Controls the visual theme of the embedded screen. You can match your application's theme for a consistent user experience. ```tsx prismatic.showDashboard({ selector: "#dashboard-div", theme: "DARK", }); ``` See the [Theming](https://prismatic.io/docs/embed/theming.md) article for more information about customizing the visual appearance of embedded screens. ##### autoFocusIframe[​](#autofocusiframe "Direct link to autoFocusIframe") **Type:** `boolean` Automatically focuses the embedded iframe when it loads. This can improve keyboard navigation for users. ```tsx prismatic.showDashboard({ selector: "#dashboard-div", autoFocusIframe: true, }); ``` ##### screenConfiguration[​](#screenconfiguration "Direct link to screenConfiguration") **Type:** `object` Customizes the behavior and appearance of embedded screens. The available configuration options vary by screen type. See each screen's documentation for specific configuration options: * [Dashboard screen configuration](https://prismatic.io/docs/embed/additional-screens/show-dashboard.md#screenconfiguration) * Other screens may have specific configuration options as well ```tsx prismatic.showDashboard({ selector: "#dashboard-div", screenConfiguration: { dashboard: { hideTabs: ["Attachments", "Components"], }, }, }); ``` ##### filters[​](#filters "Direct link to filters") **Type:** `object` Apply filters to limit what the customer sees in embedded screens. The available filter options vary by screen type: * **components** - Filter components by category, label, or custom filter query * **integrations** - Filter integrations by category or label * **marketplace** - Filter marketplace items by category, label, filter query, and more ```tsx prismatic.showDashboard({ selector: "#dashboard-div", filters: { marketplace: { category: "Data Platforms", label: "Available Integrations", }, }, }); ``` ##### translation[​](#translation "Direct link to translation") **Type:** `object` Provide custom translations for internationalization. This allows you to display the embedded screens in different languages or customize the text to match your application's terminology. See the [Translations and Internationalization](https://prismatic.io/docs/embed/translations-and-internationalization.md) article for detailed information about providing translations. #### Next steps[​](#next-steps "Direct link to Next steps") Choose which screen you want to embed: * [Embedding the customer dashboard](https://prismatic.io/docs/embed/additional-screens/show-dashboard.md) * [Embedding the connections screen](https://prismatic.io/docs/embed/additional-screens/show-connections.md) * [Embedding component screens](https://prismatic.io/docs/embed/additional-screens/show-components.md) * [Embedding the logs screen](https://prismatic.io/docs/embed/additional-screens/show-logs.md) --- #### Embedding Component Screens You can embed component screens in your application using the `showComponents()` and `showComponent()` functions. These allow customers to view all available components or see details about a specific component. #### Show all components[​](#show-all-components "Direct link to Show all components") The `showComponents()` function displays all components available in your organization. ##### Basic usage[​](#basic-usage "Direct link to Basic usage") Showing all components ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "components-div"; function ComponentListView() { useEffect(() => { prismatic.showComponents({ selector: `#${id}` }); }, []); return
Loading...
; } export default ComponentListView; ``` ![Embedded components listview screen](/docs/assets/images/components-6d838fa693d43d818a6fa1e7994859fb.png) ##### Configuration options[​](#configuration-options "Direct link to Configuration options") The `showComponents()` function accepts a configuration object. See the [Additional Screens Overview](https://prismatic.io/docs/embed/additional-screens.md#common-configuration-options) for available options including `selector`, `usePopover`, `theme`, `autoFocusIframe`, `filters`, `screenConfiguration`, and `translation`. #### Show a specific component[​](#show-a-specific-component "Direct link to Show a specific component") The `showComponent()` function displays details about a specific component. You'll need to provide the component's ID, which you can retrieve using the `prismatic.graphqlRequest()` function. ##### Basic usage[​](#basic-usage-1 "Direct link to Basic usage") Show a specific component ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "component-div"; function DropboxComponent() { useEffect(() => { const showDropboxComponent = async () => { const query = `query getComponentByKey($componentKey: String!) { components(key: $componentKey) { nodes { id } } }`; const result = await prismatic.graphqlRequest({ query, variables: { componentKey: "dropbox" }, }); prismatic.showComponent({ selector: `#${id}`, componentId: result.data.components.nodes[0].id, }); }; showDropboxComponent(); }, []); return
Loading...
; } export default DropboxComponent; ``` ![Embedded component detail screen](/docs/assets/images/component-725eaac3b510439afea55c9451314504.png) Additional documentation on querying the Prismatic API as a customer user from the embedded SDK is available in the [Embedded API Requests](https://prismatic.io/docs/embed/embedded-api-requests.md) article. ##### Configuration options[​](#configuration-options-1 "Direct link to Configuration options") The `showComponent()` function accepts a configuration object with the following properties: ###### componentId[​](#componentid "Direct link to componentId") **Type:** `string` (required) The ID of the component to display. You must retrieve this ID using the Prismatic API. ```tsx prismatic.showComponent({ selector: "#component-div", componentId: "Q29tcG9uZW50OmQzNzJiZGI4LTY5MjItNGMzNS1hZGQ2LWRkMjU3NmE4ZjYyYQ==", }); ``` For all other configuration options including `selector`, `usePopover`, `theme`, `autoFocusIframe`, `screenConfiguration`, and `translation`, see the [Additional Screens Overview](https://prismatic.io/docs/embed/additional-screens.md#common-configuration-options). #### Complete examples[​](#complete-examples "Direct link to Complete examples") ##### All components with configuration[​](#all-components-with-configuration "Direct link to All components with configuration") Components list with full configuration ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "components-div"; function ComponentListView() { useEffect(() => { prismatic.showComponents({ selector: `#${id}`, usePopover: false, theme: "DARK", autoFocusIframe: true, filters: { components: { category: "Data Platforms", }, }, }); }, []); return
Loading...
; } export default ComponentListView; ``` ##### Specific component with configuration[​](#specific-component-with-configuration "Direct link to Specific component with configuration") Specific component with full configuration ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "component-div"; function DropboxComponent() { useEffect(() => { const showDropboxComponent = async () => { const query = `query getComponentByKey($componentKey: String!) { components(key: $componentKey) { nodes { id } } }`; const result = await prismatic.graphqlRequest({ query, variables: { componentKey: "dropbox" }, }); prismatic.showComponent({ selector: `#${id}`, usePopover: false, theme: "LIGHT", autoFocusIframe: true, componentId: result.data.components.nodes[0].id, }); }; showDropboxComponent(); }, []); return
Loading...
; } export default DropboxComponent; ``` #### Related articles[​](#related-articles "Direct link to Related articles") * [Additional Screens Overview](https://prismatic.io/docs/embed/additional-screens.md) * [Embedded API Requests](https://prismatic.io/docs/embed/embedded-api-requests.md) * [Embedding the Customer Dashboard](https://prismatic.io/docs/embed/additional-screens/show-dashboard.md) * [Theming](https://prismatic.io/docs/embed/theming.md) * [Translations and Internationalization](https://prismatic.io/docs/embed/translations-and-internationalization.md) --- #### Embedding the Connections Screen The `showConnections()` function embeds the connections screen, allowing your customers to manage all of their connections in one place. Version Requirement This function requires `@prismatic-io/embedded@4.2.0` or later. #### Basic usage[​](#basic-usage "Direct link to Basic usage") Showing the connections screen ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "connections-div"; function Connections() { useEffect(() => { prismatic.showConnections({ selector: `#${id}` }); }, []); return
Loading...
; } export default Connections; ``` ![Embedded connections screen](/docs/assets/images/connections-36f3a28e05c5cc32042d9caf5484078c.png) Clicking on a connection from the list view will open the connection detail screen. From there, customers can view the instances and workflows that use that connection, as well as edit or delete the connection. #### Configuration options[​](#configuration-options "Direct link to Configuration options") The `showConnections()` function accepts a configuration object. See the [Additional Screens Overview](https://prismatic.io/docs/embed/additional-screens.md#common-configuration-options) for available options including `selector`, `usePopover`, `theme`, `autoFocusIframe`, `filters`, and `translation`. #### Complete example[​](#complete-example "Direct link to Complete example") Connections screen with full configuration ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "connections-div"; function Connections() { useEffect(() => { prismatic.showConnections({ selector: `#${id}`, usePopover: false, theme: "LIGHT", autoFocusIframe: true, }); }, []); return
Loading...
; } export default Connections; ``` #### Related articles[​](#related-articles "Direct link to Related articles") * [Additional Screens Overview](https://prismatic.io/docs/embed/additional-screens.md) * [Embedding the Customer Dashboard](https://prismatic.io/docs/embed/additional-screens/show-dashboard.md) * [Theming](https://prismatic.io/docs/embed/theming.md) * [Translations and Internationalization](https://prismatic.io/docs/embed/translations-and-internationalization.md) --- #### Embedding the Customer Dashboard The `showDashboard()` function embeds the customer dashboard, giving your customers a comprehensive view for managing their integrations, instances, connections, workflows, and monitoring executions and logs. #### Basic usage[​](#basic-usage "Direct link to Basic usage") Showing the customer dashboard ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "dashboard-div"; function Dashboard() { useEffect(() => { prismatic.showDashboard({ selector: `#${id}` }); }, []); return
Loading...
; } export default Dashboard; ``` ![Open the customer dashboard in embedded](/docs/assets/images/dashboard-55df0ed0b8e270add6e7bec3c965b0a5.png) #### Configuration options[​](#configuration-options "Direct link to Configuration options") The `showDashboard()` function accepts an optional configuration object. See the [Additional Screens Overview](https://prismatic.io/docs/embed/additional-screens.md#common-configuration-options) for common options like `selector`, `usePopover`, `theme`, `autoFocusIframe`, `filters`, and `translation`. ##### screenConfiguration[​](#screenconfiguration "Direct link to screenConfiguration") **Type:** `object` Customizes the behavior and appearance of the dashboard screen. ###### screenConfiguration.dashboard.hideTabs[​](#screenconfigurationdashboardhidetabs "Direct link to screenConfiguration.dashboard.hideTabs") **Type:** `Array` Hides specific tabs from the customer dashboard. Available tab names include: * `"Attachments"` * `"Components"` * `"Credentials"` * `"Executions"` * `"Instances"` * `"Integrations"` * `"Logs"` * `"Marketplace"` Hiding tabs in the customer dashboard ```tsx prismatic.showDashboard({ selector: "#dashboard-div", theme: "LIGHT", screenConfiguration: { dashboard: { hideTabs: ["Attachments", "Components"], }, }, }); ``` #### Complete example[​](#complete-example "Direct link to Complete example") Dashboard with full configuration ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "dashboard-div"; function Dashboard() { useEffect(() => { prismatic.showDashboard({ selector: `#${id}`, usePopover: false, theme: "DARK", autoFocusIframe: true, screenConfiguration: { dashboard: { hideTabs: ["Attachments", "Components"], }, }, }); }, []); return
Loading...
; } export default Dashboard; ``` #### Related articles[​](#related-articles "Direct link to Related articles") * [Additional Screens Overview](https://prismatic.io/docs/embed/additional-screens.md) * [Embedding the Marketplace](https://prismatic.io/docs/embed/marketplace.md) * [Theming](https://prismatic.io/docs/embed/theming.md) * [Translations and Internationalization](https://prismatic.io/docs/embed/translations-and-internationalization.md) --- #### Embedding the Logs Screen The `showLogs()` function embeds the logs screen, providing your customers with access to logs from all of their instances and workflows in one location. This is the same view you would see as an organization team member when opening a customer's logs tab. #### Basic usage[​](#basic-usage "Direct link to Basic usage") Show all instance logs ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "logs-div"; function AllInstanceLogs() { useEffect(() => { prismatic.showLogs({ selector: `#${id}` }); }, []); return
Loading...
; } export default AllInstanceLogs; ``` ![Embedded logs screen](/docs/assets/images/logs-e17c272486956bd235691c0cb1a3431a.png) #### Configuration options[​](#configuration-options "Direct link to Configuration options") The `showLogs()` function accepts a configuration object. See the [Additional Screens Overview](https://prismatic.io/docs/embed/additional-screens.md#common-configuration-options) for available options including `selector`, `usePopover`, `theme`, `autoFocusIframe`, `filters`, `screenConfiguration`, and `translation`. #### Complete example[​](#complete-example "Direct link to Complete example") Logs screen with full configuration ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "logs-div"; function AllInstanceLogs() { useEffect(() => { prismatic.showLogs({ selector: `#${id}`, usePopover: false, theme: "LIGHT", autoFocusIframe: true, }); }, []); return
Loading...
; } export default AllInstanceLogs; ``` #### Related articles[​](#related-articles "Direct link to Related articles") * [Additional Screens Overview](https://prismatic.io/docs/embed/additional-screens.md) * [Embedding the Customer Dashboard](https://prismatic.io/docs/embed/additional-screens/show-dashboard.md) * [Theming](https://prismatic.io/docs/embed/theming.md) * [Translations and Internationalization](https://prismatic.io/docs/embed/translations-and-internationalization.md) --- #### Custom Marketplace UI #### Why build a custom marketplace UI?[​](#why-build-a-custom-marketplace-ui "Direct link to Why build a custom marketplace UI?") The [embedded marketplace](https://prismatic.io/docs/embed/marketplace.md) enables you to embed Prismatic's marketplace as an [iframe](https://www.w3schools.com/tags/tag_iframe.ASP) in your application. [Theming](https://prismatic.io/docs/embed/theming.md) provides flexibility with the marketplace's appearance and allows you to match your application's branding colors and typography. But, you may require further customization. With a custom marketplace UI, you can query Prismatic's API for available integrations and map them to native UI elements within your application. You can present integrations as material cards, in a table, as a listview - any format you prefer. ![Example integration marketplace UI](/docs/assets/images/example-35fd19961d64865498ce054c17b96810.png) A full example implementation of a custom UI is available on [GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/pages/examples/custom-ui-elements.tsx). #### Querying for marketplace integrations[​](#querying-for-marketplace-integrations "Direct link to Querying for marketplace integrations") The [marketplaceIntegrations](https://prismatic.io/docs/api/schema/queries.md#marketplaceintegrations)) query returns an array of integrations available in the marketplace. Since Prismatic's API is GraphQL-based, you can query for more or fewer fields, but this query (and its corresponding TypeScript types) contains the information needed to display a marketplace: * GraphQL Query * TypeScript Types GraphQL query to fetch marketplace integrations ```graphql query getMarketplaceIntegrations { marketplaceIntegrations(includeActiveIntegrations: true) { nodes { id name allowMultipleMarketplaceInstances avatarUrl category description isCustomerDeployable marketplaceConfiguration overview versionNumber firstDeployedInstance { id } deployedInstances deploymentStatus } } } ``` Corresponding TypeScript types ```ts interface MarketplaceIntegration { id: string; name: string; allowMultipleMarketplaceInstances: boolean; avatarUrl?: string; category: string; description: string; isCustomerDeployable: boolean; marketplaceConfiguration: string; overview: string; versionNumber: number; firstDeployedInstance?: { id: string }; deployedInstances: "ZERO" | "ONE" | "MULTIPLE"; deploymentStatus?: "ACTIVATED" | "PAUSED" | "UNCONFIGURED"; } type MarketplaceIntegrationsResponse = { data: { marketplaceIntegrations: { nodes: MarketplaceIntegration[]; }; }; }; ``` Key points about this query: * The `includeActiveIntegrations` parameter is optional. Include it if you have customer-specific integrations deployed to only a subset of customers and not added to your marketplace. This displays those integrations to customers who have instances of them. * The `firstDeployedInstance`, `deployedInstances`, and `deploymentStatus` properties are used for performance optimization - they're more efficient than including an `instances { nodes { } }` connection in the query. * `firstDeployedInstance` represents the first instance of this integration deployed to the customer making the query (if any). * `deployedInstances` will have one of three values - `"ZERO"`, `"ONE"` or `"MULTIPLE"` - representing the number of instances this customer has deployed. * `deploymentStatus` will have one of four values - `"ACTIVATED"`, `"PAUSED"`, `"UNCONFIGURED"` or `null` - depending on the state of the instance the customer has deployed (if any). To execute the query against Prismatic, you can either use the embedded package's `prismatic.graphqlRequest` function or leverage a GraphQL client like [graphql-request](https://www.npmjs.com/package/graphql-request). See [Embedded API Requests](https://prismatic.io/docs/embed/embedded-api-requests.md) for more details. #### Displaying integration avatars[​](#displaying-integration-avatars "Direct link to Displaying integration avatars") Each integration returned from the query above has an optional `avatarUrl` property, which you can use to fetch the icon associated with your integration. Your `avatarUrl` will look something like `/media/UUID/Integration/UUID/EnumMeta.AVATAR/UUID.png`. With an `avatarUrl`, make an authenticated API call to Prismatic with your URL as a relative path (e.g. `https://app.prismatic.io/media/UUID/Integration/UUID/EnumMeta.AVATAR/UUID.png`). Ensure your request is authenticated with your embedded JWT as a header: `Authorization: Bearer ${TOKEN}`. This request will return JSON data in this format: ```json { "data": { "url": "https://s3.us-west-2.amazonaws.com/PRESIGNED-URL" } } ``` You can then set that presigned URL as your image's `src` property. Here's an example ReactJS implementation that displays an integration's avatar icon (if available) or defaults to a generic icon if not: Example of displaying integration avatars using ReactJS ```ts function PrismaticAvatar({ avatarUrl, token }) { const [src, setSrc] = React.useState(""); useEffect(() => { let mounted = true; if (avatarUrl) { // Fetch the presigned URL from // https://app.prismatic.io/media/UUID/Integration/UUID/EnumMeta.AVATAR/UUID.png fetch(`https://app.prismatic.io${avatarUrl}`, { headers: { Authorization: `Bearer ${token}` }, }).then((response) => { response.json().then((data) => { if (mounted) { setSrc(data.url); } }); }); } return () => { mounted = false; }; }, []); // If the integration has no avatar URL, display a generic avatar icon // Otherwise, display an avatar with the presigned URL we fetched return src ? ( ) : ( ); } ``` #### Opening integration configuration windows[​](#opening-integration-configuration-windows "Direct link to Opening integration configuration windows") After mapping marketplace integrations to UI elements in your application, you'll want to make them interactive so your customers can open the configuration wizard modal and deploy instances. While you can build the config wizard from scratch and set config variables programmatically, we recommend using Prismatic's implementation of the config wizard. Config wizards are complex! To open a config wizard, invoke `prismatic.configureInstance()` with your integration's name (or an existing instance's ID). See [Embedding Marketplace](https://prismatic.io/docs/embed/marketplace.md#configure-a-specific-integration) for more details. --- #### Embedded API Requests The embedded SDK enables you to embed the marketplace and workflow builder into your application. But, you may want to query and display additional data from the Prismatic API. By leveraging the Prismatic API, you can map API data into custom UI components and fully customize your customers' integration management and deployment experience. This article details how to fetch data from the Prismatic API using the embedded SDK. For information on installing the embedded SDK and authenticating customer users, see [Installing Embedded SDK](https://prismatic.io/docs/embed/get-started/install-embedded-sdk.md). For an example of querying the Prismatic API to implement a custom marketplace UI, see our [example custom UI implementation on GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/pages/examples/custom-ui-elements.tsx). #### The Prismatic GraphQL API[​](#the-prismatic-graphql-api "Direct link to The Prismatic GraphQL API") Prismatic's API is built on GraphQL. Comprehensive API documentation is available in the [API docs](https://prismatic.io/docs/api.md), including [example queries and mutations](https://prismatic.io/docs/api/common-queries/creating-instances.md). You can test the Prismatic API using our [GraphiQL explorer tool](https://prismatic.io/docs/explorer). #### Embedded user scope and API permissions[​](#embedded-user-scope-and-api-permissions "Direct link to Embedded user scope and API permissions") When you authenticate a customer user through the embedded SDK, that user is associated with one of your customers in Prismatic. When authenticated, a customer user has access permissions limited to their customer's resources (they cannot access other customers' integrations, instances, custom components, users, etc.). For example, they can execute this query to retrieve instances deployed to their customer: ```graphql { authenticatedUser { customer { instances { nodes { id name } } } } } ``` #### Example embedded API requests[​](#example-embedded-api-requests "Direct link to Example embedded API requests") The following examples demonstrate common use cases for embedded API requests. All examples utilize `prismatic.graphqlRequest`, which accepts two parameters: * `query`: A string representing the GraphQL query or mutation to execute against the Prismatic API. * `variables`: An object containing key-value pairs of variables to include with the query or mutation. ##### Listing deployed instances[​](#listing-deployed-instances "Direct link to Listing deployed instances") This example retrieves a list of instances from the API and renders them as `
    ` elements. It demonstrates how to fetch arbitrary data from the API and render it using custom UI components. Fetch and display data from the API ```tsx import { Button, Typography } from "@mui/material"; import prismatic from "@prismatic-io/embedded"; import { useState } from "react"; /** * Get a list of instances deployed to the current user's customer */ const loadInstances = async (setInstances: Function) => { const query = `{ authenticatedUser { customer { instances { nodes { id name flowConfigs { nodes { id flow { name } apiKeys webhookUrl } } } } } } }`; const result = await prismatic.graphqlRequest({ query }); setInstances(result.data.authenticatedUser.customer.instances.nodes); }; interface FlowConfig { id: string; flow: { name: string }; webhookUrl: string; } interface Instance { id: string; name: string; flowConfigs: { nodes: FlowConfig[]; }; } function ListInstances() { const [instances, setInstances] = useState([]); return ( <> In this example, all instances that are currently deployed to the current user's customer are listed, along with each instance's webhook URLs.
      {instances.map((instance) => (
    • {instance.name} ({instance.id})
    • ))}
    ); } export default ListInstances; ``` ##### Deploying an instance[​](#deploying-an-instance "Direct link to Deploying an instance") For instances with minimal configuration requirements (e.g., only a single OAuth flow), you may want to bypass the instance configuration wizard. This example presents a "Deploy Dropbox" button that, when clicked, retrieves the current user's customer ID and the Dropbox integration ID. It then deploys a Dropbox instance and opens a new window for the user to complete the OAuth flow. Deploy an instance without the config wizard ```tsx import { Button, Typography } from "@mui/material"; import prismatic from "@prismatic-io/embedded"; import { useState } from "react"; /** * Get the ID of the version of the Dropbox integration * that is available in the integration marketplace */ const getDropboxVersionId = async () => { const query = `query getMarketplaceIntegrations($name: String) { marketplaceIntegrations( name: $name sortBy: [{field: CATEGORY, direction: ASC}, {field: NAME, direction: ASC}] ) { nodes { id name versionSequence(first: 1, versionIsAvailable: true) { nodes { id versionNumber } } } } }`; const variables = { name: "Dropbox" }; const result = await prismatic.graphqlRequest({ query, variables }); return result.data.marketplaceIntegrations.nodes[0].versionSequence.nodes[0] .id; }; /** * Get the current user's customer ID */ const getCustomerId = async () => { const query = `{ authenticatedUser { customer { id } } }`; const result = await prismatic.graphqlRequest({ query }); return result.data.authenticatedUser.customer.id; }; interface CreateInstanceProps { dropboxVersionId: string; customerId: string; instanceName: string; } /** * Create a new instance of the Dropbox integration, returning the * OAuth authorize URL where the user should be sent */ const createInstance = async ({ dropboxVersionId, customerId, instanceName, }: CreateInstanceProps) => { const query = `mutation createDropboxInstance($customerId: ID!, $integrationId: ID!, $instanceName: String!) { createInstance(input: {customer: $customerId, integration: $integrationId, name: $instanceName}){ instance { id name configVariables { nodes { authorizeUrl } } flowConfigs { nodes { id flow { name } webhookUrl } } } } }`; const variables = { customerId, integrationId: dropboxVersionId, instanceName, }; const result = await prismatic.graphqlRequest({ query, variables }); return result; }; interface DeployInstanceProps { instanceId: string; } /** * Deploy the instance after configuration */ const deployInstance = async ({ instanceId }: DeployInstanceProps) => { const query = `mutation deployDropbox($instanceId: ID!){ deployInstance(input:{id:$instanceId}) { instance { lastDeployedAt } } }`; const variables = { instanceId }; await prismatic.graphqlRequest({ query, variables }); }; interface Instance { data?: { createInstance: { instance: { id: string }; }; }; } function DeployDropbox() { const [instance, setInstance] = useState({}); return ( <> In this example, an instance of an integration named Dropbox is created, the user is redirected to an OAuth screen, and the instance is then deployed.
    Note: this assumes that you have an integration in your marketplace called "Dropbox", and that the integration has only one config variable - the Dropbox connection.
    {JSON.stringify(instance, null, 2)}
    ); } export default DeployDropbox; ``` --- #### Embedding Without SDK While we strongly recommend using the [Embedded SDK](https://prismatic.io/docs/embed/get-started/install-embedded-sdk.md) to embed the Prismatic marketplace and workflow builder, some frontend tech stacks prohibit Node.js module installation. If your frontend tech stack requires that you write custom HTML and JavaScript, you can still embed the marketplace. #### Generate a JWT[​](#generate-a-jwt "Direct link to Generate a JWT") Similar to using the SDK, you will need to generate a [JSON Web Token (JWT)](https://jwt.io/) for your user. See the [Authenticating Users](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md) documentation for details on how to generate a JWT. You can generate a JWT using your backend server or any other secure environment - the JWT should *not* be generated in the client's browser. #### Authenticate the user[​](#authenticate-the-user "Direct link to Authenticate the user") Before displaying the iframe, you must call the authentication endpoint `https://app.prismatic.io/embedded/authenticate` with your customer's JWT. This will do two things: 1. Validate your JWT 2. Verify that the associated customer user exists (a user will be created if one does not exist). #### Embed the marketplace[​](#embed-the-marketplace "Direct link to Embed the marketplace") The embedded marketplace is essentially an [iframe](https://www.w3schools.com/tags/tag_iframe.ASP) pointing to `https://app.prismatic.io/integration-marketplace/` with a few query parameters: * `jwt`: The JWT you generate (see [Authenticating Users](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md)) * `embedded`: Must be set to `"true"` * `theme`: Can be `LIGHT` or `DARK` In this pure HTML/JS example, on page load we fetch a JWT for our user and then call the authentication endpoint. Once we receive a response, we set the `src` property of our iframe appropriately. ```html

    Pure HTML Embed Example

    ``` --- #### Installing the Embedded SDK Use Claude Code to set up embedding The [Prismatic Skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) plugin for Claude Code can walk you through SDK installation, JWT backend setup, and frontend integration interactively. Run `/prismatic-skills:embedded` to get started. #### Installing the embedded SDK[​](#installing-the-embedded-sdk "Direct link to Installing the embedded SDK") To embed Prismatic in your app, include Prismatic JavaScript code with your client application. You have two choices for incorporating this code: 1. **(Recommended)** Install the [@prismatic-io/embedded](https://www.npmjs.com/package/@prismatic-io/embedded) NPM package in your web application Node.js project. We recommend installing the SDK via NPM for IntelliSense and TypeScript support. 2. Build the package from source and include it in your app through a ` ``` Once the script has loaded, the SDK is exposed on `window.prismatic` and the `prismatic.*` functions (described below) are available for use. You can also load the UMD bundle directly from a JavaScript CDN. Reference the explicit `dist/index.umd.js` path so the CDN serves the browser bundle: ```html ``` To browse the published files, see [unpkg.com/browse/@prismatic-io/embedded/](https://unpkg.com/browse/@prismatic-io/embedded/). #### Initialize the Prismatic client[​](#initialize-the-prismatic-client "Direct link to Initialize the Prismatic client") When Prismatic is embedded in your app, it's presented in an [iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) that by default points to Prismatic's application URL (). To initialize the Prismatic client, execute the `init` function: ```javascript prismatic.init(); ``` If you use a [custom white-label domain](https://prismatic.io/docs/configure-prismatic/custom-domains.md) (something like `https://integrations.my-example-company.com`), or if your Prismatic tenant is hosted in a different region (like EU or Australia), you can direct the embedded SDK to an alternative endpoint by specifying a `prismaticUrl`: Optional endpoint config for Prismatic's SDK ```javascript prismatic.init({ prismaticUrl: "https://integrations.my-example-company.com", }); // or prismatic.init({ prismaticUrl: "https://app.eu-west-1.prismatic.io", }); ``` For a complete list of public endpoints, see [Deployment Regions](https://prismatic.io/docs/configure-prismatic/deployment-regions.md#logging-in-to-additional-regions). #### Embedded screen configuration[​](#embedded-screen-configuration "Direct link to Embedded screen configuration") You can control how the embedded workflow builder, marketplace, dashboard and other pages appear in your app with these configuration options: * `usePopover`: A boolean that controls whether the screen should display as a popover. By default, the screen is embedded as an iframe into an existing DOM element. * `selector`: Specifies which DOM element to embed the iframe into when `usePopover` is false. * `theme`: Overrides the [custom theming](https://prismatic.io/docs/embed/theming.md) default behavior and can be set to `"LIGHT"` or `"DARK"` mode. --- #### Embedding Marketplace The video above shows how you can embed Prismatic's marketplace in your application. Before embedding marketplace, please review [Authenticating Users](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md). Use Claude Code to set up embedding The [Prismatic Skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) plugin for Claude Code can walk you through marketplace embedding, theming, and custom UI setup interactively. Run `/prismatic-skills:embedded` to get started. #### Embed the integration marketplace[​](#embed-the-integration-marketplace "Direct link to Embed the integration marketplace") You can embed the integration marketplace into your application with minimal code to provide your customers with a native integration deployment experience. This article shows you how to implement Prismatic's integration marketplace in your application. For information on installing the embedded SDK and authenticating customer users, see [Installing Embedded SDK](https://prismatic.io/docs/embed/get-started/install-embedded-sdk.md) Once a user has been authenticated, you can display the integration marketplace using the `prismatic.showMarketplace()` function. The `showMarketplace()` function accepts three optional parameters: * `usePopover`: Determines whether the marketplace should display as a popover. By default, the screen is embedded as an iframe into an existing DOM element. * `selector`: Specifies the DOM element to embed the iframe into when `usePopover` is false. * `theme`: Overrides [custom theming](https://prismatic.io/docs/embed/theming.md) default behavior, accepting either `"LIGHT"` or `"DARK"` mode. ##### iframe embedding method[​](#iframe-embedding-method "Direct link to iframe embedding method") When `usePopover` is set to `false`, an [iframe](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe) HTML element is embedded into the element specified by the `selector` parameter. For example, if you have a section of your application that reads: ```html
    Loading...
    ``` You can embed the integration marketplace iframe into that div, replacing the "Loading..." text, by calling: ```javascript prismatic.showMarketplace({ selector: `#integration-marketplace-placeholder`, usePopover: false, }); ``` The result is that the integration marketplace appears as part of your application: ![Prismatic integration marketplace embedded in your app](/docs/assets/images/acme-saas-example-f3c74aa7fa39bbe6367853d290ad7418.png) Theming At this point, the marketplace has not been themed to match your application. To implement custom branding, see [Theming Embedded Marketplace](https://prismatic.io/docs/embed/theming.md). For an example of the iframe embedding method, see [GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/pages/examples/embedded-marketplace.tsx). ##### Popover method[​](#popover-method "Direct link to Popover method") If you set `usePopover` to `true`, the integration marketplace will display in a popover: ```typescript prismatic.showMarketplace({ usePopover: true, }); ``` ![Enable popover for Prismatic embedded marketplace](/docs/assets/images/popover-48f5565e370c51b70252b04570d42d92.png) ##### Filtering integrations[​](#filtering-integrations "Direct link to Filtering integrations") You can filter the displayed integrations by applying `filters` to your `showMarketplace()` call. ###### Simple integration filters[​](#simple-integration-filters "Direct link to Simple integration filters") You can filter integrations by a single [category](https://prismatic.io/docs/integrations/low-code-integration-designer.md#categorizing-integrations) or [label](https://prismatic.io/docs/integrations/low-code-integration-designer.md#assigning-labels-to-an-integration) (or both). If both filters are applied, integrations must match both `category` and `label` to appear. For example, to show only integrations with the category "ERP" and the label "enterprise" in an embedded marketplace, use: Filter by category and label ```javascript prismatic.showMarketplace({ selector: `#my-div-id`, usePopover: false, filters: { marketplace: { category: "ERP", label: "enterprise", }, }, }); ``` ###### Advanced integration filters[​](#advanced-integration-filters "Direct link to Advanced integration filters") You can use more sophisticated filtering logic using a `filterQuery`. You can filter integrations based on their `name`, `labels`, or `category`. The following operators are available: * `TermOperator.equal`: Returns integrations where the first term equals the second term, works with `name` and `category`. Example: To show only the "Dropbox" integration: ```ts prismatic.showMarketplace({ filters: { marketplace: { filterQuery: [TermOperator.equal, "name", "Dropbox"], }, }, }); ``` * `TermOperator.notEqual`: Returns integrations where the first term does not equal the second term, works with `name` and `category`. Example: To show all integrations except those with the category "ERP": ```ts prismatic.showMarketplace({ filters: { marketplace: { filterQuery: [TermOperator.notEqual, "category", "ERP"], }, }, }); ``` * `TermOperator.in`: Returns integrations whose `labels` include a specific label. Example: To show integrations with a "paid" label: ```ts prismatic.showMarketplace({ filters: { marketplace: { filterQuery: [TermOperator.in, "labels", "paid"], }, }, }); ``` * `TermOperator.notIn`: Returns integrations that do not have specified labels. * `TermOperator.startsWith`: Returns integrations with names or categories that begin with a specific string. Example: To show all integrations starting with "Algolia": ```ts prismatic.showMarketplace({ filters: { marketplace: { filterQuery: [TermOperator.startsWith, "name", "Algolia"], }, }, }); ``` This will match integrations with names like `Algolia - Dropbox` or `Algolia - SFTP`. * `TermOperator.doesNotStartWith`: Returns integrations with names or categories that do not start with a specific string. * `TermOperator.endsWith`: Returns integrations with names or categories that end with a specific string. * `TermOperator.doesNotEndWith`: Returns integrations with names or categories that do not end with a specific string. You can combine multiple conditions using `and` and `or` operators. For example, to show all integrations that have the category "ERP" and label "paid", plus the Dropbox and Slack integrations: ```ts import prismatic, { BooleanOperator, TermOperator, } from "@prismatic-io/embedded"; prismatic.showMarketplace({ filters: { marketplace: { filterQuery: [ BooleanOperator.or, [ BooleanOperator.and, [TermOperator.equal, "category", "ERP"], [TermOperator.in, "labels", "paid"], ], [TermOperator.equal, "name", "Dropbox"], [TermOperator.equal, "name", "Slack"], ], }, }, }); ``` ##### Configure a specific integration[​](#configure-a-specific-integration "Direct link to Configure a specific integration") You can use custom UI elements (buttons, divs, hyperlinks, etc.) to start integration deployment. Call the `prismatic.configureInstance()` function from your UI element to display a configuration screen for a specific integration. Provide the integration name as `integrationName`. You can include other display parameters (like `usePopover` and `selector`) as described in the iframe embedding and popover methods above. You can also specify an optional `skipRedirectOnRemove` boolean parameter, which defaults to `false`. This determines whether users should be redirected to the integration listview upon removing an integration. If you're embedding the marketplace, you likely want to keep this value set to `false`. If you're implementing a custom API wrapper and handling integration display yourself, you might want to set this to `true`. For example, to display the "Salesforce" integration configuration when a button is clicked: ```jsx const deploySalesforce = () => { prismatic.configureInstance({ integrationName: "Salesforce", skipRedirectOnRemove: false, usePopover: true, }); }; ; ``` For an example of implementing a custom marketplace UI and opening the configuration screen for a specific integration with `prismatic.configureInstance()`, see [GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/pages/examples/custom-ui-elements.tsx#L212-L219). ##### Edit an existing instance's configuration[​](#edit-an-existing-instances-configuration "Direct link to Edit an existing instance's configuration") If you want to open the configuration wizard for an existing instance directly (for example, from a custom UI that lists a customer's deployed instances), you can call `prismatic.editInstanceConfiguration()`. This function embeds the configuration wizard for the instance identified by `instanceId` into the DOM element specified by `selector`. The wizard is rendered as if it is already inside a modal, which makes it a good fit for embedding inside your own dialog or drawer. `editInstanceConfiguration()` accepts the following parameters: * `instanceId` (required): The ID of the instance to configure. * `selector` (required): The DOM element to embed the configuration wizard iframe into. * `theme` (optional): Overrides [custom theming](https://prismatic.io/docs/embed/theming.md) default behavior, accepting either `"LIGHT"` or `"DARK"` mode. * `screenConfiguration.configurationWizard` (optional): The same configuration wizard options described in [Hiding UI elements in marketplace](#hiding-ui-elements-in-marketplace), except for `isInModal` (which is always set to `true`). * `onSuccess` (optional): Callback invoked when the customer successfully deploys the instance. * `onCancel` (optional): Callback invoked when the customer cancels configuration. * `onDelete` (optional): Callback invoked when the customer deletes the instance. The function returns a cleanup function that removes its internal event listeners. Call it when your component unmounts (for example, from a React `useEffect` cleanup). For example, to open an instance's configuration wizard inside a dialog in a React app: Edit an instance's configuration in a dialog ```tsx import React from "react"; import prismatic from "@prismatic-io/embedded"; import { Dialog, DialogContent } from "@mui/material"; function ConfigurationDialog({ instanceId, onClose, }: { instanceId: string; onClose: () => void; }) { const embeddedDivId = "edit-instance-configuration-div"; React.useEffect(() => { const cleanup = prismatic.editInstanceConfiguration({ instanceId, selector: `#${embeddedDivId}`, theme: "LIGHT", screenConfiguration: { configurationWizard: { triggerDetailsConfiguration: "hidden", }, }, onCancel: onClose, onSuccess: onClose, onDelete: onClose, }); return () => cleanup?.(); }, [instanceId, onClose]); return ( ); } ``` For a complete example, see [GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/pages/examples/edit-instance-configuration.tsx). ##### Listening to marketplace events[​](#listening-to-marketplace-events "Direct link to Listening to marketplace events") The embedded marketplace emits custom JavaScript [events](https://www.w3schools.com/js/js_events.asp) when certain things happen: * `INSTANCE_CREATED`: Emitted when an integration's configuration screen is opened for the first time. * `INSTANCE_CONFIGURATION_OPENED`: Emitted when an instance configuration screen is opened. * `INSTANCE_CONFIGURATION_LOADED`: Emitted when an instance configuration screen has loaded. This is the optimal event to hook into for [programmatically setting config variables](#dynamically-setting-config-variables-in-marketplace). * `INSTANCE_CONFIGURATION_PAGE_LOADED`: Emitted when an instance configuration screen transitions between pages. * `INSTANCE_CONFIGURATION_CLOSED`: Emitted when an instance configuration screen is closed (regardless of whether configuration was completed). * `INSTANCE_DEPLOYED`: Emitted when an instance has been configured and enabled for a customer. * `INSTANCE_DELETED`: Emitted when an integration has been deactivated (the instance has been deleted). * `POPOVER_CLOSED`: Emitted when a user closes the popover modal (only applicable when `usePopover: true`). You can subscribe to these events to trigger additional actions in your application when instances are created, configured, deleted, etc. All events return data in this structure (except for `INSTANCE_CONFIGURATION_LOADED`, which includes additional data): ```json { "event": "INSTANCE_CONFIGURATION_OPENED", "data": { "customerId": "Q3VzdG9tZXI6OThiMjU3MDUtZmMzNC00NWYwLTk0ZDItODA0ZjFkYzEyYTZk", "customerName": "Smith Rockets", "instanceId": "SW5zdGFuY2U6YzJlYTliZjEtY2Y3MS00NTg1LTk2MjMtYjZhNDAxYjQyNWRm", "instanceName": "Salesforce", "integrationName": "Salesforce", "integrationVersionNumber": 18, "readOnly": false } } ``` In this example, we log the customer name and the integration they opened: ```typescript import { PrismaticMessageEvent } from "@prismatic-io/embedded"; window.addEventListener("message", (event) => { // Check if the message event is of type INSTANCE_CONFIGURATION_OPENED if ( event.data.event === PrismaticMessageEvent.INSTANCE_CONFIGURATION_OPENED ) { // Extract relevant data const { customerName, integrationName } = event.data.data; // Log the customer and integration name. Replace this with your desired data handling or helper functions console.log( `${customerName} opened the configuration page for ${integrationName}`, ); } }); ``` You can implement similar event listeners for instance configuration page closure, loading, creation, or deletion events. ##### User-level configuration marketplace events[​](#user-level-configuration-marketplace-events "Direct link to User-level configuration marketplace events") If [user-level configuration](https://prismatic.io/docs/integrations/config-wizard/user-level-configuration.md) is enabled for an integration, the following marketplace events will be emitted: * `USER_CONFIGURATION_OPENED`: Emitted when a ULC configuration screen is opened. * `USER_CONFIGURATION_LOADED`: Emitted when a ULC configuration screen has loaded. This is the best event to hook into for [programmatically setting config variables](#dynamically-setting-config-variables-in-marketplace). * `USER_CONFIGURATION_PAGE_LOADED`: Emitted when a ULC configuration screen transitions between pages. * `USER_CONFIGURATION_CLOSED`: Emitted when a ULC configuration screen is closed (regardless of whether configuration was completed). * `USER_CONFIGURATION_DEPLOYED`: Emitted when a ULC configuration is completed. * `USER_CONFIGURATION_DELETED`: Emitted when a ULC configuration is deleted. For all of these events, the event's `data` property will contain: ```json { "event": "USER_CONFIGURATION_LOADED", "data": { "customerId": "Q3VzdG9tZXI6ZjYxMzY4MzItMzYxYi00NmI3LTlmMmItN2ZkNTc3YzY1YTg1", "customerName": "Smith Real Estate", "instanceId": "SW5zdGFuY2U6N2I1MjFiNzItYzllNS00YjkwLWIzZGEtZTY4OTY5OWU2ZjBl", "instanceName": "Dropbox", "integrationName": "Dropbox", "integrationVersionNumber": 9, "userConfigId": "VXNlckxldmVsQ29uZmlnOjI0NDU5MjA2LWQxNGUtNDRhMy04NWE4LTJjMzgwMzg2Y2NmZg==", "userEmail": "2E52B7CB-071B-4EA2-8E9D-F64910EBDBB1", "userId": "VXNlcjpmYjc4YzE1OS1kOWMwLTQxMDctYjIyNC0zYmNhMDFlOTQ5NzY=", "userLevelConfigVariables": { "Dropbox Connection": { "status": "ACTIVE" } }, "readOnly": false, "userName": "Phil Embedmonson" } } ``` #### Dynamically setting config variables in marketplace[​](#dynamically-setting-config-variables-in-marketplace "Direct link to Dynamically setting config variables in marketplace") consider organization (customer) connections If you need to programmatically set connection values, consider implementing an [organization (customer) connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md), which enables you to configure connections for each customer. If you need to set the connection at deploy time through the embedded SDK, you can do that with an integration-scoped connection as demonstrated below. You can programmatically set values for your customers' config variables by leveraging [marketplace events](#listening-to-marketplace-events). This is particularly useful when you have access to certain values (API keys, endpoints, etc.) and want to set config variables on behalf of your customers (eliminating the need for them to look up these values). When your application receives an `INSTANCE_CONFIGURATION_LOADED` event message (or `USER_CONFIGURATION_LOADED` for ULC), the message payload includes the properties listed above, plus current config variable values. You can analyze that event's data and conditionally set values for specific config variables using the `prismatic.setConfigVars()` function. Let's examine the event message structure and how to respond with config variable values: * Event Payload * NodeJS Example * React Example Here's the structure of the `INSTANCE_CONFIGURATION_LOADED` payload. The event message's `.data.configVars` property contains all currently set configuration variables and their values: Example INSTANCE\_CONFIGURATION\_LOADED event payload ```json { "event": "INSTANCE_CONFIGURATION_LOADED", "data": { "instanceId": "SW5zdGFuY2U6ZTE4NTNkYWItZjJhMi00OGIyLTk1ZWItODRjYzQ3YzRiMzc4", "instanceName": "Test Embedded config vars", "integrationName": "Test Embedded config vars", "integrationVersionNumber": 1, "customerId": "Q3VzdG9tZXI6OThiMjU3MDUtZmMzNC00NWYwLTk0ZDItODA0ZjFkYzEyYTZk", "customerName": "Smith Rockets", "readOnly": false, "configVars": { "Acme Connection": { "inputs": { "username": { "value": "" }, "password": { "value": "" } }, "status": "PENDING" }, "My Key/Value List": { "value": [], "collectionType": "keyvaluelist", "codeLanguage": null, "dataType": "string", "pickList": null, "scheduleType": null, "timeZone": null }, "My String": { "value": "", "collectionType": null, "codeLanguage": null, "dataType": "string", "pickList": null, "scheduleType": null, "timeZone": null }, "My List": { "value": [], "collectionType": "valuelist", "codeLanguage": null, "dataType": "string", "pickList": null, "scheduleType": null, "timeZone": null }, "My String With Default": { "value": "Some Default", "collectionType": null, "codeLanguage": null, "dataType": "string", "pickList": null, "scheduleType": null, "timeZone": null }, "My List With Default": { "value": ["Foo1", "Foo2"], "collectionType": "valuelist", "codeLanguage": null, "dataType": "string", "pickList": null, "scheduleType": null, "timeZone": null }, "My Picklist": { "value": "", "collectionType": null, "codeLanguage": null, "dataType": "picklist", "pickList": ["Foo", "Bar", "Baz"], "scheduleType": null, "timeZone": null }, "My Boolean": { "value": false, "collectionType": null, "codeLanguage": null, "dataType": "boolean", "pickList": null, "scheduleType": null, "timeZone": null } } } } ``` In this example, we implement a [message event listener](https://developer.mozilla.org/en-US/docs/Web/API/Window/message_event) to monitor for the `PrismaticMessageEvent.INSTANCE_CONFIGURATION_LOADED` event. If the event contains a config variable named `Acme Connection`, we assign a value to that variable. Additionally, if this is the `Salesforce` integration, we assign values to some example config variables. Dynamically set config variable values ```typescript import prismatic, { getMessageIframe, PrismaticMessageEvent, } from "@prismatic-io/embedded"; const myListener = (message: MessageEvent) => { // Extract event and data information from the message const { event, data } = message.data; // Verify this is an "INSTANCE_CONFIGURATION_LOADED" event and // that the config screen was not opened in read-only mode if ( event === PrismaticMessageEvent.INSTANCE_CONFIGURATION_LOADED && !data.readOnly ) { // Extract integration name and config variables const { integrationName, configVars } = data; // Identify the iframe that sent the message for response targeting const iframe = getMessageIframe(message); // Check if the instance has an "Acme Connection" config variable: if (Object.keys(configVars).includes("Acme Connection")) { // Verify if they're empty or already populated: if ( configVars["Acme Connection"].inputs.username === "" && configVars["Acme Connection"].inputs.password === "" && configVars["Acme Connection"].status === "PENDING" ) { // Set username and password fields for "Acme Connection" prismatic.setConfigVars({ iframe, // Target the iframe that sent the message configVars: { "Acme Connection": { inputs: { username: { value: "My-User" }, password: { value: "supersecretpassword" }, }, }, }, }); } } // Set config variables if this is the Salesforce integration if (integrationName === "Salesforce") { prismatic.setConfigVars({ iframe, configVars: { "String Config Var": { value: "Updated Value" }, // Update a simple string "String Valuelist": { value: ["Value 1", "Value 2"] }, // Update a value list of strings // Update a key-value list "String Keyvaluelist": { value: [ { key: "A Key", value: "A Value" }, { key: "B Key", value: "B Value" }, ], }, }, }); } } }; window.addEventListener("message", myListener); ``` Here's how to implement a similar JavaScript example using a React [useEffect hook](https://reactjs.org/docs/hooks-effect.html): setConfigVars Example in React ```tsx import React, { useEffect } from "react"; import prismatic, { getMessageIframe, PrismaticMessageEvent, } from "@prismatic-io/embedded"; import Loading from "../components/Loading"; const id = "embedded-marketplace-container"; const EmbeddedMarketplace = () => { useEffect(() => { prismatic.showMarketplace({ selector: `#${id}`, usePopover: false, theme: "LIGHT", }); }, []); useEffect(() => { const listener = (message: MessageEvent) => { const { event, data } = message.data; if ( event === PrismaticMessageEvent.INSTANCE_CONFIGURATION_LOADED && !data.readOnly ) { const iframe = getMessageIframe(message); if (data.integrationName === "Test Embedded config vars") { prismatic.setConfigVars({ iframe, configVars: { "Amazon S3 Connection": { inputs: { accessKeyId: { value: "supersecretpassword" }, secretAccessKey: { value: "My-User" }, }, }, }, }); } } }; window.addEventListener("message", listener); return () => { window.removeEventListener("message", listener); }; }, []); return (
    ); }; export default EmbeddedMarketplace; ``` ##### The getMessageIframe helper function[​](#the-getmessageiframe-helper-function "Direct link to The getMessageIframe helper function") Your application needs to identify which iframe to send a `setConfigVars` message to. Your application might contain multiple iframes, potentially including multiple instances of the embedded marketplace on a single page. The `getMessageIframe` function helps identify the iframe that generated the `INSTANCE_CONFIGURATION_LOADED` event. If you need to send a `setConfigVars` message to a specific iframe, you can alternatively provide a `selector` property to the `setConfigVars` function (similar to `prismatic.showMarketplace()`): setConfigVars message by selector ```typescript prismatic.setConfigVars({ selector: "#my-marketplace-container", configVars: { "My Config Var Name": { value: "My config var value" }, }, }); ``` #### Hiding UI elements in marketplace[​](#hiding-ui-elements-in-marketplace "Direct link to Hiding UI elements in marketplace") You can optionally hide the **Back to Marketplace** link, specific tabs from the instance screen, and elements of the instance configuration wizard. This is useful for preventing customers from running tests from the **Test** tab or reconfiguring alert monitors independently. To disable specific UI elements, add a `screenConfiguration` block to your `prismatic.showMarketplace()` or `prismatic.configureInstance()` invocations: Hide the 'back' link and 'monitors' and 'test' tabs ```typescript prismatic.showMarketplace({ selector: `#${id}`, usePopover: false, theme: "LIGHT", screenConfiguration: { instance: { hideBackToMarketplace: true, hideTabs: ["Test", "Logs"], hidePauseButton: true, hideDeactivation: true, }, configurationWizard: { hideSidebar: true, isInModal: true, triggerDetailsConfiguration: "hidden", mode: "streamlined", // Hide the initial configuration page }, marketplace: { hideSearch: true, hideActiveIntegrationsFilter: true, }, }, }); ``` * `instance.hideBackToMarketplace`: Controls whether to hide the **Back to Marketplace** link in the instance configuration screen (defaults to `false`). * `instance.hideTabs`: Specifies which tabs to hide from the instance configuration screen. Available options are **Test**, **Executions**, or **Logs** tabs. No tabs are hidden by default. * `instance.hidePauseButton`: Controls whether a customer user can [pause or unpause](https://prismatic.io/docs/instances/managing.md#enabling-and-disabling-instances) an instance. * `instance.hideDeactivation`: Hides the deactivation/remove button on the instance overview screen. This is useful when your application handles instance deletion through its own UI (defaults to `false`). * `configurationWizard.hideSidebar`: Hides the left-hand sidebar from the configuration wizard (config wizard page titles and numbers). * `configurationWizard.isInModal`: Determines whether the config wizard should appear as a modal overlay on the current page. When set to `true`, it assumes your embedded marketplace is already in a modal (preventing a modal-in-modal scenario) and opens the config wizard to fill its containing `
    `. * `configurationWizard.triggerDetailsConfiguration`: Controls the display of trigger details in the config wizard. Options are `hidden` to completely hide trigger details, `default` to include the element in collapsed state, or `default-open` to include the element in expanded state. Endpoint details may also be hidden if they are not configured to be **Secured by Customer** (see [Endpoint Configuration](https://prismatic.io/docs/integrations/triggers/endpoint-configuration.md#securing-endpoints-with-api-keys)). * `configurationWizard.mode`: Can be set to `traditional` or `streamlined` and affects the [initial configuration page](https://prismatic.io/docs/embed/marketplace.md#configuration-wizard-customization). * `marketplace.hideSearch`: Hides the search bar in the marketplace. * `marketplace.hideActiveIntegrationsFilter`: Hides the top-right filter for active integrations in the marketplace. To apply these preferences globally (for multiple `showMarketplace()` and `configureInstance()` invocations), configure settings in `prismatic.init()`: Hide UI elements globally ```typescript prismatic.init({ screenConfiguration: { instance: { hideBackToMarketplace: true, hideTabs: ["Test", "Monitors"], }, }, }); ``` Screen configuration settings in `prismatic.init()` can be overridden within specific `showMarketplace()` or `configureInstance()` invocations. Note that `hideTabs` properties are not merged in this case - the `screenConfiguration` settings in `showMarketplace` / `configureInstance` completely override the default settings in `init`. #### Integration configuration detail screen[​](#integration-configuration-detail-screen "Direct link to Integration configuration detail screen") The embedded SDK configuration enables fine-tuning of the user experience for accessing the integration configuration details screen. It includes the ability to prevent marketplace users from accessing the detail screen, which is useful if you want to restrict access to the **Test**, **Executions**, **Monitors**, or **Logs** functionality. * `allow-details`: Redirects the marketplace user back to the marketplace listing after completing the configuration wizard. When a user selects an integration from the marketplace listing, they see a summary of the integration including its status. Users can manage instances from the overflow menu at the bottom. "View Details" opens the full integration details screen. You can [hide specific UI elements](#hiding-ui-elements-in-marketplace) on this page. This is the default behavior in SDK version 2.0.0 and later if no configuration is provided. * `disallow-details`: Provides the same experience as `allow-details` but removes the details option from the overflow menu. * `always-show-details`: Redirects the marketplace user to the full integration details screen after configuration or when selecting an instance from the marketplace listing. Inline configuration experience that allows marketplace users to access integration config details ```typescript prismatic.showMarketplace({ selector: `#my-div`, screenConfiguration: { marketplace: { configuration: "allow-details", }, }, }); ``` #### Configuration wizard customization[​](#configuration-wizard-customization "Direct link to Configuration wizard customization") When customers configure or reconfigure an integration in your marketplace, they will either see an initial configuration page for editing the instance name and viewing webhook URLs, or they will be taken directly to the first page of the configuration wizard. This behavior is controlled by the `screenConfiguration.configurationWizard.mode` option: * `traditional`: Presents customers with an initial configuration page where they can set the instance name and view general instance details, like flow webhook URLs. This is the default behavior in `@prismatic.io/embedded` versions before 3.0. * `streamlined`: Skips the initial configuration page. This is the default in `@prismatic.io/embedded` version 3.x and later. Note that if you enable [multiple instances](https://prismatic.io/docs/embed/marketplace.md#multiple-instances-of-one-integration-in-marketplace) of your integration to be deployed by a single customer, instance names must be unique. Customers will still see an initial configuration page for editing instance names, but webhook information will not be displayed. If your config wizard has a single page, the left-hand sidebar displaying config page names will be hidden in `streamlined` mode. If you choose `streamlined` mode but still want to display instance webhook URLs and API keys within your config wizard, [you can](https://prismatic.io/docs/integrations/config-wizard/config-pages.md#displaying-webhook-information-in-the-configuration-wizard). #### Disabling connection reuse[​](#disabling-connection-reuse "Direct link to Disabling connection reuse") By default, when a customer configures an integration that includes [a customer connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/customer-activated.md), they can create a new connection or select an existing connection. You can disable the ability to reuse existing connections by setting the `screenConfiguration.configurationWizard.connectionConfiguration` option to `inline`: Disabling connection reuse in the configuration wizard ```typescript prismatic.configureInstance({ integrationName: "My Integration", screenConfiguration: { configurationWizard: { connectionConfiguration: "inline", }, }, }); ``` #### Showing all instances in marketplace[​](#showing-all-instances-in-marketplace "Direct link to Showing all instances in marketplace") Sometimes you may want to deploy an integration to a specific customer via Marketplace without making it generally available to all customers. To achieve this, deploy an [unconfigured instance](https://prismatic.io/docs/instances/deploying.md#creating-an-unconfigured-instance) of the integration to your customer as an organization user. Then, set the `filters.marketplace.includeActiveIntegrations` marketplace option to `true` to display instances that are enabled for a customer (even if the integration is not in the marketplace): Show all instances in marketplace ```typescript prismatic.showMarketplace({ selector: `#my-div`, filters: { marketplace: { includeActiveIntegrations: true, }, }, }); ``` filters take precedence over `includeActiveIntegrations` by default Note that if you have [filters](#filtering-integrations) enabled, the `includeActiveIntegrations` option will not override the filters. By default, active integrations deployed to a customer must match the specified filters to be displayed. If you want to display all active integrations regardless of filters, in addition to marketplace integrations that match your filters, set the `includeActiveIntegrations` option to `true` and add the `strictMatchFilterQuery` property set to `false`. #### Multiple instances of one integration in marketplace[​](#multiple-instances-of-one-integration-in-marketplace "Direct link to Multiple instances of one integration in marketplace") By default, customer users can enable a single instance of an integration through the embedded marketplace. However, there are scenarios where you want customers to deploy multiple instances of an integration. For example, your customers might have multiple Shopify stores and require separate instances of your Shopify integration for each store. Enabling customers to deploy multiple instances of an integration is configured on a per-integration basis. From the marketplace configuration screen or the marketplace drawer in the workflow builder, enable the **Allow Multiple Instances** toggle. ![Allow multiple instances toggle in marketplace drawer](/docs/assets/images/allow-multiple-instances-99337e7a04bd07ebafa8ced70252447e.png) Embedded customer users who select your integration will be able to view existing enabled instances of the integration and will have the option to add additional instances. ![Multiple instances in embedded marketplace](/docs/assets/images/multiple-instances-38fcb8252c2ada35ca9b07651ebdffb9.png) Customers will see a similar view if you have enabled multiple instances of an integration for them. #### Custom marketplace UI[​](#custom-marketplace-ui "Direct link to Custom marketplace UI") The embedded marketplace listview screen is presented as an iframe. If you prefer to present marketplace integrations using your own custom UI elements, you can. This is particularly useful if you build some integrations in Prismatic and others outside of Prismatic - you can display both using consistent UI elements. For complete details, see [Custom Marketplace UI](https://prismatic.io/docs/embed/custom-marketplace-ui.md). --- #### Theming Embedded You can theme your embedded marketplace to match your app's look and feel. To create a custom theme for your embedded marketplace and workflow builder, click your organization's name at the bottom of the left-hand sidebar, then open the **Theme** tab. Permissions required You must have the *admin* or *owner* [role](https://prismatic.io/docs/configure-prismatic/organization-users.md#organization-team-member-roles) to edit custom themes. The left side of the **Theme** tab displays customizable properties (colors and styles) in 3 separate tabs: Brand, Banner & Log, and Neutral. The neutral tab allows you to choose one neutral color, and additional neutral values used throughout the theme are calculated for you based on your neutral selection. The right side provides a preview of how various UI elements will appear with those custom properties. ![Theming the Prismatic integration marketplace for your app](/docs/assets/images/theming-options-cae3cd6fdbf41e28ef38824a3894ac2a.png) Your embedded themes will be applied to your embedded application: ![Example of themed integration marketplace](/docs/assets/images/progix-themed-6479c11fc42618e4dbac2b62a8e822ed.png) ##### Light and dark mode themes[​](#light-and-dark-mode-themes "Direct link to Light and dark mode themes") In the **Theme Mode** section, you have four options: * **Light** and **Dark** control the appearance of the Prismatic application when using light or dark mode. Each team member can configure dark or light mode settings from their [profile settings page](https://prismatic.io/docs/configure-prismatic/user-settings.md#setting-light-or-dark-mode). Alternatively, they can choose to have Prismatic follow their operating system's dark/light mode theme. Customer themes Your customers will not see **Light** or **Dark** themes unless you create customer users for them and they log directly into Prismatic. * **Embedded Light** and **Embedded Dark** control the theme for your embedded marketplace. These are the themes that your customers will see in the embedded marketplace. By default, the embedded marketplace automatically switches between dark and light themes based on your customers' operating system settings. This is beneficial if your app also follows OS theme settings. The embedded marketplace will switch between dark and light modes alongside your app. To override the dark/light mode behavior for embedded and display only either the dark or light theme, add a `theme` property to your `showMarketplace` calls: Only show light mode theme ```typescript prismatic.showMarketplace({ selector: `#my-embedded-div`, usePopover: false, theme: "LIGHT", // or "DARK" }); ``` ##### Using a custom font[​](#using-a-custom-font "Direct link to Using a custom font") You can apply a custom font for your embedded marketplace. This is useful if your app uses a custom font and you want to maintain consistency in the embedded marketplace. Prismatic supports any font available on [Google Fonts](https://fonts.google.com/). To apply a custom font, add it to `prismatic.init()` as `fontConfiguration.google.families`: Use a custom font ```typescript prismatic.init({ fontConfiguration: { google: { families: ["Inter"], }, }, }); ``` ##### Customizing the loading screen[​](#customizing-the-loading-screen "Direct link to Customizing the loading screen") A loading screen appears briefly when `prismatic.showMarketplace()` is called. The screen shows a loading icon on a solid-color background. You can customize the background color and the color of the loading icon and "Loading" text. Add a `screenConfiguration.initializing` argument to do this. Add it to `prismatic.init()` to customize colors for all marketplace loading screens. Or add it to `prismatic.configureInstance()` or `prismatic.showMarketplace()` to customize colors for a specific marketplace div. Colors can be any valid CSS color. Customize loading screen colors ```typescript prismatic.init({ screenConfiguration: { initializing: { background: "#FF5733", color: "blue", }, }, }); // or prismatic.showMarketplace({ screenConfiguration: { initializing: { background: "rgb(5,102,0)", color: "rgba(255,153,255,.2)", }, }, }); ``` #### Renaming "Integration" and "Marketplace"[​](#renaming-integration-and-marketplace "Direct link to Renaming \"Integration\" and \"Marketplace\"") The embedded integration marketplace is labeled "Marketplace" by default and a collection of flows with a config wizard is called an "Integration". You can customize these concepts to match your organization's terminology. For example, your organization might refer to an integration as a "Solution" or a "Workflow". To modify these terms in the embedded marketplace and workflow builder, navigate to the settings page. Select your organization in the bottom left corner, then choose the **Embedded** tab. ![Rename marketplace and integrations in the embedded app](/docs/assets/images/rename-marketplace-integrations-937d8b30922f2b8ab97d91353e1391b6.png) Your custom terms for "integration" and "marketplace" will be displayed throughout the embedded marketplace and workflow builder interfaces. ![Renamed marketplace in the embedded app](/docs/assets/images/renamed-marketplace-e33bb695e45b89cc5d9a6dc43d5ee29a.png) For multi-language support, refer to the internationalization settings in the [i18n documentation](https://prismatic.io/docs/embed/translations-and-internationalization.md). --- #### Translations and Internationalization (i18n) #### Translations and internationalization[​](#translations-and-internationalization "Direct link to Translations and internationalization") Your customers may require non-English language support. Internationalization (i18n) enables you to localize the embedded marketplace interface into multiple languages. Review the marketplace's [translations package](https://github.com/prismatic-io/translations/tree/main/src). This repository contains all translatable phrases in the marketplace, along with their English source text. There are three categories of translatable phrases: * `SimplePhrase`: Direct string-to-string translations. Example: `input.integrationVersionLabel` "Integration Version" translates to French "Version d'intégration". * `ComplexPhrase`: Template strings that incorporate variables representing customer names, integration names, counts, etc. Example: `integrations.id__banner.customerActivateText` with value `"Please contact %{organization} to activate this integration."` This template injects your organization's name into the string. * **Dynamic Phrases**: Translations of your custom content (integration names, configuration variable keys, etc.), detailed [below](#dynamic-phrases). An example of i18n in action is available in our example embedded app [on GitHub](https://github.com/prismatic-io/embedded/blob/main/example-embedded-app/pages/examples/i18n.tsx). #### Adding an i18n dictionary for a user[​](#adding-an-i18n-dictionary-for-a-user "Direct link to Adding an i18n dictionary for a user") To apply an i18n dictionary to a user, include a `translation` property in your `prismatic.init` invocation. You can also add phrases to `prismatic.showMarketplace` or similar functions to apply translations to specific iframes. Include any `phrases` that require translation: Translating phrases ```typescript prismatic.init({ translation: { phrases: { // Static Translations: "integration-marketplace__filterBar.allButton": "Alle, bitte!", "integration-marketplace__filterBar.activateButton": "Solo activado", "detail.categoryLabel": "カテゴリー", "detail.descriptionLabel": "विवरण", "detail.overviewLabel": "概述", // Complex translation with variables: "activateIntegrationDialog.banner.text--isNotConfigurable": { _: "Veuillez contacter %{organization} pour activer cette intégration", }, }, }, }); ``` After implementing changes, reload the marketplace to view the new translations. ![Screenshot of viewing new i18n translations](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQYAAABZCAYAAADcmoqqAAAAAXNSR0IArs4c6QAAAGJlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAABJKGAAcAAAASAAAAUKABAAMAAAABAAEAAKACAAQAAAABAAABBqADAAQAAAABAAAAWQAAAABBU0NJSQAAAFNjcmVlbnNob3TTY/nxAAAB1WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj44OTwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4yNjI8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpVc2VyQ29tbWVudD5TY3JlZW5zaG90PC9leGlmOlVzZXJDb21tZW50PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KyvnQpAAAEINJREFUeAHtXQl4VNUVPoFskB0CJJCw74ssLgiFslbKIkjdQVusS2utn7ZWEFvE3VpcWmtF64Ktxd3PFgQERERFQDZRQJAlISGQECAECISEQM//yJvvzT5hJi+Z5D9++N7cd5dz/3vvf885d+Yl4mTpqbNCIQJEgAhYEGhguectESACRMBAgMTAiUAEiIAbAiQGN0iYQASIAImBc4AIEAE3BEgMbpAwgQgQARID5wARIAJuCJAY3CBhAhEgAiQGzgEiQATcECAxuEHCBCJABEgMnANEgAi4IUBicIOECUSACJAYOAeIABFwQ4DE4AYJE4gAESAxcA4QASLghgCJwQ0SJhABIkBi4BwgAkTADQESgxskTCACRIDEwDlABIiAGwIkBjdImEAEiACJgXOACBABNwRIDG6QMIEIEAESA+cAESACbgiQGNwgYQIRIAKRoYBgW+lBebVwo6w5nienzp4ORZWsgwgQgQARSGgYLW2ik+Wm1D4yKKF1gKV8Z4sI9g/OgBRuy/qIhOAbZz4lArYg8HTmZSEhh6AtBlgKtBJsGfMqN7Km+y1VLsMC4YdA0emTMu/ID/LCgbUy5+A3ISGGoGMMcB8oRIAI1BwCKZGNZHxyZ0OBPWVHQqJI0MRAayEk48BKiEBQCIAcIMcqyoKqxywcNDGYFfFKBIhA3UGAxFB3xpI9IQIhQ4DEEDIoWRERqDsIkBjqzliyJ0QgZAiQGEIGJSsiAnUHARJD3RlL9oQIhAwBEkPIoGRFRKDuIEBiqDtjyZ4QgZAhQGIIGZSsiAjUHQRIDOcxlm1ikiXiPMqFa5EzcjZcVfep9+mzZySv7JjPPPX1Ya0jhuc7jJUPul0nDTwsvd+3GigzMocaY9U0srF82muKtI5JCtnYtY9NMepMahjjtc5+8ekyr/skGZbc3sgDgoAO0RENncokR8ZKalRjp7RAPtyXMVhe6jg+kKzVnmfl8Vy5OWueDPn+dRm5/Q25O+djySkrrlK7nx3Llkm7PqhSmerIDGrbW3ZUys5WOKr/18FNcuXOd6S44pQjrTpu0M59e5dVR9XVVmetIoaMmEQZlNhGsCP3T8hw63Rcg2jBb88hEboiQQ4NI0LXBdSFOiNQuRfZXHJApmcvldVHc40csQ2iZH73ydK5UVOnEr9s0U8eaj3cKS2QD40aRDr6GEj+6sqz/Gi2TM9dJsMT28mcdhPk4VZDJSYiUqbs/q8cKC8JuNmyMxVSVFEacP7qylh65rSSwLuyo/Swo4kJKV3k8YwR4msjcGQO4ubkmXL9DUP1kk8Q6nksGrpV5bH6qiWO04HaXFIgS47slMubdKla4crcDXRR92zcXNoquQQjmCx94tKkkS58q1SoWb1TJxfM0Dh91qWSEDo3SpWOsU2MrLAgQBTmP/MHLmY9vnRE/RVna950X1D8g1yh4zG5aS+jXwPjM+XhjKHSJTZVNp3MN7viuB7Sn/7i3RyBuh3I9/3Jg1UiDZj960r2Sa4PqwV6bDl5wOlVACd0Ye4oPWTouvPUYdl1qsi4B9G1i0kx7vFjQIyr1aLAgxIti3RrvwLRA9YJSMhazmjI8r+TSlabVVeQVm2ToN/HEMoOjWvSWd4s/E5yTh2Rp9qNksa5UYJBDVSub9ZL7ki/xFhYibqwN+gkmpq1VA6dPhFoFUa+kckdZFrGIDlRUS5xaqG8rTo9lbfSeJaiLsJ7Xa+VK75/S62LRvJM+9FG+szWQyVb9Z6w9U2ByzMgIdNI/3v7sfJywXp5/+AW47M/Hc8oKZzR/2pa4tU6c/2lHhbS7LZjnVSDNXBPzmIlhUPSuJJE72rRXy6v/BmwU+bKD+8d3iovHlgnIMjj+mvA3o1byCx9wYhpDbqWweK8L/cT+bokT1LVojuo4zkssa081mq4w2I8p8cSg5ygxyldbNPSfyTjVA8Q1jQtD3l83xf6tqMkebfj1bJKXaVnC1bLos6TBVx8a/Z8maplRid1dKgw99C3suxolrzT4SqDJPzpsb/8mLpciw23BdZfq6hE6aUblVVAFo/kfS5Lju4S4Iw5Dj3Rtndb1VpD9d/XGmLA7twqOlEWF+2UIzrZwKIj1Y+fd3h7QCgMVhfkzvT+ctfuRbJW3xEBYniu/RiZnjlY/pC1OKA6zEw3NLtAxm+dK/vLjkuvuBbyT/X59+iif69ycZv51h3fJ6M2/1tW975VJm9/XzafOGA8ult1ADl0UAvijl0fmdklEB1hiZyuBRbDNU16yK+yz72Z6/LkLtJPxwfEYBVM8HtylkgTJciFnScZmK9QF2RG3nJJi4qXi+NaWrMb918c2yPPFayRJzNHyqXxGXLkdKk8oPmn7V0qL7RxJh2z8MIjO6TwdIl82OlaaRmVIHvUYpiscYsVWhdcHehxr+oBK2+xLnKQ+QIt8+i+z6Vv43Tpp//mdbpehm57XV5Tt6hHo2Zm1Y5rrC7iEVrXkuJdTsSwpHi3410H/vTA2AGPDmqFvNJ2vOoRJf8r2i6z8r9SPdIcbT1f8LVsOLFf3uxwpUFSsGDu3LPQ2GhuadbPka8mb2qNKwHXYfWxXGN3r1CAFxftUneia8DYTGzaTd4o3GSQAgodVZ/u2X2rZERSe4mqYhziuX2rDVJAPd+pazOnYKOMSTn3Igykna8EoiMmOayGmpbuunje73iN7uIxGjj7REZue8MIoO1SU9yU3aVF6g4Uyp9a/liSG8YaAeNhurhG6Y67qHiHmc3p+pEu2IkpXQWuCQLMIBWUX1+yX7DbepKrm3SXt3THBilAsON3iGki2yvdgyxdWDDJH9Q4CPRFvbBYXmw7TmIaOAeFPdVvpo1J7mRYJWYwEpZGnroEo5PPWRD+9ICbkqUbyP0tBxvWD/RAXy9o5GwxzNe3Lf0u7VKjH2gbRPLbFpcI0muLOG8BNaQVIvpXpfYwrINLEloZWsD8vy6hp6RFx0u+7tz+ZEBipoxQC+Oi+HPlkT9SCQHmaqb6/JjEgco29X2tskUtgZta9A3azAtER5BiRS1wJdD/FlFxMj19kNyTNsDw7eEC4JTiJV1wiDVsLS00rDyQglV66kJ4+/Bma5LjHmWwy1sF1gXcsi1KMumVi9/6HGQJS2TV8b2SrW8oytHFWqyWRl+1YiAol6Fk4RpE7GPZpa31ebuHddEsMk6Wq+twhS5oWA8XqdWDNIg/PTBvMtXqhXtglc6KlUmoiD1g0wJGVumhn/PLjwte0+Yak7Lms+u+VhDDkKS2Rn8vU99+UKLzW27H6k79asEGv3ggHoCTgkVFzjvVXNkkh8qrFmNwPXqMUeIq1wUb7D4eiI7wN119e7+dr+YMwAM7PP7dnr1A5hX9IPempxrECyJzFaSBlD1JpO7nnogPAVdvZZ7OXyVLdZFepZYD3oKMUyu4I6agXLnlGNJMr+oV/v0YtQ4Wq+8/QYlh6dHd8pvmFzuq8adHlOJU5gGPMsub080+wu2wiomj+dz6rCbuawUxwI1AbGFq9hInDBBIHKfPAiGGDervIyK95MguRx04UYjWSWOaho4Hfm76xKcZgUQzW7/4lrLtRKH50ePV07FpQ5djz0B0nL1/rcf67UxEZP7XGl+Yoq8j/3FCG6emjYlbGSHrHtvMMP+x02HXNwX+cw995kng328syZcxSZ0cj2F+I67kyfdHJgT/7nQJaCL+g90Z0k3bgg6ueixWMkGdGZX5kNcfvUOvOYXfyCdKCgiMDktoi2KG+NOjq55M5as7ZNUDmwksGsS8IMAJ7tPGE/lOsQxgBj3hCtUG8UzrNmoGswlWwgLdhVxloe7++NJRD5eorms+fH7n4GaBD/+zpt2N4BO+YPRom+HyWueJhs+JPJ30CPHWtAtx61NubnGhuiQtjaPK0SmdBCcJroFHswKcUR9Ui2RYUjvD7THTc04V61FmqvEPx5qQQHSc0ryvPKAnHDUpsBBg0j+Tv9ow32HF4Ihutp4kYAIjSAfBzj1ArQgED3E0B0sHLgS+GDUxpZvHLmDX/7h4p3xYtM2I8sOPfzDvMxmZ2N5hsrsWREwBQUuQx/EzZcbfMEFcwRQcTcOauV+/RASTHcQGPR7et0IQVITghAAnGnBJCnx8DwOLE6cIT+5fabg8ZnnU4U+PdqoH3JGZ2p/dqh82pH9ooHGfy7crJ+kR8N8UW5yyIMj+leI1u2CdIL22SI1bDFh4mFArj+a4YZKF4Jbu1LAo4Of7EpwQ/HHPMrk9/WL9duQQI7awSXcmnEiYu8RAPUIcrwHNl/PX+6pKHstdIbPajTJ8Pbghf9Ug5lKLJeJa+C95X8rMzKFyQ/PeMnDTy8bE/FhJDacq73a9Rl7MXyuwBALRsWdcc4MIsSkH67q46lmVzzNbDRGYzlhsIAbok6kLdFbmT4woP+pCGr4g9IguQBz14YgQ3wt4QtO6KSl6Evj9D2mQ8KXC9br4vjSOOGGVIGDnTe5NGyhT9dTip9v/o21GyEgNKPe3xJLO6TFcieBzjYHM18VWLs00PvJM5iiDDMx679aA3xN6XAnS+LTrL8xktyushidUt7EWqwaZ/OmBYOOf9bRlRt6ncuPuD40gMvp2XdOe8o1aCKbc2PQCwYYCQjysVi50/Xlqb7nSC5ma5ey8Bv0HZ/pvfcVOfQNqC1YIdo0SJRyrgDCideeYoQTiTzDZmqvZd0BN1EAWKCZFvB6TIbBkFRydYcG4+pTedERZuCWmz2mtq6r3ofi7Eug7TguSNMBoWj6e9ED/julunuISiPSU10xDoA0mtic3zMxjvcJEx3cUTLPc+sy8hx6wLGAdeBJsEpgXwZjsgegBMsUY+msH38nwpqsn/X2lmWsxFONe4xaDr46e7zNMOE/SUV2JuYXfenrkloYFUaATMVDBhHMlBZR1JSezPm864nkoSMFsJ9grCNI8JvRVF2IPVSEF1FXV6Ls1juFNF+jha6GBwP0tVm91m+mB6GF+2css4+3qS1dvZexIr/EYgx2dNNvIVtfky+I95kdeiQAR8IJAnbQYvPRVZuYs9/aI6USACFgQqFcWg6XfvCUCRMAHAiQGH+DwERGorwiQGOrryLPfRMAHAiQGH+DwERGorwiQGOrryLPfRMAHAiQGH+DwERGorwiQGOrryLPfRMAHAiQGH+DwERGorwgETQyur/uqr0Cy30SgJhEwv2Lv7b2ZVdUtaGKw/sqtqo0zPxEgAsEjAFKYV/lauDbRwb0d3dQm6F9X4vf0t2Wde2moWSmvRIAI1AwCT+vbtvGWq2AlaGKAAiCHVws3yhp9OzPez08hAkTAPgTgPsBSuEnfuBUKUoDmISEG+yBgS0SACNiBQNAxBjuUZBtEgAjYiwCJwV682RoRCAsESAxhMUxUkgjYiwCJwV682RoRCAsESAxhMUxUkgjYiwCJwV682RoRCAsESAxhMUxUkgjYiwCJwV682RoRCAsESAxhMUxUkgjYiwCJwV682RoRCAsESAxhMUxUkgjYiwCJwV682RoRCAsESAxhMUxUkgjYiwCJwV682RoRCAsESAxhMUxUkgjYiwCJwV682RoRCAsESAxhMUxUkgjYiwCJwV682RoRCAsESAxhMUxUkgjYiwCJwV682RoRCAsESAxhMUxUkgjYiwCJwV682RoRCAsE/g+mL+nL6JUQKAAAAABJRU5ErkJggg==) Use IntelliSense The Marketplace NPM package includes inline documentation showing current English values for translatable phrases. Enable IntelliSense in your code editor and hover over the target phrase to view its English source text. ![Screenshot of intellisense and i18n](/docs/assets/images/i18n-intellisense-08bfdeca8258f10fccfba864f44683f6.png) #### i18n debug mode[​](#i18n-debug-mode "Direct link to i18n debug mode") To identify which phrase corresponds to which UI element in Prismatic, enable **debug mode** by adding `debugMode: true` to the `translation` property of `prismatic.init`: Enable debug mode for translations ```typescript prismatic.init({ translation: { debugMode: true, }, }); ``` This will display phrase keys and their current values for all UI elements in the embedded marketplace: ![Screenshot of i18n debug mode](/docs/assets/images/i18n-debug-mode-17cc75ec9cd9a1f617fa5a95c10624e7.png) #### Namespaced phrases[​](#namespaced-phrases "Direct link to Namespaced phrases") Certain phrases are shared across multiple pages in the embedded marketplace. A comprehensive list of these common phrases is available on [GitHub](https://github.com/prismatic-io/translations/tree/main/src/lib/shared). For example, `common.loading` translates to `"Loading"` in English across multiple screens. Setting `common.loading` once will affect all instances of this phrase. To customize common phrases on a per-page basis, you can namespace phrases. For instance, to translate `common.loading` differently only on the [alert monitors](https://prismatic.io/docs/monitor-instances/alerting.md) page, prefix the phrase key with a `PhraseNamespace` using the format `NAMESPACE__PHRASE-KEY` (see [translations](https://github.com/prismatic-io/translations/tree/main/src)): Namespacing phrases ```typescript prismatic.init({ translation: { phrases: { "integrations.id.alert-monitors__common.loading": "Sit tight a sec...", }, }, }); ``` #### Dynamic phrases[​](#dynamic-phrases "Direct link to Dynamic phrases") As you develop integrations, you create organization-specific phrases. These custom phrases include: * Integration names * Config variable names * Config wizard page titles * Config wizard page descriptions * Config wizard helper text * Flow names (visible to customers in execution results) * Step names (visible to customers in execution results) You can translate these phrases by adding a `dynamicPhrase` property to the `phrases` object. For example, to translate an integration named `"Sync customer data with Salesforce"` to French, add a `dynamicPhrase` to your `prismatic.init` invocation: ```typescript prismatic.init({ translation: { phrases: { dynamicPhrase: { "Sync customer data with Salesforce": "Synchronisez les données clients avec Salesforce", }, }, }, }); ``` You can override any other dynamic phrases using the same method. ```typescript prismatic.init({ translation: { phrases: { dynamicPhrase: { "Microsoft Teams": "Microsoft Squadre", // Integration name "Notify a Teams channel of new leads": "Notifica un canale di Teams di nuovi lead", // Integration description "Teams Configuration": "Configurazione di Teams", // Config wizard page title "Enter your Teams authentication info": "", // Config wizard page subtitle "Teams Authentication": "Autenticazione di Teams", // Config variable "

    Teams OAuth

    ": "

    OAuth di Teams

    ", // HTML helper text in the config wizard }, }, }, }); ``` Dynamic phrases must match exactly Dynamic phrases must exactly match the phrases used in your integration. If your config variable includes capitalization or punctuation, you must include these in the dynamic phrase. Partial matches will not translate (e.g., translating `{"Customer": "Cliente"}` will not translate `"Customer Name"` to `"Cliente Name"`). Note the `

    ` tag in the last example. When providing custom HTML helper text in your configuration wizard, you must translate the complete HTML string. ##### Listing all dynamic phrases[​](#listing-all-dynamic-phrases "Direct link to Listing all dynamic phrases") To view all translatable dynamic phrases from your account, use the `prism` CLI: List all dynamic phrases for all integrations in marketplace ```bash prism translations:list ``` This command generates a `translations_output.json` file in your current directory containing all dynamic phrases for your account or a specific integration. Use this file as a reference when populating your `dynamicPhrase` object. --- #### Embedded Workflow Builder Overview Feature Availability The embedded workflow builder feature is available to customers on specific pricing plans. Refer to your pricing plan or contract, or contact the Prismatic support team to learn more. Prismatic's Embedded Workflow Builder lets your customers create and manage custom workflows right inside your application. This enables them to solve their unique integration and automation needs, without relying on your support or engineering teams. ![Embedded workflow builder](/docs/assets/images/overview-678e081dce33cddf6ab9ce6ef9e034c0.png) The UI of the embedded workflow builder is similar to the [low-code integration designer](https://prismatic.io/docs/integrations/low-code-integration-designer.md) that your team has access to, with a few key differences: 1. Workflows that customers create contain single flows. If they need multiple flows, they can create multiple workflows. 2. Configuration is done inline. Customer users configure connections and steps directly in the workflow builder, rather than in a separate [configuration wizard](https://prismatic.io/docs/integrations/config-wizard.md). 3. [Connections](https://prismatic.io/docs/integrations/connections.md) are scoped to the customer, and can be shared across workflows. That means that they can authenticate with Slack or Salesforce once, and use those connections in multiple workflows. 4. Deployment is simpler. Once they've tested their workflow, they can click a single **Enable** button to deploy it. To get started with embedding the workflow builder, first [install Prismatic embedded SDK](https://prismatic.io/docs/embed/get-started/install-embedded-sdk.md) and then see [Embedding the Workflow Builder](https://prismatic.io/docs/embed/workflow-builder/workflow-builder.md). #### AI copilot[​](#ai-copilot "Direct link to AI copilot") The embedded workflow builder includes an AI copilot that your customers can use to get help building their workflows. See our [announcement blog post](https://prismatic.io/blog/ai-copilot-for-embedded-workflow-builder-early-access/). #### Testing the embedded workflow builder[​](#testing-the-embedded-workflow-builder "Direct link to Testing the embedded workflow builder") The fastest way to test the embedded workflow builder is to use our embedded playground, which is a React application that you can run from within Prismatic. Open your organization settings and then select the **Embedded** tab. Under **Embedded Preview** select **Launch**. ![Launch embedded playground ](/docs/assets/images/launch-preview-462796fff8fa0ce6fd2a6cbd9135f1bd.png) ![Embedded playground ](/docs/assets/images/preview-6ac1783afb1f444eee28ec32b79e55f2.png) *** Classic embedded designer docs If you are looking for documentation for the classic embedded designer, see [embedded designer](https://prismatic.io/docs/embed/workflow-builder/designer). --- #### AI Copilot for Workflow Builder The embedded workflow builder includes an AI copilot that helps your customers build their workflows. The copilot can assist them with a variety of tasks, including: * Generating workflow steps based on a description of what they want to accomplish. * Troubleshooting errors in their workflow. * Looking up relevant components and actions that they can use in their workflow. ![AI copilot in embedded workflow builder ](/docs/assets/images/ai-copilot-2bcbb1335a5604d53a83b8014d4ec01d.png) Check out our [workshop webinar](https://prismatic.io/docs/insider.md#ai-copilot-for-embedded-workflow-builder-2026-03-30) covering the AI copilot. #### Enabling the AI copilot for a customer[​](#enabling-the-ai-copilot-for-a-customer "Direct link to Enabling the AI copilot for a customer") Copilot can be enabled on a per-customer basis. To enable the copilot for a customer, navigate to the customer's **Details** page in the Prismatic web app and select **Workflow builder with copilot** under the **Embedded workflow builder** section. ![Enable AI copilot for a customer ](/docs/assets/images/enable-for-customer-84ca551d545459fbe9fe125fa69d5c4c.png) #### Preparing your connector for the AI copilot[​](#preparing-your-connector-for-the-ai-copilot "Direct link to Preparing your connector for the AI copilot") If you build your own custom connectors, ensure that you provide the copilot with enough information to understand how to use your connector's actions. See [Tune Your Connector for Copilot](https://prismatic.io/docs/get-started/embedded-workflow-builder/tune-connector-for-copilot.md) for more information. --- #### Embedded Workflow Builder White-label Docs When you embed the [embedded workflow builder](https://prismatic.io/docs/embed/workflow-builder/workflow-builder.md) in your application, your customers will naturally ask for documentation on how to use it. Prismatic provides a way for you to offer white-label documentation to your customers, so that they can learn how to use the embedded workflow builder without needing to refer to Prismatic's documentation. #### Why white-label documentation?[​](#why-white-label-documentation "Direct link to Why white-label documentation?") * Empower your customers to learn and build workflows independently. * Reduce support questions and tickets. * Provide a fully branded, seamless experience for your customers. #### Providing white-label documentation to your customers[​](#providing-white-label-documentation-to-your-customers "Direct link to Providing white-label documentation to your customers") To provide white-label documentation to your customers: 1. Clone the embedded workflow builder docs repository from GitHub: [prismatic-io/embedded-workflow-builder-docs](https://github.com/prismatic-io/embedded-workflow-builder-docs). 2. Configure and build the white-labeled docs to fit your needs. 1. If you want to build static HTML, CSS and JS files that you can host on your own servers, see [Building Branded HTML Assets](https://github.com/prismatic-io/embedded-workflow-builder-docs/blob/main/BUILDING-HTML.md). 2. If you want to ingest the white-labeled markdown files into your own content management system (CMS), see [Building Branded Markdown Files](https://github.com/prismatic-io/embedded-workflow-builder-docs/blob/main/BUILDING-MARKDOWN.md). Most CMS systems support importing vanilla markdown files. --- #### White-label Documentation Preview This is a preview of the white-label documentation for the embedded workflow builder. This documentation is intended for users who are embedding the workflow builder into their own applications and want to provide their users with documentation that is branded and customized to their needs. [Preview](https://embedded-workflow-builder.netlify.app/)[GitHub](https://github.com/prismatic-io/embedded-workflow-builder-docs) https://embedded-workflow-builder.netlify.app --- #### Tips for Success with Workflow Builder Before you embed the workflow builder in your application, you'll want to prepare the building blocks that make it easy for your customers to create workflows - without needing technical expertise. This guide walks through the key steps to set up the embedded workflow builder for success. #### Build custom components for your API[​](#build-custom-components-for-your-api "Direct link to Build custom components for your API") Your customers need to interact with your product's data through the workflow builder. Custom components wrap your API into reusable, configurable actions that abstract away technical complexity and present business-friendly operations instead. When building components: * **Name actions by business function**, not technical implementation. Use "Submit Leave Request" instead of `/api/v1/leave-requests`. * **Consolidate multi-step operations** into a single action. A "Process Return" step can update inventory, issue a refund, notify the warehouse, and email the customer-all behind the scenes. * **Include event-based triggers** that understand your system. Triggers like "Return Initiated" or "Order Cancelled" are more meaningful to your customers than raw webhook endpoints. * **Handle validation internally.** Components should enforce required fields, data types, and error cases so customers don't have to think about them. For example, a webhook component might offer a "Return Initiated" trigger that handles subscribing to the right events in your system. When customers add it to a workflow, they select the event from a dropdown-the component handles the rest. See [building custom connectors](https://prismatic.io/docs/custom-connectors.md) to get started. #### Pre-configure connections[​](#pre-configure-connections "Direct link to Pre-configure connections") Connection setup is one of the biggest friction points for new users. Pre-configuring connections to your platform and common third-party systems significantly reduces the time it takes for customers to build their first workflow. * **Authenticate to your platform once** by creating an [organization (customer) connection](https://prismatic.io/docs/integrations/connections/integration-agnostic-connections/org-activated-customer.md) for your API that customers can reuse across all of their workflows. Enable **Use for workflows** on the connection so it's available without extra setup. * **Pre-configure common third-party integrations** your customers are likely to use, such as Slack, Microsoft Teams, Salesforce, HubSpot, Jira, Google Workspace, and Microsoft 365. See [Reusable Customer Connections](https://prismatic.io/docs/get-started/embedded-workflow-builder/reusable-customer-connections.md). * **Clearly document** any permissions or scopes required for each connection. When customers open the workflow builder, connections are already available in the dropdown. They don't need to supply API keys, complete OAuth flows, or do any manual configuration. #### Create workflow templates[​](#create-workflow-templates "Direct link to Create workflow templates") Starting from a blank canvas can be intimidating for non-technical users. Workflow templates provide pre-built starting points that customers can adapt to their specific needs. When creating templates: * **Focus on common use cases.** Build templates for the workflows your customers need most often. * **Keep templates complete but easy to follow.** Include all the necessary steps while keeping the overall flow understandable at a glance. * **Name templates by business outcome**, not technical implementation. "Return Initiated Workflow" is clearer than "Webhook to API template." * **Add descriptions** so customers understand when to use each template. Some useful template patterns to start with: * **Order cancellation** - Trigger on cancellation event → validate order fulfillment → process cancellation * **Return initiated** - Trigger on return event → process return → send notification and log status * **Access request** - Submit request → manager approval → security review → grant access When a customer selects a template, they receive a copy they can customize. Connections are stripped from the template, so they'll configure their own-or use pre-configured connections that are already set to **Use for workflows**. See [workflow templates](https://prismatic.io/docs/embed/workflow-builder/workflow-templates.md) for instructions on creating and publishing templates. #### Simplify complex logic inside components[​](#simplify-complex-logic-inside-components "Direct link to Simplify complex logic inside components") Non-technical users shouldn't need to think about conditional logic, error handling, or data transformation. Build that complexity into your components so customers interact with simple, linear steps. * **Manage branching and loops internally.** Customers shouldn't need to wire up conditionals for common cases-let the component handle them. * **Use business language in step names.** Steps should read like a business process, not code. * **Pre-fill common configurations** so customers can get started without having to configure everything from scratch. * **Make advanced options available but not required.** Power users can access deeper settings if they need them. A workflow that reads "When a new request arrives → Send notification to manager → When request is approved → Update leave management system" is far easier for a non-technical user to understand and trust. #### Embed the workflow builder in your application[​](#embed-the-workflow-builder-in-your-application "Direct link to Embed the workflow builder in your application") Keeping users in your application creates a seamless experience. When the workflow builder feels like a native feature, customers are more likely to adopt it and build workflows independently. * **Integrate into your existing navigation.** A "Workflows" section in your app's nav feels native. * **Match your application's branding.** Use Prismatic's theming options to align colors, fonts, and styles. * **Support both templates and blank workflows.** Offer starting points for all skill levels. * **Let customers test within your app.** Customers should be able to trigger test events and validate workflows without leaving your interface. Inside your application, customers can browse the template library, select a starting point, customize stages, connect to their integrations, and deploy-all in one place. See [embedding the workflow builder](https://prismatic.io/docs/embed/workflow-builder/workflow-builder.md) and [theming](https://prismatic.io/docs/embed/theming.md) for implementation details. #### White-label the documentation[​](#white-label-the-documentation "Direct link to White-label the documentation") Give your customers the documentation they need to be successful with the workflow builder, branded for your product. Prismatic provides a documentation template you can fork and customize. The [embedded-workflow-builder-docs](https://github.com/prismatic-io/embedded-workflow-builder-docs) repository contains a complete, white-label docs site you can deploy under your own domain. --- #### Embedding Workflow Builder The video above shows how you can embed Prismatic's workflow builder in your application. Before embedding the workflow builder, please review [Authenticating Users](https://prismatic.io/docs/get-started/embedded-marketplace/authenticate-embedded-users.md). Use Claude Code to set up embedding The [Prismatic Skills](https://prismatic.io/docs/custom-connectors/get-started/ai-assisted-development.md) plugin for Claude Code can walk you through workflow builder embedding, theming, and i18n interactively. Run `/prismatic-skills:embedded` to get started. #### Enabling workflow builder[​](#enabling-workflow-builder "Direct link to Enabling workflow builder") The embedded workflow builder is disabled for all customers by default. To enable it for a customer, open the customer from the **Customers** screen and select the **Details** tab. Enable the **Allow embedded workflow builder** option. ![Enable embedded workflow builder for a customer](/docs/assets/images/customer-allow-workflow-builder-29995262618e77f1e443d7d2caa963a6.png) #### Listing workflows[​](#listing-workflows "Direct link to Listing workflows") The `prismatic.showWorkflows` function displays all workflows owned by the customer user's customer. ```tsx import prismatic from "@prismatic-io/embedded"; import { useEffect } from "react"; const id = "workflow-list-div"; function WorkflowList() { useEffect(() => { prismatic.showWorkflows({ selector: `#${id}` }); }, []); return
    Loading...
    ; } export default WorkflowList; ``` From the `showWorkflows` screen, your customers can open existing workflows or create new workflows by clicking the **+Add workflow** button. ![List workflows as a customer user in embedded](/docs/assets/images/list-workflows-4d2e3134bfa45aaf9f608829070073e0.png) #### Opening the workflow builder directly[​](#opening-the-workflow-builder-directly "Direct link to Opening the workflow builder directly") If you know the ID of an workflow, you can open it directly using `prismatic.showWorkflow`. You will need to get the workflow's ID using the `prismatic.graphqlRequest` function: Open an embedded workflow builder directly ```ts const embeddedDivId = "embedded-workflow-div"; interface Workflow { id: string; name: string; } function Workflows() { const { authenticated } = usePrismaticAuth(); const [workflows, setWorkflows] = React.useState([]); React.useEffect(() => { const fetchWorkflows = async () => { const response = await prismatic.graphqlRequest({ query: `query getWorkflows { workflows(draft: true) { nodes { id name } } }`, }); setWorkflows(response.data.workflows.nodes); }; if (authenticated) { fetchWorkflows(); } }, [authenticated]); return ( <> Workflows {workflows.map((workflow) => ( ))}