SharePoint Modern Site copy/save Theme to another site

In case you Provisioned a beautiful site with the PNP services of SharePoint LookBook, and you liked it, but wanted to take it out to another site...

Well, MS did not yet gave us a way to save/copy a Theme... just a lot of ways to upload ones...

I tried so mush downloading a site template with


All failed for so many reasons... BTW (1) dont change the name of the .pnp file, and if you get some "access denied" error, either become global admin, or use "-ExcludeHandlers" (i needed Fields, Files, Lists, Pages)

Anyways... how I got it working

1. I used this endpoint to get the actual palette

2. And extraced the data accroding to this MS article while converting the RGB to HEX
code made in html/js in the end

3. I uploaded the theme to the Tenant
With SPO services, for some reason, you can't connect anymore to a single site, only to the entire Tenant, more specifically to the admin, i.e. and therefor the theme will now be available through all sites (settings -> change the look -> theme)

The PS commands: 

Connect-SPOService -Url
Add-SPOTheme -Identity "like grey" -Palette $themepalette -IsInverted $false


Instructions for converter 
0. create html file with the code below and double click it
1. Go to via browser
2. Copy everything
3. Paste in the text box
4. click button
5. paste in PS
6. run the PS commands above

CODE for Converter 

<style>body { width70%;     marginauto;margin-top71px;}textarea { width100%height300px; }</style>
<h1>your /_api/SP.Web.GetContextWebThemeData to $themepalette</h1>
<div><textarea id="in"></textarea></div>
<div><button onclick="clicks()" >clicks</button></div>
<div id="res"></div>

function clicks(){
    let d = document.querySelector("#in").value
    d = d.replace('<d:GetContextWebThemeData xmlns:d="" xmlns:m="" xmlns:georss="" xmlns:gml="">''')
    d = d.replace('</d:GetContextWebThemeData>''')
    d = d.replace('This XML file does not appear to have any style information associated with it. The document tree is shown below.''')
    d = d.trim()

    let j = JSON.parse(d)
    j = j.Palette.Colors

    let out = '$themepalette = @{ '
    let keys = ["themePrimary""themeLighterAlt""themeLighter""themeLight"
        , "themeTertiary""themeSecondary""themeDarkAlt""themeDark"
        , "themeDarker""neutralLighterAlt""neutralLighter""neutralLight"
        , "neutralQuaternaryAlt""neutralQuaternary""neutralTertiaryAlt""neutralTertiary"
        , "neutralSecondaryAlt""neutralSecondary""neutralPrimary""neutralPrimaryAlt"
        , "neutralDark""black""white""primaryBackground""primaryText"]

    for (let i = 0i < keys.lengthi++) {
        const k = keys[i];
        if (!j[k]) {
            console.log(`no key ${k}`)
        out += `"${k}" = "${dcRgbToHex(j[k])}"; `
    out += '}'

    let ta = document.querySelector("#in")
    ta.value = out;

function dcRgbToHex(dc){
    dc = dc.DefaultColor
    return rgbToHex(dc.Rdc.Gdc.B)

function componentToHex(c) {
  var hex = c.toString(16);
  return hex.length == 1 ? "0" + hex : hex;

function rgbToHex(rgb) {
  return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);


Popular posts from this blog

OverTheWire[.com] Natas Walkthrough - JUST HINT, NO SPOILERS

SOLVED The item could not be indexed successfully because the item failed in the indexing subsystem

Asp.Net Ending Response options, Response.End() vs CompleteRequest()