Power BI Custom Visuals API

5.11.0 · active · verified Tue Apr 21

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

Warnings

Install

Imports

Quickstart

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.

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);

view raw JSON →