Build a connector
We welcome all contributions to Panora; from small UI enhancements to brand new integrations. We love seeing community members level up and give people power-ups!
Introduction
Setup your environnement
We made a docker file that builds Panora from sources, specifically to help you locally test your contributions. Here’s how to use it.
Copy env variables
cp .env.example .env
Rules for env variables
- You don’t need Stytch variables if you selfhost, it’s only used in cloud version of Panora so you can discard the stytch variables.
- To provide 3rd party variables you have the option to manage a 3rd party oAuth app by yourself by setting the credentials as such:
(You must create by hand inside the 3rd party a custom oAuth2 app and paste the credentials using the following form)
PROVIDER_VERTICAL_SOFTWAREMODE_ATTRIBUTE
where
PROVIDER is any 3rd party name
VERTICAL is for example { CRM, TICKETING, MARKETINGAUTOMATION, ...}
SOFTWAREMODE is { CLOUD, ONPREMISE }
ATTRIBUTE is for example { CLIENT_ID, CLIENT_SECRET, SUBDOMAIN, ... }
i.e HUBSPOT_CRM_CLOUD_CLIENT_ID
Removed previously installed dependencies
rm -rf node_modules .pnpm-store ./packages/api/dist ./packages/api/node_modules ./apps/webapp/node_modules ./apps/frontend_snippet/node_modules
Mac Users only:
echo -e "node-linker=hoisted\npackage-import-method=clone-or-copy" > .npmrc
Start the Dockerfile
docker compose -f docker-compose.dev.yml up
Adding new Integrations ✨
Make sure you are inside packages/api/src
where the server lives !
You want to add a new 3rd Party not yet supported ? 🧑🎤
Ie: Slack, Hubspot, Jira, Shopify …
First choose wisely which vertical the 3rd party belongs to among these:
- crm
- ticketing
- accounting
- ats
- filestorage
- hris
- marketingautomation
For the sake of the guide, now on we’ll consider adding a 3rd party belonging to the crm
vertical.
1. Look into the packages/shared/src/utils.ts
file and check if the provider you want to build has its metadata set inside the providersConfig
object.
It should be available (if not contact Panora team) with active
field set to false
meaning the integration has not been built.
Actually an integration is built in 2 parts :
- the authentication part (oauth, api key, basic etc) which is built by the Panora team
- the service integration where the mapping is created with our unified model which is what you’ll build
2. Build your provider service
You want to map a common object to your new 3rd Party ? 👩🎤
Ie: Contact, Ticket, Deal, Company …
For the sake of this guide, let’s map the common object contact
under crm
vertical to my3rdParty (in reality it would be a real 3rd party name).
DISCLAIMER: an integration is considered valid when all common objects have been mapped. Then, after the PR is accepted we’ll be able to set active
field to true
inside providersConfig
.
1. Add a new service to map your common object to your 3rd party
Create a new service folder with the name of your 3rd party. Let’s call it my3rdParty.
cd crm/contact/services/my3rdParty
You’ll now create 3 files.
index.ts
_where your service is created and direct interaction with your 3rd party API is handled
It must implement the IContactService
interface.
export interface IContactService {
addContact(
contactData: DesunifyReturnType,
linkedUserId: string
): Promise<ApiResponse<OriginalContactOutput>>;
syncContacts(
linkedUserId: string
): Promise<ApiResponse<OriginalContactOutput[]>>;
}
@Injectable()
export class My3rdPartyService implements IContactService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
CrmObject.contact.toUpperCase() + ':' + My3rdPartyService.name,
);
this.registry.registerService('my3rdParty', this);
}
async addContact(
contactData: 3rdPartyContactInput,
linkedUserId: string,
): Promise<ApiResponse<3rdPartyContactOutput>> {}
async syncContacts(
linkedUserId: string,
): Promise<ApiResponse<3rdPartyContactOutput[]>> {}
}
Check other implementations under /crm/contacts/services
to fill the core functions.
The keen readers may have noticed 3rdPartyContactInput
and 3rdPartyContactOutput
.
This is where types.ts
comes in:
Go to the 3rd party API and insert the correct types asked by the API.
export interface 3rdPartyContact {
//INSERT THE CORRECT TYPE HERE
}
export type 3rdPartyContactInput = Partial<3rdPartyContact>;
export type 3rdPartyContactOutput = 3rdPartyContactInput;
Last but not least, inside mappers.ts
you have to build the mappings between our unified common object contact
and your third party specific type 3rdPartyContact
.
It must implement IContactMapper
interface.
export interface IContactMapper {
desunify(
source: UnifiedContactInput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[]
): DesunifyReturnType;
unify(
source: OriginalContactOutput | OriginalContactOutput[],
customFieldMappings?: {
slug: string;
remote_id: string;
}[]
): UnifiedContactOutput | UnifiedContactOutput[];
}
export class My3rdPartyMapper implements IContactMapper {
desunify(
source: UnifiedContactInput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): 3rdPartyContactInput {}
unify(
source: 3rdPartyContactOutput | 3rdPartyContactOutput[],
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): UnifiedContactOutput | UnifiedContactOutput[] {}
}
Check other implementations under /crm/contacts/services
to fill the core functions.
2. Enable your new service
To make sure the service is enabled, dependencies and imports must be added. We built a script that does it in seconds. You can execute the given command from the root directory of Panora.
cd packages/api && pnpm install && pnpm run validate-connectors --vertical="crm" --objectType="contact"
The script will automatically scan the /crm/contact/services
folder and detect any new service folder so all dependencies and imports are updated across the codebase.