Home Services Work About Blog Contact Let's Talk
Blog / SPFx Development
SPFx Development

Power BI Embedded in SharePoint: Full SPFx Integration Guide

Why Use SPFx Instead of SharePoint's Native Power BI Web Part

SharePoint Online ships with a built-in Power BI report web part. For straightforward embedding — a single report, standard permissions, no programmatic interaction — it is adequate. But the native web part has real limitations that surface quickly in enterprise deployments: you cannot pass filters programmatically from other page elements, you cannot control the embed token or apply row-level security based on the current user's custom attributes, you cannot respond to report events (like a user clicking a visual), and you cannot adjust the iframe size based on the report's actual page dimensions.

The SPFx approach uses the powerbi-client npm package — the same SDK that Power BI's own embedding technology uses — loaded inside a web part. This gives you the full Power BI JavaScript API, which exposes methods to set filters, refresh data, respond to selection events, apply bookmarks, and control virtually every aspect of the embedded report experience. The trade-off is complexity: you manage embed tokens, handle token refresh, and wire up the SDK lifecycle yourself. This guide walks through each of those pieces in production-ready detail.

A secondary benefit of the SPFx approach is that you can combine the Power BI report with other SharePoint data on the same page. A common pattern is a Fluent UI filter bar rendered by one web part that calls the Power BI SDK's report.setFilters() method on another web part's report instance — something completely impossible with the native web part's iframe boundary.

Workspace Setup: Permissions, App Registration, and Service Principal

Before writing a line of SPFx code, the Power BI workspace must be correctly configured. The two authentication models for Power BI Embedded are user owns data (the embedded report uses the logged-in user's Power BI licence) and app owns data (an Azure AD service principal owns the embed token, and individual users do not need Power BI licences). For SharePoint intranet scenarios, user-owns-data is typically used because your Microsoft 365 users already have Power BI Pro or Premium Per User licences.

For user-owns-data, the required setup is minimal: register an Azure AD application, grant it the Report.Read.All and Dataset.Read.All delegated permissions for the Power BI Service API, and add the application to the Power BI workspace as a Member or Viewer. In your SPFx solution, you will use the Microsoft Graph client (or the AadHttpClient from SPFx) to acquire a token for https://analysis.windows.net/powerbi/api with these scopes.

JSON — package-solution.json webApiPermissionRequests for Power BI
{
  "webApiPermissionRequests": [
    {
      "resource": "Power BI Service",
      "scope": "Report.Read.All"
    },
    {
      "resource": "Power BI Service",
      "scope": "Dataset.Read.All"
    }
  ]
}

After deploying the SPFx solution, a SharePoint administrator must approve these permission requests in the SharePoint Admin Center under API access. Once approved, the SPFx framework's AadHttpClient can request tokens for the Power BI service on behalf of the logged-in user without any additional OAuth configuration in your web part code.

Embed Token API: Generating Tokens from SPFx

The Power BI REST API provides a GenerateToken endpoint for each report that returns a short-lived embed token. For user-owns-data embedding, you call this endpoint using the logged-in user's delegated access token — not a service principal token. The embed token is separate from the access token: the access token authenticates the API call, while the embed token is passed to the powerbi-client SDK to authenticate the embedded iframe.

TypeScript — Fetching the embed token from the Power BI REST API in SPFx
import { AadHttpClient, IHttpClientOptions } from '@microsoft/sp-http';

interface IEmbedConfig {
  embedToken: string;
  embedUrl: string;
  reportId: string;
  expiry: string;
}

const getEmbedConfig = async (
  aadClient: AadHttpClient,
  workspaceId: string,
  reportId: string
): Promise<IEmbedConfig> => {
  // Step 1: Get report metadata (embedUrl)
  const reportRes = await aadClient.get(
    `https://api.powerbi.com/v1.0/myorg/groups/${workspaceId}/reports/${reportId}`,
    AadHttpClient.configurations.v1
  );
  const report = await reportRes.json();

  // Step 2: Generate the embed token
  const tokenBody = JSON.stringify({
    accessLevel: 'View',
    allowSaveAs: false,
  });
  const tokenOptions: IHttpClientOptions = {
    body: tokenBody,
    headers: { 'Content-Type': 'application/json' },
  };
  const tokenRes = await aadClient.post(
    `https://api.powerbi.com/v1.0/myorg/groups/${workspaceId}/reports/${reportId}/GenerateToken`,
    AadHttpClient.configurations.v1,
    tokenOptions
  );
  const tokenData = await tokenRes.json();

  return {
    embedToken: tokenData.token,
    embedUrl: report.embedUrl,
    reportId: report.id,
    expiry: tokenData.expiration,
  };
};
Watch Out

Embed tokens expire, typically within one hour. If the SharePoint page is open for more than an hour without a reload, the embedded report will silently fail to load for new page interactions. Implement a token refresh mechanism that checks the expiry field and calls report.setAccessToken(newToken) before expiry.

SPFx Web Part Component: Embedding the Report with powerbi-client

Install powerbi-client as a dependency in your SPFx project. The package is a vanilla JavaScript SDK with TypeScript types — it does not depend on React, Angular, or any framework. In your React functional component, use a useRef to hold a reference to the container div, and initialise the Power BI embed inside a useEffect that runs after the embed config has been fetched.

TypeScript — Power BI report embed in a React functional component
import { useRef, useEffect, useState } from 'react';
import * as pbi from 'powerbi-client';
import { models } from 'powerbi-client';

const powerbiService = new pbi.service.Service(
  pbi.factories.hpmFactory,
  pbi.factories.wpmpFactory,
  pbi.factories.routerFactory
);

const PowerBIReport: React.FC<{ embedConfig: IEmbedConfig }> = ({ embedConfig }) => {
  const containerRef = useRef<HTMLDivElement>(null);
  const reportRef = useRef<pbi.Report | null>(null);

  useEffect(() => {
    if (!containerRef.current || !embedConfig.embedToken) return;

    const config: models.IReportEmbedConfiguration = {
      type: 'report',
      id: embedConfig.reportId,
      embedUrl: embedConfig.embedUrl,
      accessToken: embedConfig.embedToken,
      tokenType: models.TokenType.Embed,
      settings: {
        navContentPaneEnabled: false,    // hide report page nav
        filterPaneEnabled: false,         // hide filter pane
        background: models.BackgroundType.Transparent,
      },
    };

    const report = powerbiService.embed(
      containerRef.current,
      config
    ) as pbi.Report;

    report.on('loaded', () => console.log('Power BI report loaded'));
    report.on('error', (event) => console.error('Power BI error:', event.detail));

    reportRef.current = report;

    return () => {
      powerbiService.reset(containerRef.current!);
      reportRef.current = null;
    };
  }, [embedConfig]);

  return <div ref={containerRef} style={{ width: '100%', height: '600px' }} />;
};

Row-Level Security: Filtering Data by Current User

Row-Level Security (RLS) in Power BI ensures that embedded reports only show data relevant to the current user. There are two layers to configure: RLS roles defined in the Power BI dataset (in Power BI Desktop or the web service), and the EffectiveIdentity parameter on the embed token that tells Power BI which role to apply for this particular embed session.

For user-owns-data embedding, Power BI applies RLS automatically based on the logged-in user's identity — if you have defined an RLS role that filters data by [EmailAddress] = USERPRINCIPALNAME(), Power BI enforces it without any extra configuration in your SPFx code. This is the simplest and recommended approach for Microsoft 365 environments where all users are in the same Azure AD tenant.

For app-owns-data embedding (where a service principal generates the token), you must explicitly specify the effectiveIdentity in the GenerateToken request body. Pass the user's email or UPN from the SPFx page context to ensure each user gets a personalised data view, even though the token comes from a service principal.

TypeScript — GenerateToken with EffectiveIdentity for app-owns-data RLS
const tokenBody = JSON.stringify({
  accessLevel: 'View',
  identities: [
    {
      username: currentUserEmail, // from this.context.pageContext.user.email
      roles: ['RegionSalesRole'],     // role defined in Power BI Desktop
      datasets: [datasetId],
    },
  ],
});
Note

RLS roles must exist in the dataset before the embed token is generated — if you reference a role that does not exist in the dataset definition, the GenerateToken call returns a 400 error. Verify role names are case-sensitive and match exactly what is defined in Power BI Desktop.

Responsive Container Sizing: Matching Report Dimensions

One of the most common complaints about Power BI embedded reports is that they appear cut off or show excessive whitespace. The root cause is a mismatch between the container div's dimensions and the report's canvas size. Power BI's powerbi-client library does not auto-resize the container — it renders the report inside an iframe sized to the container you provide. You must size the container correctly, and ideally use the report's actual page dimensions as a guide.

The recommended approach for responsive SharePoint pages is a percentage-width container with an aspect-ratio based height. Most Power BI reports are designed at 16:9 (1280x720) or 4:3 (1366x768). A CSS aspect-ratio container adapts to the SharePoint page column width automatically and works correctly on mobile breakpoints.

CSS — Responsive Power BI container using aspect-ratio
/* Applied to the container div via inline styles or CSS Modules */
.pbiContainer {
  width: 100%;
  aspect-ratio: 16 / 9;       /* matches standard Power BI canvas */
  border-radius: 12px;
  overflow: hidden;
  border: 1px solid var(--border);
}

/* In JavaScript, listen for the report 'rendered' event and read the actual */
/* page dimensions if you need pixel-perfect sizing for non-standard reports  */

For reports with variable page counts or custom page sizes, listen to the Power BI pageChanged event and call report.getActivePage() to retrieve the current page's defaultSize object. Recalculate the container height based on the page's width-to-height ratio and the current container width. This guarantees the report always fills its container exactly, regardless of which page the user navigates to within a multi-page report.

Performance: Caching Embed Configs and Lazy Loading

The token fetch and embed initialisation add latency to the web part's initial load. In our benchmarks, the two Power BI API calls (report metadata and token generation) together take 800ms–1.5s on average. Combined with the powerbi-client bundle size (~450 KB minified), a naive implementation can delay the page's LCP by 2+ seconds. Two optimisations address this.

First, load powerbi-client dynamically inside the useEffect hook, only when the component is about to embed. Use a dynamic import: const pbi = await import('powerbi-client'). This removes the library from the initial SPFx bundle and defers its download until the user scrolls the report into view (or the page fully loads). With lazy loading, the rest of the SharePoint page loads and renders normally before the Power BI library downloads.

Second, cache the embed config in the component state and only re-fetch when the token is within 5 minutes of expiry. Store the expiry timestamp (from the GenerateToken response) in a React ref. On each render, check if the current time is within 5 minutes of expiry — if so, fetch a fresh token and call report.setAccessToken(freshToken) without re-initialising the embed. This keeps the report visible and interactive while silently refreshing the token in the background.

Troubleshooting: Common Embed Failures and Debug Techniques

Power BI embedded errors are frustrating because the report container simply shows a grey screen with a generic error message. The error event from the SDK provides more detail, and the Power BI activity log in your Azure tenant provides the full picture. Enable detailed error logging in development by subscribing to the SDK's error event and logging event.detail to the browser console.

  • "Authentication failed" or blank iframe: The most common cause is an expired or invalid embed token. Check that the GenerateToken response status was 200, and log the full token response. A 401 from the Power BI API means the user's access token scope is incorrect — verify the webApiPermissionRequests in package-solution.json were approved by the tenant admin.
  • "No report with ID found": Verify the workspace ID and report ID in your web part properties match the actual IDs in the Power BI service URL. The workspace ID appears in app.powerbi.com/groups/<workspaceId> and the report ID in /reports/<reportId>.
  • RLS not applying: If the report shows all data regardless of the logged-in user, the RLS role may not be applied to the dataset, or the EffectiveIdentity username format is wrong. Power BI expects the UPN (e.g., [email protected]), not the display name. Check this.context.pageContext.user.loginName vs this.context.pageContext.user.email — they differ in some tenants.
  • Report renders but shows no data: The user does not have access to the underlying dataset's data source. This is a Power BI gateway or data source credentials issue, not an SPFx problem. Check the dataset's gateway status in the Power BI service.

Key Takeaways

Use SPFx with powerbi-client instead of the native SharePoint web part whenever you need programmatic filter control, event handling, or custom row-level security based on SPFx page context values.

Declare Power BI delegated permissions in package-solution.json and obtain admin approval in SharePoint Admin Center — the SPFx AadHttpClient then handles token acquisition transparently.

Always implement token refresh: store the expiration from GenerateToken, check it before each user interaction, and call report.setAccessToken() rather than reinitialising the full embed.

For user-owns-data embedding in Microsoft 365 tenants, RLS applies automatically using the logged-in user's UPN — no explicit EffectiveIdentity is required in the token request.

Use a CSS aspect-ratio container and dynamic import of powerbi-client to keep the SharePoint page's LCP fast while still rendering the embedded report at the correct dimensions.

AT

Akshara Technologies

Microsoft 365 Development Specialists

With 10+ years building enterprise SharePoint, SPFx, Power Automate, and Flutter solutions for clients across India, USA, UAE, and Australia — we write from production experience, not documentation.

Related Articles

Embed Power BI in SharePoint the Right Way

From embed tokens to row-level security — Akshara Technologies delivers production-grade Power BI SharePoint integrations for enterprise clients.

Start Your Project View Case Studies