Power BI Custom Visuals API
The `powerbi-visuals-api` package provides the official TypeScript type definitions, interfaces, and enums necessary for developing custom visuals for Microsoft Power BI. It defines the comprehensive contract between a custom visual and the Power BI host environment, enabling developers to interact with data, leverage host services (like tooltips, selections, and authentication), and manage visual capabilities such as on-object formatting and local storage. The current stable version is 5.11.0. Microsoft maintains a relatively active release cadence, frequently introducing new features, deprecating old ones, and addressing bugs or security concerns. This API is the authoritative source for types and definitions, essential for any developer creating or maintaining custom visualization components for the Power BI platform, ensuring type safety and compatibility with the Power BI ecosystem.
Common errors
-
Property 'storageService' does not exist on type 'IVisualHost'.
cause Attempting to access the deprecated `storageService` on the host object.fixUpdate your code to use `this.host.storageV2Service` for local storage operations. Ensure your visual targets API version 5.8.0 or later in its `pbiviz.json`. -
TypeError: Cannot read properties of undefined (reading 'categorical') at MyCustomVisual.update
cause The `dataViews` property on `VisualUpdateOptions` or its elements (`dataViews[0]`, `dataViews[0].categorical`) are `undefined` when the visual receives an update without data or with a different data view type.fixAlways add robust null/undefined checks for `options.dataViews`, `options.dataViews[0]`, and its sub-properties (e.g., `categorical`, `table`, `matrix`) before attempting to access them. For example: `if (dataView && dataView.categorical && dataView.categorical.categories) { ... }` -
Argument of type '{ foo: string; }' is not assignable to parameter of type 'SelectionId'.cause Attempting to create a `SelectionId` or use a selection method with an incorrectly structured object, or providing an object that does not conform to the `SelectionId` interface.fixUse the `createSelectionIdBuilder()` method from `IVisualHost` to correctly construct `SelectionId` objects: `this.host.createSelectionIdBuilder().withCategory(category, index).createSelectionId();`
Warnings
- breaking The `storageService` (legacy local storage API) was removed in version 5.11.0. Visuals attempting to use it will fail.
- breaking The internal data structure for `SelectionId` within matrix data views was updated in version 5.3.0. Persisted `selectionIds` or `identityIndex` values from older API versions might no longer be relevant or correctly resolve in matrix data views, potentially breaking selection logic in visuals.
- gotcha The `acquireAADTokenService` was introduced in version 5.7.0 to enable visuals to obtain Microsoft Entra ID (Azure AD) access tokens. Proper integration requires specific Power BI tenant and visual configurations (e.g., app registration, permissions).
- gotcha On-Object Formatting interfaces (`visualOnObjectFormatting`, `subSelectionService`) and capabilities (`supportsOnObjectFormatting`, `enablePointerEventsFormatMode`) were introduced in v5.8.0. Implementing these features requires careful adherence to the new interfaces and capabilities.
- gotcha The `CustomVisualHostEnv` was extended with `DashboardHost` in v5.9.0 to represent tiles and dashboards in embedded environments. Visuals that rely heavily on host environment specific features might need adjustments to handle different `CustomVisualHostEnv` types.
Install
-
npm install powerbi-visuals-api -
yarn add powerbi-visuals-api -
pnpm add powerbi-visuals-api
Imports
- IVisual
const IVisual = require('powerbi-visuals-api').IVisual;import { IVisual } from 'powerbi-visuals-api'; - VisualUpdateOptions
import VisualUpdateOptions from 'powerbi-visuals-api';
import { VisualUpdateOptions } from 'powerbi-visuals-api'; - IVisualHost
import { IVisualHost, IVisual } from 'powerbi-visuals-api/lib/visuals/visual';import { IVisualHost } from 'powerbi-visuals-api';
Quickstart
import {
IVisual, IVisualHost, VisualUpdateOptions, VisualConstructorOptions,
VisualUpdateType, VisualDataView, DataViewValueColumnGroup
} from 'powerbi-visuals-api';
// A minimal example of a custom visual structure, demonstrating lifecycle and data handling.
class MyCustomVisual implements IVisual {
private targetDiv: HTMLElement;
private host: IVisualHost;
constructor(options: VisualConstructorOptions) {
this.targetDiv = options.element;
this.host = options.host;
const initialText = document.createElement('p');
initialText.textContent = 'Custom Visual Initialized';
this.targetDiv.appendChild(initialText);
}
public update(options: VisualUpdateOptions): void {
console.log('Visual updated with options:', options);
this.targetDiv.innerHTML = ''; // Clear previous content
const dataView: VisualDataView | undefined = options.dataViews && options.dataViews[0];
if (dataView && dataView.categorical && dataView.categorical.categories) {
const categories = dataView.categorical.categories[0];
const values = dataView.categorical.values && dataView.categorical.values[0];
if (categories && categories.values) {
const ul = document.createElement('ul');
categories.values.forEach((category, index) => {
const li = document.createElement('li');
const value = values && values.values && values.values[index] !== undefined ? values.values[index] : 'N/A';
li.textContent = `${category}: ${value}`;
ul.appendChild(li);
});
this.targetDiv.appendChild(ul);
} else {
const noCategoryText = document.createElement('p');
noCategoryText.textContent = 'No categories found in data.';
this.targetDiv.appendChild(noCategoryText);
}
} else {
const noDataText = document.createElement('p');
noDataText.textContent = 'No data available or data view structure is not categorical.';
this.targetDiv.appendChild(noDataText);
}
// Example of host interaction
this.host.displayMessage('Visual update processed.', 3000);
}
public destroy(): void {
// Perform any cleanup here when the visual is removed from the canvas
console.log('Visual destroyed');
}
}
// --- Mocking environment for demonstration purposes ---
// In a real Power BI environment, this would be handled by the host application.
const mockElement = document.createElement('div');
mockElement.style.width = '300px';
mockElement.style.height = '200px';
mockElement.style.border = '1px solid #ccc';
document.body.appendChild(mockElement);
const mockHost: IVisualHost = {
// Minimal mock for common host services used in visuals
createSelectionIdBuilder: () => ({ withCategory: () => ({ createSelectionId: () => ({}) }) }),
createSelectionManager: () => ({ select: () => Promise.resolve([]) }),
displayMessage: (message, duration?) => console.log(`Host Message: ${message}`),
locale: 'en-US',
authenticationService: {
get ; () => Promise.resolve(null),
acquireAADToken: () => Promise.resolve(null)
},
storageV2Service: {
get: (key: string) => Promise.resolve(null),
set: (key: string, data: string) => Promise.resolve()
},
// More host services would be mocked here in a complete testing setup
eventService: { register: () => {}, unregister: () => {} },
hostServices: { get: () => undefined, set: () => {}, overrideSelectionMode: () => {}, onVisualCommand: () => {} },
securityService: { isCustomVisualEnabled: () => true },
applyAdvancedFilter: () => {}, tooltipService: { show: () => {}, hide: () => {} },
launchUrl: (url) => console.log(`Launching URL: ${url}`),
downloadService: { exportVisualsContent: () => Promise.resolve({success: true, status:''}), exportVisualsContentExtended: () => Promise.resolve({success: true, status:'', extendedResult:{}}) },
createOpaqueUtils: () => ({ areHierarchicallyRelated: (a,b) => false }),
subSelectionService: { setSubSelections: () => {}, removeSubSelections: () => {}, showCustomTooltip: () => {}, hideCustomTooltip: () => {} },
fetchDataByProperty: () => Promise.resolve(null),
setTelemetryLog: () => {}
};
const mockVisualConstructorOptions: VisualConstructorOptions = {
element: mockElement,
host: mockHost,
visuals: {}
};
const visual = new MyCustomVisual(mockVisualConstructorOptions);
const mockUpdateOptions: VisualUpdateOptions = {
viewport: { width: 300, height: 200 },
type: VisualUpdateType.Data,
dataViews: [{
metadata: {
columns: [
{ displayName: 'Category', queryName: 'Category', roles: { Category: true }, type: { text: true } },
{ displayName: 'Value', queryName: 'Value', roles: { Value: true }, type: { numeric: true } }
]
},
categorical: {
categories: [{
source: { displayName: 'Category', queryName: 'Category', roles: { Category: true }, type: { text: true } },
values: ['Product A', 'Product B', 'Product C', 'Product D']
}],
values: [{
source: { displayName: 'Value', queryName: 'Value', roles: { Value: true }, type: { numeric: true } },
values: [150, 220, 90, 310]
}] as DataViewValueColumnGroup[]
}
}],
viewMode: 0, // ViewMode.View
editMode: 0, // EditMode.Default
operationKind: 0, // VisualDataChangeOperationKind.Create
duration: 0,
isInFocus: false
};
visual.update(mockUpdateOptions);