Use this file to discover all available pages before exploring further.
Actions are functions provided by the UI extensions SDK that your extension can call to interact with HubSpot. Calling an action triggers behavior in HubSpot, such as displaying an alert banner, reloading the page, or opening a modal.You can access actions using either a props-based approach or a hook-based approach:
Props-based approach: destructure actions from the callback passed to hubspot.extend(), then pass it to your component as a prop.
Hook-based approach: call the useExtensionActions hook directly within your component.
The actions available depend on where your extension is loaded. Universal actions work in all extension points. CRM property actions are only available in CRM record extension points.
Note that some UI components include a set of actions separate from the SDK actions below, such as the CRM action components.
Both approaches to access actions provide identical functionality, so the choice is a matter of preference. As a general guideline:
Hook-based approach: cleaner component APIs, no prop drilling.
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. If you instead want to render an alert within a card, check out the Alert component.For example, the code below results in an app card that displays a success alert after fetching data from an external source. Note that the addAlert action is passed into hubspot.extend() and the Extension component, then is triggered when the hubspot.fetch() function successfully executes.
import React, { useState } from "react";import { Text, Button, LoadingSpinner } from "@hubspot/ui-extensions";import { hubspot } from "@hubspot/ui-extensions";hubspot.extend(({actions}) => <Extension addAlert={actions.addAlert}/>);const Extension = ({ addAlert }) => { const [totalUsers, setTotalUsers] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const fetchUserData = async () => { try { setLoading(true); setError(null); const response = await hubspot.fetch('https://myExternalData.com/api/data'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const result = await response.json(); setTotalUsers(result.stats.totalUsers); // Show success alert banner addAlert({ title: "Data fetched successfully", message: `Retrieved total users from API`, type: "success" }); } catch (err) { setError(err.message); console.error('Error fetching data:', err); // Show error alert banner addAlert({ title: "Data Fetch Failed", message: `Failed to retrieve data: ${err.message}`, type: "danger" }); } finally { setLoading(false); } }; if (loading) { return ( <> <LoadingSpinner /> <Text>Fetching user data...</Text> </> ); } return ( <> <Text> Click the button to fetch the total number of registered users from the API. </Text> <Button onClick={fetchUserData} variant="primary"> Fetch user data </Button> {totalUsers !== null && ( <Text format={{ fontWeight: "demibold" }}> Number of users: {totalUsers} </Text> )}</> );};
Use the copyTextToClipboard action to copy text to your clipboard. This action can be accessed through the actions argument (actions.copyTextToClipboard) and returns a promise that resolves once the system clipboard has been updated. Its functionality is provided by the Clipboard: writeText() method and follows the same requirements.This action only works after the user has interacted with the page after loading (transient activation).
import React from "react";import { Button, Flex, hubspot, TextArea } from "@hubspot/ui-extensions";hubspot.extend(({ actions }) => <Extension actions={actions} />);function Extension({ actions }) { const textToCopy = `Copy me!`; // Use copy action on event handler async function handleOnClick() { try { // The function is async, make sure to await it. await actions.copyTextToClipboard(textToCopy); actions.addAlert({ type: "success", message: "Text copied to clipboard.", }); } catch (error) { // User error handling. copyTextToClipboard can fail with a `notAllowed` error. console.log(error); actions.addAlert({ type: "warning", message: "Couldn't copy text.", }); } } return ( <Flex direction="column" gap="md"> <TextArea label="Text" value={textToCopy} /> <Button onClick={handleOnClick}>Copy text</Button> </Flex> );}
This action should be run by explicit user interaction, otherwise the action will fail by running before the page has rendered. For example, the following implementation would fail:
function CopyButtonBadExample({ actions }) { const textToCopyWithoutUserPermission = `Please don't try this, it will fail`; useEffect(() => { /** * Don't run copyTextToClipboard without explicit interaction. * This will fail because the action will run before the page * has rendered. */ async function badStuff() { try { await actions.copyTextToClipboard(textToCopyWithoutUserPermission); actions.addAlert({ type: 'success', message: 'text copied to clipboard', }); } catch (error) { console.log(error); actions.addAlert({ type: 'warning', message: "can't copy value", }); } } badStuff(); }, []);
To add another layer of UI to your extension, you can include overlays using the Modal and Panel components.
Modal: a pop-up dialog box best suited for short messages and action confirmations. A 'danger' variant is included for destructive actions, such as deleting a contact.
Panel: a slide-out sidebar best suited for longer, compartmentalized tasks that users might need to perform, such as multi-step forms. Includes a 'modal' variant to obscure page content outside of the panel to focus the user on the panel task.
Add the Modal or Panel component into the overlay prop.
Panel
Modal
import { Button, Panel, PanelSection, PanelBody, PanelFooter, Text, hubspot } from "@hubspot/ui-extensions";hubspot.extend(({ actions }) => <OverlayExampleCard actions={actions} />);const OverlayExampleCard = ({ actions }) => { return ( <> <Button overlay={ <Panel id="my-panel" title="Example panel"> <PanelBody> <PanelSection> <Text>Welcome to my panel. Thanks for stopping by!</Text> <Text>Close the panel by clicking the X in the top right, or using the button below</Text> </PanelSection> </PanelBody> <PanelFooter> <Button variant="secondary" onClick={() => { actions.closeOverlay("my-panel"); }} > Close </Button> </PanelFooter> </Panel> } > Open panel </Button> </> );};
import { Button, Modal, ModalBody, ModalFooter, Text, hubspot } from "@hubspot/ui-extensions";hubspot.extend(({ actions }) => <OverlayExampleCard actions={actions} />);const OverlayExampleCard = ({ actions }) => { return ( <> <Button overlay={ <Modal id="default-modal" title="Example modal" width="md"> <ModalBody> <Text>Welcome to my modal. Thanks for stopping by!</Text> <Text>Close the modal by clicking the X in the top right, or using the button below</Text> </ModalBody> <ModalFooter> <Button onClick={() => actions.closeOverlay("default-modal")}>Close modal</Button> </ModalFooter> </Modal> } > Open modal </Button> </> );};
By default, overlays include a close button in the top right, as shown in the example below.
Only one Modal can be open at a time per extension. Opening a Modal when another one is already open will cause the first one to close.
A Modal can be opened from a Panel, but a Panel can’t be opened from a Modal.
Use the openIframeModal action to open an iframe in a modal window. This action accepts two arguments: a payload object that describes the modal, and an optional callback function that runs when the modal is closed.The payload object for openIframeModal includes the following fields:
Field
Type
Description
uri
String
The URL to load in the iframe.
height
Number
The height of the modal in pixels.
width
Number
The width of the modal in pixels.
title
String
The title displayed at the top of the modal.
flush
Boolean
When true, removes the default padding around the iframe content.
For example, the following code would result in an extension that opens an iframe on button click. The iframe is configured to contain the Wikipedia homepage with a height and width of 1000px and no padding. Upon closing the modal, a message will be logged to the console.
import { Link, Button, Text, Box, Flex, hubspot } from "@hubspot/ui-extensions";hubspot.extend(({ actions }) => <Extension openIframe={actions.openIframeModal} />);const Extension = ({ openIframe }) => { const handleClick = () => { openIframe( { uri: "https://wikipedia.org/", height: 1000, width: 1000, title: "Wikipedia in an iframe", flush: true, }, () => console.log("This message will display upon closing the modal.") ); }; return ( <> <Flex direction="column" align="start" gap="medium"> <Text> Clicking the button will open a modal dialog with an iframe that displays the content at the provided URL. Get more info on how to do this: <Link href="https://developers.hubspot.com/docs/platform/create-ui-extensions#open-an-iframe-in-a-modal"> here </Link> </Text> <Box> <Button type="submit" onClick={handleClick}> Click me </Button> </Box> </Flex> </> );};
When the user completes an action inside the iframe, the modal should close returning the user to the main page. To close the modal, the integration can use window.postMessage to signal that the user is done. The following messages are accepted:
{"action": "DONE"}: the user has successfully completed the action.
{"action": "CANCEL"}: the user has canceled the action.
Note: The domain where the action originates must match the domain of the uri you passed into the openIframeModal action. If the domains do not match, the message will be ignored.
To make GraphQL requests, your app must include the following scopes:
collector.graphql_schema.read
collector.graphql_query.execute
fetchCrmObjectProperties
While this method is still supported, you may want to switch to using the useCrmProperties hook instead
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.
Use refreshObjectProperties to refresh the property data on the CRM record, and any CRM data components on the record without needing to refresh the page. This includes cards added to the record through HubSpot’s UI. This method will work for the CRM objects that you include in the extension’s .json file in the objectTypes array.
import React, { useState } from 'react';import { Divider, Button, Input, Flex, hubspot} from '@hubspot/ui-extensions';hubspot.extend(({ actions }) => ( <Extension refreshObjectProperties={actions.refreshObjectProperties} />));const Extension = ({ refreshObjectProperties,}) => { // Your extension logic goes here // Refresh all properties of the object on the page refreshObjectProperties(); return ( // Your extension body );};
Use onCrmPropertiesUpdate to subscribe to changes made to properties on the CRM record and run hubspot.fetch() functions based on those changes. This only includes changes made from within the HubSpot UI, not property updates from outside the UI, such as via APIs. This action is intended to be used like a React hook.The full API for this method is as follows:
As an example, the following function subscribes to updates made to the contact’s first and last name properties, then logs those properties to the console.