A HubSpot app card is a React component built with HubSpot's UI extensions SDK. It shows your product's data and actions inside a customer's HubSpot, on CRM records, in the preview panel or in the help desk sidebar. For a SaaS company the build is the small part. The decisions that matter are how the card reaches your backend for each customer account, what HubSpot's Marketplace review checks in it, and whether you still have a legacy CRM card to migrate before it disappears in November 2026.
An app card ships inside your HubSpot app, so every customer who installs the app gets it, and you maintain it across their accounts after launch. Appnigma's marketplace apps service covers this case. It delivers "a native app, marketplace listing preparation, and review support" for HubSpot and Salesforce. If you are an admin adding a card someone else built, HubSpot's guide to installing and managing app cards is the page you want.
Updated October 2026. Checked against HubSpot's UI extensions, app card and data fetching documentation and the Marketplace listing requirements. This update corrects where cards can appear and replaces the serverless pattern with hubspot.fetch() and request signing. It also updates the project structure, adds the legacy CRM card sunset dates, and removes an example and engagement figures that had no source.
Where an app card can appear and who places it
An app card renders in one of four CRM surfaces, and the customer's admins decide which. HubSpot's app cards reference says that when the location field is omitted, the card "is eligible to appear on every surface that's compatible with its card type and objectTypes." Admins then place it with the page editor. Per the app cards overview, that means Super Admins and users with Customize record page layout permission.
| Location value | Where it shows | Worth knowing |
|---|---|---|
| crm.record.tab | Middle column of record pages, in default or custom tabs | Also the target accounts preview panel in the sales workspace for companies |
| crm.record.sidebar | Right sidebar of record pages | Can't use CRM data components, and also the deals sidebar in the sales workspace |
| crm.preview | The record preview panel across the CRM | Includes previews opened from index pages, board views and lists |
| helpdesk.sidebar | Ticket sidebars in help desk | Needs the tickets scope and tickets in objectTypes |
Two more UI extension types sit next to cards. An app home page is "a full-screen extension, ideal for dashboards," and a settings page holds your configuration UI inside HubSpot's settings. Neither is a card on the HubSpot Home dashboard or the configuration panel of a workflow action. Older guides, including an earlier version of this page, described both as app card surfaces.
Object coverage is wider than most guides list. Beyond the four core objects, the reference supports commerce objects such as orders, invoices, quotes and subscriptions. Leads, custom objects and app objects are supported too. Appointments, courses, listings, services and projects work once activated in the data model builder. Each object needs its read scope, such as crm.objects.deals.read. A card that targets an object your app has no scope for will not show there.
How the card gets your product's data
The card can't call your API the way your own front end does. HubSpot's UI extensions overview says it loads each extension "in a secure, sandboxed environment," and the card renders HubSpot's components rather than your own HTML. HubSpot's data fetching guide lists the options. CRM data hooks such as useCrmProperties and useAssociations read the current record. hubspot.serverless() runs JavaScript on HubSpot's infrastructure. hubspot.fetch() sends requests "to a backend that you manage, or to a public endpoint."
For a Marketplace app, the choice between the last two is made for you. The serverless functions reference says "only privately distributed apps with static auth are currently supported" and that apps using OAuth "cannot use 2026.03 serverless functions." A Marketplace app must use OAuth, so its cards call your own backend with hubspot.fetch(). Serverless functions fit internal tools in a single account, and even there the overview says "an Enterprise subscription is required to install an app with serverless functions." Cards themselves carry no tier condition. HubSpot's app card guide for admins lists them for all products and plans.
hubspot.fetch() looks like the browser's fetch, but it runs through a HubSpot service with its own rules. Every request URL must match a prefix in the permittedUrls.fetch array of your app's configuration, wildcards aren't supported, and anything else fails with a 403. The guide's limits section sets a 15 second default timeout, configurable "up to 120 seconds," a 1 MB cap on request and response payloads, and "up to 20 concurrent requests per account" for each app. Only the Authorization request header passes through, and your response headers never reach the card.
import React, {
useEffect, useState
}
from "react";
import {
hubspot, Text, LoadingSpinner, ErrorState
}
from "@hubspot/ui-extensions";
hubspot.extend(({
context
}) => <AccountHealth context={
context
}
/>);
const AccountHealth = ({
context
}) => {
const [state, setState] = useState({
loading: true
});
useEffect(() => {
hubspot
.fetch(`https://api.yourapp.com/hubspot/health/${context.crm.objectId}`, {
timeout: 10000
})
.then((res) => (res.ok ? res.json() : Promise.reject(res.status)))
.then((data) => setState({
data
}))
.catch(() => setState({
error: true
}));
}, [context.crm.objectId]);
if (state.loading) return <LoadingSpinner label="Loading account health" />;
if (state.error) return <ErrorState title="Account health is unavailable right now" />;
return <Text>Health score {
state.data.score
}
of 100</Text>;
};The backend side is where the security lives. HubSpot appends userId, portalId, userEmail and appId as query parameters. The URL is part of the signed string, so they can be trusted once the signature checks out. The request validation guide describes the X-HubSpot-Signature-v3 header, an HMAC SHA-256 of the method, URI, body and timestamp using your app's client secret. It also says to "reject the request if the timestamp is older than 5 minutes."
import crypto from "node:crypto";
export function isFromHubSpot(req, clientSecret) {
const timestamp = req.get("X-HubSpot-Request-Timestamp");
if (!timestamp || Date.now() - Number(timestamp) > 5 * 60 * 1000) return false;
const uri = `https://${req.get("host")}${req.originalUrl}`;
// decode the characters HubSpot's guide lists before hashing
const body = req.rawBody ? req.rawBody.toString("utf8") : "";
const expected = crypto
.createHmac("sha256", clientSecret)
.update(`${req.method}${uri}${body}${timestamp}`, "utf8")
.digest("base64");
const given = req.get("X-HubSpot-Signature-v3") || "";
return given.length === expected.length && crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected));
}Once the request is verified, portalId tells your backend which customer account is asking. Look up that account's stored OAuth tokens on your side and call HubSpot's APIs from there if the card needs more than the hooks return. No token ever reaches the card's code. HubSpot's guide also warns against storing secrets in the React code.
What the project looks like on the developer platform
App cards live in a developer platform project, the file-based format HubSpot uses for new apps. The reference puts each card in src/app/cards/ as a *-hsmeta.json configuration file plus a .jsx or .tsx component, with one shared package.json. The app's own settings, including scopes and permittedUrls, sit in src/app/app-hsmeta.json.
{
"uid": "account-health-card",
"type": "card",
"config": {
"name": "Account health",
"description": "Health score and renewal risk from YourApp.",
"entrypoint": "/app/cards/AccountHealth.jsx",
"objectTypes": ["companies", "deals"]
}
}Keep the uid stable. HubSpot identifies the card by it, so you "can change the card's title without removing historical or stateful data, such as the position on the CRM record." Changing the uid after customers have placed the card makes it a new card.
The commands follow HubSpot's UI extensions overview. hs project add adds a card to an existing project, hs project upload deploys it, and hs project dev starts a local server with hot reload. The SDK ships testing utilities and an ESLint configuration that "catches common issues like using unavailable browser APIs," and deployed cards have logs for renders and hubspot.fetch() requests. Our guide to building a HubSpot app covers the project setup, OAuth and webhooks around the card.
What Marketplace review checks in an app card
App cards get their own review when the app is listed. The app cards overview says cards in a Marketplace app "are subject to a technical review from the HubSpot Ecosystem Quality team." The listing requirements then add rules that apply only to apps with cards.
The naming rules catch the most teams. A card name can't use HubSpot's names or a generic product name with them, so "App card for HubSpot" fails. The card can't use the HubSpot logo or any brand logo other than your own as its icon. Forms need submit buttons, destructive actions need the destructive button style, and each card, modal or panel gets "only one primary button."
The sensitive data rule is the one that can change your architecture. A listed app with cards "must not access, request, or use sensitive data scopes," and the card must not display sensitive information. The app cards overview adds that apps with Sensitive Data scopes can't use hubspot.fetch() or serverless functions. If your product needs those scopes, HubSpot says you'll "need to use separate apps for each capability." For health, finance or identity data, settle that split before the card is built.
Images and scripts should be bundled with relative links rather than absolute URLs, with exceptions only for "a reputable CDN." The initial listing review takes up to 10 business days, so a card that fails these rules costs a review cycle. Our HubSpot app certification guide covers what certification adds after listing.
Migrating legacy CRM cards to app cards
If your app still has a classic CRM card, the older static sidebar card, it has days left, not months. HubSpot's migration guide says legacy CRM cards "will be sunset on November 2, 2026," and after that date they are removed from customer views. The original deprecation changelog gave October 31, 2026. The migration UI and migrate API keep running "until December 1, 2026," then they are discontinued.
The migration has two parts. First you build a replacement app card, either from scratch or from HubSpot's Legacy CRM Card Converter sample, which renders tiles and action buttons from your existing endpoint. The app must be on the projects framework at version 2025.2 or newer. Then you swap the cards in customer views, from the project's Review and migrate banner or the migrate views API. Customers don't need to do anything, and the app card takes the legacy card's place in their views.
Three details in the guide catch teams out. The swap "cannot be reversed," so test the new card with a subset of users first through feature flags. A legacy card that supports tickets needs two app cards, one for crm.record.sidebar and one for helpdesk.sidebar, with different titles. Listed apps get the hs-release-app-cards flag during migration, which keeps the new card hidden until you release it.
Is an app card worth building for your app?
An app card earns its place when a rep or agent would otherwise leave HubSpot to look something up in your product. Account health, billing status, usage, call summaries and open support issues are the usual examples. The card puts that answer on the record the rep already has open. A card with an action, such as creating something in your product from the deal, does more than one that only displays data, because it replaces a tab switch with a click.
A card adds little when your integration only syncs data in the background and nobody needs to see it in HubSpot. It also adds little when your customers' admins are unlikely to place it, since a card exists only where someone with page layout permission adds it. A sync-only first version is fine, as long as the app's scopes and backend are designed so a card can be added later without a new install.
A card also counts toward listing. The listing requirements define app activity to include signed requests from extensions such as app cards, and active installs are accounts "showing successful app activity within the past 30 days." A card reps open every day keeps an install active even when your sync is quiet.
The cost to plan for is maintenance, not the first build. Each card depends on your backend staying fast under HubSpot's timeout and on the SDK version you built against. Every customer account sees the same card, so a slow endpoint shows up as a broken card on every installed account at once. Teams coming from Salesforce will recognize the pattern from Lightning Web Components in a managed package. The difference is that HubSpot hosts the card's code and your data travels over hubspot.fetch() to your own backend.
Mistakes that break app cards after install
These are failure modes that show up after customers install the app, each tied to a documented limit.
- New cards that never appear. If an account has customized a record's middle column, the reference says admins must customize the middle column view before newly created extensions are visible. Tell customers in your install guide.
- Running out of concurrent requests. The 20 request limit is per app, per customer account, not per user. As worked arithmetic, a card that makes three
hubspot.fetch()calls on render hits the limit when seven reps in one account open records at the same moment, since 7 × 3 = 21. Requests over the limit are rejected with a 429, so batch into one call per render. - Responses that grow past 1 MB. A card that returns a full activity history works in a test account and fails on a customer's busiest record. Page the data and send only what the card shows.
- Trusting `portalId` without the signature. Anyone can call a public endpoint with a forged query string. Verify the v3 signature first, then use the metadata.
- CRM data components in the sidebar. Sidebar cards can't use them, so a card built in the middle column can fail when moved to
crm.record.sidebar. - A missing `permittedUrls` entry for a new environment. A new API host or path outside the listed prefixes returns 403 until the app config is uploaded again.
Frequently Asked Questions
What is a HubSpot app card?
An app card is a UI extension that renders a React component on CRM records, in the preview panel or in the help desk sidebar. It ships inside a HubSpot app and is placed by the customer's admins.
Can a HubSpot app card call my own API?
Yes, through hubspot.fetch(), to URLs listed in your app's permittedUrls. Requests are signed with your client secret and carry portalId and userId, with a 1 MB payload cap and a 15 second default timeout.
Do app cards need serverless functions?
No. Serverless functions currently work only in privately distributed apps with static auth, and installing one needs an Enterprise subscription. Marketplace apps use OAuth, so their cards call your backend with hubspot.fetch().
Are app cards required to list on the HubSpot Marketplace?
No. But if your app includes cards, they go through a technical review against extra listing rules on naming, logos, buttons and sensitive data.
When do legacy HubSpot CRM cards stop working?
HubSpot's migration guide says legacy CRM cards are removed from customer views on November 2, 2026. The migration UI and API stay available until December 1, 2026.
Can Appnigma build our HubSpot app card?
Yes. Appnigma's marketplace apps service builds the native app, including its cards, and provides "HubSpot listing review and certification support where eligible."
About the author
Sunny Chauhan is the co-founder and CEO of Appnigma AI, which builds and maintains customer integrations and native Salesforce and HubSpot apps for B2B SaaS teams. Before Appnigma, he was on Salesforce's AppExchange team, where he worked with more than 300 SaaS partners. He has spent close to a decade on the Salesforce platform, including work at Zennify, and holds Salesforce certifications including Platform Developer II, Platform App Builder, Administrator, Data Cloud Consultant, and AI Associate.
Related Articles
- How to Build a HubSpot App Your Customers Can Install
- HubSpot App Certification Requirements
- HubSpot OAuth and Scopes Reference
- HubSpot Webhooks Implementation Guide
- HubSpot Custom Integration Build Guide
Sources
- HubSpot Developers, UI extensions overview
- HubSpot Developers, App cards overview
- HubSpot Developers, App cards reference
- HubSpot Developers, Fetching data for UI extensions
- HubSpot Developers, Serverless functions overview
- HubSpot Developers, Serverless functions reference
- HubSpot Developers, Validating requests from HubSpot
- HubSpot Developers, App Marketplace listing requirements
- HubSpot Developers, Migrate a legacy CRM card to an app card
- HubSpot Developer Changelog, Deprecating support for classic CRM cards
- HubSpot Knowledge Base, Install and manage app cards


