Develop custom search web part using PnP JS (PnP modern search) – in this tutorial, we will learn about how to develop a SharePoint Online custom search web part using the PnP JS and SharePoint Framework (SPFx).
Key-Highlights: Develop custom search web part using PnP JS
- Project scaffolding process
- Launch Visual Studio editor
- SearchService.ts file code
- SpFxSearchWebPart.ts file (web part main file) code
- Demo – SPFx framework search results using the PnP JS
Project scaffolding process: Develop custom search web part using PnP JS
Create a folder name “SPFx_Search”.
Navigate to the above created folder.

Enter the below parameters when asked:
Let's create a new SharePoint solution. ? What is your solution name? sp-fx-search ? Which baseline packages do you want to target for your component(s)? SharePoint Online only (latest) ? Where do you want to place the files? Use the current folder Found npm version 6.14.7 ? Do you want to allow the tenant admin the choice of being able to deploy the solution to all sites immediately without running any feature deployment or adding apps in sites? No ? Will the components in the solution require permissions to access web APIs that are unique and not shared with other c omponents in the tenant? No ? Which type of client-side component to create? WebPart Add new Web part to solution sp-fx-search. ? What is your Web part name? SPFx_Search ? What is your Web part description? SPFx_Search description ? Which framework would you like to use? (Use arrow keys) > No JavaScript framework React Knockout

Then we will get the below screen:

We will need to install SharePoint PnP and Office UI Fabric React to our project, now run the following commands in sequence:
npm install sp-pnp-js --save npm install office-ui-fabric-react --save
The above two commands will install and save the required files in the project. We can see all dependencies in the package.json file.
"dependencies": { "@microsoft/sp-client-base": "~1.0.0", "@microsoft/sp-core-library": "~1.0.0", "@microsoft/sp-webpart-base": "~1.0.0", "@types/webpack-env": ">=1.12.1 <1.14.0", "office-ui-fabric": "^2.6.3", "sp-pnp-js": "^2.0.2" },
Launch Visual Studio editor: Develop custom search web part using PnP JS and SPFx
Open the project code in the visual studio editor by typing the “code .” command.
Create SearchService.ts file with the two classes MockSearchService (for the mockup search test) and SearchService (for actual Sharepoint Online search test).

SearchService.ts file code:
'use strict'; import * as pnp from 'sp-pnp-js'; export interface ISearchResult { link : string; title : string; description : string; author:string; } export interface ISearchService { GetMockSearchResults(query:string) : Promise<ISearchResult[]>; } export class MockSearchService implements ISearchService { public GetMockSearchResults(query:string) : Promise<ISearchResult[]>{ return new Promise<ISearchResult[]>((resolve,reject) => { resolve([ {title:'This is test title 1',description:'Test Title 1 description',link:'https://globalsharepoint2020.sharepoint.com Jump ',author:'Global SharePoint1'}, {title:'This is test title 2',description:'Test Title 2 description',link:'https://globalsharepoint2020.sharepoint.comJump ',author:'Global SharePoint2'}, ]); }); } } export class SearchService implements ISearchService { public GetMockSearchResults(query:string) : Promise<ISearchResult[]>{ const _results:ISearchResult[] = []; return new Promise<ISearchResult[]>((resolve,reject) => { pnp.sp.search({ Querytext:query, RowLimit:20, StartRow:0 }) .then((results) => { results.PrimarySearchResults.forEach((result)=>{ _results.push({ title:result.Title, description:result.HitHighlightedSummary, link:result.Path, author:result.Author }); }); }) .then( () => { resolve(_results);} ) .catch( () => {reject(new Error("Error")); } ); }); } }
Now open the SpFxSearchWebPart.ts file (web part main file):
SpFxSearchWebPart.ts file (web part main file)code:
'use strict'; import { Version,Environment,EnvironmentType } from '@microsoft/sp-core-library'; import { IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-property-pane'; import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base'; import { escape } from '@microsoft/sp-lodash-subset'; import styles from './SpFxSearchWebPart.module.scss'; import * as strings from 'SpFxSearchWebPartStrings'; import * as pnp from 'sp-pnp-js'; import * as SearchService from './Services/SearchService'; import {SPComponentLoader} from "@microsoft/sp-loader"; export interface ISpFxSearchWebPartProps { description: string; } export default class SpFxSearchWebPart extends BaseClientSideWebPart <ISpFxSearchWebPartProps> { public constructor(){ super(); SPComponentLoader.loadCss("https://static2.sharepointonline.com/files/fabric/office-ui-fabric-js/1.2.0/css/fabric.min.css Jump "); SPComponentLoader.loadCss("https://static2.sharepointonline.com/files/fabric/office-ui-fabric-js/1.2.0/css/fabric.components.min.css Jump "); } public render(): void { this.domElement.innerHTML = ` <div class="${styles.spFxSearch}"> <div class="${styles.container}"> <div class="ms-Grid-row ms-bgColor-themeLight ms-fontColor-white ${styles.row}"> <div class="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1"> <div> <span class="ms-font-xl ms-fontColor-white">${escape(this.properties.description)}</span><br/> <div class="ms-Grid"> <div class="ms-Grid-row"> <div class="ms-Grid-col ms-u-sm10"><input class="ms-TextField-field" id="txtInput" placeholder="Search..." /></div> <div class="ms-Grid-col ms-u-sm2"> <Button class="ms-Button ms-Button--primary" id="btnSearchQuerySubmit" type="submit" value="Submit">Search</Button></div> </div> </div> <div class="ms-List ms-Grid-col ms-u-sm12" id="searchResultsDisplay"></div> </div> </div> </div> </div> </div>`; this.EventListners(); } private EventListners():void { const btnSearch = this.domElement.querySelector("#btnSearchQuerySubmit"); const queryText:HTMLElement = <HTMLInputElement>this.domElement.querySelector("#txtInput"); btnSearch.addEventListener('click',() => { (new SpFxSearchWebPart()).OnChangeEvent(queryText); }); } public OnChangeEvent(text:HTMLElement):void { (new SpFxSearchWebPart()).renderSearchResults((<HTMLInputElement>text).value) .then((html) =>{ const element = document.getElementById("searchResultsDisplay"); element.innerHTML = html; }); } private renderSearchResults(query:string):Promise<string> { const _search:SearchService.ISearchService = Environment.type == EnvironmentType.SharePoint ? new SearchService.SearchService() : new SearchService.MockSearchService(); let resultsHtml:string = ''; return new Promise<string>((resolve) => { if(query){ _search. GetMockSearchResults(query) .then((results) => { results.forEach((result) => { resultsHtml += `<div class=""ms-ListItem ms-Grid-col ms-u-sm8"> <a href="${result.link}"><span class="ms-ListItem-primaryText" >${result.title}</span></a> <span class="ms-ListItem-secondaryText">${result.author}<span> <span class="ms-ListItem-tertiaryText">${result.description}</span> <span class="ms-ListItem-metaText">10:15a</span> <div class="ms-ListItem-actions"> <div class="ms-ListItem-action" targerUrl="${result.link}"><i class="ms-Icon ms-Icon--OpenInNewWindow"> </i></div> </div> </div>`; }); }) .then( () => { setTimeout(() => { const action:HTMLCollectionOf<Element> = document.getElementsByClassName("ms-ListItem-action"); for(let i=0;i<action.length;i++){ action[i].addEventListener('click',(e)=>{ window.open((e.currentTarget as Element).getAttribute("TargerUrl")); }); } },300); resolve(resultsHtml); } ); } else{ resultsHtml += "Please provide search query input in searchbox....."; resolve(resultsHtml); } }); } protected get dataVersion(): Version { return Version.parse('1.0'); } protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration { return { pages: [ { header: { description: strings.PropertyPaneDescription }, groups: [ { groupName: strings.BasicGroupName, groupFields: [ PropertyPaneTextField('description', { label: strings.DescriptionFieldLabel }) ] } ] } ] }; } }
Demo – SPFx framework search results using the PnP JS (PnP Modern search)
Run the web part using the gulp serve command.
Then add the web part to the workbench.html page.
Pass “Test” as search text, then hit the “Search” button, we can see the search result which was defined in the SearchService.ts file as mockup data.

Now, let’s add the same web part to the SharePoint Online page, for example – https://globalsharepoint2020.sharepoint.com/_layouts/15/workbench.aspx.
Then pass “Global” as search text, hit on the “Search” button, and now we can see the search results from the actual Sharepoint Online tenant which matches the “Global” keyword.

Summary: Develop custom search web part using PnP JS and SPFx
Thus, in this article, we have learned about how to develop a SharePoint Online custom search web part using the PnP JS and SharePoint Framework (SPFx).
Source Code: PnP modern search using SPFx
The above source code can be downloaded from here.
See Also: PnP modern search tutorial
You may also like the below SharePoint SPFx articles:
- SharePoint Online: CRUD operations using SPFx and PnP JS
- SharePoint Online: Use theme color in SharePoint SPFx framework web part
- SharePoint Online: CRUD operations using SPFx ReactJS framework
- SharePoint Online: CRUD operations using SPFx no javascript framework
- Develop your first hello world web part in sharepoint framework (SPFx)
- Understanding solution structure in SharePoint framework (SPFx)
- Create custom property in SharePoint Framework – SPFx web part pane
- Get list items from SharePoint using SPFx framework(No Javascript Framework)
- Custom search result page in SharePoint Online – SPFx PnP Modern Search solution
1 comments on “PnP modern search: Develop custom search web part using PnP JS and SharePoint Framework(SPFx)”