This documentation covers all components, APIs, and patterns used across the HubSpot UI Extensions examples repository. The examples demonstrate how to build custom UI extensions for HubSpot CRM using React/TypeScript.
All extensions follow the same initialization pattern:
import { hubspot } from '@hubspot/ui-extensions';
// Basic extension
hubspot.extend(() => <Component />);
// Extension with actions
hubspot.extend(({ actions }) => <Component actions={actions} />);
// Extension with context and actions
hubspot.extend<'crm.record.tab'>(({ context, actions }) =>
<Component context={context} actions={actions} />
);
Extensions use generic type parameters to specify their placement:
hubspot.extend<'crm.record.tab'>
- CRM record tab- Context and actions are strongly typed with interfaces from
@hubspot/ui-extensions
Retrieves CRM object properties.
Parameters:
properties
: Array of property names to fetch
Returns: Promise with property values
Example:
const properties = await actions.fetchCrmObjectProperties([
'firstname', 'lastname', 'email'
]);
Listens for property changes.
Parameters:
properties
: Array of property names to watchcallback
: Function called when properties change
Example:
actions.onCrmPropertiesUpdate(['lifecyclestage'], (updatedProperties) => {
setProperties(updatedProperties);
});
Refreshes properties displayed on the page.
Example:
await actions.refreshObjectProperties();
Shows alerts to users.
Parameters:
type
: 'success' | 'warning' | 'danger' | 'info'message
: Alert message string
Example:
actions.addAlert({
type: 'success',
message: 'Property updated successfully'
});
Closes modals/panels.
Parameters:
id
: Overlay identifier
Example:
actions.closeOverlay('modal-id');
Calls serverless functions.
Parameters:
functionName
: Name of the serverless functionoptions
: Configuration object
Options:
propertiesToSend
: Array of CRM properties to sendparameters
: Object with function parameterspayload
: Request payload
Example:
const result = await hubspot.serverless('updateDeal', {
propertiesToSend: ['amount', 'dealstage'],
parameters: { dealId: '123' },
payload: { newAmount: 50000 }
});
Makes HTTP requests to external APIs.
Parameters:
url
: API endpoint URLoptions
: Fetch options object
Example:
const response = await hubspot.fetch('https://api.example.com/data', {
method: 'GET',
headers: { 'Authorization': 'Bearer token' }
});
Flexible layout container.
Props:
direction
: 'row' | 'column' | 'row-reverse' | 'column-reverse'gap
: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'flush'align
: 'start' | 'center' | 'end' | 'stretch'justify
: 'start' | 'center' | 'end' | 'between' | 'around'wrap
: 'nowrap' | 'wrap' | 'wrap-reverse'
Example:
<Flex direction="column" gap="md" align="center">
<Text>Item 1</Text>
<Text>Item 2</Text>
</Flex>
Basic container with flex properties.
Props:
flex
: Number (flex-grow ratio)
Example:
<Flex direction="row">
<Box flex={1}>Left content</Box>
<Box flex={2}>Right content (wider)</Box>
</Flex>
Basic container component for grouping content.
Example:
<Tile>
<Heading>Title</Heading>
<Text>Content</Text>
</Tile>
Form container with submission handling.
Props:
onSubmit
: Function called on form submissionpreventDefault
: Boolean to prevent default form submission
Example:
<Form onSubmit={handleSubmit} preventDefault={true}>
<Input name="email" label="Email" required />
<Button type="submit">Submit</Button>
</Form>
Text input field.
Props:
name
: Input name attributelabel
: Input labelrequired
: Boolean for required validationplaceholder
: Placeholder textdescription
: Help text below inputtooltip
: Tooltip textvalue
: Controlled valueonChange
: Change handler
Example:
<Input
name="firstname"
label="First Name"
required
placeholder="Enter first name"
description="Your legal first name"
tooltip="This will appear on official documents"
value={firstName}
onChange={setFirstName}
/>
Multi-line text input.
Props:
name
: Input name attributelabel
: Input labelmaxLength
: Maximum character limitplaceholder
: Placeholder textvalue
: Controlled valueonChange
: Change handler
Example:
<TextArea
name="description"
label="Description"
maxLength={500}
placeholder="Enter description"
value={description}
onChange={setDescription}
/>
Dropdown selection component.
Props:
options
: Array of option objectsvalue
: Selected valueonChange
: Change handlerlabel
: Select labelplaceholder
: Placeholder text
Option Object:
label
: Display textvalue
: Option value
Example:
<Select
label="Status"
options={[
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' }
]}
value={status}
onChange={setStatus}
/>
Date selection input.
Props:
format
: Date format stringvalue
: Date valueonChange
: Change handlerlabel
: Input label
Example:
<DateInput
label="Start Date"
format="MM/DD/YYYY"
value={startDate}
onChange={setStartDate}
/>
Numeric input with validation.
Props:
min
: Minimum valuemax
: Maximum valuevalue
: Numeric valueonChange
: Change handlerlabel
: Input label
Example:
<NumberInput
label="Price"
min={0}
max={10000}
value={price}
onChange={setPrice}
/>
Text display component.
Props:
variant
: 'default' | 'microcopy'format
: 'currency' | 'date' | 'datetime' | 'number' | 'time'inline
: Boolean for inline display
Example:
<Text variant="microcopy" format="currency">
{price}
</Text>
Heading text component.
Example:
<Heading>Section Title</Heading>
Status indicator component.
Props:
variant
: 'success' | 'warning' | 'error' | 'info'
Example:
<StatusTag variant="success">Active</StatusTag>
Metrics display components.
Example:
<Statistics>
<StatisticsItem label="Total Sales" value="$50,000" />
<StatisticsItem label="New Leads" value="25" />
</Statistics>
Alert message component.
Props:
title
: Alert titlevariant
: 'success' | 'warning' | 'danger' | 'info'children
: Alert content
Example:
<Alert title="Success" variant="success">
Operation completed successfully
</Alert>
Interactive button component.
Props:
variant
: 'primary' | 'secondary' | 'destructive'size
: 'xs' | 'sm' | 'md' | 'lg'type
: 'button' | 'submit' | 'reset'disabled
: Boolean for disabled stateoverlay
: Overlay configuration objectonClick
: Click handler
Overlay Configuration:
type
: 'modal' | 'panel'title
: Overlay titlebody
: Overlay contentwidth
: Overlay widthheight
: Overlay height
Example:
<Button
variant="primary"
size="md"
onClick={handleClick}
overlay={{
type: 'modal',
title: 'Confirmation',
body: <ConfirmationForm />,
width: 'md'
}}
>
Open Modal
</Button>
External link component.
Props:
href
: URL destination
Example:
<Link href="https://example.com">Visit Website</Link>
Data table component.
Props:
bordered
: Boolean for table borderspaginated
: Boolean for paginationpageCount
: Number of pagesshowButtonLabels
: Boolean for pagination button labels
Example:
<Table bordered paginated pageCount={5}>
<TableHead>
<TableRow>
<TableCell>Name</TableCell>
<TableCell>Email</TableCell>
</TableRow>
</TableHead>
<TableBody>
{data.map(item => (
<TableRow key={item.id}>
<TableCell>{item.name}</TableCell>
<TableCell>{item.email}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
CRM-specific association table.
Props:
objectTypeId
: CRM object type IDpropertyColumns
: Array of property column definitionspreFilters
: Array of pre-applied filterssort
: Sort configurationpagination
: Pagination configuration
Property Column Definition:
propertyName
: CRM property namelabel
: Column header labelsortable
: Boolean for sortable column
Example:
<CrmAssociationTable
objectTypeId="0-1"
propertyColumns={[
{ propertyName: 'name', label: 'Company Name', sortable: true },
{ propertyName: 'industry', label: 'Industry', sortable: false }
]}
sort={{ propertyName: 'name', direction: 'ASC' }}
pagination={{ pageSize: 10 }}
/>
CRM deal stage tracking component.
Props:
properties
: Array of CRM properties to displayshowProperties
: Boolean to show/hide properties
Example:
<CrmStageTracker
properties={['amount', 'closedate', 'dealstage']}
showProperties={true}
/>
Bar chart visualization.
Props:
data
: Array of data objectsaxes
: Axis configurationoptions
: Chart options
Example:
<BarChart
data={salesData}
axes={{
x: { property: 'month' },
y: { property: 'sales' }
}}
options={{
title: 'Monthly Sales',
legend: { show: true }
}}
/>
Line chart visualization.
Props:
data
: Array of data objectsaxes
: Axis configurationoptions
: Chart options
Example:
<LineChart
data={trendData}
axes={{
x: { property: 'date' },
y: { property: 'value' }
}}
options={{
title: 'Sales Trend',
smooth: true
}}
/>
Modal overlay component.
Props:
title
: Modal titleonClose
: Close handlersize
: 'sm' | 'md' | 'lg' | 'xl'
Example:
<Modal title="Edit Record" onClose={handleClose} size="md">
<ModalBody>
<Form>
<Input name="name" label="Name" />
</Form>
</ModalBody>
<ModalFooter>
<Button variant="secondary" onClick={handleClose}>Cancel</Button>
<Button variant="primary" onClick={handleSave}>Save</Button>
</ModalFooter>
</Modal>
Modal content area.
Example:
<ModalBody>
<Text>Modal content goes here</Text>
</ModalBody>
Modal footer with actions.
Example:
<ModalFooter>
<Button variant="secondary">Cancel</Button>
<Button variant="primary">Confirm</Button>
</ModalFooter>
Side panel component.
Props:
title
: Panel titleonClose
: Close handlervariant
: 'default' | 'flush'
Example:
<Panel title="Details" onClose={handleClose}>
<PanelBody>
<Text>Panel content</Text>
</PanelBody>
<PanelFooter>
<Button onClick={handleClose}>Close</Button>
</PanelFooter>
</Panel>
Loading indicator component.
Example:
{loading && <LoadingSpinner />}
Error state display component.
Props:
title
: Error titlemessage
: Error message
Example:
<ErrorState
title="Failed to Load"
message="Unable to fetch data. Please try again."
/>
Empty state display component.
Props:
title
: Empty state titlemessage
: Empty state message
Example:
<EmptyState
title="No Data"
message="No records found matching your criteria."
/>
Multi-step process indicator.
Props:
currentStep
: Current step numbertotalSteps
: Total number of stepssteps
: Array of step objects
Step Object:
title
: Step titlecompleted
: Boolean for completion status
Example:
<StepIndicator
currentStep={2}
totalSteps={3}
steps={[
{ title: 'Details', completed: true },
{ title: 'Review', completed: false },
{ title: 'Confirm', completed: false }
]}
/>
Key-value pair display component.
Example:
<DescriptionList>
<DescriptionListItem label="Name" value="John Doe" />
<DescriptionListItem label="Email" value="[email protected]" />
</DescriptionList>
interface ExtensionProps {
actions: {
fetchCrmObjectProperties: FetchCrmObjectPropertiesAction;
onCrmPropertiesUpdate: OnCrmPropertiesUpdateAction;
refreshObjectProperties: RefreshObjectPropertiesAction;
addAlert: AddAlertAction;
closeOverlay: (id: string) => void;
};
context: CrmContext;
}
interface FetchCrmObjectPropertiesAction {
(properties: string[]): Promise<Record<string, any>>;
}
interface OnCrmPropertiesUpdateAction {
(properties: string[], callback: (updatedProperties: Record<string, any>) => void): void;
}
interface AddAlertAction {
(alert: { type: 'success' | 'warning' | 'danger' | 'info'; message: string }): void;
}
interface MenuItem {
id: number;
name: string;
price: number;
description: string;
category: string;
available: boolean;
}
interface Company {
id: string;
name: string;
industry: string;
city: string;
state: string;
distance?: number;
}
interface PropertyData {
name: string;
value: any;
type: string;
label: string;
}
enum PropertyType {
Bedroom = 'bedroom',
Studio = 'studio',
BedroomX2 = 'bedroom_x2',
}
enum ListingItemStatus {
Available = 'success',
InProgress = 'warning',
Occupied = 'error',
}
enum FlexDirection {
Row = 'row',
Column = 'column',
RowReverse = 'row-reverse',
ColumnReverse = 'column-reverse',
}
// Basic state management
const [data, setData] = useState<DataType[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string>('');
// Effect for data fetching
useEffect(() => {
const fetchData = async () => {
setLoading(true);
try {
const result = await hubspot.fetch('/api/data');
setData(result);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
// Memoized callback
const handleAction = useCallback((item: DataType) => {
// Handle action
}, [dependencies]);
interface FormData {
name: string;
email: string;
message: string;
}
const [formData, setFormData] = useState<FormData>({
name: '',
email: '',
message: ''
});
const [errors, setErrors] = useState<Partial<FormData>>({});
const handleInputChange = (field: keyof FormData, value: string) => {
setFormData(prev => ({ ...prev, [field]: value }));
// Clear error for this field
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: undefined }));
}
};
const validateForm = (): boolean => {
const newErrors: Partial<FormData> = {};
if (!formData.name) newErrors.name = 'Name is required';
if (!formData.email) newErrors.email = 'Email is required';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
// Centralized error handling
const handleAsyncOperation = async () => {
try {
setLoading(true);
const result = await someAsyncOperation();
setData(result);
actions.addAlert({
type: 'success',
message: 'Operation completed successfully'
});
} catch (error) {
console.error('Operation failed:', error);
actions.addAlert({
type: 'danger',
message: 'Operation failed. Please try again.'
});
} finally {
setLoading(false);
}
};
// Show loading states during async operations
{loading ? (
<LoadingSpinner />
) : error ? (
<ErrorState title="Error" message={error} />
) : data.length === 0 ? (
<EmptyState title="No Data" message="No records found" />
) : (
<DataDisplay data={data} />
)}
// Reusable component patterns
const PropertyCard = ({ property }: { property: PropertyData }) => (
<Tile>
<Flex direction="column" gap="sm">
<Heading>{property.label}</Heading>
<Text format={property.type}>{property.value}</Text>
</Flex>
</Tile>
);
// Compose multiple components
const PropertyList = ({ properties }: { properties: PropertyData[] }) => (
<Flex direction="column" gap="md">
{properties.map(property => (
<PropertyCard key={property.name} property={property} />
))}
</Flex>
);
// Use strong typing for props
interface ComponentProps {
data: DataType[];
onSelect: (item: DataType) => void;
loading?: boolean;
error?: string;
}
// Type guard functions
const isValidData = (data: any): data is DataType => {
return data && typeof data.id === 'string' && typeof data.name === 'string';
};
// Generic component types
interface GenericListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
keyExtractor: (item: T) => string;
}
This comprehensive API documentation covers all the components, patterns, and best practices found across the HubSpot UI Extensions examples repository. Each component includes detailed prop definitions, usage examples, and integration patterns with the HubSpot platform.