{"id":16183,"library":"powerbi-visuals-api","title":"Power BI Custom Visuals API","description":"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.","status":"active","version":"5.11.0","language":"javascript","source_language":"en","source_url":"https://github.com/microsoft/powerbi-visuals-api","tags":["javascript","PowerBI","BI","visuals","visualization","typescript"],"install":[{"cmd":"npm install powerbi-visuals-api","lang":"bash","label":"npm"},{"cmd":"yarn add powerbi-visuals-api","lang":"bash","label":"yarn"},{"cmd":"pnpm add powerbi-visuals-api","lang":"bash","label":"pnpm"}],"dependencies":[],"imports":[{"note":"ESM imports are standard for modern Power BI visual development with TypeScript. CommonJS `require` is generally not used for type imports in this context.","wrong":"const IVisual = require('powerbi-visuals-api').IVisual;","symbol":"IVisual","correct":"import { IVisual } from 'powerbi-visuals-api';"},{"note":"`VisualUpdateOptions` is a named export, not a default export. Incorrectly using default import will lead to type errors.","wrong":"import VisualUpdateOptions from 'powerbi-visuals-api';","symbol":"VisualUpdateOptions","correct":"import { VisualUpdateOptions } from 'powerbi-visuals-api';"},{"note":"Importing directly from 'powerbi-visuals-api' is the correct and recommended way. Deep imports from internal paths like `/lib` are unstable and should be avoided.","wrong":"import { IVisualHost, IVisual } from 'powerbi-visuals-api/lib/visuals/visual';","symbol":"IVisualHost","correct":"import { IVisualHost } from 'powerbi-visuals-api';"}],"quickstart":{"code":"import {\n  IVisual, IVisualHost, VisualUpdateOptions, VisualConstructorOptions,\n  VisualUpdateType, VisualDataView, DataViewValueColumnGroup\n} from 'powerbi-visuals-api';\n\n// A minimal example of a custom visual structure, demonstrating lifecycle and data handling.\nclass MyCustomVisual implements IVisual {\n    private targetDiv: HTMLElement;\n    private host: IVisualHost;\n\n    constructor(options: VisualConstructorOptions) {\n        this.targetDiv = options.element;\n        this.host = options.host;\n        const initialText = document.createElement('p');\n        initialText.textContent = 'Custom Visual Initialized';\n        this.targetDiv.appendChild(initialText);\n    }\n\n    public update(options: VisualUpdateOptions): void {\n        console.log('Visual updated with options:', options);\n        this.targetDiv.innerHTML = ''; // Clear previous content\n\n        const dataView: VisualDataView | undefined = options.dataViews && options.dataViews[0];\n\n        if (dataView && dataView.categorical && dataView.categorical.categories) {\n            const categories = dataView.categorical.categories[0];\n            const values = dataView.categorical.values && dataView.categorical.values[0];\n\n            if (categories && categories.values) {\n                const ul = document.createElement('ul');\n                categories.values.forEach((category, index) => {\n                    const li = document.createElement('li');\n                    const value = values && values.values && values.values[index] !== undefined ? values.values[index] : 'N/A';\n                    li.textContent = `${category}: ${value}`;\n                    ul.appendChild(li);\n                });\n                this.targetDiv.appendChild(ul);\n            } else {\n                const noCategoryText = document.createElement('p');\n                noCategoryText.textContent = 'No categories found in data.';\n                this.targetDiv.appendChild(noCategoryText);\n            }\n        } else {\n            const noDataText = document.createElement('p');\n            noDataText.textContent = 'No data available or data view structure is not categorical.';\n            this.targetDiv.appendChild(noDataText);\n        }\n\n        // Example of host interaction\n        this.host.displayMessage('Visual update processed.', 3000);\n    }\n\n    public destroy(): void {\n        // Perform any cleanup here when the visual is removed from the canvas\n        console.log('Visual destroyed');\n    }\n}\n\n// --- Mocking environment for demonstration purposes ---\n// In a real Power BI environment, this would be handled by the host application.\nconst mockElement = document.createElement('div');\nmockElement.style.width = '300px';\nmockElement.style.height = '200px';\nmockElement.style.border = '1px solid #ccc';\ndocument.body.appendChild(mockElement);\n\nconst mockHost: IVisualHost = {\n    // Minimal mock for common host services used in visuals\n    createSelectionIdBuilder: () => ({ withCategory: () => ({ createSelectionId: () => ({}) }) }),\n    createSelectionManager: () => ({ select: () => Promise.resolve([]) }),\n    displayMessage: (message, duration?) => console.log(`Host Message: ${message}`),\n    locale: 'en-US',\n    authenticationService: {\n        get ; () => Promise.resolve(null),\n        acquireAADToken: () => Promise.resolve(null)\n    },\n    storageV2Service: {\n        get: (key: string) => Promise.resolve(null),\n        set: (key: string, data: string) => Promise.resolve()\n    },\n    // More host services would be mocked here in a complete testing setup\n    eventService: { register: () => {}, unregister: () => {} },\n    hostServices: { get: () => undefined, set: () => {}, overrideSelectionMode: () => {}, onVisualCommand: () => {} },\n    securityService: { isCustomVisualEnabled: () => true },\n    applyAdvancedFilter: () => {}, tooltipService: { show: () => {}, hide: () => {} },\n    launchUrl: (url) => console.log(`Launching URL: ${url}`),\n    downloadService: { exportVisualsContent: () => Promise.resolve({success: true, status:''}), exportVisualsContentExtended: () => Promise.resolve({success: true, status:'', extendedResult:{}}) },\n    createOpaqueUtils: () => ({ areHierarchicallyRelated: (a,b) => false }),\n    subSelectionService: { setSubSelections: () => {}, removeSubSelections: () => {}, showCustomTooltip: () => {}, hideCustomTooltip: () => {} },\n    fetchDataByProperty: () => Promise.resolve(null),\n    setTelemetryLog: () => {}\n};\n\nconst mockVisualConstructorOptions: VisualConstructorOptions = {\n    element: mockElement,\n    host: mockHost,\n    visuals: {}\n};\n\nconst visual = new MyCustomVisual(mockVisualConstructorOptions);\n\nconst mockUpdateOptions: VisualUpdateOptions = {\n    viewport: { width: 300, height: 200 },\n    type: VisualUpdateType.Data,\n    dataViews: [{\n        metadata: {\n            columns: [\n                { displayName: 'Category', queryName: 'Category', roles: { Category: true }, type: { text: true } },\n                { displayName: 'Value', queryName: 'Value', roles: { Value: true }, type: { numeric: true } }\n            ]\n        },\n        categorical: {\n            categories: [{\n                source: { displayName: 'Category', queryName: 'Category', roles: { Category: true }, type: { text: true } },\n                values: ['Product A', 'Product B', 'Product C', 'Product D']\n            }],\n            values: [{\n                source: { displayName: 'Value', queryName: 'Value', roles: { Value: true }, type: { numeric: true } },\n                values: [150, 220, 90, 310]\n            }] as DataViewValueColumnGroup[]\n        }\n    }],\n    viewMode: 0, // ViewMode.View\n    editMode: 0, // EditMode.Default\n    operationKind: 0, // VisualDataChangeOperationKind.Create\n    duration: 0,\n    isInFocus: false\n};\n\nvisual.update(mockUpdateOptions);\n","lang":"typescript","description":"This quickstart demonstrates a basic Power BI custom visual lifecycle, including initialization, processing data updates, and interacting minimally with the host environment. It shows how to implement `IVisual` and mock `IVisualHost` for testing."},"warnings":[{"fix":"Migrate to `storageV2Service` for local storage functionality. This service was introduced in beta in 5.6.0 and became stable by 5.8.0. Ensure your visual uses the `storageV2Service` interface on `IVisualHost`.","message":"The `storageService` (legacy local storage API) was removed in version 5.11.0. Visuals attempting to use it will fail.","severity":"breaking","affected_versions":">=5.11.0"},{"fix":"Test visuals extensively after upgrading to ensure selection logic for matrix data views functions correctly. Re-persisted `selectionIds` using the new API version might be necessary for compatibility.","message":"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.","severity":"breaking","affected_versions":">=5.3.0"},{"fix":"Refer to the Power BI custom visuals documentation for Microsoft Entra ID integration. Ensure your visual's capabilities.json requests the necessary permissions and that the Power BI environment is configured to allow AAD token acquisition for custom visuals.","message":"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).","severity":"gotcha","affected_versions":">=5.7.0"},{"fix":"To support On-Object Formatting, update your `capabilities.json` with `supportsOnObjectFormatting` and `enablePointerEventsFormatMode` and implement the relevant interfaces like `IVisualOnObjectFormatting`.","message":"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.","severity":"gotcha","affected_versions":">=5.8.0"},{"fix":"If your visual targets embedded Power BI dashboards or tiles, ensure your host environment checks and logic are robust enough to handle the extended `CustomVisualHostEnv` type introduced in 5.9.0.","message":"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.","severity":"gotcha","affected_versions":">=5.9.0"}],"env_vars":null,"last_verified":"2026-04-21T00:00:00.000Z","next_check":"2026-07-20T00:00:00.000Z","problems":[{"fix":"Update 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`.","cause":"Attempting to access the deprecated `storageService` on the host object.","error":"Property 'storageService' does not exist on type 'IVisualHost'."},{"fix":"Always 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) { ... }`","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.","error":"TypeError: Cannot read properties of undefined (reading 'categorical') at MyCustomVisual.update"},{"fix":"Use the `createSelectionIdBuilder()` method from `IVisualHost` to correctly construct `SelectionId` objects: `this.host.createSelectionIdBuilder().withCategory(category, index).createSelectionId();`","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.","error":"Argument of type '{ foo: string; }' is not assignable to parameter of type 'SelectionId'."}],"ecosystem":"npm"}