Skip to content

Instantly share code, notes, and snippets.

@jackcoldrick90
Last active March 27, 2024 08:28
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save jackcoldrick90/faa4f25eb6f07a6bc50b84589a574b3d to your computer and use it in GitHub Desktop.
Save jackcoldrick90/faa4f25eb6f07a6bc50b84589a574b3d to your computer and use it in GitHub Desktop.
This custom code snippet can be used to associate a contact to a company based on the company name property that is stored at a contact level. It's particularly useful if your customers are using a freemail address and aren't supplying a company website - just the name of their company. If no company is found a new record will be created in the …
// Import the Hubspot NodeJS Client Library - this will allow us to use the HubSpot APIs
const hubspot = require('@hubspot/api-client');
/*
This function is called when the custom code action is executed. It takes 2 arguements. The first is the event object which contains information on the currently enrolled object.
The second is the callback function which is used to pass data back to the workflow.
*/
exports.main = (event, callback) => {
// Instantiate a new HubSpot API client using the HAPI key (secret)
const hubspotClient = new hubspot.Client({
accessToken: process.env.HUBSPOTTOKEN
});
// Retrive the currently enrolled contacts "company" property
hubspotClient.crm.contacts.basicApi.getById(event.object.objectId, ["company"])
.then(results => {
// Get data from the results and store in variables
let companyName = results.body.properties.company;
//console.log("SEARCH TERM: " + companyName); // - FOR DEBUG
// Create search criteria
const filter = { propertyName: 'name', operator: 'EQ', value: companyName }
const filterGroup = { filters: [filter] }
const sort = JSON.stringify({ propertyName: 'name', direction: 'DESCENDING'})
const properties = ['name']
const limit = 1
const after = 0
const searchCriteria = {
filterGroups: [filterGroup],
sorts: [sort],
properties,
limit,
after
}
// Search the CRM for Companies matching "companyName" variable defined earlier
hubspotClient.crm.companies.searchApi.doSearch(searchCriteria).then(searchCompanyResponse => {
//console.log("RESULTS: " + searchCompanyResponse.body.total); // - FOR DEBUG
// If total equals 0 no results found
if(searchCompanyResponse.body.total == 0){ //NO MATCH FOUND - CREATE COMPANY AND ASSOCIATE
// console.log("COMPANY " + companyName + "NOT FOUND: CREATE + ASSOCIATE") // - FOR DEBUG
//Create a Company object
const companyObj = {
properties: {
name: companyName,
},
}
//Create the Company using Company object above
hubspotClient.crm.companies.basicApi.create(companyObj).then(companyCreateResponse =>{
//Associate Company with Contact using the ID returned from the previous request
hubspotClient.crm.companies.associationsApi.create(companyCreateResponse.body.id,'contacts', event.object.objectId,'company_to_contact');
});
}else{ // MATCH FOUND - ASSOCIATE COMPANY TO CONTACT
// console.log("COMPANY " + companyName + " FOUND: ASSOCIATE RECORDS"); // - FOR DEBUG
//Associate Company with Contact
hubspotClient.crm.companies.associationsApi.create(searchCompanyResponse.body.results[0].id,'contacts', event.object.objectId,'company_to_contact');
}
});
callback({outputFields: {}});
})
.catch(err => {
console.error(err);
});
}
@chrisoklepke
Copy link

Hey @jackcoldrick90

I'm not good with code so forgive me for asking stupid questions. Thank you for providing this use case. However, I can't get it to work. I got my HAPIKEY and from what I understand it should run as is, right? Or do I need to adjust something first? I get an ERROR Unhandled Promise Rejection when testing.

Thank you so much for helping the community and us developer noobs.

Cheers,
Chriso

@jackcoldrick90
Copy link
Author

Hey @chrisoklepke, no problem at all! So in theory the code should work so long as you are correctly using your portals API key. I'm happy to take a look if you want to link me to the workflow in question.

@chrisoklepke
Copy link

Hey @jackcolrick90, I'm terribly stupid. I was working on a first version with the HAPIKEY selected and when I tried your template in a new WF block I forgot to select it. I appreciate your help but it works now as expected. Sorry for interrupting your day with this nonsense

@jackcoldrick90
Copy link
Author

No worries, glad you got it sorted!

@Joe-Sepha
Copy link

Hey @jackcoldrick90 - this is awesome and so useful, thanks so much for putting it together!

Could I also use it to associate to a company based on a different field? Now that multiple associations with association labels are available, this could enable some really clever stuff.

For example, if I wanted to match a different custom field (let's say 'Partner Name') up with the Company Record's Company Name.

Would it be a simple case of swapping out the existence of 'company' for 'partner_name' in your above snippet?

Apologies if that's oversimplifying it - I'm just a simple HubSpotter with a very rudimentary understanding of code ha!

Thanks heaps,
Joe

@chrisoklepke
Copy link

Hey @Joe-Sepha - it's the exact same use case for me. I'm looking for the partner property on the contact and like to associate it with the respective company as a partner. You can look at what I put together here but probably a bit questionable. Works though:
https://gist.github.com/chrisoklepke/a397c2e880d235405fc15fed05d20a34

Line 64 (and 58 if you wan to create one if none is found) is where the association type is set to partner in my case:
hubspotClient.crm.companies.associationsApi.create(searchCompanyResponse.body.results[0].id,'contacts', event.object.objectId,'partner');

So company_to_contact gets replaced with your association type. Please, someone correct me if I'm wrong as I'm a beginner in Node myself.

@casmanrojo
Copy link

Hey @jackcoldrick90

The property "name" is the one that I should change with the internal value of my property? I don't know a lot about coding but this is the solution my team and I have been searching for many weeks.

Thanks.

@jackcoldrick90
Copy link
Author

Hey @Joe-Sepha, @chrisoklepke is correct, you can simply swap out the field names based on your own needs! Glad this is helpful :)

@Joe-Sepha
Copy link

Hey @jackcoldrick90 & @chrisoklepke - thank you so much, this is a gamechanger!

@casmanrojo
Copy link

casmanrojo commented Nov 17, 2021

Hey @jackcoldrick90 I cant set the WF up can you show me how the code looks in the workflow? What triggers should I use?

@Sara-E
Copy link

Sara-E commented Jun 22, 2022

Hey @jackcoldrick90
I am a programmer and want to use the custom code in my workflows.
I'm having a hard time finding documentation that is specific to writing custom code within a workflow.
Can you or anyone direct me to the correct place to be looking?
For example, your line of code: hubspotClient.crm.companies.associationsApi.create(searchCompanyResponse.body.results[0].id,'contacts', event.object.objectId,'company_to_contact');
Where can I find the object definition for hubspotClient.crm.companies.associationsApi that you used and see a list of other action options that can be used?
So too searchCompanyResponse.body?
Thanks very much

@bretthooker
Copy link

Sara-E, did you find this documentation?

@MariaFernanda1997
Copy link

Hi, everyone.

I need to do some similar but associate custom objects creating on deals with contacts by default. Is possible with this code?

@jackcoldrick90
Copy link
Author

Hi @MariaFernanda1997 - This code won't work for that use case. Can you explain a little bit more about what you are trying to achieve and I can see if I can create a fork to suit your needs.

@MariaFernanda1997
Copy link

Hi, @jackcoldrick90 thanks for answering, currently I have two custom objects that are created inside the deal and then associated with the contact. When you create one of the objects, the deal is associated but then the user has to manualy asign the contact or the other objects that are in top of the deal. I would like to know if there is a way to not ask for any manual associations of the objects on top, once the user registers the custom object.

@jackcoldrick90
Copy link
Author

@MariaFernanda1997 - The code you need would look something like this: https://gist.github.com/jackcoldrick90/24e96f8e6d962e7c7ecd787f8df6b052.

It first retrieves the deal linked to the custom object. It then uses the CRM Associations API to retrieve the contact linked to the deal. It then uses the CRM Associations API to link the custom object to the contact. If your deal has more than one contact you will need to make additional requests and modify.

I hope it helps.

@Steve-Kv
Copy link

Steve-Kv commented Nov 1, 2023

Hey @jackcoldrick90 - I've just copy/pasted this over to a sandbox portal, changed the secret and hit test against a contact record (within the action editor), the logs returned the following:

TypeError: Cannot read properties of undefined (reading 'properties')

I ran it by Phind AI, which suggested results.body was undefined, so I added the following to debug a little:

hubspotClient.crm.contacts.basicApi.getById(event.object.objectId, ["company"])
    .then(results => {
    console.log(results);
    if (results.body) {
// rest of code...
} else {
      console.log("results.body is undefined");
    }
    })
    .catch(err => {
      console.error(err);
    });
}

The logs then returned:

2023-11-01T11:32:37.344Z	INFO	SimplePublicObjectWithAssociations {
  id: '201',
  properties: {
    company: 'Dunder Mifflin',
    createdate: '2019-12-06T15:25:27.197Z',
    hs_object_id: '201',
    lastmodifieddate: '2023-11-01T10:08:32.513Z'
  },
  createdAt: 2019-12-06T15:25:27.197Z,
  updatedAt: 2023-11-01T10:08:32.513Z,
  archived: false
}
2023-11-01T11:32:37.403Z	INFO	results.body is undefined

Memory: 73/128 MB
Runtime: 1517.77 ms

I'm not very code-savvy and can just about read it and not much else, so I've hit a bit of a brick wall. Do you have any ideas as to where the code is falling over in my instance?

Following a successful poc that this works, I'll be tweaking the code slightly to use a custom contact/company property to function off of, is there anything worth considering using a custom property instead of a vanilla HS property?
EDIT: I forgot to add, I'd also like to set an association label when the association is made (and add it if it is missing) - but will see where I get to with this first!

I would be extremely in your debt if you or any others could give guidance 🙏, thank you!

@chrisoklepke
Copy link

Hey @Steve-Kv, it's been a while since this Gist got created. The response objects from the API look a bit different now, that's why you're getting these errors. You should be able to use the v3 of the API library instead of v8. Then it should work.
Screenshot 2023-11-01 at 16 51 32

@evolv-vhilaire
Copy link

@jackcoldrick90 I want to edit this so that I can associate a custom object with contacts that match (based on email domain field) but I don't want it to create anything if the search turns up nothing. Would all I have to do is remove the "if total equals zero" and "create company" sections?

@z-churn
Copy link

z-churn commented Mar 27, 2024

Hey @jackcoldrick90 and everyone here. I wanted to ask if you all have been successful in adapting this code for other object associations to use in a custom coded workflow, specifically creating a deal-to-contact association automatically based on a common field/variable value that is always an email address. The goal is to automate the association process for Deals and their existing Contacts. Here has been my logic in adapting this code for my purposes, please let me know if I am missing something here:

  • I do not need to look for the deal itself as it is available through the workflow enrollment; I just define the Deal Record ID and then use it in the code. const hs_deal_id = event.inputFields['hs_object_id'];

  • My next task is to use an existing field value which is always an email address to look for the existing correlating contact object (these contacts will always be in the system). I am assuming much of the filtering above is not needed for this? Can I adapt the search filters to include only the email as the property like this:

const filter = { propertyName: 'email', operator: 'EQ', value: contactEmail }
	const filterGroup = { filters:	[filter] 	}
        const sort = JSON.stringify({ propertyName: 'email', direction: 'DESCENDING'})
        const properties = ['email']
        const limit = 1
        const after = 0

const searchCriteria = {
          filterGroups: [filterGroup],
          sorts: [sort],
          properties,
          limit,
          after
        }

And then I would run the rest of the code but instead of company, search through contacts and adjust the association to be deals to contacts? I want to make sure that I am on the right track. Thank you in advance!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment