Setting the new page header in a modern SharePoint page using C# or PowerShell

This week a question came up about how to set the Header in a modern page using code. Normally in these cases you could go to the PnP Provisioning library. Using the PnP Schema you can provision a page and specify some properties of the header that shows up when deployed. You can specify:

  • Title
  • ServerRelativeImageUrl
  • Translate X
  • Translate Y

Unfortunately, there has been some updates to the modern pages which allows for a new layout for headers and the current PnP library doesn’t have support for them yet. Notice the image below, we have a few new layouts and a new field called “Topic Header”.

So what I am going to do, is explain how we can achieve the new layout with the Topic Header field. I looked into the existing PnP codebase to see how the current header implementation was done, and it gave me a lot of insight into how to solve this problem.

How to achieve this

In order to create a new page and specify a new page header we actually have to create a new page in the Site Pages list and then update some of the hidden properties of the site page. Specifically we need to update the following fields:

  • LayoutWebpartsContent
  • PageLayoutType
  • CanvasContent1
  • _TopicHeader
  • ClientSideApplicationId

Where do the properties for a header live?

The thing about modern web parts is that a lot of them store the properties that are being rendered within the web part itself. Because of this, I actually have to set the HTML and JSON object of the web part on the site page’s LayoutsWebpartsContent field. It’s quite complex, so in order to be as safe as possible and get the correct HTML, I decided the best approach  would be to create a template page (pictured above) and use that as a way to get the proper data for my newly provisioned pages.

C# Implementation

This C# example is actually going to use the PnP Core library (not required). We’ll get a reference to my template page and grab the LayoutWebpartsContent property. This will return all of the HTML required for the header. Then, we’ll create a new article page, update a few required properties and then update the LayoutWebpartsContent property from the template values.

Link to gist

  using (var ctx = new OfficeDevPnP.Core.AuthenticationManager().GetAppOnlyAuthenticatedContext(newSiteUrl, clientId, clientSecret))
            {
                var pages = ctx.Web.Lists.GetByTitle("Site Pages");
                ctx.Load(pages);
                
                //get template page
                var templatePage = pages.RootFolder.GetFile("Project-Home.aspx").ListItemAllFields;
                ctx.Load(templatePage);
                ctx.ExecuteQuery();
                
                //this is our template page content
                var _customPageHeader = templatePage[ClientSidePage.PageLayoutContentField]; //LayoutWebpartsContent
                var _canvasContent = templatePage["CanvasContent1"];

                //create new page in site page library
                var item = pages.RootFolder.Files.AddTemplateFile("/sites/test/sitepages/BeauTest.aspx", TemplateFileType.ClientSidePage).ListItemAllFields;
              
               //update page header from template information
                item[ClientSidePage.PageLayoutContentField] = _customPageHeader;        
                item[ClientSidePage.ClientSideApplicationId] = ClientSidePage.SitePagesFeatureId; //ClientSideApplicationId - b6917cb1-93a0-4b97-a84d-7cf49975d4ec
                item["CanvasContent1"] = _canvasContent; //"

“; item[“_TopicHeader”] = “Service Line”; item.Update(); ctx.Load(item); ctx.ExecuteQuery(); }

 

PowerShell Implementation

The following example is the equivalent of the C# code using CSOM, and instead of using a template file, I’ve hard coded the HTML into the code itself. This way, if you wanted to add some tokens in your HTML to dynamically replace the “Topic Header”, or change the layout you could do so directly in that HTML string.

Link to Gist

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")
 

$admin = 'youraccount@tenant.OnMicrosoft.Com'
$password = Read-Host 'Enter Password' -AsSecureString

$context = New-Object Microsoft.SharePoint.Client.ClientContext("https://tenant.sharepoint.com/sites/testpnp");
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($admin , $password)
$context.Credentials = $credentials


$pagesLibrary = $context.Web.Lists.GetByTitle('Site Pages');
$context.Load($pagesLibrary);

$newPageitem = $pagesLibrary.RootFolder.Files.AddTemplateFile("/sites/testpnp/sitepages/TestPage.aspx", "ClientSidePage").ListItemAllFields;

$newPageitem["Title"] = "Project Home";
$newPageitem["ClientSideApplicationId"] = "b6917cb1-93a0-4b97-a84d-7cf49975d4ec";
$newPageitem["PageLayoutType"] = "Article";
$newPageitem["LayoutWebpartsContent"] = '
'; $newPageitem["CanvasContent1"] = "
"; $newPageitem["_TopicHeader"] = "Service Line"; $newPageitem.Update(); $context.Load($newPageitem); $context.ExecuteQuery();

 

A special thanks

I’d like to thank Garry Trinder for mentioning the current limitation and providing me with the idea on figuring out how to solve this issue and subsequently creating this post!

Thanks to the PnP Team for setting up a lot of the framework and for making this post possible. Sharing is caring.