Skip to main content

Project Structure

The folder structure of your Paragraph project defines what integrations are available. The src/integrations/ folder from your project root (the location of your paragon.json file) defines what integrations are available:
Each subfolder in the src/integrations/ folder contains:
  • workflows/ subfolder for each workflow for the integration.
  • config.ts configures the display settings for the Connect Portal used for this integration, including the description and overview text. See more below in Configuring an Integration.
  • inputs.ts configures integration-level User Settings exposed to your customers in the Connect Portal. For example, if you need to prompt your users to select a Field Mapping for Salesforce or a Destination Folder for Google Drive, you can define a User Setting. See more below in Configuring User Settings.

Adding an Integration

To add an integration, you can use the CLI wizard:
para new integration
The CLI will prompt you to search the integration you want to use and install the necessary dependencies.

Configuring an Integration

The config.ts file in any integration folder describes the Connect Portal configuration. Example:
import { IIntegrationConfig } from '@useparagon/core/integrations';

export const config: IIntegrationConfig = {
	description: "Sync records from Microsoft Dynamics",
	overviewText: `Connect your Microsoft Dynamics 365 account and sync your Dynamics 365 accounts, contacts, leads, or opportunities. Enable your sales team to close more deals by keeping your Dynamics 365 CRM records up to date - without manual data entry.
  
Our Dynamics 365 integration enables you to:  
  
- Automatically create or update records in Dynamics 365
- Sync records from Dynamics 365
- Receive updates when a record in Dynamics 365 is created or updated`,
}
The configuration includes the following fields:
  • Short description of the integration
  • An overview of the integration, as a string of Markdown-formatted text

Configuring User Settings

The inputs.ts file in any integration folder defines integration-level User Settings exposed to your customers in the Connect Portal. For example, if you need to prompt your users to select a Field Mapping for Salesforce or a Destination Folder for Google Drive, you can expose these options in the Connect Portal. Example:
import { createInputs } from '@useparagon/integrations/salesforce';

const integrationInputs = createInputs({
    "fieldMapping": {
        id: "fieldMapping",
        type: 'field_mapping',
        title: "Map a custom Salesforce Object to an object in TaskLab",
        required: true,
    }
});

export default integrationInputs;
Define inputs as entries in the object passed to the createInputs function.
  • The key ("fieldMapping") is how you will reference this input in other Paragraph workflows.
  • The id property of the object is how you will reference the value of this User Setting in the SDK or API and must be a stable identifier. Changing this property will result in existing selections for this setting losing their values in your customers’ Connect Portal.
  • The type property of the object refers to the type of User Setting that is shown. Standard options include "text", "boolean", "number", and "password".
    • Integration-specific options like "field_mapping" are defined in the integration-specific types and can be found with your editor autocomplete (e.g. Ctrl-space in VS Code).
  • The title property of the object is what will be displayed in the Connect Portal for your customers, to explain usage for this User Setting.
  • Optionally, include:
    • required: Specify if this User Setting will be required for input by your customers.
    • tooltip: Specify a tooltip that will explain usage of this input for your customers.
Workflow-level User Settings (settings specific to a particular workflow) can be defined within Workflows with the same format, in the inputs property of the workflow file.

Custom Integrations

To define a Custom Integration in your project, create a subfolder with the name custom.[integrationName], where integrationName is the lowercased, alphanumeric name of the integration (for example: “GitHub Enterprise” -> custom.githubenterprise).
Any changes to the name property of your Custom Integration will affect the subfolder name and string used in paragon.connect().
In this folder, add the following config.ts file to configure the Custom Integration and its authentication and display settings:
import {
  ICustomIntegrationConfig,
  createConfigInputs,
  credentials,
} from '@useparagon/core/integration';

export const inputs = createConfigInputs({});

const config: ICustomIntegrationConfig = {
  name: '',
  description: '',
  accentColor: '#000000',
  authenticationType: '', // oauth | oauth_client_credential | ropc_app | basic
  
  // For OAuth and ROPC
  accessTokenUrl: ``,
  scopes: '',

  // For OAuth only
  authorizationUrl: ``,
  includeClientIdAndSecrets: false,
  usePKCEInCodeExchange: true,
  
  apiBaseUrl: ``,
  testEndpointPath: ``,
  authorization: {
    type: 'bearer',
    token: `${credentials.oauthAccessToken}`,
  },
};

export default config;
When defining workflows for Custom Integrations, you can import ICustomIntegration from the core library to get started:
import {
  // ...
  ICustomIntegration,
  // ...
} from '@useparagon/core';

export default class extends Workflow<
  ICustomIntegration,
  IPersona<typeof personaMeta>,
  DefaultInputToResultMap
> {
  define(
    integration: ICustomIntegration,
    context: IContext<DefaultInputToResultMap>,
    connectUser: IConnectUser<IPersona<typeof personaMeta>>,
  ) {
  }
}

Custom Request Options

For OAuth, Client Credentials, and ROPC integrations, you can override the default behavior of each authentication request using requestOptions. This is useful when an API deviates from standard OAuth conventions. The credentials object exposed in @useparagon/core/integration provides access to values at each stage of the auth flow:
PropertyDescription
credentials.clientIdThe OAuth app’s client ID
credentials.clientSecretThe OAuth app’s client secret
credentials.oauthAccessTokenThe user’s access token
credentials.authRequest.paramsQuery params from the authorization redirect (e.g. code)
credentials.tokenRequest.responseThe token endpoint response body as JSON
credentials.savedCredentialsPreviously saved credentials (e.g. refresh_token)
credentials.usernameUsername (ROPC only)
credentials.passwordPassword (ROPC only)
Example
import {
  ICustomIntegrationConfig,
  credentials,
} from '@useparagon/core/integration';

const config: ICustomIntegrationConfig = {
  // ...
  requestOptions: {
    accessTokenOptions: {
      enabled: true,
      configuration: {
        bodyType: 'x-www-form-urlencoded',
        body: {
          client_id: `${credentials.clientId}`,
          client_secret: `${credentials.clientSecret}`,
          grant_type: 'authorization_code',
          code: `${credentials.authRequest.params.code}`,
        },
      },
    },
    authorizationCodeOptions: {
      enabled: true,
      configuration: {
        queryParams: {
          client_id: `${credentials.clientId}`,
          response_type: 'code',
          scope: `${credentials.scope}`,
          // access_type: 'offline', // required by some providers (e.g. Google) for refresh tokens
        },
      },
    },
    refreshTokenOptions: {
      tokenUrl: `https://auth.example.com/oauth/token`,
      configuration: {
        bodyType: 'x-www-form-urlencoded',
        body: {
          client_id: `${credentials.clientId}`,
          client_secret: `${credentials.clientSecret}`,
          grant_type: 'refresh_token',
          refresh_token: `${credentials.savedCredentials.refresh_token}`,
        },
      },
    },
    saveCredentialsOptions: {
      credentialSchema: {
        access_token: `${credentials.tokenRequest.response.access_token}`,
        refresh_token: `${credentials.tokenRequest.response.refresh_token}`,
      },
    },
  },
};