Tips and Tricks for Building HubSpot CRM Extensions | Sean Matthews, Left Hook | HubSpot Dev Day

Sean Matthews walks through building HubSpot CRM extensions, from the JSON response format HubSpot expects to mocking with Postman, OAuth setup, and ngrok for local development. He covers error handling, undocumented limits like the 5-second fetch timeout, and patterns for saving custom properties and managing iframe state.
Chapters
On tips and tricks for building HubSpot CRM extensions I'm Sean Matthews, co founder of Left Hook. I've been working on these for past two plus years now and hopefully what you'll gain from this talk are basically pains that you can avoid doing yourself and also tips for speeding up your development process along the way. So who are we? Why listen to us Left Hook started building into universal integrations since 2012, full stack since 2016. Full stack is JavaScript for us for now and the universal integrations just as a quick clarification, that's any integration or app that is used by end users without having to go through a lot of setup, et cetera from the you know, for the install or things like that. So you can think Zapier apps, HubSpot apps, things like that where anyone can just walk up and turn it on and get up and running. We've built for over 20 different marketplaces. I've touched personally over 250 different APIs, so had a lot of experience against a lot of these different things. We'll have four HubSpot CRM extension specific but generally apps published by the end of 2020 and hopefully many more to come after that. So what are CRM extensions? Again, you can read through that list, but the Cliff Note version is I want to be able to see data from external sources or apps within my HubSpot account, especially if it's data that's related to a company, a contact or a deal. So think of it as I want to go retrieve tickets, or if I'm an invoicing tool, I want to receive to see invoices. Or if I'm a document management tool, I want to see documents associated to a company or contact. You can also get way more sophisticated than that, or you can get really some basic interactions as well. That's the basic idea. The other thing you can do with them are interactive. So CRM extensions can be interactive through iframes that pop up, whatever URL you point it to, but basically whatever customized interface you want and through actions which are clicks and essentially HTTP requests against the service that you provide. So let's, let's get to some normal set of terms philosophy major. So definitions are really important to me, but maybe not to you. Hopefully if we get this down correctly, then we'll be able to talk about exactly what's relating to what, especially when we start talking about the JSON object that you're supposed to return to the front end. So the definitions are really important there. It also helps you Understand what you're building. So a CRM extension is the equivalent of one card. A card inside HubSpot is basically this side object here. So the side object is essentially a related object to whatever the thing you're viewing. So that's a card. In addition, every card can have card properties. So you might think of this as a list of items that might be related. So it could be a number of invoices related to a contact. The card would be related invoices or contact invoices, something like that. Every card can have a title. It can have multiple card properties. Again, those are the items. And it can have settings, action, primary action, or secondary actions. Talk about the actions in a moment. Every card property can have multiple little P properties. The first three are always displayed. Touch on that later. And then those can have their own actions. So you might want to take atomic action on a specific item, which we'll cover that in a bit, and every action. So this is both true for the card actions and for the card property actions. Those actions can be iframe pop ups, which you can completely control. They can be external links, so basically a click over to a new tab. Or they can be HTTP requests, which you can define the type of method and also the data that gets sent that hopefully covers all the terminology and how they all relate to each other. This is a quick view of what that looks like, really. I pulled this from the HubSpot documentation. The card title card, that's just this whole box. The boxes inside. Those are the card properties. And these lines here, the key value pairs or however you want to think of them. Those are the little P properties. These are titles for each of the card properties. And then the actions, you have the action drop down here for each of the card properties, each of the items, and you have the primary action plus secondary actions for the whole card. And then the settings action settings. Really it points to wherever you want it to, but it should be the place where they go to manage how the CRM extension works or how your app works. Just to really hone in or just nail on the point, all of this JSON and it goes way beyond this page. I just wanted to give you a visual example that translates into this. So HubSpot will make a fetch request every time a page loads, expect to return this JSON and it will go ahead and display it like this. One thing you'll note here, there's not a lot of CSS that you can do inside the normal card property or the card as a whole. Really, the Only place that you have some CSS variability are in this status property idea, which I'll cover in a moment. Other than that, the only time you get to go play around with CSS is inside the iframe. Okay. Tips and tricks overview. So that's the high level CRM extensions, probably pretty valuable. They can be complex to build. They do expect a very specific JSON object and there's some things you have to understand about relationships and properties and tiers in order for you to have a successful request ultimately, but also make for a good experience. So some tips and tricks mock the fetch response and then fine tune found that to be really valuable when you're working with many stakeholders that need to understand what you're building before you go build it. So spend the time on the front end mocking stuff up. We've hopefully made that easier for you. I'll cover that in the first slide. First, next deck. Next slide. There we go. Second error message handling. They don't give you a lot of options. I just want to cover what you can do. Undocumented things is number three. That's things that don't show up in the documentation or hard to find or are not very clear. I'll just throw some out there that are just at the current snapshot of the moment, what's available or what's there that is subject to change. Basically they've been iterating on the platform pretty rapidly as is evidenced by the, well, the growth of the platform plus the new custom objects that they just released this past week. So local development, if you're anything like me or my team, as you go to do the development, you like to see things locally live before you go push them to any service. That way you can rapidly iterate locally. Obviously you, you can do that against tests and unit tests. It's also nice to see it within the HubSpot environment so that you know, okay, this is going to work as I expect. And then last part is going to be talking about hybrid and advanced extensions. That's just to hopefully with all this in hand, give you some ideas of how to make things more robust and interesting for the end user. Because that's ultimately what we're here for. All right, so let's get into mocks. We have created a Postman collection. If you don't use Postman, you should check it out. You can feel free to use any other mocking service. The Postman collection is just easy to get up and running. It's also they give it to you for free. So let's go with that. We're going to go through that right now. Basically the steps are pull down, pull down the collection, run it as a mock, create the CRM card if you haven't already, copy and paste the mock, authenticate it. That's key. Can't actually see the mock data unless you go ahead and authenticate and then navigate to the record. And we'll show exactly how that works when you go tweak and save. So let's go Postman collection. So this Postman collection will have really, it's just two requests in here. The whole point of it is to just show you how you can actually get sample data easily tweaked, saved and sent. Actually, let me undo some things it did. There we go. Okay, so first thing you do, pull it down. You can either edit it here and you can see here you can do a few things. One thing to note about the collection is it has variables inside. These variables are client id, client secret, redirect URL authorized. You can read those. We I piped these into the the create OAUTH token request. You can do however you'd like. Just know that these are preset and ultimately when you get the collection, it will. You'll need to go replace these in order to get this authentication step to work. So first things first, make sure you go update these variables with your own values. These, by the time you see this, will be reset and won't work for you. So don't try that. The other thing to note about variable variables within Postman collection variables basically goes global variables. If you set them on your Postman account, get overwritten by collection variables, which get overwritten by environment variables. So you have an environment selected already and you already use one of these variable names. Just make sure that you've either go reset things or set them appropriately or go select no environment. That's just going to help save you a lot of headache if you are confused as to why things aren't piping correctly. Okay, so got the collection. Go ahead and set it as a mock. There's a few ways to do that. You can do this where you see mocks and monitors, add a mock. Or you can go ahead and just use this the where they call it a burger. Nope, that's a ellipsis or just menu item, whatever. Mock the collection here. What it'll do is generate a mock server for you, give you a URL. This URL. The way that it works inside Postman is that it will take this whole collection and then it will look For a basically a swapped out host. So they'll go generate that mock, go look at that again, that URL and any paths you can then modify. So this request here, a full example, I can create an example body, which I've done and we've saved it for you. This is just a tweaked version of HubSpot's version or body or example body that is actually going to be really useful for. Essentially what you can do is tweak this and then every time you make a request with that path in place and you can actually get really complex with Postman, it will return the body that you save here. So pretty straightforward. Importantly, you can get sophisticated if you'd like. I typically would just make a new endpoint every time I'm trying to iterate quickly. All right, so that's the body, that's the mock. I've already set the server up. You saw the URL. I've copied that URL over into the CRM extension card already, but let's go to that for a second. So here's my app. This doesn't look familiar to you? Just go ahead and create an app inside the OAUTH or the developer account and go ahead and set up things that you need to. The two things that are important for our flow is that you make sure you get the redirect URL in there. Make sure you have the scopes set up. Other than that, feel free to do as much other things as you want on setup. So for the CRM cards, go ahead and create a card. It's going to ask for a few basic things. The first thing that you'll notice is going to ask and require that you create or you provide a fetch URL. So let's go ahead and look at it. Data fetch URL the fetch URL has to be an HTTPs endpoint. Cover that in a moment, but ultimately provide the Postman mock again, I added the path here. You can name your path whatever you'd like, but as long as an A path matches something in the collection, Postman will return whatever body it is in this examples that you saved. So there we go. That's my data fetch URL set up. I've turned this on for contacts and then I've also done these statuses. Sorry, these properties, one of them being status, one of them being ticket type. There's a lot of other things to do here in the properties thing that I could really unpack with you if you'd like to. We'll talk about that in a Moment. Ultimately you have two options for displaying properties and the one property that you really have to care about getting right and configuring inside this interface app options on the fly, every other property you can map on the fly. So we'll get into that. Lastly, I don't have any custom actions pre programmed here, but this is where you'd go whitelist URLs that custom actions are going to be hitting up. And that's just base URL so it tells the front end. Yep, that's fine. Go ahead and hit that one. Okay, so let's go back to here. We've created run the mock, created the CRM card. I've copied and posted it inside the fetch section. Authentication. Here we go. So go to your settings, go to auth if you've added the redirect URL. Side note, they do let you use localhost. It'll automatically prepend or remove the S so you don't have to use HTTPs. You can mock that if you need to locally, but ultimately there you go. Copy the full URL here. Paste that in a browser anywhere, whatever account you're doing. Just want to make sure I got the right one. It's my sample account, so I'll go to sample. Quick note, if you've already authenticated before and HubSpot recognizes that for that account, it'll automatically run you through and provide the code right off the bat. Otherwise you'd see the normal flow that says X would like to get access to Y. Would you like to allow access? Copy this code, come back to Postman. Let's go to our Create oauth token. And as you can see here, we've got the client ID app id client secret. Got this all pre programmed for you. So all you have to do is code in or paste in the code. As long as you've already preset these variables on the collection. Don't remember, don't forget to do that. Hit send. It'll generate the token and then you can forget about it. So all you need to do in order to enable the CRM extensions on that account is just make sure you generated the token correctly. That's how HubSpot knows. Okay, this account has, has, has this set up. All right, now that we've done that, we should be able to navigate to the HubSpot Record, review the output, tweak the sample and then refresh, see how it looks. So I'll go here, I'll load it, I'll pull the full example open, I'll move my Head over there, you can see here's the full example and there's a few more things here. Talk about that in a moment. Again, this is the card. It's the whole box. So this is the card. I'll expand the card open. These are card properties. You can have an unlimited number or an unknown number. You start to get into performance issues after a few hundred. I'll tell you about that later. But ultimately these are the items that we have returned in the results object of the sample data. So this is what I mean. Take a look at this with your stakeholders, figure out, hey, do you want this? Do you want more things to show up here? What do you want for actions? And then go back to your mock data inside Postman, click on your get request, go to examples, hit the full example body and now you can go modify this as much as you'd like. So I'm going to change this to Blazing Speed Part 2. No real reason, I just mixed a bunch of different movie titles. Okay, saved that. Let's go back. Let's refresh. Fun Times API has now become Blazing Speed part two. So it's that quick and easy to go do. You can do that for every single property here. You can do that for the actions as well and you can do that for a few other pieces. Last thing to note, anything that's an iframe will get popped up in this iframe as it looks important that you kind of save state but ultimately it will load whatever URL you send it and we can dive into more details about specific property items as you'd like. But feel free to play along with play around with this. The only other thing to note here is when I mentioned you can do you can pass along properties to the card property. So again card property capital p property lowercase p you can pass along additional properties in this. Let's see if I can find it here in this properties array and they'll they HubSpot's front end will display it on the fly. So you can do label data type value. Just as long as you have those within the property, it will display them on the fly. If it's a currency data type, you just need the currency code which is really helpful especially if you're somebody goes or uses international currency. So that's how you pass along properties on the fly. Otherwise anything else that that shows up on the object will have to be a pre mapped property. So you notice all these other ones don't show up, only status does because I pre match that and ticket Type last last. Note. The results have these. Each object ID has to be unique in the results that I don't know about you. Sometimes I don't have object IDs and if you don't make sure they're unique, it'll throw an error. Okay, feel free to ask questions about that. I'm happy to answer questions and any of the HubSpot team is always happy as well. So that's our number one. Number two error handling default error for CRM extension you can't really control. So that's basically if your service fails to load, if you have a deserialization error within your your data or if there's other issues with the extension at all, then you'll get a default error message within the extension body or the the card body that you can't control. So that's just the impossible side. You no control over that. However the can control. The easiest one to control is going to be the action hooks. Those are what you set. Basically you could tell HubSpot you here for this edit action or the reassign action or resolve or delete the resolve action for example. That's what we've set inside the object body. Let's go back to it. Resolve custom actions. That's the delete the front. Sorry about that. Put. There we go. So I've done a put and I've said here's the URI and I've generated this on the fly. So now my service will whenever this put request is received will know to go run the resolve action. Notice that when I did click that button inside HubSpot it automatically refreshed the page for me. What it will also do is if I return a 200 okay with a message string it will return or a message key with a string it will display that message to the end user. Will also do the same thing for four hundreds and five hundreds. So that's where you have the easiest handler at all of all. Basically if you have a custom action you can customize the message that gets returned to the end user and displayed that's on user clicks. Some of the more difficult errors to handle are kind of use case errors. So authentication may be stale somewhere. On the downside stream downstream side. Some of the other ones like there's maybe you have a configuration item or a configuration detail that needs to be done by an admin in your app. Maybe your user doesn't have access to the app. Maybe there's some other pieces of it that are specific to the way you built built out the App you can actually display errors in. They don't give you a generic type of error pane to go use, but you can go ahead and hack or tap into. Hack's the wrong word. You can tap into this view possibility here. So you can remove all the card options or the card properties altogether and then just use these buttons and have the button be labeled with something like please re authenticate. And when they click it, maybe it links out to your app to a specific page or maybe please configure your settings, something like that. You can also put a message right within here that says something like for example, you might say error with your app or please see admin. And then you could put a red status message just to catch your attention and then some way to go resolve it for them. So something like please contact your admin to give you permissions to use this or something to that effect. So this is. You could use this to your advantage and don't be afraid to go do that. That would be an error case. They don't give you other places to go do it. So you can't do an alert generally in the background. But just so you know, that's how to handle errors. Undocumented things. I kind of hinted at this. The documentation right now at least the legacy docs which they're currently pointing to, which they'll likely will be updating sometime in the near future, says five valid card properties. I went back and forth with the team on this. That used to be the case. It used to be if you returned more than five results, they would just show the first five and then give you an ability to link out to the more. So you could say, yeah, there's 25 more over here, 25 tickets, for example. In recent times they've actually lifted that restriction. So now you can have as many card properties that we could test. The team over there at HubSpot actually tested and got to a few thousand before they started seeing real performance issues. So that's something just to keep in mind here that you can have a full long list of items for the user. If you're trying to be concerned about how many you let them see, you can still take advantage of. There's a property you can pass in called all items Link. So you can limit the array of results if you'd like to the first five, for example, and then tell give them a link to go look at all items. The other thing that's important here and the other reason you might want to do that, instead of returning everything that's related or retrieved is that requests fail within five seconds. So they have to respond to the timeout within five seconds. You have to respond within five seconds. So if you can't get all the results within that amount of time, just return the first five, 10, whatever it is and then tell them here's a link to go look at all the other ones. There's no lazy load option, there's no click to load more pagination options. So those kind of go hand in hand where you could theoretically return everything, but only if it takes five seconds or less. All right, those are the two main things that we've run into on the undocumented side. Local development noted already. The oauth settings allow for local host, which is great for the redirect because you can go test your oauth flow entirely. However, the card does not allow you to do local host. So how do you go resolve this? What we've done internally at Left Hook is use ngrok to do tunneling. Ngrok, if you haven't heard of it, it's basically a number of different things, but they do tunneling. It was an open source project. I think they've started to monetize it. So you can. They have a whole free tier. You can even just install it yourself. But we typically just sign up for an account and go use it. Really easy to get started. Takes all of five minutes to go download, run inside your terminal and then you point it to whatever, whatever port you want. I have it listening to 3001 locally because one of my projects is running in that running express server there. Serverless, offline. But same same idea. It'll generate the URL, copy and paste that URL right into the HubSpot fetch for the or the HubSpot CRM card fetch URL. So local dev example you see here, that's my route that I've been pointing to and that's my ngrok tunnel. Now whenever I load this, I have here the local example. So that's full example and then local dev example. So let me go do a quick tweak. Whatever, whatever you use for local dev you can go ahead and modify. I use webstorm. I'll go ahead and change this to you say goodbye. I say hello and save this depending if you have live load, that's great. I don't at the moment have that configured. So I'll go reset my local dev environment, go back to the URL, reload ngrok facilitates the request, sends it back and voila. So I can see my Changes live this is really good and really helpful when you're starting to pipe your extension into live data sources. So obviously before we were working with a mock starting to pipe in live data, make sure that a your logic is good internally, but b any sort of data that's coming back from your external sources are configured correctly. Okay? That's how you can trust it's going to work when you push it to whatever environment you're pushing to. So our last tips and tricks talk about hybrid and advanced. Hybrid is pretty much as how it sounds. CRM extension is going to display data and never save any data on the record inside HubSpot. So what we've done in the past or for other folks is we've created custom properties. So every time a page loads and retrieves makes that fetch request against our service, we'll actually use that as an event and we'll think, okay, I need to go update a custom property or I need to go save additional data here. So you can get a lot more usage out of it, especially if you start saving custom data and custom properties on the object and that becomes a that lets you to get to some more sophisticated use cases. So I call that a hybrid approach because it uses not only the CRM extension, just normal let me go review things, but also ties it into internal API requests against HubSpot and Custom Properties. The advanced is something similar. You can think about it, but I typically think of advanced CRM extensions as ones that use iframes. Also try to facilitate people through a full process and maybe thinks of the CRM extension as more of a bolt on full app for experience for the HubSpot user. So iFrames really are a key to that as well as things like managing state. Really important to note is that if someone, anyone can at any time x out on one of those iframes, you can't stop them from doing it. So you have to make sure you're saving your state whenever you're doing that, or that it's stateless. It doesn't really matter what they're doing. The other thing about advanced CRM extensions that HubSpot's regularly releasing new features and more hooks within the platform. So that's something to keep an eye out for because that's going to likely result in some more sophisticated either specific hooks for your type of app, whatever you're building, or potentially just more surface area for you to get kind of ingrained inside the HubSpot experience. Okay, that was a lot. Let's recap. CRM extensions powerful. They can do a lot of great things for your end user to make their experience a delight. Documentation can be confusing depending on what you're looking at, depending what you're looking for. Hopefully things will get updated. Make sure you just give them feedback and they'll get it updated and they're really happy to answer questions. I am also available at certain times to go answer questions as well, but hopefully this helps to clarify some of the concepts as well. The easiest way to get started is mocking really good for stakeholder agreement. Make sure you show people what's going to look like before you go build it. Consider the local development with Angrock tunneling instead of doing a push to whatever service, refreshing, looking how it, seeing how it looks, coming back, tweaking it, going, pushing again. That really can speed up your dev time considerably. Consider their advanced or hybrid extensions. So once you get past the basic idea of just displaying related data, start getting more interactive and start to explore that and then what I'm really looking forward to is when that starts to combine with the newly announced custom objects and then any other things that they've started to add other hooks into the platform. It's going to get really interesting to see what people build with their CRM extensions. Okay, I'm around, happy to answer questions. Also invite around I mean inside the Slack team and also for some Q and A. And if you have any questions with HubSpot team, they're always responsive and we'll get you a thorough answer on either side, either the Slack team or inside email. This is a quick about me, but you can have access to a lot of this stuff on our website and we'll make sure we post things afterwards. Thanks very much and hopefully this was helpful for you. If not, well, tell me what I can do to make it better. Otherwise hit us up. Let us know what we can do to improve it in the future. Have a great day. Bye.










