Office 365 provides more and more extension points available as REST services. Recently, I was working with the control that allows to manage the themes. The official documentation presents 3 endpoints that allow to list, add and delete theme (https://docs.microsoft.com/en-us/sharepoint/dev/declarative-customization/site-theming/sharepoint-site-theming-rest-api), the endpoints are:
- siteURL/_api/thememanager/AddTenantTheme
Method allows to add tenant theme. As an argument, it accepts JSON with 2 properties: theme name and palette specifies the theme’s colors.
- siteURL/_api/thememanager/DeleteTenantTheme
Method allows to delete theme. As an argument, it accepts the theme name.
- siteURL/_api/thememanager/GetTenantThemingOptions
Method lists all available themes and returns them in the form of ThemingOptions object.
Additional methods
Below I present two additional endpoints that are not officialy announced in the docs (you have to consider using theme on your production environment), but can really useful to bring more possibilities to your solutions:
- siteURL/_api/thememanager/ApplyTheme
Method allows to apply theme to the site.
- siteURL/_api/thememanager/UpdateTenantTheme
Method allows to update the existing theme defition.
[2019-01-15]: I have created a PR to update the official docs.
SPFx usage
I have created a service class that allows to work with O365 themes that can be used in SPFx projects.
import { SPHttpClient, SPHttpClientResponse } from "@microsoft/sp-http"; | |
import { IPartialTheme } from "@uifabric/styling/lib"; | |
export interface IThemingOptions { | |
'@odata.context': string; | |
hideDefaultThemes: boolean; | |
themePreviews: IThemePreview[]; | |
} | |
export interface IThemePreview { | |
/** | |
* Name od the SP theme | |
*/ | |
name: string; | |
/** | |
* JSON representing theme. | |
*/ | |
themeJson: string; | |
} | |
export interface ISPThemeService { | |
addTheme(themeName: string, theme: IPartialTheme): Promise<boolean>; | |
getThemes(): Promise<IThemingOptions>; | |
deleteTheme(themeName: string): Promise<boolean>; | |
applyTheme(theme: IThemePreview, siteUrl: string): Promise<boolean>; | |
updateTheme(themeName: string, theme: IPartialTheme): Promise<boolean> | |
} | |
export class SPThemeService implements ISPThemeService { | |
private _siteUrl: string; | |
private _spHttpClient: SPHttpClient; | |
constructor(spHttpClient: SPHttpClient, siteUrl: string) { | |
this._spHttpClient = spHttpClient; | |
this._siteUrl = siteUrl; | |
} | |
/** | |
* Adds theme to the tenant. | |
* @param themeName Theme name. | |
* @param theme Theme partial definition containing palette property. | |
*/ | |
public async addTheme(themeName: string, theme: IPartialTheme): Promise<boolean> { | |
try { | |
let url = `${this._siteUrl}/_api/thememanager/AddTenantTheme`; | |
const body = JSON.stringify({ | |
name: themeName, | |
themeJson: JSON.stringify(theme) | |
}); | |
let addThemeResponse: SPHttpClientResponse = await this._spHttpClient.post(url, SPHttpClient.configurations.v1, { | |
body: body | |
}); | |
if (!addThemeResponse || !addThemeResponse.ok) { | |
throw new Error(`Something went wrong when add theme. ThemeName=${themeName}.`); | |
} | |
const addThemeJSONResult = await addThemeResponse.json(); | |
if (!addThemeJSONResult) { | |
throw new Error(`Cannot read JSON result when add theme. ThemeName=${themeName}.`) | |
} | |
return addThemeJSONResult.value; | |
} catch (err) { | |
console.log(`[Error][SPThemeService.addTheme] : + ${err.message}`); | |
return false; | |
} | |
} | |
/** | |
* Gets available tenant themes. | |
* @param siteUrl Site URL which will be used to execute query. | |
*/ | |
public async getThemes(): Promise<IThemingOptions> { | |
try { | |
let url = `${this._siteUrl}/_api/thememanager/GetTenantThemingOptions`; | |
let getThemesResponse: SPHttpClientResponse = await this._spHttpClient.get(url, SPHttpClient.configurations.v1); | |
if (!getThemesResponse || !getThemesResponse.ok) { | |
throw new Error(`Something went wrong when obtaining tenant themes.`); | |
} | |
const themesJSONResult = await getThemesResponse.json() as IThemingOptions; | |
if (!themesJSONResult) { | |
throw new Error("Cannot read JSON result when obtaining themes. ") | |
} | |
return themesJSONResult; | |
} catch (err) { | |
console.log(`[Error][SPThemeService.getThemes] : ${err.message}`); | |
return null; | |
} | |
} | |
/** | |
* Deletes theme. | |
* @param themeName Theme name to be deleted. | |
*/ | |
public async deleteTheme(themeName: string): Promise<boolean> { | |
try { | |
let url = `${this._siteUrl}/_api/thememanager/DeleteTenantTheme`; | |
let deleteResult: SPHttpClientResponse = await this._spHttpClient.post(url, SPHttpClient.configurations.v1, { | |
body: JSON.stringify({ | |
name: themeName | |
}) | |
}); | |
if (!deleteResult || !deleteResult.ok) { | |
throw new Error(`Something went wrong when delete theme. ThemeName=${themeName}.`); | |
} | |
return true; | |
} catch (err) { | |
console.log(`[Error][SPThemeService.deleteTheme] : ${err.message}`); | |
return false; | |
} | |
} | |
/** | |
* Apply theme to the site. | |
* @param theme Partial theme containing palette property. | |
* @param siteUrl | |
*/ | |
public async applyTheme(theme: IThemePreview, siteUrl: string): Promise<boolean> { | |
try { | |
let url = `${siteUrl}/_api/ThemeManager/ApplyTheme`; | |
let applyThemeResult: SPHttpClientResponse = await this._spHttpClient.post(url, SPHttpClient.configurations.v1, { | |
body: JSON.stringify({ | |
name: theme.name, | |
themeJson: theme.themeJson | |
}) | |
}); | |
if (!applyThemeResult || !applyThemeResult.ok) { | |
throw new Error("Something went wrong when applying theme."); | |
} | |
let applyThemeResultJson = await applyThemeResult.json(); | |
if (!applyThemeResultJson) { | |
throw new Error("Cannot read answer when applying theme.."); | |
} | |
return true; | |
} catch (err) { | |
console.log(`[Error][SPThemeService.applyTheme] : ${err.message}`); | |
return false; | |
} | |
} | |
/** | |
* Update theme. | |
* @param themeName Theme name to be updated. | |
* @param theme Partial theme containing palette property. | |
*/ | |
public async updateTheme(themeName: string, theme: IPartialTheme): Promise<boolean> { | |
try { | |
let url = `${this._siteUrl}/_api/thememanager/UpdateTenantTheme`; | |
const body = JSON.stringify({ | |
name: themeName, | |
themeJson: JSON.stringify(theme) | |
}); | |
let updateThemeResponse: SPHttpClientResponse = await this._spHttpClient.post(url, SPHttpClient.configurations.v1, { | |
body: body | |
}); | |
if (!updateThemeResponse || !updateThemeResponse.ok) { | |
throw new Error(`Something went wrong when update theme. ThemeName=${themeName}.`); | |
} | |
const updateThemeJSONResult = await updateThemeResponse.json(); | |
if (!updateThemeJSONResult) { | |
throw new Error(`Cannot read JSON result when update theme. ThemeName=${themeName}.`) | |
} | |
return updateThemeJSONResult.value; | |
} catch (err) { | |
console.log(`[Error][SPThemeService.updateTheme] : ${err.message}`); | |
return false; | |
} | |
} | |
} |
Coming soon
In the next post, I will share some thoughts about forcing immediate inheritance of the themes in the connected sites. Stay tuned!