How to Build a HubSpot App That Displays Data in CRM Cards

In this tutorial, we explore how to build a HubSpot app that leverages CRM cards to display data from external systems directly within HubSpot's contact records. You'll learn how to set up your app using Ngrok for local development, configure routes in Node.js, and create a custom CRM card that showcases a random fact, This guide is perfect for anyone looking to enhance their HubSpot app with dynamic, integrated content.

How to Build a HubSpot App That Displays Data in CRM Cards
Do not index
Do not index
You may have encountered CRM cards if you’re building a HubSpot app. These cards display information from other systems within HubSpot's contact, company, deal, and ticket records, making it easy to show relevant data alongside HubSpot. This tutorial builds on the OAuth starter guide, so start there, and then we’ll modify it to enable custom cards in your HubSpot app.

What We’ll Build

For this demo, we’ll create a CRM card that displays a random fact when a user opens it, illustrating how to work with CRM cards and call an external API to share data.
 
 
notion image
 

1. Set Up Your OAuth Starter to Work with Ngrok

To create a custom CRM card in HubSpot, your app must be accessible online. Ngrok can expose your local server to the web.
Install Ngrok from the Ngrok website, then start it with:
ngrok http 3000
This creates a tunnel to your localhost on port 3000, giving you a public URL like https://<your-ngrok-id>.ngrok-free.app.
 
Update REDIRECT_URI in index.js with the Ngrok URL, keeping the OAuth callback at the end.

const REDIRECT_URI = `https://<your-ngrok-id>.ngrok-free.app/oauth-callback`;
 
Then, in the HubSpot dashboard, update any local URLs with the Ngrok URL to ensure HubSpot can communicate with your server.
 
notion image
 

2. Add a Route for CRM Cards

The next step is to set up a route in your Node.js app to handle requests from HubSpot and return data for the CRM card.
Add this route in your index.js file to fetch random facts and display them on the CRM card:

const cache = new NodeCache({ stdTTL: 600 }); // Initialize a cache with a TTL of 600 seconds (10 minutes)

app.get("/crmcard", async (req, res) => {
  res.setHeader("Content-Type", "application/json"); // Ensure the response is in JSON format
  console.log(req.query); // Log the query parameters sent by HubSpot for debugging

  // Destructure the important parameters from the query string
  const { associatedObjectId, firstname } = req.query;

  try {
    // Fetch a random fact using a helper function (more on this below)
    const randomFact = await fetchRandomFact();

    // Construct the data structure that HubSpot will display on the CRM card
    const cardData = {
      results: [
        {
          objectId: associatedObjectId || "123", // Use the ID from HubSpot or a default value
          title: `Random Fact for ${firstname}`, // Dynamic title based on the contact's first name
          randomFact: randomFact || "No fact available", // Display the fetched fact or a fallback message
          link: "https://www.randomfunfacts.com/", // A link to more fun facts
        },
      ],
    };

    // Send the response back to HubSpot
    res.status(200).json(cardData);
  } catch (error) {
    console.error("Error handling data fetch:", error); // Log any errors that occur
    res.status(500).json({ error: "Failed to fetch data for CRM card." }); // Respond with an error message if something goes wrong
  }
});

 
And you will also need a helper function that fetches a random fact:
async function fetchRandomFact() {
  const cachedFact = cache.get("randomFact"); // Check if the fact is already cached
  if (cachedFact) {
    return cachedFact; // Return the cached fact if it exists
  }

  try {
    // Make a request to a public API to get a random fact
    const response = await request({
      uri: "https://uselessfacts.jsph.pl/random.json?language=en",
      json: true,
    });
    const fact = response.text; // Extract the fact from the API response
    cache.set("randomFact", fact); // Cache the fact for future use
    return fact;
  } catch (error) {
    console.error("Failed to fetch random fact:", error); // Log any errors that occur
    return null; // Return null if the API call fails
  }
}

 
 

3. Enable CRM Cards in the HubSpot Developer Dashboard

Next, enable CRM cards in your app.
Log in to your HubSpot developer account, go to your app's settings, and navigate to the "CRM cards" section. Here, you can create CRM cards.
notion image
Update the data fetch URL to your Ngrok URL with /crm-cards at the end.
 
Toggle on the "Appears on this type" option for Contacts and choose firstname, lastname, and email for properties that sent from HubSpot.
 
Finally, set up the card properties to display the randomFact we return on the card.
notion image
 

4. Try it out

Now, let's try it out.
 
Run node index.js to start your server, then open your HubSpot developer demo account. Since this is built on the OAuth starter, the install flow will launch, allowing you to install the app if you haven’t already.
Once your server is running, navigate to the account where you installed the app. Go to "Contacts," open a contact record, and you should see the random fact displayed on the CRM card. If this is your first time running the card, it might appear at the bottom of the list, so scroll down to find it.
Here’s a demo video to give you a preview of what it looks like in action.
 
 
 
 
And there you have it! You’ve successfully created a custom CRM card in HubSpot. For a real deployment, you’d use a live URL, but this short tutorial highlights the high level process.

We build third-party apps and integrations

Partner with us →

Written by

Lola
Lola

Lola is the founder of Lunch Pail Labs. She enjoys discussing product, SaaS integrations, and running a business. Feel free to connect with her on Twitter or LinkedIn.