Create UI extensions with React (BETA)

  • Sales Hub
    • Enterprise
  • Service Hub
    • Enterprise

After creating a project and a private app within it, you can create a UI extension to customize your HubSpot CRM UI. In this guide, you'll learn how UI extensions work and how to build and customize them.

You can also follow the quickstart guide to build and deploy an example UI extension to your account. Or, check out HubSpot's other sample projects.


  • This guide assumes that you're familiar with the general steps to set up your local environment with the CLI and configure a development sandbox, if needed. If you haven't done so, check out the projects setup guide before proceeding.
  • Before getting started, check out the UI extensions overview for general information, best practices, and limitations.
  • You should also ensure you've updated to the latest version of the CLI by running npm install -g @hubspot/cli@next.
  • This guide assumes that you've already created a project and a private app within it.

Set up extension files

Below, learn more about each file along with example code. You can also follow the quickstart guide to create these files from an example project, or view HubSpot's other sample projects.

Within the /src/app directory, create an /extensions directory with the following files:

  • example-card.json: the custom card configuration.
  • Example.jsx: the React file that serves as the front end. You can learn more about how to customize your UI extension front end in the UI extension SDK reference below.
  • package.json: metadata about the extension's front end. This file is required and can be used to include dependencies for your React front end.

In the extensions directory, you'll also need to install the HubSpot UI extensions npm package by running npm i @hubspot/ui-extensions.


type   string

The type of extension. Must be crm-card.

data   object

Custom card metadata, including:

  • title (string): the name of the custom card.
  • location (string): where the card appears on the CRM record.
    • "": places the card on Custom tab of the middle pane.
    • "crm.record.sidebar": places the card in the right sidebar.
  • uid (string): the extension's unique identifier. This can be any string, but should meaningfully identify the extension. HubSpot will identify the extension by this ID so that you can change the extension's title without removing historical or stateful data, such as the card's position on the CRM record.
  • module (object): where the custom card font end React code lives.
objectTypes   array

Defines which types of CRM object records the extension will appear on. This will also enable you to pull data from those records when using the fetchCrmObjectProperties method. Learn more about compatible objects.

// Example extension config file { "type": "crm-card", "data": { "title": "Example Card", "location": "", "uid": "unique-extension-name", "module": { "file": "Example.jsx" }, "objectTypes": [{ "name": "contacts" }] } }


// Example React front end import React, { useState } from 'react'; import { Button, Text, Input, Stack, hubspot, } from '@hubspot/ui-extensions'; hubspot.extend(({ context, runServerlessFunction, actions }) => ( <Extension context={context} runServerless={runServerlessFunction} sendAlert={actions.addAlert} /> )); const Extension = ({ context, runServerless, sendAlert }) => { const [text, setText] = useState(''); const run = () => { runServerless({ name: 'myFunc', parameters: { text: text } }).then((resp) => sendAlert({ message: resp.response }) ); }; return ( <> <Text> <Text format={{ fontWeight: 'bold' }}> Your first UI Extension is ready! </Text> Congratulations {context.user.firstName}! You just deployed your first HubSpot UI extension. This example demonstrates how you would send parameters from your React frontned to the serverless function and get response back. </Text> <Stack> <Input name="text" label="Send to serverless" onInput={(t) => setText(t)} /> <Button type="submit" onClick={run}> Click me </Button> </Stack> </> ); };


// Example package.json { "name": "example-extension", "version": "0.1.0", "description": "", "license": "MIT", "main": "Example.jsx", "scripts": { "dev": "hs-ui-extensions-dev-server dev", "build": "hs-ui-extensions-dev-server build" }, "repository": { "type": "git", "url": "" }, "dependencies": { "@hubspot/ui-extensions": "latest", "react": "^18.2.0" } }

Compatible objects

You can create extensions for both standard object and custom object records. In the card's JSON configuration file, you'll define this within the objectTypes array. 

When building an extension for custom objects, you'll reference the object as p_objectName (case sensitive). To get this value, make a GET request to the custom object schema API, then look for the fullyQualifiedName in the response. Take the fullyQualifiedName, then remove the HubID number, and use the resulting value for the configuration file.

For example, for a custom object with the fullyQualifiedName of p123456_Cats, the correct value to use for the configuration file would be p_Cats.

// example card.json "objectTypes": [ { "name": "p_Cats" } ]

Start local development

With your project files created locally, you can now use hs project dev to upload the files to HubSpot and start a local development server to view the custom card. Changes you make to your extension files, along with changes to any serverless function files, will 

hs project dev
  • After running hs project dev, select the account you want to work in:
    • To create your extension in an existing sandbox, use the arrow keys to select the sandbox, then press Enter.
    • To create and test your extension in a new development sandbox, select < Test on a new development sandbox >. Then, name the sandbox and press Enter. HubSpot will then create the new development sandbox in the production account. This sandbox will sync with the production account's data, including CRM object definitions and up to 100 of the most recently created contacts and their associated deals, tickets, and companies (up to 100 each).
    • To create and test your extension in the production account, select < ! Test on this production account ! >.
Please note: when creating a new development sandbox, if you receive the error The personal access key you provided doesn't include sandbox permissions, you'll need to deactivate the account's Personal Access Key, then create a new one with sandbox permissions. To do so, run hs auth, then follow the prompts to select your account. Then, click Deactivate next to the personal access key, and generate a new one with the proper scopes.

Once the project is created, built, and deployed in the selected account, the local development server will start and you can begin building and modifying your extension. 

  • The browser will automatically refresh to pick up the latest saved front end code (updates made to the React files).
  • Changes made to configuration files, such as app.json and hsproject.json, require a manual upload before you can continue development. To upload those changes, first stop the local development server with q, then run hs project upload. After your changes are uploaded, run hs project dev again to restart the server.

With the local development server running, you can view the extension from a contact record in HubSpot (as configured by objectTypes in the example-card.json file):

  • Log in to your HubSpot account.
  • In your HubSpot account, navigate to ContactsContacts. Then, click the name of a contact to view its record.
  • Click the Custom tab at the top of the contact record.


Add UI extension functionality

Registering the extension

UI extensions, like any React front-end, are written as React components. However, unlike usual react components, you must register them with HubSpot by including hubspot.extend() inside the component file instead of exporting them. This is not required when you create a sub-component. For better code, reuse and include them inside your extension.

The hubspot.extend() function receives the following arguments:

  • context: the context object that includes information about the account and user.
  • runServerlessFunction: the serverless functions to run in the component.
  • actions: the actions to include in the component.

If you followed the steps above to create your UI extension from scratch, you'll have already copied the code below into your React front end Example.jsx file. 

hubspot.extend(({ context, runServerlessFunction, actions }) => ( <HelloWorld context={context} runServerless={runServerlessFunction} onAlertClick={actions.addAlert} /> ));


View the full API for the runServerlessFunction call below. Note that propertiesToSend in the call is another way to fetch CRM properties on the serverless function side, rather than using fetchCromObjectProperties on the React client side. Making this call on the server side will fetch property data when the serverless function is called, versus only when the extension loads. Learn more about fetching CRM data.

runServerlessFunction(params: ServerlessRunnerParams): Promise<ServerlessExecutionResult> type ServerlessRunnerParams = { /** * Name of the serverless function that is configured as a key for the function in serverless.json file. */ name: string; /** * Names of CRM object properties to be retrieved and supplied to * the function as `context.propertiesToSend` (Optional) */ propertiesToSend?: string[]; /** * Additional parameters to be supplied to the function as * `context.parameters` (Optional) */ parameters?: JsonValue; } Response body type ServerlessExecutionResult = | { status: 'SUCCESS'; response: JsonValue; } | { status: 'ERROR'; message: string; };
Please note: the status returned by runServerlessFunction is automatically determined by HubSpot and cannot be set manually using sendResponse(). If the serverless function runs successfully, it will automatically return a status of SUCCESS. To trigger an error status, you'll need to build it into your serverless function.

Display alert banners

Use the addAlert method to send alert banners as a feedback for any actions to indicate success or failure. addAlert is a part of the actions object that can be passed to extension via hubspot.extend.

Use the addAlert method to send alert banners as a feedback for any actions to indicate success or failure. addAlert is a part of the actions object that can be passed to extension via hubspot.extend.

The AddAlert method accepts the following props:

  • message: The alert text.
  • type: the alert color variant. Can be one of:
    • info: a blue alert to provide general information.
    • success: a green alert indicating a positive outcome.
    • warning: a yellow alert indicating caution.
    • danger: a red alert indicating a negative outcome.
    • tip: a white alert to provide guidance.



// Snippet from app/extensions/HelloWorld.jsx hubspot.extend(({ context, runServerlessFunction, actions }) => ( <HelloWorld context={context} runServerless={runServerlessFunction} onAlertClick={actions.addAlert} /> )); // the following method executes a serverless function // and displays an alert based on the response const HelloWorld = ({ context, runServerless, onAlertClick }) => { const [inputValue, setInputValue] = useState(""); const executeServerless = async () => { const serverlessResult = await runServerless({ name: "get-data", parameters: { inputValue }, }); if (serverlessResult.status === 'SUCCESS') { onAlertClick({message: serverlessResult.response.alertMessage, type: "success"}); } else { console.error("Error executing serverless: ", serverlessResult.message); } }; return ( // components );

In line 21 above, the alertMessage value can be passed by the serverless function by including it in sendResponse.

// Snippet from app/app.functions/get-data.js */ export.main = async (context = {}, callback) => { const message = context.parameters['inputValue']; callback({alertMessage: `You typed "${message}".`}); }

Fetch CRM property data

There are two ways to fetch CRM property data:

  • fetchCrmObjectProperties, which can be included in your React files to fetch property data client side at extension load time.
  • propertiesToSend, which can be included in your serverless functions to fetch property data on the back end at function invocation time.


Using the fetchCrmObjectProperties method, you can get property values from the currently displaying CRM record without having to use HubSpot's APIs. This method is a part of the actions object that can be passed to the extension via hubspot.extend. You'll first need to add the object to objectTypes inside the card's .json config file. The objects you specify in objectTypes will also set which CRM objects will display the extension.

hubspot.extend(({ actions }) => ( <HelloWorld fetchProperties={actions.fetchCrmObjectProperties} /> )); const HelloWorld = ({ runServerless, fetchProperties }) => { const [firstName, setFirstName] = useState(""); const [lastName, setLastName] = useState(""); useEffect(() => { fetchProperties(["firstname", "lastname"]) .then(properties => { setFirstName(properties.firstname); setLastName(properties.lastname); }); }, [fetchProperties]); return ( <Text>Hello {firstName} {lastName}</Text> ); }

The response for fetchCrmObjectProperties is formatted as:

// example fetchCrmObjectProperties response { "property1Name": "property1Value", "property2Name": "property2Value" }

Open an iframe in a modal

Similar to addAlert and fetchCrmObjectProperties, you can pass openIframeModal to the extension through the actions object.

hubspot.extend(({ context, runServerlessFunction, actions }) => ( <HelloWorld context={context} runServerless={runServerlessFunction} openIframe={actions.openIframeModal} /> ));

openIframeModal takes the following payload:

interface OpenIframeActionPayload { uri: string; height: number; width: number; associatedObjectProperties?: string[]; }

Fetch account and user information

The context object, which is passed to the extension component via hubspot.extend, contains information related to the authenticated user and HubSpot account. The following is an example of the information you can retrieve via context.

context { user: UserContext, portal: PortalContext } UserContext { id: number, //Logged-in user's id email: string; //primary email emails: string[]; //all associated emails firstName: string; lastName: string; roles: string[]; //role names teams: Team[ { id: number; name: string; teammates: number[]; //id of team mates ]; locale: string; } PortalContext { id: number; timezone: string; }

Was this article helpful?
This form is used for documentation feedback only. Learn how to get help with HubSpot.