Deploy Managed Metadata fields using site designs and site scripts

(Notice: This post may show unsupported methods for provisioning fields)

You may be in a scenario where you want to create Managed Metadata columns using modern provisioning. This post is going to outline a method that can be used to provision Managed Metadata columns using site designs. Site designs are a new model for provisioning assets (fields,content types, lists, etc…) in modern SharePoint. Information on site designs and site scripts can be found here.

Multiple ways to create columns

Using a site script,  you can create both list and site columns using two different actions — “addSPField” and “addSiteColumn”. The first will deploy a list column and the second action will deploy a site column. However, these two actions only support basic field types in SharePoint. These field types include:

  • Text
  • Note
  • Number
  • Boolean
  • User
  • DateTime

Complex columns such as people fields and managed metadata fields need to be created using the Field Element definition. Using site designs and site scripts, we can provision complex fields using the Field definition with the addSPFieldXml and createSiteColumnXML actions.

There are many ways you can get the Field definition after they have been created in the SharePoint UI. Here are a couple options you could use to export out the schema XML for a list and get the field definitions.


You could also use PnP PowerShell to export out the schema

 $list = Get-PnPList -Identity "your list" -Includes SchemaXml
 $schema = $list.SchemaXml

Which complex fields are supported?

If look through the site script documentation you may notice, it doesn’t actually define which field types are supposed through XML. In regards to a complex field such as a Managed Metadata field, we took to twitter to see if Microsoft had any input. Sean Squires mentioned that currently they do not support Managed Metadata Fields, at least in an elegant way (which to me means, if it works great! if it doesn’t…it’s not officially supported).

How it can be achieved

Managed Metadata Fields are complex fields that actually require two fields to be deployed in order to function properly. One field is a TaxonomyFieldType and another is a Note field. This means we actually have to deploy two fields during the provisioning process.

When you export out a list schema and find your Managed Metadata field, you’ll notice it references the Note field using the ID of the Note field in the TextField node. You may also notice there are tokens for properties such as WebId, SiteId, ListId in your Field definition. In order for the deployment of the field to work we need to remove these token properties.

Secondly, we’ll need to update the <Property> nodes inside the <Customziation> node for the following properties.

  • SspId
  • TermSetId
  • AnchorId

By default, these properties may be using braces to identify the GUID for the Term store, Term set and Anchor. We need to remove the braces and leave the rest of the GUID string.

Here is an example of a cleaned up XML for a Managed Metadata field. Notice how I have removed the {braces} from around the GUIDs for SspdId, TermSetId and AnchorId (in bold).

<Field Type="TaxonomyFieldType" Name="MyTestMMD" SourceID="" StaticName="MyTestMMD" DisplayName="My Test Tax" Group="Test group" ShowField="Term1033" Required="FALSE" EnforceUniqueValues="FALSE" Mult="FALSE"> <Default /> <Customization> <ArrayOfProperty> <Property> <Name>SspId</Name> <Value xmlns:p4="" xmlns:q1="" p4:type="q1:string">1ba8a7c0-a373-4bb7-8ee0-2d130933743a</Value> </Property> <Property> <Name>GroupId</Name> <Value xmlns:p4="" xmlns:q2="" p4:type="q2:string">0e8f395e-ff58-4d45-9ff7-e331ab728beb</Value> </Property> <Property> <Name>TermSetId</Name> <Value xmlns:p4="" xmlns:q2="" p4:type="q2:string">487ba508-4fba-49f1-be4f-ceee2624da0a</Value> </Property> <Property> <Name>TextField</Name> <Value xmlns:p4="" xmlns:q6="" p4:type="q6:string">{44a5d269-ce40-4a8f-b0d6-6b7bcb95ff71}</Value> </Property> <Property> <Name>AnchorId</Name> <Value xmlns:p4="" xmlns:q3="" p4:type="q3:string">00000000-0000-0000-0000-000000000000</Value> </Property> <Property> <Name>IsPathRendered</Name> <Value xmlns:p4="" xmlns:q7="" p4:type="q7:boolean">false</Value> </Property> <Property> <Name>IsKeyword</Name> <Value xmlns:p4="" xmlns:q8="" p4:type="q8:boolean">false</Value> </Property> <Property> <Name>Open</Name> <Value xmlns:p4="" xmlns:q5="" p4:type="q5:boolean">false</Value> </Property> </ArrayOfProperty> </Customization> </Field>

A couple notes

If you fail to do this successfully, it may look like your column is working. However, SharePoint will not create managed or crawled properties correctly, thus making your column not searchable.

Final site script actions

The next thing we want to do is escape the XML so can we can use it inside a JSON site script. Then finally, we can add the Field definitions to our site script using the “createSiteColumnXml” action.

Here is an example of the two site script actions required to deploy a Managed Metadata field using site designs.

   "verb": "createSiteColumnXml",  
    "schemaXml": "<Field ID=\"{44a5d269-ce40-4a8f-b0d6-6b7bcb95ff71}\" SourceID=\"http:\/\/\/sharepoint\/v3\" Type=\"Note\" Name=\"MyTestMMDTaxHTField\" StaticName=\"MyTestMMDTaxHTField\" DisplayName=\"MyTestMMDTaxHTField\" Group=\"Test group\" ShowInViewForms=\"FALSE\" Required=\"FALSE\" Hidden=\"TRUE\" CanToggleHidden=\"TRUE\" />"  
  "verb": "createSiteColumnXml",  
  "schemaXml": "<Field Type=\"TaxonomyFieldType\" Name=\"MyTestMMD\" SourceID=\"http:\/\/\/sharepoint\/v3\" StaticName=\"MyTestMMD\" DisplayName=\"My Test Tax\" Group=\"Test group\" ShowField=\"Term1033\" Required=\"FALSE\" EnforceUniqueValues=\"FALSE\" Mult=\"FALSE\"> <Default></Default> <Customization> <ArrayOfProperty> <Property> <Name>SspId</Name> <Value xmlns:q1=\"\" p4:type=\"q1:string\" xmlns:p4=\"\">1ba8a7c0-a373-4bb7-8ee0-2d130933743a</Value> </Property> <Property> <Name>GroupId</Name> <Value xmlns:q2=\"\" p4:type=\"q2:string\" xmlns:p4=\"\">0e8f395e-ff58-4d45-9ff7-e331ab728beb</Value> </Property> <Property> <Name>TermSetId</Name> <Value xmlns:q2=\"\" p4:type=\"q2:string\" xmlns:p4=\"\">487ba508-4fba-49f1-be4f-ceee2624da0a</Value> </Property> <Property> <Name>TextField</Name> <Value xmlns:q6=\"\" p4:type=\"q6:string\" xmlns:p4=\"\">{44a5d269-ce40-4a8f-b0d6-6b7bcb95ff71}</Value> </Property> <Property> <Name>AnchorId</Name> <Value xmlns:q3=\"\" p4:type=\"q3:string\" xmlns:p4=\"\">00000000-0000-0000-0000-000000000000</Value> </Property> <Property> <Name>IsPathRendered</Name> <Value xmlns:q7=\"\" p4:type=\"q7:boolean\" xmlns:p4=\"\">false</Value> </Property> <Property> <Name>IsKeyword</Name> <Value xmlns:q8=\"\" p4:type=\"q8:boolean\" xmlns:p4=\"\">false</Value> </Property> <Property> <Name>Open</Name> <Value xmlns:q5=\"\" p4:type=\"q5:boolean\" xmlns:p4=\"\">false</Value> </Property> </ArrayOfProperty> </Customization> </Field>"  

You can see a full site script in this Gist.

Caveats & “uh ohs”

Using the site script actions above, you should be able to successfully create Managed Metadata fields using site designs. Just be aware that if you see some negative impacts when provisioning these…they are not fully supported by Microsoft.

Augment SharePoint site provisioning with messaging in real-time with

Modern provisioning in SharePoint has taken on a new framework called Site designs. Site designs are like templates in that they can be used each time a new site is created in your Office 365 tenancy. A site design is essentially a list of actions (site scripts) that you want SharePoint to execute when creating new sites. These actions may include:

  • Creating a new list or library (or modifying the default one created with the site)
  • Creating site columns, content types, and configuring other list settings
  • Applying a theme
  • Setting a site logo
  • Adding navigation
  • Triggering a Microsoft Flow
  • Installing a deployed solution from the app catalog
  • Setting regional settings for the site
  • Setting external sharing capability for the site

Extending site designs

While site designs are continually being improved and expanded on by Microsoft, there are some gaps. From the beginning, Microsoft was aware they wouldn’t be able to fill all  gaps from the start so they allowed us to extend the out of the box provisioning using Microsoft Flow and Azure Functions using the “triggerFlow” action. To learn more about this functionality follow this PnP documentation.


This model is extremely extensible and allows you to move some of the workload over to an Azure Function or Azure Automation Services to run PowerShell & C# to apply more custom provisioning artifacts such as pre-configured pages and web parts.

No Messaging?

Currently there is a limitation using this model. Given that the provisioning process has moved off into Azure, we are unable to notify the user what the current stage the Azure Function is currently running at.

Here is what the current messaging system looks like


I’ve been told that that ability to provide our own messaging into this dialog is “coming”, but currently I wanted to overcome this limitation.

Clippy is back!

Months ago when SPFx application customizers were released I jokingly built a Clippy extension to run on modern SharePoint pages. The goal was to show how to call Microsoft Graph from an application customizer.


I thought I would supercharge the functionality of Clippy by connecting him to the site design provisioning process. + Express

In order to do this, I had to come up with a mechanism for providing Clippy real-time updates during the provisioning process. I had recently started playing around with and I believed this would be the best route forward.

I implemented inside an Express api which is currently living up in the Azure. The plan is use this as a messaging relay between the Azure Functions and my Clippy extension.

This is what the new flow looks like


We start with a site design that sends the new site URL to Microsoft Flow. Microsoft Flow will pick up that trigger and send an HTTP request to a Durable Azure Function orchestrator in Azure.

The Durable Azure Function orchestrator allows me to split out provisioning tasks (create lists, create pages, provision navigation, apply branding) while at the same time, maintaining state across my Azure Function tasks. As the provisioning is happening, I will send updates to

The will receive the message from the Azure Function and update Clippy in real-time in SharePoint.

Socket.IO Configuration


In order to configure to be the messaging system, I had to make sure I was working with individual clients and not broadcasting messages across all Clippy extensions in my tenancy. To do this, I am emitting from Clippy a “room” name. This room is the siteURL of the current site Clippy is running.


By doing this, whenever  I send a message from to Clippy, I know exactly where to send it to.


Messaging to Clippy is done through a POST request to Socket.Io The POST request contains a message, the provisioning status (complete/running) and the URL (unique identifier for the room). I take these parameters and emit the message to the Clippy extensions where room name is equal to the Site URL.


Clippy Configuration

Configuring Clippy is quite simple, it’s as easy as connecting to in Azure and subscribing to the “emit” events.

Inside the _renderPlaceHolders() function of my application customizer, I am going to create a new Clippy and register to


Inside the _registerSocketIO, I instantiate a new socket request to Once connected, I am going to send a message with my “room” name. This room name is going to be uniquely identified by the siteUrl of the current site being provisioned.


I have two basic actions I am subscribing to — “provisioningUpdate” and “provisioningComplete”. When receives a message and broadcasts that, Clippy will pick it up and “speak” to the user the current update from provisioning.

The Azure Function

As previously mentioned, I am using a Durable Function to do the provisioning process. To learn about Durable Functions follow this link.  Here is what the Orchestrator tasks looks like.

Asure Function Design.png

Notice that I am passing in the siteUrl coming from my Microsoft Flow HTTP trigger. Inside, I have a couple tasks that are running.

  • Establish site as a hub site
  • Provisioning Lists
  • Provision Pages
  • Provision Terms

Also notice that I have an _emitUpdate() function which is running before and after my asynchronous tasks. During the provisioning process, this _emitUpdate is going to POST a message to

What a task looks like

Let’s take a look at what an individual task looks like.


My Azure Function task is going to create a new AppOnly context to SharePoint, load a provisioning template that contains a set of lists to be created and uses ApplyProvisioningTemplate(template) to apply the template to my site.

Inside _emitUpdate

My _emitUpdate is fairly simple. It’s going to take a message from the Orchestrator, create a new HttpClient and execute a POST message to my with the current status of the provisioning process.


Putting it together

Without going to deep into this (because it really is more high level education than anything)… let’s see what these messages look like in real time.

If Gif is missing… wait for it to load. Full size view can be found here.

ClippyProvision-min (1)

Want to learn more?

If you are looking to learn more about how I put this together, join me at SharePoint Saturday Denver on October 6th and I’ll be also be demoing this solution at SharePoint Saturday New England! Sign up!

Customizing the SharePoint Starter-Kit for Office 365 Part 3 – Collab Footer

The SharePoint Starter Kit is an open source initiative that provides an end to end solution for provisioning pre-built sites, web parts, extensions, site designs and more in an Office 365 environment. If you’ve explored it’s functionality and are looking to see how you can configure and customize it yourself, today we’ll be talking about the Collab footer.

The Collab footer is the menu bar (highlighted in red) at the bottom this image.


The Collab footer is a SharePoint Framework customizer that allows for showing a set of standardized links and supports personalized links for the current logged in user.  You may be thinking this is very similar to the Portal footer. In fact, it is very similar in that it shares the same personal links. That means when a user configures a personal link on the Collab footer, it will automatically show up on the Portal footer in the hub site as well!

The personal links are stored in a property on the User Profile that needs to be created by an admin. Instructions to do so can be found on the GitHub repository at this page.

Here is what the Collab footer looks like when hovering over nested links.


Where are the links stored?

Unlike the Portal footer, where the links are stored in a SharePoint list… the links in the Collab Footer are stored in the Term store which gets automatically provisioned during the deployment of the Starter Kit.


How to modify the links in the Collab Footer

In order to modify the links in the footer, you’ll need to edit the properties on each term within the PnP-CollabFooter-SharedLinks term set. Let’s make a modification to the Legal Policies link. By default, it links to and looks like this.


If you’d like to change the label for the link, just rename the term in the term set. Select the term and change the default label.


The link value is stored on the navigation property of the term in the term set. Click on the term and select Navigation from the tabbed menu.


Let’s modify the default value to now link to a new library in our HR site that holds policy documents and click save.



Modify the link icon

If you are adding your own links or wish to change the icon of an existing link, it’s very simple. The icon’s used for the links are stored on custom properties on the term and references UI Fabric Icons.

Navigate to the UI Fabric Icons site and select an icon that suits your needs. Let’s modify the lock icon to be the “Documentation” icon instead of a padlock. Hover over the icon with your mouse, and copy the value underneath it.


To change the icon, navigate to the Term Store and select the term you wish to change. Select Custom Properties from the tabbed menu and you’ll find a custom property called PnP-CollabFooter-Icon. The default value for Legal Policies is “Lock”. This value is the icon class from UI Fabric.

Replace this value in the custom property of the term with the Fabric Icon we copied.


If we reload the Collab footer we will see the new icon!


My new icon isn’t working!

You may find when you update the Icon with a different one, that it doesn’t render and instead you see a square box instead.


The reason for this is the SharePoint Framework isn’t using the latest version of Office UI Fabric. Because of this, you’ll have to choose a new icon.

Customize a menu for each Team site

After deploying the Starter Kit, you will notice the Collab footer is identical on both the HR and the Marketing site (this is by design). In the real world, it might make sense for department sites to have their own footer. In my environment, I had deployed an IT site along side the HR and Marketing team sites.

Let’s create a new footer for the Information Technology team site. In order to do so, here are the list of things we’ll need to do.

  1. Create a new term set for the IT site
  2. Remove the current Collab footer
  3. Add Collab footer back with new clientsidecomponentproperties

Create new Term Set


Start by creating a new Term Set under the PnPTermSets group. We’ll call it “PnP-CollabFooter-ITLinks”Create a couple terms, update their links in the Navigation menu and add the custom property for the icon from the Custom Properties menu.

Remove the current Collab footer.

In order to remove the existing Collab footer that was deployed from the Starter Kit, we’ll need to use PowerShell.

Remove-PnPCustomAction -Identity "CollabFooter"

Add updated Collab Footer

Now that we have removed the existing footer, we need up add the footer back and update the clientsidecomponentproperties before we do.

Clientsidecomponentproperties is a JSON object that defines a set of properties to be used by a SharePoint Framework component. Notice the property “sourceTermSet”.

Add-PnPCustomAction -Name "CollabFooter" -Title "CollabFooter" -ClientSideComponentId c0ab3b94-8609-40cf-861e-2a1759170b43 -Location "ClientSideExtension.ApplicationCustomizer" -ClientSideComponentProperties "{""sourceTermSet"":""PnP-CollabFooter-ITLinks"",""personalItemsStorageProperty"":""PnP-CollabFooter-MyLinks""}"

In the sourceTermSet value, I have set the value to the name of the new PnP-CollabFooter-ITLinks term set set we created earlier. Run this command and then navigate to your Information Technology site and see the new footer!


Final Thoughts

I hope you find this post helpful. The SharePoint Starter Kit is a great example of how to provision solutions into Office 365. It includes 17 different web parts and 7 application customizers which are free for you use to use and modify! If you have any questions or would like more information on this post, please comment and let me know!

Sharing is caring.

How To: Create Modern SharePoint site templates using Office 365 Site Designs

One of the biggest questions I see is, “How do we create site templates using Office 365 groups?”. It is no surprise that site templates were one of the most widely used features in previous versions of SharePoint. Site templates give site owners and SharePoint admins a way to incorporate some governance into the creation of team sites and expedite the creation process. However, this out-of-the-box functionality, until recently wasn’t available for new modern sites.  In this blog we’ll step through creating our own using an Office 365 group.

Before we begin you should be aware that we no longer call them “site templates”. In the modern SharePoint world, Microsoft has chosen to designate the names “site designs” for modern templating. So if you find yourself needing to search google, keep this in mind.

Secondly, site designs aren’t the only way to create templated sites within SharePoint. A very common and community driven framework known as PnP Provisioning it also a very common way to create templates and provision new sites into your Office 365 & SharePoint environments. If you would like to know the difference’s between site designs and PnP Provisioning please check out Chris O’Brien’s recent post.

A walk through

The Site Script

The first step to creating a modern site template is to create a site script using a JSON file. A site script is a file that defines that actions that SharePoint will run when a user selects your site design. Site scripts are provisioned using SharePoint Online Management Shell. For this example, we are going to create a new department team site template to be used within an Office365 Group. However, we will be adding in some PnP scripts to add a global navigation using the SharePoint Framework to the newly created site. To learn more about what actions are available, click here

 "$schema": "schema.json",
     "actions": [
           "verb": "applyTheme",
           "themeName": "Custom Black"
             "verb": "createSPList",
             "listName": "Team Projects",
             "templateType": 100,
             "subactions": [
                     "verb": "SetDescription",
                     "description": "List to hold Team Project Statuses"
                     "verb": "addSPField",
                     "fieldType": "Text",
                     "displayName": "Project Status",
                     "addToDefaultView": true,
                     "isRequired": true
                     "verb": "addSPField",
                     "fieldType": "User",
                     "displayName": "Project Manager",
                     "addToDefaultView": true,
                     "isRequired": true
                     "verb": "addSPField",
                     "fieldType": "Note",
                     "displayName": "Project Notes",
                     "isRequired": false
            "name":"Provision Assets for Group",
            "fieldDisplayName":"Project Status",
    "elmType": "div",
    "txtContent": "@currentField",
    "style": {
        "color": "#fff",
        "padding-left": "14px",
        "background-color": {
            "operator": "?",
            "operands": [
                    "operator": "==",
                    "operands": [
                    "operator": "?",
                    "operands": [
                            "operator": "==",
                            "operands": [
                            "operator": "?",
                            "operands": [
                                    "operator": "==",
                                    "operands": [
                                    "operator": "?",
                                    "operands": [
                                            "operator": "==",
                                            "operands": [
         "bindata": { },
 "version": 1


In this scenario, we will be using the following actions:


This action will create a new list named “Team Projects”. It will also create associated columns pertaining to a typical project.


This action will create those new fields on the Team Projects list.


This action will apply a created theme (either out of the box, or a custom one using the new fabric theme generator) and apply it to the newly created site.


This action is going to fire off a Microsoft Flow, passing the URL of the newly created site. The flow will trigger an Azure Function which is going to provision some SPFx components using PnP PowerShell.


This action is going to apply a JSON object for the Column Formatter to apply on the Team Projects list.

Create a Theme

Creating a theme for a modern site is a little bit different than we’ve done in the past using composed looks in classic SharePoint. We are going to want to use the new Theme Generator when creating themes for modern sites. This generator, though limited at the time, allows us to modify a primary color, body color and a background color.


Start by playing around with the color selectors. You’ll notice that at the bottom, the Fabric Palette will change colors and you can see how some fonts and controls will be themed when loaded into your SharePoint site. I am going to build a black and yellow theme. You’ll also notice it gives you a specified output in JSON,SASS and PowerShell. These will be used depending on how we upload the new theme to SharePoint — in our scenario, we’ll be using PowerShell.

Copy the contents of the PowerShell output and open up PowerShell. We’ll provision the new theme to our environment using the SharePoint Online Management Shell. For more detailed information on this process, please follow this post. To provision the theme, we’ll connect to our tenant and then using the Add-SPOTheme command we’ll provision the JSONObject

    $themepallette = @{
  "themePrimary" = "#00ffff";
  "themeLighterAlt" = "#f3fcfc";
  "themeLighter" = "#daffff";
  "themeLight" = "#affefe";
  "themeTertiary" = "#76ffff";
  "themeSecondary" = "#39ffff";
  "themeDarkAlt" = "#00c4c4";
  "themeDark" = "#009090";
  "themeDarker" = "#005252";
  "neutralLighterAlt" = "#f8f8f8";
  "neutralLighter" = "#f4f4f4";
  "neutralLight" = "#eaeaea";
  "neutralQuaternaryAlt" = "#dadada";
  "neutralQuaternary" = "#d0d0d0";
  "neutralTertiaryAlt" = "#c8c8c8";
  "neutralTertiary" = "#a6a6a6";
  "neutralSecondaryAlt" = "#767676";
  "neutralSecondary" = "#666666";
  "neutralPrimary" = "#333";
  "neutralPrimaryAlt" = "#3c3c3c";
  "neutralDark" = "#212121";
  "black" = "#000000";
  "white" = "#fff";
  "primaryBackground" = "#fff";
  "primaryText" = "#333"
Connect-SPOService -Url
Add-SPOTheme -Name "Custom Black" -Palette $themepallette -IsInverted $false

If you have a good eye, you’ll notice the -Name parameter is also the same name that is in the applyTheme action of my site script. This will be the new theme we provision when our new Site Design runs.

If you are wondering where this theme shows up, navigate to your SharePoint site, hit the gear in the top right and select Change the look. You’ll see your new theme!



Package and Upload SharePoint Framework Navigation

The reason I am deploying a custom global navigation solution is because Hub Sites currently do not exist in Office 365 tenants. So in the meantime, I’d still like to apply a global navigation across all of my sites that I create so that they feel “connected” in the new flat structure.

The first thing you will want to do is head over to the SharePoint extensions GitHub and pull down Paolo’s global navigation application customizer. This SPFx solution, is a global navigation menu that is injected into the header (and/or footer) of your SharePoint sites. It is a tenant-wide deployment, which means it will automatically be available to all site collections without having to add the app through site contents. The documentation provided should give you all the information required to set this up. If you need information on how to build customizations using SPFX, start here.

I have downloaded and built the SharePoint Framework solution and uploaded it to my app catalog within my tenancy. The next thing I need to do, is configure a global navigation that I want all of my modern sites to use and then we’ll proceed to creating some PowerShell scripts.



Create Azure Function & Call PnP to provision Global Navigation

As mentioned previously, we’ll be calling an Azure Function via the Microsoft Flow trigger. There are a couple ways to do this, either by setting up a Queue Function using App Authentication, or you configure your Azure Function to grab your credentials from environment variables. The preferred method is to set up your credentials using App-Only authentication as outlined in that blog post. You’ll want to use this post to figure out how to add PnP Modules to your Azure Function. In my scenario, I am actually going to set up an HTTPTrigger Azure Function because why not show a different example… and also because I may have a need to call this function directly in other scenarios.

Below is my Azure Function PowerShell. The PowerShell code is also available on Paolo’s github for the menu.

    #incoming webUrl from the Microsoft Flow call
    # POST method: $req
    $requestBody = Get-Content $req -Raw | ConvertFrom-Json
    $name = $
    # GET method: each querystring parameter is its own variable
    if ($req_query_name) 
        $webUrl = $req_query_name 

    Connect-PnPOnline -AppId $env:SPO_AppId -AppSecret $env:SPO_AppSecret -Url $webUrl
    $context = Get-PnPContext
    $web = Get-PnPWeb
    $ca = $web.UserCustomActions.Add()
    $ca.ClientSideComponentId = "b1efedb9-b371-4f5c-a90f-3742d1842cf3"
    $ca.ClientSideComponentProperties = "{""TopMenuTermSet"":""TenantGlobalNav"",""BottomMenuTermSet"":""TenantGlobalFooter""}"
    $ca.Location = "ClientSideExtension.ApplicationCustomizer"
    $ca.Name = "TenantGlobalNavBarCustomAction"
    $ca.Title = "TenantGlobalNavBarCustomAction"
    $ca.Description = "Custom action for Tenant Global NavBar Application Custom"

The code is going to recieve a parameter called webUrl and it will be the url of the new site created from my site design. This url is used in the Connect-PnPOnline command.

Create Microsoft Flow

Creating the Microsoft Flow is required because it will be the mechanism for which we pass a webUrl parameter to our azure function (we can’t call an Azure Function directly from a site script).The flow will consist of two actions: When a HTTP request is received and a HTTP requestAs previously mentioned, most tutorials will likely show you using flow to add a message to an Azure queue. I will be calling the Azure Function directly.

Use this JSON to place into your body JSON for your Http Request received 

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



Here is the full Flow. You need to grab the Uri to your Azure Function. You can get this URL by opening up your Azure Function and selecting </> Get function url in the top right of your code window.


You’ll notice that inside the body of the HTTP request, I am passing a parameter called webUrl, which is equal to the web url of the new site I am creating. This property comes through within the Flow request.


Update triggerFlow action in site script

Now that we have configured our Flow and our Azure Function, we need to connect the Flow to the site script (JSON file above). To do this, copy the Flow URL, When a HTTP request is received in your Flow (it’s outlined in grey in the image).

Copy and paste this into our site script in the triggerAction verb like so:


            "name":"Provision Assets for Group",


            "name":"Provision Assets for Group",

Upload site script and site design

That’s it! The only thing left to do is add the site script and site design using PowerShell. I have created the site script and saved it into a JSON file called SiteScriptProvisionGlobalNav.json.


    Connect-SPOService -Url ''
    #get JSON content and add site script
    Get-Content 'C:\Users\Me\SiteScriptProvisionGlobalNav.json'-Raw | Add-SPOSiteScript  -Title "Aerie Team Site"

Running this in PowerShell will return the ID of the new site script. We’ll use this ID to add the site design to the tenant. Notice -SiteScripts parameter is equal to the ID of the newly uploaded site script and -WebTemplate is “64”. The value 64 represents an Office 365 Group and the value 68 would represent a communication site.


Add-SPOSiteDesign -Title "Aerie Team Site" -WebTemplate "64" -SiteScripts "7a5c6fd8-0953-4381-a04d-6a4400448d0c" -Description "Creates a new Aerie Team Template Site with Global Nav"-PreviewImageUrl "" -PreviewImageAltText "site preview"



Add your team site!

Finally, the last thing to do is create your new team site from your site design and watch the magic happen!

Navigate to the /_layouts/15/SharePoint.aspx page and select + Create Site

Site Slection

Remember that we’ll be selecting a team site. On the next window, we should see our new site design.


After clicking next and finish, we should start seeing our site script do it’s work. It will start by applying our Custom Black theme, creating a new Team Projects list and subsequently triggering a flow to add global navigation to our newly created site



Now check out the final product! We have a newly created group site, with a SPFx global navigation menu in the header of the site!



How To: PnP Starter-Kit for Office 365 Part 2 – Tiles

Welcome back to another post in my blog series on the PnP Starter-kit. If you’ve found this post, it’s likely you already know what the PnP Starter Kit is. However, if you do not, I’ve written a quick starter post about it here. The PnP Starter Kit is an open source initiative that provides an end to end solution for provisioning pre-built sites, web parts, extensions, site designs and more in an Office 365 environment.

Today we’ll be talking about the Tiles web part (highlighted in red)!


The Tiles web part is a grid of links that can be customized using the out of the box page editing experience. This web part allows you to customize which icon to load, the size of the tiles and where the tiles link to. To start customizing, set the page in edit mode and edit the web part.



If you open up the properties, you will notice in the header a description that specifies how to change/add the icons to your tile. There is a link that brings you to the Office UI Fabric Icon page. This is important, but current limitation (feature?) of the Tiles web part is that you can only source icons from the Office Fabric Icon library.

One caveat you should be aware of as you navigate and start choosing icons, is that the Office UI Fabric site may be more up to date than the icons available in the SPFx solution.  This is because the version of Office UI Fabric used in the SharePoint Framework is a little behind the current release of Office UI Fabric. If you select an icon and it doesn’t load, this would be why.

Configure the Tiles

Unlike other implementations of tile links (promoted links for example), the Tiles web part isn’t list driven. What I mean by that, is in order to change the tiles and their links, you’ll have to actually open up and edit the web part every time. While it is a different editing experience, it actually has some real sweet under the covers. When you click “Configure the Tiles”, you’ll notice a new editing modal pop up.


This editing pane is a table that you can modify properties such as the Title, Description, URL, UI Fabric Icon and specify whether the link should load in the same window or a new tab.

What gets me excited most about this property pane, is that it is not built specifically for this web part. In fact, if you are building SPFx solutions and have a need for inserting complex data types of lists/collections, you can use this! The best part is that this control is included in the re-usable property pane controls repository here.

Create a new Link

Let’s say we are going to build a new Tile link which links to the Microsoft Forms web site. Let’s start by clicking “Configure the tiles” and adding a new record for our new link. We’ll put the title as “Forms” and specify the URL to



Next, we need to find an Icon to load into our tile. Let’s navigate to the UI Fabric website and find the icon for Microsoft Forms.


You’ll notice when you hover of the icon, it shows the name of the icon “OfficeFormsLogo”. This is actually the class name that UI Fabric uses to render icon. Let’s copy that value and we’ll paste it into our property pane for a new tile in the “UI Fabric icon name” field.


Now if you click “Add and Save”, you should see your new tile link!




Customizing the PnP Starter-Kit for Office 365 Part 1 – Portal Footer

If you’ve found this post, it’s likely you already know what the PnP Starter Kit is. However, if you do not, I’ve written a quick starter post about it here. The PnP Starter Kit is an open source initiative that provides an end to end solution for provisioning pre-built sites, web parts, extensions, site designs and more in an Office 365 environment.

While it really is an exhaustive example of how this can be done, many people are hoping to augment the solution to suit their own business needs. Because of this, I am going to start a series about how to configure and change certain aspects of the starter kit. In this post, we’ll talk about how to modify the footer in the hub site, aka “Portal Footer”.

The Portal Footer is the orange bar (highlighted in red) at the bottom this image.


The portal footer is an expanding footer that allows for showing standard company links and supports personalized links for the current logged in user. The personal links are stored in a property on the User Profile that needs to be created by an admin. Instructions to do so can be found on the GitHub repository at this page.

Here what the footer looks like expanded.


You’ll notice there are columns with a list of links inside. They are grouped by “Applications”, “Internal Modules”, “News” and “My Links” (the links that are held on the user profile).  The “Applications”, “Internal Modules” and “News” group of links are stored within a SharePoint list on the portal site in a list called “PnP-PortalFooter-Links”, which are deployed automatically with the Starter Kit. If you navigate to the list you will be able to see and modify the existing links and link groups that show in the footer.

Modify a company wide link in the footer

To modify an existing link in the footer, all you need to do is edit one of the links in the PnPPortalFooterLinks list. Let’s do that now. I am going to open up the CRM link item under “Applications” and change the title from “CRM” to “NEW CRM TITLE”.

Once I save the item, and reload the page and expand my footer, I’ll see my new title.


Permission the portal link items

While I rarely recommend item level permissions, there are times where this may make sense. Generally speaking, item level permissions can make it hard for IT staff members to manage what content users have access to in SharePoint. Also, in a list with many items, item level permissions become a performance concern. However, in the case of this links list, it’s unlikely we’ll have that many items so it should be fairly easy to manage.

The footer link can only access items in a list that the current logged in user has access to, thus allowing us to show or hide links to specific users. Let’s say that the ERP application in the “Applications” group should only be visible to a specific ERP team. Using out of the box permission, we can remove access and grant permissions to the item for that team only.

Add my own personal links

As previously mentioned, the portal footer allows for users to create and link to their favorite locations that they access the most. To do this, expand the footer using the arrows on the right. You’ll see the ability to add and edit the links on your profile.


If you are a SharePoint admin, you can navigate to the User Profiles in the SharePoint Admin center and see the values for links that users are storing on their profiles.



Update company name

If you look at the footer, you’ll notice it has a copyright for Contoso 2018 and also has a help desk link to Changing these values is the most common request I have seen from users who have tested out the starter kit and want to use these extensions. In order to do this, we actually need to modify the PnP Provisioning template provided with the solution because these values aren’t stored anywhere in SharePoint.

If you have downloaded/cloned the repository, navigate to the provisioning folder and open up the hubsite.xml file. This is the template that defines the structure of the portal site. It includes all of the pages, the navigation, the web parts and identifies which application customizers will load for this site. Towards the bottom of the file you will see an XML node called <pnp:CustomActions>, inside it defines the Portal Footer properties.

Let’s say I want to change the values highlighted in red that correspond to the Copyright and Help link in the footer. My fictional company is Cameronsoft and we manufacture fake widgets, so I definitely want to have my company’s name in the footer at all times. I will change these values in the template.xml before deploying the PnP starter kit.


Now, if i deploy the starter kit, my company’s information will be listed at the bottom instead of Contoso!


Updating an already deployed footer

Let’s say you’ve deployed the footer but you didn’t realize that it would be labeled with Contoso. I wouldn’t try to re-deploy the starter-kit, because you may find complications with that. So here is a set of PowerShell scripts to help you do so. Using PnP, you’ll need to connect to the hub site and remove the existing portal footer, and re-add a new one with new properties.

Remove-PnPCustomAction -Identity "PortalFooter"

Add-PnPCustomAction -Name "PortalFooter" -Title "PortalFooter" -ClientSideComponentId b8eb4ec9-934a-4248-a15f-863d27f94f60 -Location "ClientSideExtension.ApplicationCustomizer" -ClientSideComponentProperties "{""linksListTitle"":""PnP-PortalFooter-Links"",""copyright"":""@Copyright CameronSoft, 2018"",""support"":"""",""personalItemsStorageProperty"":""PnP-CollabFooter-MyLinks""}"

We start by connecting to the hub site using Connect-PnPOnline.

Then we remove the application customizer with the name “PortalFooter”. Followed up by Add-PnPCustomAction. Notice, the clientsidecomponentproperties is a JSON object that defines the values on the footer. Just update these to include your company instead of Constoso!

Modify Footer code

You may find the footer isn’t displayed exactly how you like, or you want to add your own custom functionality. Well, no problem there! This is an open source project and you are free to change anything you like. To find the footer code, open your sp-starter-kit folder in VS Code and navigate to /solution/src/extensions/portalfooter This is where you can modify the code to suite your needs. Once complete, just rebuild and package the solution then upload it to your app catalog! Instructions on SPFx and how to build and deploy apps can be found here.

Final Comments

I hope you find this post helpful. As I mentioned, I am going to be doing a short series on this for those of you who wish to customize or change the PnP Starter kit. If you have any questions, I suggest going to the GitHub repository first. Eric Overfield has done a fantastic job producing documentation for this solution. If you can’t find what you are looking for, you can ask a question in the issues list or you can just comment on this post and I’d be happy to help!

Edit HTML files in SharePoint Library

In Modern Office 365, one of the neat new features is the ability to edit HTML files by opening up a file from a Modern library. The HTML contents would load in the browser and were able to edit the HTML without having to download the file or edit the file using SharePoint Designer — this was a very powerful capability for many people.

Well, recently Microsoft has removed this capability (not sure why) as noted by Mark Rackley. Please vote in the UserVoice!

 What to do?

This wasn’t the first occurrence of this issue. I have found others posted about this topic on other social forums such as Reddit and Facebook. Because of this, I have decided to just build a simple little CommandSet button that will run on SharePoint libraries, which bypasses the new Microsoft changes and allows you to edit an HTML file in the browser. In order to do this, all we need to do is append &p=5 into the browser URL.

The Solution

The solution was quite simple. Build a SPFx CommandSet button, which allows the user to select an HTML file and then navigate to the view page with &p=5 appended to it.



The code is quite simple. When a user selects a file with a file type of “html”, show the command button. When the button is clicked, grab the FileName, file path and folder path. Construct a new URL, and append &p=5.

What it looks like

Select an HTML file, the CommandSet button shows up



Click “Edit HTML”, the page will redirect to the modern list view display form, with &p=5 to load the default edit view of the file.


Try it out

If you’d like to try it out, please do. I have uploaded the source and the sppkg file to my GitHub Repository.  The sppkg file will be in the sppkg folder. Upload this to your app catalog. Subsequently, you can deploy the app via Site Contents or using PowerShell.  Below is a sample PowerShell command for you. (This is old code, there are other ways to provision this)

 Connect-PnPOnline -Url
 $context = Get-PnPContext
 $web = Get-PnPWeb
 $ca = $web.UserCustomActions.Add()
 $ca.RegistrationType = "List"
 $ca.RegistrationId = "101"
 $ca.ClientSideComponentId = "6599b3cc-7631-4a55-962e-43d1757977ec"
 $ca.ClientSideComponentProperties = "{""sampleTextOne"":""Edit HTML""}" 
 $ca.Location = "ClientSideExtension.ListViewCommandSet.CommandBar"
 $ca.Name = "EditHTML CommandButton"
 $ca.Title = "EditHTML CommandButton"
 $ca.Description = "EditHTML CommandButton"


PnP-Starter-Kit for Standard Release Tenants

I am going to preface this blog post with — if you have an issue with the solution setup I have below, please do not add issues to the PnP-Starter-Kit issues list. This solution below, is a replication of the pnp-starter-kit and not managed by the PnP Team.

I’d also like to say, that you don’t need this post. The PnP-Starter-Kit should be deployed into First-Release tenants. I’ve had reservations on whether or not to post this.

What is it?

The PnP-Starter-Kit is an open sourced initiative that was released by the PnP-Team to show all of the capabilities that currently exist around provisioning in Office 365 using the SharePoint Framework and PnP Provisioning. For more information on the project, the GitHub repository can be found here.

One of the requirements of the PnP-Starter-Kit, is that it requires your tenant to be configured for “First Release for everyone”. This is because some of the web parts utilize the new AadHttpClient and MsGraphClient. These are currently in preview, but require permissions to be requested and approved under “API Management” in the admin center.

What’s in this my solution?

This solution is essentially an exact replica of the PnP repository, except that I have removed all of the web parts that require First Release and I have modified the deploy scripts and PnP Provisioning XML files to also support standard release tenants. What this means is that you will not have the LOB application or any of the web parts that generally show up on the personal page (except the people directory).

Why do this?

You may be wondering why I would do this. Since all of this is brand new and uses experimental features, it should only be deployed into a test/demo tenant, where turning on First Release shouldn’t be a problem. However, through some of the issues lists and comments I’ve seen on social media, it appears many people want to deploy the web parts into standard release tenants to test out the web parts, so I figured “why not?”.

You can get a demo tenant for free and set that tenant to first-release. However, if you really feel you want to deploy this into Standard Release… here you go.

How to deploy

The deployment of this is exactly the same as the full PnP Starter Kit, except you don’t have to configure any of the API management stuff.

GitHub Repository Here

  1. Make sure you’ve updated your PowerShell module to the latest version.
  2. Make sure your development environment is configured correctly here.
  3. Make sure your tenant is configured following this page for the User Profile property for the Portal Footer.
  4. Make sure your tenant has an app catalog
    1. Wait 24 hours after creating one
  5. Make sure your account is a Term Store Administrator

To deploy the starter kit, you’ll need to upload the sharepoint-portal-showcase.sppkg from the /package folder to your app catalog.  This will provision the web parts to all of your site collections for use. If you’d like to also include the 3 demo sites, and pre-built pages included in the full starter kit, then open up Powershell and navigate to the provisioning folder. Once inside the folder, execute the following command to start the provisioning process.

.\deploy.ps1 -TenantUrl -SitePrefix Demo



If you have any questions, just ask me. Please note, that I will not really be maintaining this repository, so it will not be receiving any of the new bug fixes and updates from the PnP Team.

Please do not add issues about this package to the PnP-Starter-Kit GitHub repository issues list.

OCR Support in Office 365

Last year at Ignite, Microsoft announced OCR capability using “computer vision technology” would be coming to Office 365! This stirred up a lot of excitement as this was a feature many people have been wanting for years! Microsoft posted an article at techcommunity about the new advancement in Intelligent Search using OCR which can be found here. Well I am happy to say it exists and it sure is awesome!

Supported Types

Per the techcommunity article above, the supported types are “bmp”, “png”, “jpeg”, “jpg”, “gif”, “tif”, “tiff”, “raw”, and also “arw”, “cr2”, “crw”, “erf”, “mef”, “mrw”, “nef”, “nrw”, “orf”, “pef”, “rw2”, “rw1”, “sr2”.

To test this capability, I have uploaded a mock up design for a Help Desk Add-In I have built for Office 365 into a Document library.


The jpg looks like this


You’ll notice inside the image there are 4 tickets have been mocked up with a bit of bacon ipsum. The ticket titles are “Trouble with SharePoint”, “Computer not Working”, “No Trouble Just Saying Hi” and “Trouble with SharePoint” again.  My plan to test out OCR is to search for the contents of that Title using SharePoint search!

Test: Search for Ticket values

My first test will be trying to find one of the tickets labeled “Trouble with SharePoint” and look at the results! Not only does it match with the values in the image, but it’s also picking up the other ticket values as well.



It should be no surprise that when I search for some of the description of the tickets, that they should return in search results as well. I’ve decided to search for the first sentence of the description in the mock up. Here is the result!

SearchResult2Does it work in Modern too?

In my previous examples I was using SharePoint Classic Search. If you were wondering if it works in Modern search as well, you bet!

modernsearchHow does it work?

My guess is that OCR in SharePoint is using Azure Media Services to convert text content in digital files into digital text. The reason for assuming this is because of the following naming convention used in SharePoint.

Whenever SharePoint finds text within your images, the values get stored on the item in a field called MediaServiceOCR. Take a look at the JSON response from querying for the list item.


I wasn’t able to find a default managed property for this field but that isn’t a huge problem because SharePoint automatically creates a crawled property called ows_MediaServiceOCR. Using this crawled property, I can create whatever managed property mappings that I want.



Some Comments

OCR PDFs have native support in Office 365. However, scanned documents which are PDFs currently aren’t generating values in the MediaServiceOCR column. I’ve been testing this functionality with no success — yet.

I have noticed some inconsistencies with the OCR functionality. I have tested this on multiple libraries and I have noticed it hasn’t been creating the MediaServiceOCR values on some items (doesn’t exist). I’ll keep you posted when I find more information about this.



Security Trimmed Hub Site Navigation Updates!

I had recently posted a blog  about a SharePoint Framework (SPFx) application customizer I built for use with hub sites in Office 365. The problem I currently have with Hub navigation is that all of the links are static links. This means, that whoever creates the common navigation must be cognizant of what links end users have access to. With that in mind, I built a new navigation the replaces the out-of-the-box hub site navigation elements with a security trimmed listing of sites within a Hub.

For brevity, this isn’t an all inclusive tutorial. It’s just discussing the high level changes to the original implementation of the application customizer.

How it works

The new navigation uses a combination of the SharePoint Search API and the new Hub sites API. When a user lands on a site collection that belongs with in a hub, I grab the hub site information to find all of the sites that exist within the hub using the Search API. The benefit of this, is that I can security trim a listing of sites with in the navigation. That means I can have sites in a hub that a user may not have access to, yet include them as elements in the global hub site navigation.


What’s wrong with V1?

While this approach is great and I have had quite a bit of success building out security trimmed navigation, the default implementation lacks the ability to use the existing hub site navigation elements. This is why I have released v2 which is available on GitHub for download.

The new application customizer still implements the “Sites” listing node in the navigation.

Adding existing Hub navigation

Version 2 is almost identical to version 1 with the exception that I am now pulling in the static elements from the default hub navigation. To do this, I’ve made some modifications to the base ReactHubsiteNavbarApplicationCustomizer.ts file, to set the Navigation property equal to the navigation array that returns from the _api/web/hubsitedata endpoint.

this._currentHubSiteData = await  searchService.getHubSiteData().then((hubData: IAssociatedSite) => {
return searchService.getHubID(hubData).then((hub: IHubSiteData) => {
//add existing hub navigation
hub.Navigation = hubData.navigation;
return hub;
return null;

Now that I have the elements as part of the _currentHubSiteData object, I just have to modify my HubNavBar.tsx component to accommodate loading the existing navigation nodes. One thing that isn’t working currently with the commandbar, is the ability to have a navigation element with children, in which the parent link is clickable.


You may notice the methods are almost identical. Rather than refactoring… I’ve separated them out because I have some future changes I’d like to make specifically to the rendering of the trimmed sites listing.

Ability to edit navigation

Now that we are showing the existing navigation elements and hiding the existing Hub navigation html…I needed a way to allow users to add and edit the original Hub navigation. There is an endpoint at _api/Navigation/SaveMenuState for saving changes to the menu, however it is undocumented by Microsoft so I will avoid using it for now. Instead, I am going to re-use the out-of-the-box Hub site navigation control to manage adding/editing elements.

To do this, I am checking if the user has ManageWeb permission, and then I will show an edit button which will show/hide the original Hub navigation.
//if user has manage web permissions, show edit button
const hasPermission:boolean = this.props.context.pageContext.web.permissions.hasPermission(SPPermission.manageWeb);


Caveat: The out of the box permission for managing Hub navigation is the new “Edit” permission. Which means that users in the Member group have access to change the navigation. I think this is a bit too much permission, so I am restricting the permissions to “ManageWeb”.

The Result


The end result is a combination of security trimmed sites listing within the hub and the existing hub site navigation. User’s with ManageWeb permissions on the Hub site will be able to see the edit button to modify the existing navigation elements.


Because of the caching in the customizer, it may take 15 minutes for changes to appear!

GitHub –

Code is in pre-release and subject to change (refactoring).