Programmatically Apply Organizational Theme To Multiple Power BI Reports Using Semantic Link Labs

Power BI Core Visuals team led by Miguel Myers published a huge update last week : Organizational Themes. It’s been a long standing ask by the users. It allows Power BI admins to manage and distribute centrally stored themes to all developers in the organization. Read the blog post for details. Currently, as the above blog explains, org themes don’t update existing reports automatically, which makes sense. But what if you want to bulk update many published reports with the org themes? The latest version of Semantic Link Labs to the rescue (v 0.11.0) !!!
Organizational Theme
I have three report themes published in the tenant.
Semantic Link Labs
Using Semantic Link Labs, I want to apply one of the themes (the Sales Dark Theme
for illustration purposes) to be applied to all reports in a workspace. To show that this works for reports in Pro workspaces as well, I published three reports to a Pro workspace. Using Semantic Link Labs, I will:
Install SLL version >= 0.11.0
Get the JSON of the
Sales Dark Theme
from the org themesGet a list of all the reports in the workspace
If the report theme is different from the org theme, apply the theme to each report
- else, skip
The base code is very simple - get org theme, apply the theme:
theme_json = theme.get_org_theme_json(theme='MyTheme') with connect_report(report=report, workspace=workspace,readonly=False) as rpt: rpt.set_theme(theme_json=theme_json)
Below, I make it a bit more robust with error handling, catch a few edge cases and do some comparison etc:
%pip install semantic-link-labs --q
from sempy_labs.report import connect_report
from sempy_labs.theme import list_org_themes, get_org_theme_json
import sempy.fabric as fabric
import json
def theme_content(theme_dict):
# remove the 'name' field for content comparison
return {k: v for k, v in theme_dict.items() if k != "name"}
def apply_org_theme(workspace, org_theme_name=None):
"""
For each report in the workspace, applies the org theme if not already set or if only the name is different.
"""
# get org themes and select the desired one
org_themes = list_org_themes()
if org_themes.empty:
raise ValueError("org themes nt found.")
if org_theme_name:
row = org_themes[org_themes["Theme Name"] == org_theme_name]
if row.empty:
raise ValueError(f"Org theme '{org_theme_name}' not found.")
theme_id = row["Theme Id"].iloc[0]
else:
theme_id = org_themes["Theme Id"].iloc[0]
org_theme_name = org_themes["Theme Name"].iloc[0]
org_theme_json = get_org_theme_json(theme_id)
if isinstance(org_theme_json, str):
org_theme_json = json.loads(org_theme_json) #if theme returned is not json, just in case
org_theme_content = theme_content(org_theme_json)
org_theme_name_val = org_theme_json.get("name", org_theme_name)
# all reports in the workspace; can be Fabric or Pro workspace
reports = fabric.list_reports(workspace=workspace)
updated = []
for ix, row in reports.iterrows():
report_id = row["Id"]
report_name = row["Name"]
try:
with connect_report(report=report_id, workspace=workspace, readonly=False, show_diffs=False) as rpt:
# get custom theme fallback to base theme.
try:
# custom theme
report_theme = rpt.get_theme("customTheme")
except Exception:
try:
report_theme = rpt.get_theme("baseTheme")
except Exception:
report_theme = {}
if isinstance(report_theme, str):
report_theme = json.loads(report_theme)
report_theme_content = theme_content(report_theme)
report_theme_name_val = report_theme.get("name", "")
# compare different scenarios
# sometime the theme details could be same but name could be different
content_match = json.dumps(report_theme_content, sort_keys=True) == json.dumps(org_theme_content, sort_keys=True)
name_match = report_theme_name_val == org_theme_name_val
if content_match and name_match:
print(f"Report '{report_name}' already has the '{org_theme_name}' org theme.")
continue
elif content_match and not name_match:
print(f"Report theme details are the same but the theme name is different. Updated the report with the new theme '{org_theme_name}'.")
rpt.set_theme(theme_json=org_theme_json)
updated.append(report_name)
else:
print(f"Applying org theme '{org_theme_name}' to '{report_name}'...")
rpt.set_theme(theme_json=org_theme_json)
updated.append(report_name)
except Exception as err:
print(f"failed to update '{report_name}': {err}")
return updated
Example:
There is no API, yet, to update the Org theme but I am sure it will be made available in the future.
Note again that I executed the notebook in a Fabric workspace but the reports are in a Pro workspace ! Michael Kovalsky and other contributors have been adding many new functions to the ReportWrapper
class, you should take a look at it for all the possibilities.
Subscribe to my newsletter
Read articles from Sandeep Pawar directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Sandeep Pawar
Sandeep Pawar
Microsoft MVP with expertise in data analytics, data science and generative AI using Microsoft data platform.