Reading Time: 3 minutes

Hi all,
I just pushed my last code contribution on GitHub, here you can find full code solution.

Update: Source code is also available on the official GitHub of Microsoft SharePoint PnP
https://github.com/SharePoint/sp-dev-fx-extensions/tree/master/samples/react-command-page-model-pnpjs

PnP/PnPjs is an open source initiative which consists of a collection of fluent libraries for consuming SharePoint, Graph, and Office 365 REST APIs in a type-safe way. You can use it within SharePoint Framework, Nodejs, or any JavaScript project.

The awesome aspect of this library is that it is possible to call the SharePoint RESTI API or Microsoft Graph without necessarily having to define a complex architecture (like custom external web services using CSOM), without authentication problems, but most of all in a type-safe way, with all the power of the client-side coding.

Seen all this, I wrote an SPFx extension using @pnp/sp that allow creating Modern Pages based on prefilled modern pages marked as “Page Model”, inside the Site Pages Library, and code defined pages. Users can select a Modern page as a model just setting a custom property page named “Is Template” to “Yes”.

General needs

People often need to create periodically editorial pages with the same composition, sections structure and webpart configuration, in order to give users the same users experience between pages with different contents but with the same communicative purpose. e.g.

  • Employee of the month
  • Weekly post from General Manager
  • New hires list

This SPFX extension allows users to define their own page models and reuse them easily, without export page template with PnP but directly in the Modern Team Site.

Prerequisites

You need to add a Choice (yes/no) Site Column to Page Library named “Is Template” to “Site Page” Content Type

How to set a Modern Page as Page Model

And finally… Modern Page Model with PnP/PnPjs in action


Code deep-dive

The solution is pushed in my GitHub repository, but I would like to focus on two snippets

How to copy a Modern Page from an existing page

const templatePage = await ClientSidePage.fromFile(sp.web.getFileByServerRelativeUrl(templatePageUrl));
await templatePage.copyPage(sp.web, pagename + ".aspx", pagename, false);

Define your own ClientSideWebpartPropertyTypes namespace (optional)

import {
    ClientSideText, 
    ClientSideWebpart,
    sp,
    ClientSidePage
} from "@pnp/sp";

export declare namespace MyClientSideWebpartPropertyTypes {
    /**
     * Properties for People (component id: 7f718435-ee4d-431c-bdbf-9c4ff326f46e)
     */
    interface People {
        layout: "1" | "2";
        persons?: any[];
    }
}

Create a Modern Page with PnP/PnPjs using your own ClientSideWebpartPropertyTypes

const page = await sp.web.addClientSidePage(pagename + ".aspx");
const partDefs = await sp.web.getClientSideWebParts();
const section = page.addSection();
const column1 = section.addColumn(4);

// find the definition we want, here by id
const partDef = partDefs.filter(c => c.Id === "7f718435-ee4d-431c-bdbf-9c4ff326f46e");

// optionally ensure you found the def
if (partDef.length < 1) {
    // we didn't find it so we throw an error
    console.log('ops');
    throw new Error("Could not find the web part");
}
// create a ClientWebPart instance from the definition
const part = ClientSideWebpart.fromComponentDef(partDef[0]);
part.setProperties<MyClientSideWebpartPropertyTypes.People>({
    layout: "2",
    persons: [
        {
            "id": "i:0#.f|membership|jsmith@federicoporceddu.onmicrosoft.com",
            "upn": "jsmith@federicoporceddu.onmicroosft.com",
            "role": "",
            "department": "",
            "phone": "",
            "sip": ""
        }
    ]
});
// add a text control to the second new column
column1.addControl(part);
const column2 = section.addColumn(8);
//// add a text control to the second new column
column2.addControl(new ClientSideText("Lorem ipsum dolor sit amet");
page.disableComments();
await page.save();

In 1.3.0 version pnp/pnpjs team introduce ClientSidePage copyPage extension method, so you need to use this version or a major.

Future improvements

With this sample, I would like to show how it is easy work with PnP/PnPjs, but ovviously it is not a full solution. In the future I will add this features

  • Deploy “Is Template” site column as an asset from this SPFx extension solution
  • Hide pages models from search results
  • Host pages models in a different site/library in order to share them cross-site or just for isolate site pages from a “site template”

Hope you enjoy my new code contribution!

Cheers
Federico

7 thoughts on “Modern Page Model with PnP/PnPjs”
  1. Thank you for sharing Federico , i am studying your code and its helping me allot.
    I wonder if you could help me to find the IDs of the webparts in SharePoint online (like the one u added {people webpart}) and how to set their properties ?
    I tried to search about it but no result.

    1. Hi Ahmad!
      Thank you for your comment 🙂
      If you need to get all the manifest details of the ClientWebparts deployed, you can use _api/web/GetClientSideWebParts rest API: it returns the list of ClientWebparts (uploaded to the appcatalog) with the manifest details.
      It is already wrapped in PnpJs with

      const partDefs = await sp.web.getClientSideWebParts();

      Let me know if it helps 🙂
      Cheers!
      Federico

    1. Hi Binh,
      yes this version is a sample, it works only on SharePoint online.
      If you want, you can migrate to SharePoint 2019 / on-premise, reworking code and use the same approach.
      Cheers!
      Federico

Leave a Reply

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