The Ultimate Guide to SharePoint Site Templates and Site Scripts

The Ultimate Guide to SharePoint Site Templates and Site Scripts

In my previous blog post, I mentioned you could disable external sharing on a modern site also via SharePoint site templates. I didn’t cover SharePoint site templates in that post, because I wanted to write a separate, more thorough article about the topic — this one! After reading, if you feel like this expost is anything short of “ultimate”, please let me know in the comments section. I would like this blog post to serve as both a very thorough beginner’s guide to developers new to SharePoint site templates, as well as a source of scripts and ideas to more experienced developers. I’ll try to keep this post up to date. Thank you! 😉

Is this your first time learning about SharePoint site templates? Start by watching my video Getting started with Site Templates in SharePoint Online below!

Table of Contents

  1. What are SharePoint site templates and site scripts
  2. Creating a site script
    1. Creating a Logic App/Power Automate flow and triggering it from a site script
    2. Tools for the job
    3. Exporting an existing list as a site script
    4. Exporting an existing site as a site script
    5. Applying a color theme that is not deployed to tenant
    6. What if site templates can’t do the thing you want?
  3. Deploying your site script and site template
    1. The Web Template property
    2. The Design Package Id property
    3. Deploying site templates and site scripts via PnP provisioning
  4. Maintaining your site templates and site scripts
    1. Printing existing site template and site script information
    2. Modifying an existing site template
    3. Modifying an existing site script
    4. Deleting your site templates and site scripts
  5. Applying site templates via the graphical user interface
  6. Limiting who can use site templates
  7. Customizing the default SharePoint site templates
  8. Applying site templates programmatically
  9. Large site templates
  10. Monitoring what site templates and site script actions have been run for a site
  11. Associating a site template to a hub site
  12. Summary of operations available through PowerShell and REST
    1. Undocumented REST operations
  13. The future of SharePoint site templates

What are SharePoint Site Templates and Site Scripts

A SharePoint site template is a set of pre-defined actions that get executed to a site after the site has been created. With site templates, you can, for example, add lists with desired content types or columns, set the site logo, register an extension, or join the site to a hub site. If you are familiar with PnP provisioning templates or SharePoint on-premises site definitions, SharePoint site templates are a similar thing.

I can honestly say that I like SharePoint site templates. You can automate some essential tasks with them, and they are extremely easy to create and run for sites. When getting started, though, something one might find confusing is how the terms “site script” and “site template” are used almost synonymously.

A site script is a JSON string that contains the actions to be done on a site. A site template, on the other hand, is like a container for the site script. You can attach one or more site scripts to a site template, and when you apply the site template, you execute the actions defined in all of the site scripts. It’s like the site template is the visible surface part that can group several site scripts together, and the site scripts are all the action underneath.

Another thing people tend to find a bit confusing is the nature of site templates. As I mentioned before, site templates are just a set of actions that get executed on a site when the site template is applied. They are not templates that get attached to sites. Often when people make changes to site templates, they expect those changes to be reflected automatically to all the sites where the site template has previously been run. That’s not how site templates work.

Site templates are more like scripts (they consist of site scripts after all) that you run to configure a site automatically. After you have configured a site using a site template, the site configuration stays that way even if you changed the site template. To change the site configurations again, you need to run another site template. At the end, there is a section Monitoring what site templates and site script actions have been run for a site that contains information which can help you in this kind of a situation.

There can be 100 site scripts and 100 site templates in total per tenant (previously 30 site scripts and 20 site templates). They require a bit of work to set up, but after that, you can use them from many places. An end user can select your custom site template when they are creating a new modern site in SharePoint, performing the actions right there and then. You can also apply site templates to existing sites programmatically by using CSOM, PowerShell or REST.

Creating a Site Script

When you start building a site script, you should first check out the JSON schema for all the available actions. From there, you can copy-paste verbs into your JSON file and then just edit the parameter values.

It is good to check the schema regularly as new features get introduced quite often. For example, in the future, you can expect to see new verbs for configuring site pages and web parts, and also some actions for other Office 365 applications (announced at Ignite 2018).

If you’d like to see some site script examples or take some as a base for your work, you can find a lot of samples in the Site Scripts GitHub repo.

Below, I’ve constructed a site script example, which applies a custom multi-colored theme to the site, disables external sharing, and finally starts a Power Automate flow.

The actions will get executed in the order in which you define them. And keep in mind that the schema is case sensitive!

{
    "$schema": "schema.json",
    "actions": [
        {
            "verb": "applyTheme",
            "themeName": "Multicolour theme"
        },
        {
            "verb": "setSiteExternalSharingCapability",
            "capability": "Disabled"
        },
        {
            "verb": "triggerFlow",
            "url": "https://prod-24.westeurope.logic.azure.com:443/workflows/a237bcf9...",
            "name": "Cool stuff!", 
            "parameters": 
            { 
               "event": "Microsoft Event", 
               "product": "SharePoint" 
            }
        }
    ], 
    "bindata": { }, 
    "version": 1
};

Being able to start a Flow or a Logic App by applying a site template opens a massive amount of possibilities. You can, for example, integrate it with an Azure Function that applies a PnP provisioning template on the site, or even make calls to Microsoft Graph. That again allows you to do a ton of other cool things. You could even automatically provision Teams with a site template, Power Automate and Microsoft Graph whenever you create a new group-connected team site. Use your imagination and the possibilities are endless!

Tools for the job

To make editing the JSON markup easier, you can reference the JSON schema in your site script and get syntax highlighting in, e.g., Visual Studio code. You can also validate your JSON markup using, e.g., JSON schema validator, if you again supply the schema reference.

{
    "$schema": "https://developer.microsoft.com/json-schemas/sp/site-design-script-actions.schema.json",
    "actions": [], 
    "bindata": { }, 
    "version": 1
};

In addition to constructing the JSON by hand, you can also use third-party tools with graphical user interfaces that generate the site scripts for you, such as sitedesigner.io by Mikko Punamäki or Site Template Studio by Yannick Plenevaux. Both of these methods of constructing site scripts have their pros and cons.

Tools always have a little learning curve. As a developer, you most likely already read JSON very fluently and find it more efficient to edit the JSON directly — at first at least. After getting used to a tool, it might become as fast for you to create site templates with it. Using tools also minimizes the risk of making typos that result in your site template no longer matching the schema.

On the other hand, tools can have bugs that cause such errors. Also, Microsoft’s documentation always gets updated first, and naturally it takes a little while for third-party tools to get updated to include new features or changes.

My recommendation is to try both ways and see which one you prefer. Most likely you’ll end up utilizing both of the methods in different scenarios.

When it comes to the two tools I just mentioned:

  • The sitedesigner.io web site is great if you just want to quickly generate your site script. You can open it in your browser and get to work immediately.
  • The Site Template Studio is an SPFx web part that you first need to install to your SharePoint Online tenant. However, the benefit of the web part is that you can also deploy site templates through it! No need to run a separate PowerShell script.

At Ignite 2018, Microsoft announced that they will be creating their own “site script editor” feature in the future. Whether this allows you to generate scripts in a similar manner as the third-party tools, or simply edit the JSON and update it to the tenant more easily, is yet to be seen.

Creating a Logic App/Power Automate flow and Triggering it from a Site Script

These days you can also trigger a Logic App with the “triggerFlow” action! This change was introduced after the February 2019 Power Automate & Power Apps licensing changes, and how the HTTP actions now require a premium license. If you are unsure of the licensing rules, create a logic app to be on the safe side.

To create a Power Automate flow, which you can trigger from a site script, go to the Power Automate designer. Click on My flows, select Create from blank, and include the When an HTTP request is received trigger. Copy the JSON schema from below into the body and add the HTTP POST URL value from the trigger to your site script.

For logic apps, you need to go to Azure portal and setup a Logic apps resource. For this, you need permissions to an Azure subscription or a resource group, which most likely requires help from your company admin, unless you are one yourself.

{
     "type": "object",
     "properties": {
         "webUrl": {
             "type": "string"
         },
         "parameters": {
             "type": "object",
             "properties": {
                 "event": {
                     "type": "string"
                 },
                 "product": {
                     "type": "string"
                 }
             }
         }
     }
 }

Flow Trigger

If you want to add custom parameters in addition to or instead of the default event and product parameters, you can do that. Just remember to include your changes in both your site script as well as the Power Automate flow trigger schema.

Exporting an existing list as a site script

If you are familiar with PnP provisioning, you have most likely configured sites and then exported them as templates. With site templates, you can export an existing list configuration as a site script in a similar manner. You can do it with the following script. The Get-SPOSiteScriptFromList commandlet prints out the site script to the console, and you can copy it from there to a file of your choice.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$listUrl = "https://mytenant.sharepoint.com/sites/sitename/lists/listname"

$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

Get-SPOSiteScriptFromList -ListUrl $listUrl

This feature is also available through the REST API.

If your list contains view formatting, make sure the exported markup that you copy-paste to your site script doesn’t contain new lines. If it does, the view formatting won’t get applied and you won’t be able to open the view formatting panel in the new list. The formatting can be escaped or a normal part of the complete site script JSON.

"formatterJSON": "{\n  \"schema\": \"https://developer.microsoft.com/json-schemas/sp/view-formatting.schema.json\",\n  \"additionalRowClass\": \"=if([$Impact] == 'Low', 'sp-field-severity--good', if([$Impact] == 'Normal','sp-field-severity--warning', if([$Impact] == 'High','sp-field-severity--severeWarning', if([$Impact] == 'Critical','sp-field-severity--blocked', ''))))\"\n}"

Either way works.

"formatterJSON": { "schema": "https://developer.microsoft.com/json-schemas/sp/view-formatting.schema.json", "additionalRowClass": "=if([$Impact] == 'Low', 'sp-field-severity--good', if([$Impact] == 'Normal','sp-field-severity--warning', if([$Impact] == 'High','sp-field-severity--severeWarning', if([$Impact] == 'Critical','sp-field-severity--blocked', ''))))" }

Exporting an existing site as a site script

The ability to export entire site configurations was announced at the SharePoint Conference in May 2019. With the Get-SPOSiteScriptFromWeb commandlet you can export the settings of your choosing as well as the configurations for lists/libraries at the same time; no need to export lists with their own command separately (make sure to use their internal names).

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$siteUrl = "https://mytenant.sharepoint.com/sites/sitename"
$relativeListUrls = "/lists/listname", "/lists/listname-2", "/lists/listname-3"

Connect-SPOService $adminSiteUrl

Get-SPOSiteScriptFromWeb –WebUrl $siteUrl -IncludeTheme -IncludeBranding -IncludeSiteExternalSharingCapability –IncludeRegionalSettings -IncludeLinksToExportedItems –IncludeLists $relativeListUrls

Note that the command will not export page layout and web part configurations — those features are not yet available in site templates, although they have been mentioned to be implemented sometime in the future.

Applying a color theme that is not deployed to tenant

Typically when you want to apply a color theme via a site template, you first install the theme to your tenant and then reference it by name in your site script. However, sometimes you don’t want to offer a certain color theme to be available for everyone to use in the entire tenant but rather have a more limited audience who can use it. This is achievable through site templates as you can limit their permissions to only a group of users, and even set the site template to be usable only on existing sites via the site template panel.

This feature is actually not documented in the official JSON schema, but ever since exporting sites became possible, we learned of the syntax for how we can make this happen. This is how you need to define your custom color theme in your site script when you don’t want to install it to your tenant but only use it via site templates:

   {
      "verb": "applyTheme",
      "themeJson": {
        "version": "2",
        "isInverted": false,
        "palette": {
          "themeLight": "#fffdd3ce",
          "themeTertiary": "#ff7088aa",
          "black": "#ff3f3f3f",
          "neutralSecondary": "#ff53c7bd",
          "neutralTertiaryAlt": "#ffc8c8c8",
          "themeSecondary": "#ff567095",
          "themeDarker": "#ff1c3557",
          "neutralQuaternary": "#ffd0d0d0",
          "neutralPrimaryAlt": "#ff818181",
          "neutralPrimary": "#ff8c8c8c",
          "themeDark": "#fff87060",
          "themeLighter": "#fffee7e5",
          "neutralTertiary": "#ffd4d4d4",
          "accent": "#fff87060",
          "neutralQuaternaryAlt": "#ffdadada",
          "themeLighterAlt": "#ffd3dce8",
          "neutralLighter": "#fff4f4f4",
          "neutralLight": "#ffeaeaea",
          "neutralDark": "#ff555555",
          "themeDarkAlt": "#fff87060",
          "neutralLighterAlt": "#fff8f8f8",
          "white": "#ffffffff",
          "themePrimary": "#ff102542",
          "HyperlinkActive": "#ff1c3557",
          "CommandLinksPressed": "#ff1c3557",
          "NavigationPressed": "#ff1c3557",
          "EmphasisHoverBorder": "#ff1c3557",
          "TopBarPressedText": "#ff1c3557",
          "HeaderNavigationPressedText": "#ff1c3557",
          "Hyperlinkfollowed": "#ff1c3557",
          "EmphasisHoverBackground": "#fff87060",
          "EmphasisBorder": "#fff87060",
          "AccentText": "#ff102542",
          "CommandLinksHover": "#ff102542",
          "RowAccent": "#ff102542",
          "NavigationAccent": "#ff102542",
          "NavigationHover": "#ff102542",
          "EmphasisBackground": "#ff102542",
          "HeaderNavigationHoverText": "#ff102542",
          "HeaderNavigationSelectedText": "#ff102542",
          "SuiteBarBackground": "#ff102542",
          "Hyperlink": "#ff102542",
          "ContentAccent1": "#ff102542",
          "AccentLines": "#ff567095",
          "HeaderAccentLines": "#ff567095",
          "ButtonPressedBorder": "#ff567095",
          "SuiteBarHoverBackground": "#ff7088aa",
          "StrongLines": "#fffdd3ce",
          "HeaderStrongLines": "#fffdd3ce",
          "SuiteBarHoverText": "#fffdd3ce",
          "ButtonPressedBackground": "#fffdd3ce",
          "ButtonHoverBorder": "#fffdd3ce",
          "ButtonHoverBackground": "#fffee7e5",
          "SelectionBackground": "#7ffdd3ce",
          "HoverBackground": "#7ffee7e5",
          "NavigationHoverBackground": "#7ffee7e5",
          "PageBackground": "#ffffffff",
          "EmphasisText": "#ffffffff",
          "SuiteBarText": "#ffffffff",
          "TileText": "#ffffffff",
          "BackgroundOverlay": "#d8ffffff",
          "HeaderBackground": "#d8ffffff",
          "FooterBackground": "#d8ffffff",
          "DisabledBackground": "#fff8f8f8",
          "HeaderDisabledBackground": "#fff8f8f8",
          "ButtonBackground": "#fff8f8f8",
          "ButtonDisabledBackground": "#fff8f8f8",
          "SubtleEmphasisBackground": "#fff4f4f4",
          "DialogBorder": "#fff4f4f4",
          "NavigationSelectedBackground": "#c6eaeaea",
          "TopBarBackground": "#c6eaeaea",
          "DisabledLines": "#ffeaeaea",
          "HeaderDisabledLines": "#ffeaeaea",
          "ButtonDisabledBorder": "#ffeaeaea",
          "SuiteBarDisabledText": "#ffeaeaea",
          "SubtleLines": "#ffc8c8c8",
          "HeaderSubtleLines": "#ffc8c8c8",
          "ButtonGlyphDisabled": "#ffc8c8c8",
          "DisabledText": "#ffd4d4d4",
          "CommandLinksDisabled": "#ffd4d4d4",
          "HeaderDisableText": "#ffd4d4d4",
          "ButtonDisabledText": "#ffd4d4d4",
          "Lines": "#ffd4d4d4",
          "HeaderLines": "#ffd4d4d4",
          "ButtonBorder": "#ffd4d4d4",
          "CommandLinks": "#ff53c7bd",
          "Navigation": "#ff53c7bd",
          "SubtleEmphasisText": "#ff53c7bd",
          "TopBarText": "#ff53c7bd",
          "HeaderNavigationText": "#ff53c7bd",
          "ButtonGlyph": "#ff53c7bd",
          "BodyText": "#ff8c8c8c",
          "WebPartHeading": "#ff8c8c8c",
          "HeaderText": "#ff8c8c8c",
          "ButtonText": "#ff8c8c8c",
          "ButtonGlyphActive": "#ff8c8c8c",
          "TopBarHoverText": "#ff8c8c8c",
          "StrongBodyText": "#ff555555",
          "SiteTitle": "#ff555555",
          "CommandLinksSecondary": "#ff555555",
          "SubtleEmphasisCommandLinks": "#ff555555",
          "HeaderSiteTitle": "#ff555555",
          "TileBackgroundOverlay": "#7f3f3f3f",
          "ContentAccent2": "#ff00485b",
          "ContentAccent3": "#ff288054",
          "ContentAccent4": "#ff767956",
          "ContentAccent5": "#ffed0033",
          "ContentAccent6": "#ff682a7a"
        }
      }
    }

What if site templates can’t do the thing you want?

Adding managed metadata columns

The basic createSiteColumn action supports only the following types of columns: Text, Note, Number, Boolean, User, and DateTime.

If you’d like to create managed metadata columns using site templates, I highly recommend you to read Beau Cameron’s blog post about how to make that happen.

Adjusting page layouts, web parts etc.

Currently site templates cannot, for example, adjust page layouts and web part settings. However, there is a thing called PnP provisioning engine (which I’ve already mentioned a couple of times in this blog post) that does a similar thing as site templates but is more feature rich. It is very likely, that you’ll find what you need in its latest schema.

PnP provisioning is great, but there are a few things it can’t do compared to site templates:

  • It can’t be initiated via the out-of-the-box SharePoint graphical user interface.
  • It can’t trigger a Flow or a Logic app.
  • It can’t automatically configure list view formatting.

It is typically the first bullet, why people want to rather use site templates over PnP templates. But you don’t necessarily have to settle with either or. You can combine them, which means you are able to trigger the application process from the SharePoint ootb GUI but you have the PnP provisioning features available. This is basically how it is done:

  1. Create a new resource group in Azure or ask your admin to do it for you.
  2. Create the following resources in the resource group: Azure logic app and function app.
  3. With the function app, a storage account is also created. Go to the storage account -> queues, and create a new queue.
  4. Add “When a HTTP request is received” trigger to your logic app. Then, add an action that adds the site information the trigger receives to the Azure storage queue.
  5. Create an Azure function that gets triggered when a new item appears in the queue.
  6. In the Azure function, apply your PnP template.

The whole process is described in great detail in this article.

Deploying Your Site Script and Site Template

Before you can use your site script, you need to deploy it to your tenant. You also need to create the site template where you’ll attach the deployed site script. At the moment, you can manage site templates and site scripts only via PowerShell or REST, but in the future, you will also be able to manage site templates via the admin center.

You can deploy your site script and attach it to a new site template with the PowerShell script below. Before running the script, change the variable values to fit your purpose. Is your site script for a team or a communication site? What would be a descriptive name and description for it? Do you want to use a custom preview image or the default one? The script below expects the JSON file to be in the same folder with it. However, you can change the path to point somewhere else too, if you like.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$siteScriptFile = $PSScriptRoot + "\CustomTeamSiteScript.json"
$webTemplate = "64" #64 = Team Site, 68 = Communication Site, 1 = Groupless Team Site
$siteScriptTitle = "Custom Team Site Script"
$siteDesignTitle = "Custom Team Site Template"
$siteDesignDescription = "Custom team site template with multi-colored theme, external sharing disabled and some cool stuff via Power Automate."
$previewImageUrl = "/SiteDesignPreviewImages/custom-team-site-design-preview-image.png" # if left empty, the default preview image will be used.
$designPackageId = "6142d2a0-63a5-4ba0-aede-d9fefca2c767" # The default site template to use as a base when creating a communication site, more info later.

$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

$siteScript = (Get-Content $siteScriptFile -Raw | Add-SPOSiteScript -Title $siteScriptTitle) | Select -First 1 Id

Add-SPOSiteDesign -SiteScripts $siteScript.Id -Title $siteDesignTitle -WebTemplate $webTemplate -Description $siteDesignDescription -PreviewImageUrl $previewImageUrl -DesignPackageId $designPackageId

The Web Template property

When you are creating a site template, you need to define which web template it is for. You probably already noticed from the script above that you can use certain numeric values to make your site template available for certain type of sites. What the value actually does, it maps your site template to the correct dropdown menu in the Create site UI.

The value just handles how your site template appears in the UI. You can programmatically run any of the site templates on any site, even classic ones. If some actions in a site template are not compatible with the site type, those actions will either be skipped or they will fail gracefully. If you set the WebTemplate value to anything else than 64, 68 or 1, your site template is hidden from the Create site UI. However, it can still be run on any site programmatically, and on all modern sites via the site template panel after the site has been created. The dropdown in the site template panel on existing modern sites shows all site templates.

WebTemplate value Appearance
64 Team site dropdown for users who are allowed to create Office 365 groups.
68 Communication site dropdown.
1 Team site dropdown for users who are not allowed to create Office 365 groups. Manage who can create Office 365 Groups
Any other value Only the site template panel on existing modern sites.

The Design Package Id property

When you create a site using your custom site template, what happens in the background is that the site actually gets created first using one of the default site templates. For team sites, this always means the Team Site design, but for communication sites, there are three different site templates to choose from.

If you don’t provide a value for the property at all, then the Topic site template is used. However, you can specify your site template to use one of the other two site templates by setting their GUID to the DesignPackageId property. You can see the GUIDs in the table below.

Default site template Design package ID value
Topic null or 00000000-0000-0000-0000-000000000000
Showcase 6142d2a0-63a5-4ba0-aede-d9fefca2c767
Blank f6cc5403-0d63-442e-96c0-285923709ffc

Deploying Site Templates and Site Scripts via PnP Provisioning

In the past, PnP provisioning templates were used just for configuring individual SharePoint sites (still the most common use case today). Typically the process went something like this: a user places an order for a site, which is then picked up by your provisioning pipeline. Your provisioning pipeline creates the base site, fetches the PnP template from somewhere (e.g., SharePoint document library) and uses the template to automatically apply the defined configurations to the new site via the PnP engine.

These days you can also do a lot of other things with PnP provisioning, such as deploy your SharePoint site templates and site scripts to your tenant. When this capability was released in early 2018, I originally got the impression that you would be able to apply a site template to a site using a provisioning template. For a moment, I giggled at the idea of creating an infinite loop by applying a site template that applies a PnP template via Power Automate, which again applies the site template. Then I found out that the feature was about deploying site templates and site scripts, and my first thought was “Err, uh, that’s weird?”.

Back then I was thinking about the situation from the traditional point of view that the same template would be applied every time a new site was created. But “PnP tenant templates” are an entirely different thing. If you have a more productized solution, which you need to set up in multiple different tenants, you can use the PnP provisioning tenant templates to setup setup/update configurations related to the entire tenant, not just a single site. If this piqued your interest, have a look at the latest PnP Provisioning Schema.

Maintaining your site templates and site scripts

One important thing to be aware of is that you can attach multiple site scripts to one site template. Why is this important? Think about a situation where you have many site templates, and all of them include a set of certain actions. For example, all the site templates apply the same company branded color theme and set the same regional settings but have different list configurations. As time goes by, you might need to make changes to these actions. It would end up being a lot of work to update each of the site templates individually.

Instead, you can have one or more “common” site scripts, where you’ll put the actions that are shared by your site templates. Then you include both this common site script and the site template specific site script in that single site template. This way, you’ll only need to modify one site script when you want to make changes to the actions shared by all your site templates.

Printing existing site template and site script information

After you have deployed your site script and site template to your tenant, you can output their properties — and information about all other deployed site scripts and designs — anytime with the script below. You’ll most likely find yourself running this script whenever you want to modify or apply your site templates, as you’ll need the id values.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

Get-SPOSiteDesign
Get-SPOSiteScript

Modifying an existing site template

You can edit a deployed site template by using the Set-SPOSiteDesign commandlet. Here we’ll just add a second site script to our existing site template. Note that you also need to include the existing site script in the SiteScripts parameter value if you want to keep it as a part of the site template. The site scripts will get executed in the order in which they are defined in this parameter. You should consider carefully, if one of the site scripts should be executed before the other.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$siteScriptFile = $PSScriptRoot + "\CommonSiteScript.json"
$siteScriptTitle = "Common Site Script"
$siteDesignId = "eb13d8b6-c835-4b07-80fd-d01bd10de666"
$existingSiteScripts = "4970ebaf-a3c0-461c-9516-0c8bf961b138"

$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

$newSiteScript = (Get-Content $siteScriptFile -Raw | Add-SPOSiteScript -Title $siteScriptTitle) | Select -First 1 Id

Set-SPOSiteDesign -Identity $siteDesignId -SiteScripts $existingSiteScripts, $newSiteScript.Id

Modifying an existing site script

Later, if you want to make changes to the common site script, you can do that by modifying the original JSON file (yes, do add these files to version control) and updating the site script Content property using the Set-SPOSiteScript commandlet.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$siteScriptId = "d59210a8-334b-41f1-9562-236562cc9855"
$siteScriptFile = $PSScriptRoot + "\CommonSiteScript.json"

$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

Set-SPOSiteScript -Identity $siteScriptId -Content (Get-Content $siteScriptFile -Raw)

Deleting your site templates and site scripts

If you ever want to delete any of your site templates or scripts, you can do that with these commands. Deleting a site template doesn’t automatically remove the site script attached to it, you need to do that separately. Remember though, that another site template might use the same site script, in which case you should not delete it.

If you delete a site script attached to a site template and then print the site template information, it will look like as if the site script was still in place. However, the actions defined in that site script are no longer executed when the site template is applied. Even though this doesn’t break any functionality, it’d be good for clarity to always remove site scripts from site templates before deleting them. Instructions for doing that are presented later in this article.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$siteDesignId = "eb13d8b6-c835-4b07-80fd-d01bd10de666"
$siteScriptId = "4970ebaf-a3c0-461c-9516-0c8bf961b138"

$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

Remove-SPOSiteDesign -Identity $siteDesignId
Remove-SPOSiteScript -Identity $siteScriptId

Applying Site Templates via the Graphical User Interface

Now that we are all set up, let’s start applying your custom site template to sites! You can give it a quick whirl by going to SharePoint Online and creating a new site via the GUI. After clicking Create site and selecting either Team site or Communication site (whichever you created the site template for), you should see your custom site template listed in the drop-down menu. In this case it is the Custom Team Site Template. When you finish with the site creation wizard, you’ll get directed to the new site and will see the actions defined in the site script being applied.

Custom team site template selected
Custom team site template applied

These days you can also apply site templates to a site via the UI after the site has been created. This happens through the new site template panel which you can find behind the cogwheel → Site templates option if you are a site owner or admin, and your tenant is in targeted release.

Limiting Who Can Use Site Templates

There might be situations in which you want to limit who can create new sites using your custom site templates. It may be because of security concerns or a case of usability. For example, if your site template triggers a Power Automate flow, you need to evaluate whether those actions are something you want everyone to be able to execute. Or if you have a lot of custom site templates and some people only need to use a few of them, you might as well hide the rest of the site templates from their view. This makes it easier for them to pick the right site template from the drop-down menu.

All the site templates are public and visible to everyone by default (unless group creation has been limited in the tenant — check the The Web Template property section for more info). After you grant permissions to certain groups or users, only those will be able to see and use the site template. Limiting the visibility of the default site templates is not possible.

You can set the site template permissions with the script below. You can assign permissions to security groups as well as individual users. If you want to assign permissions to several principals, separate them with a comma. The value of the Rights parameter is always View.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$siteDesignId = "eb13d8b6-c835-4b07-80fd-d01bd10de666"
$principals = "Security Group Name", "user@mytenant.onmicrosoft.com"

$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

Grant-SPOSiteDesignRights -Identity $siteDesignId -Principals $principals -Rights View
Get-SPOSiteDesignRights -Identity $siteDesignId

Later, if you want to remove the permissions, use the Revoke-SPOSiteDesignRights commandlet and specify the principals you want to remove permissions from. If you remove all permissions, the site template will become public again.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$siteDesignId = "eb13d8b6-c835-4b07-80fd-d01bd10de666"
$principals = "user@mytenant.onmicrosoft.com"

$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

Revoke-SPOSiteDesignRights -Identity $siteDesignId -Principals $principals
Get-SPOSiteDesignRights -Identity $siteDesignId

Customizing the Default SharePoint Site Templates

SharePoint Online has four default site templates: one for team sites (Team Site) and three for communication sites (Topic, Showcase, and Blank). It is possible to add additional actions to these default site templates. This happens via the IsDefaut property.

When you set the IsDefaut property of your site template to True, your site template will no longer be visible in the drop-down menu. Instead, the actions defined within your site template will be executed whenever any of the default site templates (of that site type) is used. For example, if you create a custom site template for communication sites and set its IsDefault property to True, the actions included in it will be executed whenever any of the default site templates (Topic, Showcase or Blank) is used.

Default SharePoint site templates
Custom communication site template applied

You can toggle this setting whenever you want with the script below. You can also set the IsDefault property to True already when you are originally creating your site template.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$siteDesignId = "b80571ae-656b-44c9-842e-bfc9ea6d84ea"
$isDefault = $true # or $false

$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

Set-SPOSiteDesign -Identity $siteDesignId -IsDefault:$isDefault

Applying Site Templates Programmatically

As a developer, you most likely also want to apply your site templates programmatically. You can do that to existing sites via PowerShell, CSOM, and the REST API. Applying the default site templates to existing sites is not possible.

You can apply your site template on a site with the script below. The GUID for the Identity parameter can be printed out with the Get-SPOSiteDesign command that was presented earlier in this post. If your site template has more than 30 verbs in total, you need to use the Add-SPOSiteDesignTask commandlet instead. See the Large site templates section for more information.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$siteUrl = "https://mytenant.sharepoint.com/sites/siteUrl"
$siteDesignId = "eb13d8b6-c835-4b07-80fd-d01bd10de666"

$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

Invoke-SPOSiteDesign -Identity $siteDesignId -WebUrl $siteUrl

The CSOM version requires a bit more code but is still quite simple. Here you’ll first get the admin site context to be able to get the tenant object, which you’ll then use for applying your site template on a site.

public void ApplySiteDesign(string siteUrl, Guid siteDesignId)
{
   try
   {
      using (var ctx = GetClientContext(GetAdminSiteUrl(siteUrl)))
      {
         var tenant = new Tenant(ctx);
         tenant.ApplySiteDesign(siteUrl, siteDesignId);
         ctx.ExecuteQuery();
      }
   }
   catch (Exception e)
   {
      Console.WriteLine($"Failed to apply site template: {e.Message}");
   }
}

public static string GetAdminSiteUrl(string url)
{
    const string suffix = ".sharepoint.com";
    var index = url.IndexOf(suffix, StringComparison.OrdinalIgnoreCase);
    url = url.Substring(0, index + suffix.Length);
    return url.Insert(index, "-admin");
}

public static ClientContext GetClientContext(string url)
{ 
    // Create a client context using your preferred authentication method
}

You can also apply a site template to an existing site by calling the REST API either with delegated or application permissions. Here are a couple of examples of how you can make the request from Power Automate.

To apply the site template as the Power Automate flow author, you can use the Send an HTTP request to SharePoint action. No need to take any extra steps to handle authentication.

If you wish to apply the site template with the application identity, you need to authenticate with a certificate. I’ve written another blog article about how to setup a certificate for authentication, step-by-step.

Apply Site Template via Power Automate

If you have ever created communication sites programmatically, you’ve most likely noticed the SiteDesignId parameter. You can’t specify your own site template id in it. Or well, you can when using PnP CSOM or REST API, but it has no effect; the Topic site template will be applied regardless. You need to create the site first using one of the default site templates and then separately apply your own site template after the site has been created.

Large site templates

Each “normal” site template is limited to total of 30 verbs/actions (including sub-actions) or 20 000 characters. A large site template, on the other hand, is limited to 300 actions (again, including sub-actions) or 100 000 characters. There is no other difference between normal and large site templates themselves. However, there is a big difference on how you need to apply them.

For regular site templates, you can use the Invoke-SPOSiteDesign commandlet. Also, at the moment you can only create sites via the UI using the regular site templates. Site templates with more than 30 actions will get stuck on Initializing…

Support for successfully creating sites with large site templates is coming to the UI in the near future. However, you can already apply a large site template via the site template panel (cogwheel → Site templates — targeted release) after the site has been created first with another site template. You can also associate a large site template to a hub site and it will run successfully for the sites that are added to the hub. More about that later.

Applying a large site template is also possible programmatically with the Add-SPOSiteDesignTask commandlet (below) or the AddSiteDesignTaskToCurrentWeb REST operation.

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
$siteUrl = "https://mytenant.sharepoint.com/sites/sitename"
$siteDesignId = "6b24b2e7-68e8-4f14-972c-24453d661e22"

$cred = Get-Credential
Connect-SPOService $adminSiteUrl -Credential $cred

$task = Add-SPOSiteDesignTask -SiteDesignId $siteDesignId -WebUrl $siteUrl

The Add-SPOSiteDesignTask commandlet does not execute the site template immediately. Instead, it adds it to a queue and schedules it to be run asynchronously in the background.

If you change your mind and want to cancel the execution of the queued large site template, you can do it with the Remove-SPOSiteDesignTask commandlet — if you are quick enough. After the execution has started, you can no longer cancel it.

Monitoring what site templates and site script actions have been run for a site

So how do you know when your large site template has been successfully executed? To monitor the status, you can run the following script in conjunction with the script in the previous section.

Get-SPOSiteDesignTask -Identity $task.id -WebUrl $siteUrl

$run = Get-SPOSiteDesignRun -WebUrl $siteUrl | select -First 1

if ($run.id -eq $task.id)
{
    Get-SPOSiteDesignRunStatus -Run $run
}

Get-SPOSiteDesignTask prints the created site template task information. The task exists before and during execution, and is deleted immediately after all actions in the site template have been performed.

Get-SPOSiteDesignRun gets a collection of all the site templates that have ever been run for the site. Get-SPOSiteDesignRunStatus, again, prints out all the individual actions executed by a single site template. By running the script above, you can check if your task is still in the queue or executing, what actions it has executed so far while it is running, and when it has finished (task no longer found).

The last two commandlets are also useful if you, later on, want to find out what operations have been performed on a site via site templates and site scripts. Perhaps you want to make a change to all sites where a certain site template or action has been run in the past? The Get-SPOSiteDesignRun and Get-SPOSiteDesignRunStatus commandlets can help you get that information. You can also find this information in the UI via the new site templates panel.

Associating a site template to a hub site

These days you can also associate a site template to a hub site. What this means is that when you join a site to the hub, the site template will automatically get applied to the joined site.

The site template WebTemplate value doesn’t necessarily need to match the site type you are planning on joining the hub. As mentioned in The WebTemplate property section, the property value only controls in which dropdown your site template appears in the Create site UI.

You can join both team sites and communication sites to the same hub, and the site template will get executed successfully for both. If some actions in the site template are not compatible with the site type, those actions will either be skipped or they will fail gracefully.

If you do not yet have a hub site, you can create one with the script I’ve presented in another one of my blog posts: My most used PowerShell scripts for managing SharePoint Online. After you have your hub site up and running, there’s two ways you can use to associate a site template to your hub: using the graphical user interface or PowerShell.

If you are content doing the association via the UI, you need to go to the hub site, and click on the cogwheel → Hub site settings. There you have a dropdown menu for selecting the site template.

If you prefer to associate your site template to a hub programmatically, you can do that with the script below. Currently there is no operation for associating a site template to a hub via the REST API.

$hubSiteUrl = "https://mytenant.sharepoint.com/sites/hub"
$siteDesignId = "8a72217f-8383-4c74-9ff8-7b2c9e43fecb"

$adminSiteUrl = "https://mytenant-admin.sharepoint.com"
Connect-SPOService $adminSiteUrl

Set-SPOHubSite $hubSiteUrl -SiteDesignId $siteDesignId # You can also set Title, Description and LogoUrl if you want

Now you have a site template attached to a hub site, and it will get executed to all sites that are added to the hub!

So what happens if you remove a site from the hub? By now, you should already be familiar with the nature of site templates, so you can probably guess what happens when you remove a site from the hub — or what doesn’t happen, rather. The changes that are made when you join a site to a hub don’t get reversed if you later decide to remove the site from the hub. The site will remain as it is.

Summary of operations available through PowerShell and REST

There are PowerShell commandlets and REST API operations for almost all of the tasks related to site templates. However, it can be a bit tricky to spot which commands match each other between these technologies, especially when not all operations are documented/supported for REST. Below, you can find a table of those matches.

PowerShell REST API
Add-SPOSiteDesign CreateSiteDesign
Add-SPOSiteDesignTask AddSiteDesignTaskToCurrentWeb
Add-SPOSiteScript CreateSiteScript
Get-SPOSiteDesign GetSiteDesigns
Get-SPOSiteDesign (with -Identity) GetSiteDesignMetadata
Get-SPOSiteDesignRights GetSiteDesignRights
Get-SPOSiteDesignRun Undocumented
Get-SPOSiteDesignRunStatus Undocumented
Get-SPOSiteDesignTask Undocumented
Get-SPOSiteScript GetSiteScripts
Get-SPOSiteScript (with -Identity) GetSiteScriptMetadata
Get-SPOSiteScriptFromList GetSiteScriptFromList
Get-SPOSiteScriptFromWeb Undocumented
Grant-SPOSiteDesignRights GrantSiteDesignRights
Invoke-SPOSiteDesign ApplySiteDesign
Remove-SPOSiteDesign DeleteSiteDesign
Remove-SPOSiteDesignTask Undocumented
Remove-SPOSiteScript DeleteSiteScript
Revoke-SPOSiteDesignRights RevokeSiteDesignRights
Set-SPOSiteDesign UpdateSiteDesign
Set-SPOSiteScript UpdateSiteScript

Undocumented REST operations

Even though some of the REST operations are not documented, it doesn’t mean that they aren’t available. Another matter is, if they are actually officially supported by Microsoft. If you wish to use them, here you can find the required information for making the calls.

The URL consists of a reference to the site where you want to get or manage the information + the API operation path.

Here you can download a Power Automate flow that I used for experimenting with these operations. You’ll receive a .zip file you can import to Power Automate.

Get Site Template Run

URL: site URL + /_api/Microsoft.SharePoint.Utilities.WebTemplateExtensions.SiteScriptUtility.GetSiteDesignRun
Method: POST
Body: Leave empty

Example response:

{
  "d": {
    "results": [
      {
        "__metadata": {
          "id": "https://laurakokkarinen.sharepoint.com/sites/sitename/_api/Microsoft.SharePoint.Utilities.WebTemplateExtensions.SiteDesignRun9a029c87-7bd5-4ff5-814b-33554f1f57d4",
          "uri": "https://laurakokkarinen.sharepoint.com/sites/sitename/_api/Microsoft.SharePoint.Utilities.WebTemplateExtensions.SiteDesignRun9a029c87-7bd5-4ff5-814b-33554f1f57d4",
          "type": "Microsoft.SharePoint.Utilities.WebTemplateExtensions.SiteDesignRun"
        },
        "ID": "048a8f00-a453-4859-8dbe-5666b0b62837",
        "SiteDesignID": "fa2c0f2d-a975-4d31-a070-ac47e68c08a4",
        "SiteDesignTitle": "Internal project",
        "SiteDesignVersion": 1,
        "SiteID": "e53a0551-aae4-45d6-bb6f-fe59ae28d40a",
        "StartTime": "1566919075000",
        "WebID": "e093a487-dcb8-4531-8814-bca4be03f95c"
      }
    ]
  }
}

Get Site Template Status And Schema

URL: site URL + /_api/Microsoft.SharePoint.Utilities.WebTemplateExtensions.SiteScriptUtility.GetSiteDesignRunStatusAndSchema
Method: POST
Body: You can get the “runId” from the GetSiteDesignRun response. Note that GetSiteDesignRun response returns an array, so there can potentially be information on multiple runs.

{
"runId": "2eaf46f9-0566-4351-9f85-229f19de1149"
}

Example response:

{
  "d": {
    "GetSiteDesignRunStatusAndSchema": {
      "__metadata": {
        "type": "Microsoft.SharePoint.Utilities.WebTemplateExtensions.SPSiteScriptStatusAndSchema"
      },
      "ActionStatus": {
        "__metadata": {
          "type": "Collection(Microsoft.SharePoint.Utilities.WebTemplateExtensions.SiteScriptActionStatus)"
        },
        "results": [
          {
            "ActionIndex": 0,
            "ActionKey": "00000000-0000-0000-0000-000000000000",
            "ActionTitle": "Update site logo",
            "LastModified": "1566919681000",
            "OrdinalIndex": 0,
            "OutcomeCode": 0,
            "OutcomeText": null,
            "SiteScriptID": "f2a131a2-2c2e-4edf-9418-98ddd50c6d06",
            "SiteScriptIndex": 0,
            "SiteScriptTitle": "Internal project"
          },
          {
            "ActionIndex": 1,
            "ActionKey": "00000000-0000-0000-0000-000000000000",
            "ActionTitle": "Apply theme Multicolored Theme",
            "LastModified": "1566919683000",
            "OrdinalIndex": 1,
            "OutcomeCode": 0,
            "OutcomeText": null,
            "SiteScriptID": "f2a131a2-2c2e-4edf-9418-98ddd50c6d06",
            "SiteScriptIndex": 0,
            "SiteScriptTitle": "Internal project"
          }
        ]
      },
      "Schema": "{\"recipes\":[{\"actions\":[{\"stages\":[\"Update site logo\"]},{\"stages\":[\"Apply theme Multicolored Theme\"]}],\"recipeGuid\":\"f2a131a2-2c2e-4edf-9418-98ddd50c6d06\",\"recipeName\":\"Internal project\"}],\"siteDesignTitle\":\"Internal project\",\"siteDesignVersion\":1}"
    }
  }
}

Get Site Template Task

URL: site URL + /_api/Microsoft.SharePoint.Utilities.WebTemplateExtensions.SiteScriptUtility.GetSiteDesignTask
Method: POST
Body: You can get the “taskId” if you, e.g., create the task in your code using the AddSiteDesignTaskToCurrentWeb operation (this one is officially documented) and get the ID from the response.

{
"taskId": "7177969d-bb02-4b55-b022-b2e419f2e138"
}

Example response:

{
  "d": {
    "GetSiteDesignTask": {
      "__metadata": {
        "type": "Microsoft.SharePoint.Utilities.WebTemplateExtensions.SiteDesignTask"
      },
      "ID": "26bfc180-9422-40b8-8c71-8a754c3815d3",
      "LogonName": "i:0#.f|membership|laura.kokkarinen@laurakokkarinen.onmicrosoft.com",
      "SiteDesignID": "e4d17c1d-a11b-48e0-8c99-684ec101207f",
      "SiteID": "e53a0551-aae4-45d6-bb6f-fe59ae28d40a",
      "WebID": "e093a487-dcb8-4531-8814-bca4be03f95c"
    }
  }
}

Remove Site Template Task

URL: site URL + /_api/Microsoft.SharePoint.Utilities.WebTemplateExtensions.SiteScriptUtility.RemoveSiteDesignTask
Method: POST
Body:

{
"taskId": "7177969d-bb02-4b55-b022-b2e419f2e138"
}

Example response:

{
  "d": {
    "GetSiteDesignTask": null
  }
}

Get Site Script From Web

As of this writing, this is currently still an experimental feature and not enabled in normal tenants. I’ll update this section when it becomes available.

URL: site URL + /_api/Microsoft.SharePoint.Utilities.WebTemplateExtensions.SiteScriptUtility.GetSiteScriptFromWeb
Method: POST

The Future of Site Templates

New actions are continuously being added to the SharePoint site template schema. Microsoft wants site templates to be able to do everything PnP provisioning templates can do at some point [source]. However, due to the limited amount of resources, the development of site templates is much slower than the development of the community-driven PnP provisioning templates. That’s why we are still using PnP provisioning until site templates have time to catch up. Whether that will actually ever happen is up for debate.

There are still a lot of things you can do with PnP provisioning that you can’t do with SharePoint site templates. But then again, there are also things you can’t do with PnP provisioning that you can do with site templates, such as restrict external sharing and start a Power Automate flow. So, now, site templates and PnP provisioning templates complement each other, and that’s why you’ll most likely find yourself using them both.

Here is a small recap of the features that we can expect in the future (announced at Ignite 2018):

  • Site template management from the admin center. Hopefully, this will include all CRUD operations for site templates and site scripts: deploying new site templates and scripts; listing, editing and deleting the existing ones.
  • Site script editor. Will this be a script generator, or simply allow you to edit the JSON by hand and update it more easily to the tenant?
  • Site script actions for site pages and web parts. Actions for adjusting page layouts and adding web parts with preset configurations is a highly requested capability. It will be interesting to see if this will include all of the same capabilities as PnP templates, or if there will even be something we haven’t been able to do before.
  • Script actions for other Office 365 applications. What could this include? Possibility to automatically provision a Team for a new group connected Team site, for example? Send an email? Call Graph? How wild ideas do I dare to come up with?

 

Is this your first time using site templates or have you already had some fun with them? What do you think, what are the cool things and what things you find yourself missing the most? Let me know in the comments below! Also, if you found this article useful and would like to read similar content in the future, follow me on Twitter to be notified of future posts.

Thank you for reading; it is greatly appreciated as always! There wouldn’t be much point writing this blog if you didn’t. 😉 Until next time!

Laura



238 thoughts on “The Ultimate Guide to SharePoint Site Templates and Site Scripts”

  • Laura, this is a great guide and I appreciate you writing. I’m starting to delve into it. I wanted to ask you if update it from time to time? For example, in the tools sections, the tool “Site Template Studio” is 4 years outdated and seems to be abandoned project. And the part about ADDING MANAGED METADATA COLUMNS – it seems the Get-SPOSiteScriptFromWeb support this and will extract the info about this column.
    Anyway I’ll keep reading now, please keep up the good work, you have no idea how long I searched for a complete guide for this complicated thing.

  • Hi Laura, great article. However one little error in your example code of Get-SPOSiteScriptFromWeb. Your code did not work, after some frustrated searching I found that the parameter is -Includedlists in stead of -Includelists. Maybe you can update your example code to save others the search. Kind regards, Nico

  • Hi Laura, Excellent article. I am having problems to do the site design in English & French. I have been searching for few days and couldn’t find any related article. When the default site is in French it should display all custom library/lists names in French. How do we do this? I tried 2 site scripts with different LCID and did not seem to work. Your advice in this regard would be much appreciated.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.