Customising SharePoint using their API and a React app

Kiki Gerritsen
Team Rockstars IT
Published in
6 min readFeb 9, 2023

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.

Tune your SharePoint

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.

SharePoint’s default theming options

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:

SharePoint REST proxy local

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.

https://github.com/MystBug/share-point-theming-app

--

--