Bulk update Power BI reports with the new organizational theme
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) !!!
💡
Before you proceed, please note that Organizational Theme is a Preview feature and is available only to admins. Semantic Link Labs needs the report to be in PBIR format for below function to work. You can read about PBIR, its use cases & limitations here. Also, Semantic Link Labs currently uses an internal API which will be updated when the public API for org themes is available.
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 Themefrom the org themes - Get 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:
- CopyCopy
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:
CopyCopy
%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:

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.
💡
If you are a Power BI developer with no prior experience in notebooks and Python, I recommend checking out Kurt Buhler’s new notebook tutorial to get started. Also bookmark these example notebooks.
About the Author
Sandeep Pawar
Principal Program Manager @ Microsoft Fabric CAT
Reference:
Pawar, S (2025). Programmatically Apply Organizational Theme To Multiple Power BI Reports Using Semantic Link Labs. Available at: Programmatically Apply Organizational Theme To Multiple Power BI Reports Using Semantic Link Labs [Accessed: 14th July 2025].




