Back to Blog
4 min read

Power BI Embedded: Analytics in Your Applications

Power BI Embedded integrates interactive reports and dashboards directly into your applications, providing rich analytics without requiring users to leave your app or have Power BI licenses.

Embedding Options

embed_for_customers:
  description: External users access embedded content
  licensing: App owns data (capacity-based)
  authentication: Your app authenticates
  use_case: Customer-facing portals

embed_for_organization:
  description: Internal users with Power BI licenses
  licensing: User owns data (per-user)
  authentication: Azure AD
  use_case: Internal applications

Setting Up Embedded Analytics

Register Azure AD Application

# Create app registration
az ad app create \
    --display-name "PowerBI-Embedded-App" \
    --reply-urls "https://myapp.com/callback" \
    --required-resource-accesses @manifest.json
// manifest.json - Required permissions
{
    "resourceAppId": "00000009-0000-0000-c000-000000000000",
    "resourceAccess": [
        {
            "id": "4ae1bf56-f562-4747-b7bc-2fa0874ed46f",
            "type": "Scope"
        },
        {
            "id": "7f33e027-4039-419b-938e-2f8ca153e68e",
            "type": "Scope"
        }
    ]
}

Create Embedded Capacity

# Create Power BI Embedded capacity
az powerbi embedded-capacity create \
    --resource-group analytics-rg \
    --name myembeddedcapacity \
    --location eastus \
    --sku-name A2 \
    --sku-tier PBIE_Azure \
    --administration-members "admin@company.com"

Embedding Reports

JavaScript SDK

<!DOCTYPE html>
<html>
<head>
    <script src="https://cdn.powerbi.com/libs/powerbi-client/2.19.1/powerbi.min.js"></script>
</head>
<body>
    <div id="reportContainer" style="height: 600px;"></div>

    <script>
        // Get embed token from your backend
        async function embedReport() {
            const response = await fetch('/api/embed-token');
            const embedConfig = await response.json();

            const reportContainer = document.getElementById('reportContainer');

            const report = powerbi.embed(reportContainer, {
                type: 'report',
                id: embedConfig.reportId,
                embedUrl: embedConfig.embedUrl,
                accessToken: embedConfig.token,
                tokenType: 1,  // Embed token
                settings: {
                    filterPaneEnabled: false,
                    navContentPaneEnabled: false,
                    background: models.BackgroundType.Transparent
                }
            });

            report.on('loaded', function() {
                console.log('Report loaded');
            });

            report.on('error', function(event) {
                console.error('Error:', event.detail);
            });
        }

        embedReport();
    </script>
</body>
</html>

Backend Token Generation (C#)

using Microsoft.PowerBI.Api;
using Microsoft.PowerBI.Api.Models;
using Microsoft.Rest;

public class PowerBIEmbedService
{
    private readonly string _workspaceId;
    private readonly string _reportId;
    private readonly PowerBIClient _client;

    public PowerBIEmbedService(IConfiguration config)
    {
        _workspaceId = config["PowerBI:WorkspaceId"];
        _reportId = config["PowerBI:ReportId"];

        var credential = new ClientCredential(
            config["AzureAd:ClientId"],
            config["AzureAd:ClientSecret"]
        );

        var authResult = await AuthenticationContext
            .AcquireTokenAsync("https://analysis.windows.net/powerbi/api", credential);

        _client = new PowerBIClient(
            new Uri("https://api.powerbi.com"),
            new TokenCredentials(authResult.AccessToken)
        );
    }

    public async Task<EmbedConfig> GetEmbedConfig(string username, string[] roles = null)
    {
        var report = await _client.Reports.GetReportInGroupAsync(
            Guid.Parse(_workspaceId),
            Guid.Parse(_reportId)
        );

        var generateTokenRequest = new GenerateTokenRequestV2
        {
            Reports = new List<GenerateTokenRequestV2Report>
            {
                new GenerateTokenRequestV2Report(Guid.Parse(_reportId))
            },
            Datasets = new List<GenerateTokenRequestV2Dataset>
            {
                new GenerateTokenRequestV2Dataset(report.DatasetId)
            }
        };

        // Apply RLS if roles specified
        if (roles != null && roles.Length > 0)
        {
            generateTokenRequest.Identities = new List<EffectiveIdentity>
            {
                new EffectiveIdentity(
                    username,
                    datasets: new List<string> { report.DatasetId },
                    roles: roles.ToList()
                )
            };
        }

        var embedToken = await _client.EmbedToken.GenerateTokenAsync(generateTokenRequest);

        return new EmbedConfig
        {
            ReportId = report.Id.ToString(),
            EmbedUrl = report.EmbedUrl,
            Token = embedToken.Token,
            TokenExpiry = embedToken.Expiration
        };
    }
}

Interactive Features

Filtering Reports

// Apply filters programmatically
const filter = {
    $schema: "http://powerbi.com/product/schema#basic",
    target: {
        table: "Sales",
        column: "Region"
    },
    operator: "In",
    values: ["North", "South"]
};

report.setFilters([filter]);

// Date range filter
const dateFilter = {
    $schema: "http://powerbi.com/product/schema#advanced",
    target: {
        table: "Sales",
        column: "Date"
    },
    logicalOperator: "And",
    conditions: [
        { operator: "GreaterThanOrEqual", value: "2022-01-01" },
        { operator: "LessThan", value: "2022-04-01" }
    ]
};

report.setFilters([dateFilter]);

Handling Events

// Selection event
report.on('dataSelected', function(event) {
    const data = event.detail;
    console.log('Selected data:', data.dataPoints);

    // Navigate to detail page
    if (data.dataPoints.length > 0) {
        const customerId = data.dataPoints[0].identity[0].equals;
        window.location.href = `/customer/${customerId}`;
    }
});

// Page change event
report.on('pageChanged', function(event) {
    console.log('New page:', event.detail.newPage.displayName);
});

// Button click event (for bookmarks)
report.on('buttonClicked', function(event) {
    console.log('Button clicked:', event.detail);
});

Row-Level Security

Define Roles in Power BI

// In Power BI Desktop, create role "RegionalManager"
[Region] = USERNAME() OR [ManagerEmail] = USERPRINCIPALNAME()

Apply RLS in Embed Token

var identity = new EffectiveIdentity(
    username: "user@company.com",
    datasets: new List<string> { datasetId },
    roles: new List<string> { "RegionalManager" }
);

// For dynamic RLS
var identity = new EffectiveIdentity(
    username: userEmail,
    datasets: new List<string> { datasetId },
    roles: new List<string> { "SalesRep" },
    customData: region  // Pass custom data for RLS filter
);

Performance Optimization

// Pre-load reports
powerbi.preload({
    type: 'report',
    embedUrl: embedUrl
});

// Use bootstrap for faster initial load
const config = {
    type: 'report',
    embedUrl: embedUrl,
    settings: {
        background: models.BackgroundType.Transparent
    }
};

const report = powerbi.bootstrap(container, config);

// Later, apply token when available
report.setAccessToken(accessToken);

Best Practices

performance:
  - Use Premium/Embedded capacity
  - Pre-load reports when possible
  - Minimize visual count per page
  - Use aggregations for large datasets

security:
  - Always use embed tokens (not AAD tokens for external)
  - Implement RLS for data security
  - Set short token expiry
  - Validate user permissions server-side

user_experience:
  - Handle loading states
  - Provide error feedback
  - Enable mobile layout for responsive
  - Consider white-labeling options

Conclusion

Power BI Embedded enables rich analytics in any application:

  • Interactive reports and dashboards
  • Secure multi-tenant access
  • Programmatic control and customization
  • Scalable capacity-based pricing

It’s the foundation for analytics-powered applications.

Resources

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.