Customising SharePoint using their API and a React app
SharePoint is a great tool to provide information and whatnot, but when it comes to customisation, you are required to either have full admin rights on the tenant and knowledge in PowerShell or be a developer.
When our manager came up to me asking if it would be possible to add custom theming to SharePoint, I had no idea how that would be possible. He found me a link stating that it would be possible to add the theme using the SharePoint API and I ploughed my way through the documentation.
To get started, I used the link he provided: https://learn.microsoft.com/en-us/sharepoint/dev/declarative-customization/site-theming/sharepoint-site-theming-rest-api
I found a few endpoints I could use to access the API, but I had no idea how I could get access to it. Calling it directly from a Front-end app wouldn’t work, because SharePoint is guarded with authentication…
The problem
Before I continue explaining what I did and where I actually started, I want to tell you what actual problem needs a solution I came up with.
When you want theming on SharePoint, you get a certain amount of default theming nobody wants to use. Especially when you want to have some customisation; you end up with the default themes they provided.
And, of course, when there is an option to have customisation; we want it! And thus, started my research on how to add this custom theming to SharePoint.
Research
To begin; I started searching the web for some information on how to get access to the endpoints provided by Microsoft and I found this LinkedIn article written by Andrew Koltyakov.
Andrew states that it “is a quick guide on how to configure local development of React apps against real SharePoint data and APIs”.
React, you say? Right up my alley! You can find the article here: https://www.linkedin.com/pulse/getting-started-react-local-development-sharepoint-andrew-koltyakov/
This article helped me set up a small server which communicates with SharePoint.
Getting started
I had already created my React app using npx create-react-app my-app
, and in Andrew’s article, I found that the package sp-rest-proxy
lends a nice hand to do the back-end work for me.
npm install sp-rest-proxy --save-dev
Adding the next section to my scripts section in package.json
starts the proxy server with npm run proxy
"scripts": {
// ...
"proxy": "node ./api-server.js",
}
And adding proxy settings, which will correspond with the start-up settings of sp-rest-proxy
{
"proxy": "http://localhost:8081"
}
Now, in the root of your directory, you will have to create a new JavaScript file called api-server.js
and add:
const RestProxy = require('sp-rest-proxy');
const settings = {
port: 8081
};
const restProxy = new RestProxy(settings);
restProxy.serve();
If you run npm run proxy
, you will be prompted asking you to add some credentials.
npm run proxy
> sharepoint-site-theming@0.1.0 proxy
> node ./api-server.js
? SharePoint URL site_url.sharepoint.com
? Authentication strategy User credentials (NTLM)
? User name user_mail@site_url.com
? Password ********
SharePoint REST Proxy has been started on http://localhost:8081
With SharePoint’s own explanation and examples, I will explain the necessary credentials.
site_url
can be something like: contoso.sharepoint.com- I used “User credentials” to get access but there are more options to choose from.
- Username will be an e-mail address that has administrator rights on your SharePoint tenant.
- And the Password would be self-explanatory.
Running this will create a new folder and file in your root directory called config/private.json
(make sure you add that to your .gitignore
if you are thinking of adding your project to git). If you shut down your proxy server and re-instantiate it. sp-rest-proxy
will use the private.json
to log you in, this way you don’t have to fill in the questions every time.
Access
When you run npm run proxy
you will notice that it serves you http://localhost:8081
. Open that in your browser and you will see something like this:
Now, if you remember the SharePoint page I shared earlier, you can add either of these endpoints in the “REST Relative URL”. To test if the connection is working you can add the GET request provided by SharePoint: /_api/thememanager/GetTenantThemingOptions
there and you should get a response like:
{
"d": {
"GetTenantThemingOptions": {
"__metadata": {
"type": "SP.Utilities.ThemingOptions"
},
"hideDefaultThemes": false,
"themePreviews": {
"__metadata": {
"type": "Collection(SP.Utilities.JsonTheme)"
},
"results": []
}
}
}
}
The URL for theme management REST commands is based on _api/thememanager. For example, the following are the endpoints for the commands:
http://<site url>/_api/thememanager/AddTenantTheme
http://<site url>/_api/thememanager/DeleteTenantTheme
http://<site url>/_api/thememanager/GetTenantThemingOptions
http://<site url>/_api/thememanager/ApplyTheme
http://<site url>/_api/thememanager/UpdateTenantTheme
To add a theme you will have to create some JSON structure that can be used by SharePoint. The below example is a POST request provided by SharePoint.
function RestRequest(url,params) {
var req = new XMLHttpRequest();
req.onreadystatechange = function ()
{
if (req.readyState != 4) // Loaded
return;
console.log(req.responseText);
};
// Prepend web URL to url and remove duplicated slashes.
var webBasedUrl = (_spPageContextInfo.webServerRelativeUrl + "//" + url).replace(/\/{2,}/,"/");
req.open("POST",webBasedUrl,true);
req.setRequestHeader("Content-Type", "application/json;charset=utf-8");
req.setRequestHeader("ACCEPT", "application/json; odata.metadata=minimal");
req.setRequestHeader("x-requestdigest", _spPageContextInfo.formDigestValue);
req.setRequestHeader("ODATA-VERSION","4.0");
req.send(params ? JSON.stringify(params) : void 0);
}
RestRequest("/_api/thememanager/AddTenantTheme");
var pal = {
"palette" : {
"themePrimary": "#1BF242",
"themeLighterAlt": "#0d0b00",
"themeLighter": "#0b35bc",
"themeLight": "#322d00",
"themeTertiary": "#6a5f00",
"themeSecondary": "#1B22F2",
"themeDarkAlt": "#ffe817",
"themeDark": "#ffed4b",
"themeDarker": "#fff171",
"neutralLighterAlt": "#252525",
"neutralLighter": "#282828",
"neutralLight": "#313131",
"neutralQuaternaryAlt": "#3f3f3f",
"neutralQuaternary": "#484848",
"neutralTertiaryAlt": "#4f4f4f",
"neutralTertiary": "#c8c8c8",
"neutralSecondaryAlt": "#d0d0d0",
"neutralSecondary": "#dadada",
"neutralPrimary": "#ffffff",
"neutralDark": "#eaeaea",
"black": "#f8f8f8",
"white": "#1f1f1f",
"primaryBackground": "#1f1f1f",
"primaryText": "#ffffff",
"error": "#ff5f5f"
}
}
RestRequest("/_api/thememanager/AddTenantTheme", {name:"Sounders Rave Green", themeJson: JSON.stringify(pal)});
A simple copy-paste action did not do the trick for me. I found out that the line req.setRequestHeader("x-requestdigest”, _spPageContextInfo.formDigestValue);
broke my code and simply removing it worked.
Error handling with this example became a bit of a hassle, so I installed Axios to make the requests for me npm install axios
. This enabled me to create the next POST function:
type RequestResponse = {
data: {
themePreviews: themeType[]
};
status: number;
statusText: string;
};
type ThemeType = {
name: string;
themeJson?: {
palette: {
[key:string]: string;
themePrimary: string;
themeLighterAlt: string;
themeLighter: string;
themeLight: string;
themeTertiary: string;
themeSecondary: string;
themeDarkAlt: string;
themeDark: string;
themeDarker: string;
neutralLighterAlt: string;
neutralLighter: string;
neutralLight: string;
neutralQuaternaryAlt: string;
neutralQuaternary: string;
neutralTertiaryAlt: string;
neutralTertiary: string;
neutralSecondaryAlt: string;
neutralSecondary: string;
neutralPrimaryAlt: string;
neutralPrimary: string;
neutralDark: string;
black: string;
white: string;
primaryBackground: string;
primaryText: string;
bodyBackground: string;
bodyText: string;
disabledBackground: string;
disabledText: string;
error: string;
accent: string;
};
};
};
const config = {
headers: {
"Content-Type": "application/json;charset=utf-8",
ACCEPT: "application/json; odata.metadata=minimal",
"ODATA-VERSION": "4.0",
},
};
const postTheme = (params: ThemeType) =>
axios
.post(
cleanUrl("/_api/thememanager/AddTenantTheme"),
params
? JSON.stringify({
name: params.name,
themeJson: JSON.stringify(params.themeJson),
})
: void 0,
config
)
.then((response) => {
const { data, status, statusText }: RequestResponse = response;
console.log("postTheme response", { data, status, statusText });
if (status !== 200) {
console.error("postTheme: status is not 200", {
status,
data,
statusText,
});
}
return { data, status, statusText };
})
.catch((error) => {
console.error("postTheme failed", error);
throw error;
});
Now we can use the postTheme function with the required theme structure that SharePoint needs:
const post = (theme: themeType) => {
try {
postTheme(theme as themeType).then((response) => {
if (response.status === 200) {
console.log("success", response);
} else {
console.log(response);
}
});
} catch (error: any) {
console.log(error);
}
};
const soundersRaveGreen: themeType = {
name: "Sounders Rave Green",
themeJson: {
palette: {
themePrimary: "#7ee23b",
themeLighterAlt: "#99588a",
themeLighter: "#df6d4f",
themeLight: "#8444ff",
themeTertiary: "#f7931a",
themeSecondary: "#c0d6e4",
themeDarkAlt: "#00ff00",
themeDark: "#c1141a",
themeDarker: "#fff000",
neutralLighterAlt: "#bb9955",
neutralLighter: "#733337",
neutralLight: "#133331",
neutralQuaternaryAlt: "#90ee90",
neutralQuaternary: "#8b0000",
neutralTertiaryAlt: "#ffc0cb",
neutralTertiary: "#ff8c00",
neutralSecondaryAlt: "#9acd32",
neutralSecondary: "#f08080",
neutralPrimaryAlt: "#008b8b",
neutralPrimary: "#b8860b",
neutralDark: "#bdb76b",
black: "#8b008b",
white: "#556b2f",
primaryBackground: "#9932cc",
primaryText: "#e9967a",
error: "#8fbc8f",
accent: "#483d8b",
bodyBackground: "#2f4f4f",
bodyText: "#00ced1",
disabledBackground:"#8a2be2",
disabledText: "#663399",
},
},
};
post(soundersRaveGreen);
This is all you need to know to get a decent start on building an app that can CRUD themes with SharePoint.
But where’s React?
As you can see, everything I noted before is not based on any React code. I concluded that it is not necessary to have a React app running (it does make it easier though) to get this theming uploaded in SharePoint. Nevertheless, I took the advantage to build an app myself and tried to exhaust all endpoints provided by SharePoint. You can find what I have created on GitHub! Feel free to fork it if you want a head start.