diff --git a/search/search_index.json b/search/search_index.json index 3d32df44..5553eeac 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"PnP Modern Search v4 \u00b6 The PnP 'Modern Search' solution is a set of SharePoint Online modern Web Parts allowing SharePoint super users, webmasters and developers to create highly flexible and personalized search based experiences in minutes. Before modern pages and web parts built on SPFx was introduced search driven scenarios was covered by the highly flexible classic search web parts, which supported any developer to add any HTML, CSS or JavaScript they wanted to tailor their specific scenario. In the modern world this was replaced by the Highlighted Content Web Part and a not very configurable search solution for Microsoft Search. To close the gap of customization and freedom the PnP Modern Search web parts got started back in 2017, and have stabilized on v3. While allowing flexibility it introduces security measures to block JavaScript and CSS injection, key to many of the enterprise companies using the web parts today in productions. As the project progressed and the search API's are moving from SharePoint to Microsoft Graph there was a need to restructure and re-invent the web parts. Hence v4 was born. The goal of v4 is to solve scenarios already solved by v3, but at the same time allow greater flexibility in how you extend the solution using web components and custom developer solutions outside of HTML/handlebars. As more and more Microsoft Search functionality is exposed via the Microsoft Graph Search API's, we will keep on investing in v4 to surface these great capabilities. Looking for the v3 documentation? Here you go! PnP Modern Search v3.x deprecation v4 uses a brand new code architecture and replace the older v3 codebase . There will be no new features added to v3.x, but we will continue to provide bug fixes and minor changes as needed. As v4.x is not yet at feature parity with v3.x, you can still use the v3.x packages to meet your requirements. Also not that there is not an auto-upgrade path from v3 to v4 due to the new architecture, so you are perfectly ok to stay on the v3 version until v4 provides the features validating your upgrade. However, the main focus is on the v4 version, and new search functionality backed by the Microsoft Graph Search API will be v4 only. v3 and v4 don't share the same package name, Web Part and solution IDs meaning you can have them side by side on a page if necessary without overlap. What's included? \u00b6 The solution includes the following Web Parts: Component Description Search Results Retrieve data from a data source and render them in a specific layout. Search Filters Filter and refine data displayed in 'Search Results' Web Parts. Search Verticals Browse data as silos (i.e. tabs) from multiple data sources. Search box Let users enter free text queries sent to 'Search Results' Web Parts. Supported browsers \u00b6 Here is the list of supported browsers: Chrome Firefox Edge Edge Chromium Brave PnP Modern Search do not explicitly support Internet Explorer 11 . We think there are plenty of other options for enterprise scenarios in the market. Maybe it's time to move on. For developers, it represents an huge amount of time to make the solution compatible for a very low benefit. Hope you understand, ain't personal ;). Extensibility model \u00b6 By getting this solution, you also benefit from an advanced extensibility model allowing you to customize the solution according to your requirements if default features don't do the job for you. The supported extensions are: Custom layouts . Custom web components . Custom Handlebars customization (helpers, partials, etc.) . Custom event handlers for adaptive cards actions . Custom query modifiers . Custom data sources . Custom suggestions providers . With these available customizations options, you can do pretty much anything! Note Extensibility samples are centralized in a dedicated repository: https://github.com/microsoft-search/pnp-modern-search-extensibility-samples/tree/main . Use them to get started to create your own or reuse existing samples in your projects. Troubleshooting \u00b6 If you encounter an issue, please use the GitHub issues list of this repository . However, we will ask you to verify your issue as described here: Using Query tools to verify issues Also, to help us to resolve your issue, you can include screenshots or error messages coming from: The faulty Web Part itself. Errors displayed in the browser console (typically pressing F12). Errors displayed in the SharePoint console (pressing CTRL+F12) Issues, questions, feedback? \u00b6 For any issue, question or feedback, please the official GitHub repository . We will be happy to help you! Q&A \u00b6 We have a list of frequently asked questions available in our separate Q&A section . If you have a question, it might be already answered there. About \u00b6 PnP Modern Search version 4 initially made by Franck Cornu based on a fork of the @aequos 'Modern Data Visualizer' solution. Maintainers & contributors \u00b6 Here is the list of main contributors of the PnP Modern Search (all versions included) Franck Cornu (Ubisoft) - @FranckCornu Mikael Svenson (Microsoft) - @mikaelsvenson Yannick Reekmans - @yannickreekmans Albert-Jan Schot - @appieschot Tarald G\u00e5sbakk (Norwegian Armed Forces) - @taraldgasbakk Brad Schlintz (Microsoft) - @bschlintz Richard Gigan - @PooLP Matthew Stark Fabio Franzini (Apvee Solutions) - @franzinifabio Paolo Pialorsi (PiaSys.com) - @PaoloPia Patrik Hellgren (SherparsGroupAB) - @PatrikHellgren Erfan Darroudi @edarroudi Kasper Larsen (Fellowmind) - @kasperlarsen","title":"Introduction"},{"location":"#pnp-modern-search-v4","text":"The PnP 'Modern Search' solution is a set of SharePoint Online modern Web Parts allowing SharePoint super users, webmasters and developers to create highly flexible and personalized search based experiences in minutes. Before modern pages and web parts built on SPFx was introduced search driven scenarios was covered by the highly flexible classic search web parts, which supported any developer to add any HTML, CSS or JavaScript they wanted to tailor their specific scenario. In the modern world this was replaced by the Highlighted Content Web Part and a not very configurable search solution for Microsoft Search. To close the gap of customization and freedom the PnP Modern Search web parts got started back in 2017, and have stabilized on v3. While allowing flexibility it introduces security measures to block JavaScript and CSS injection, key to many of the enterprise companies using the web parts today in productions. As the project progressed and the search API's are moving from SharePoint to Microsoft Graph there was a need to restructure and re-invent the web parts. Hence v4 was born. The goal of v4 is to solve scenarios already solved by v3, but at the same time allow greater flexibility in how you extend the solution using web components and custom developer solutions outside of HTML/handlebars. As more and more Microsoft Search functionality is exposed via the Microsoft Graph Search API's, we will keep on investing in v4 to surface these great capabilities. Looking for the v3 documentation? Here you go! PnP Modern Search v3.x deprecation v4 uses a brand new code architecture and replace the older v3 codebase . There will be no new features added to v3.x, but we will continue to provide bug fixes and minor changes as needed. As v4.x is not yet at feature parity with v3.x, you can still use the v3.x packages to meet your requirements. Also not that there is not an auto-upgrade path from v3 to v4 due to the new architecture, so you are perfectly ok to stay on the v3 version until v4 provides the features validating your upgrade. However, the main focus is on the v4 version, and new search functionality backed by the Microsoft Graph Search API will be v4 only. v3 and v4 don't share the same package name, Web Part and solution IDs meaning you can have them side by side on a page if necessary without overlap.","title":"PnP Modern Search v4"},{"location":"#whats-included","text":"The solution includes the following Web Parts: Component Description Search Results Retrieve data from a data source and render them in a specific layout. Search Filters Filter and refine data displayed in 'Search Results' Web Parts. Search Verticals Browse data as silos (i.e. tabs) from multiple data sources. Search box Let users enter free text queries sent to 'Search Results' Web Parts.","title":"What's included?"},{"location":"#supported-browsers","text":"Here is the list of supported browsers: Chrome Firefox Edge Edge Chromium Brave PnP Modern Search do not explicitly support Internet Explorer 11 . We think there are plenty of other options for enterprise scenarios in the market. Maybe it's time to move on. For developers, it represents an huge amount of time to make the solution compatible for a very low benefit. Hope you understand, ain't personal ;).","title":"Supported browsers"},{"location":"#extensibility-model","text":"By getting this solution, you also benefit from an advanced extensibility model allowing you to customize the solution according to your requirements if default features don't do the job for you. The supported extensions are: Custom layouts . Custom web components . Custom Handlebars customization (helpers, partials, etc.) . Custom event handlers for adaptive cards actions . Custom query modifiers . Custom data sources . Custom suggestions providers . With these available customizations options, you can do pretty much anything! Note Extensibility samples are centralized in a dedicated repository: https://github.com/microsoft-search/pnp-modern-search-extensibility-samples/tree/main . Use them to get started to create your own or reuse existing samples in your projects.","title":"Extensibility model"},{"location":"#troubleshooting","text":"If you encounter an issue, please use the GitHub issues list of this repository . However, we will ask you to verify your issue as described here: Using Query tools to verify issues Also, to help us to resolve your issue, you can include screenshots or error messages coming from: The faulty Web Part itself. Errors displayed in the browser console (typically pressing F12). Errors displayed in the SharePoint console (pressing CTRL+F12)","title":"Troubleshooting"},{"location":"#issues-questions-feedback","text":"For any issue, question or feedback, please the official GitHub repository . We will be happy to help you!","title":"Issues, questions, feedback?"},{"location":"#qa","text":"We have a list of frequently asked questions available in our separate Q&A section . If you have a question, it might be already answered there.","title":"Q&A"},{"location":"#about","text":"PnP Modern Search version 4 initially made by Franck Cornu based on a fork of the @aequos 'Modern Data Visualizer' solution.","title":"About"},{"location":"#maintainers-contributors","text":"Here is the list of main contributors of the PnP Modern Search (all versions included) Franck Cornu (Ubisoft) - @FranckCornu Mikael Svenson (Microsoft) - @mikaelsvenson Yannick Reekmans - @yannickreekmans Albert-Jan Schot - @appieschot Tarald G\u00e5sbakk (Norwegian Armed Forces) - @taraldgasbakk Brad Schlintz (Microsoft) - @bschlintz Richard Gigan - @PooLP Matthew Stark Fabio Franzini (Apvee Solutions) - @franzinifabio Paolo Pialorsi (PiaSys.com) - @PaoloPia Patrik Hellgren (SherparsGroupAB) - @PatrikHellgren Erfan Darroudi @edarroudi Kasper Larsen (Fellowmind) - @kasperlarsen","title":"Maintainers & contributors"},{"location":"QnA/","text":"**Q: In version 4.10.1 the LPC (Live Person Card) hover option became available as an option for the People Layout. What is the difference between LCP and Persona card? ** A : The Live Person Card does not require any additional Graph Permissions. The LPC can be customized to show additional fields from Entra ID, but not the same way as the pnp-people or mgt-person. However it will always show equal to any other people card shown in Microsoft 365.See https://learn.microsoft.com/en-us/graph/add-properties-profilecard. Q: Is the deprecation of SharePoint-Add-in's affecting PnP Modern Search? A : No, as the project is built using the SharePoint Framework, not the deprecated add-in model. **Q: Is the PnP Modern Search package certified by a 3rd party in order to ensure compliance with GDPR or similar requirements? ** A : No, it is up to you to review the source code in order to ensure compliance with any relevant requirements. The web parts do not store, process or log any data, thus GDPR is not directly relevant. Any privacy concern of data is up to how data is stored and protected at the source level, e.g. SharePoint. **Q: Are the PnP Modern Search web parts logging data to a local or remote receiver? ** A : No, the PnP Modern Search web parts are not logging data to any receiver, not even telemetry data. **Q: What is the ID or Name of the PnP Modern Search App Registration in Azure AD/Entra? ** A : There isn\u2019t an App Registration nor Enterprise application as PnP Modern Search does not rely on an application entry. The solution uses FedAuth cookies from SharePoint when calling the SharePoint Search API, and uses the \u201cSharePoint Online Client Extensibility\u201d app registrations when calling Graph API\u2019s. The solution does elevate any permissions when calling the API\u2019s as permissions are all of the type Delegated Permissions, meaning that the permissions are bound to the current user, not the PnP Modern Search solution. **Q: I am concerned about what will happen if the project is abandoned. Will Microsoft take over the project? ** A : It is hard to predict the future, but a vast number of companies are using the PnP Modern Search web parts in their solutions. The solution is hosted on GitHub owned by the Microsoft Search team. Most likely these companies will either clone the project and make a commercial version or will provide the manpower needed to keep the project in maintenance mode.","title":"QnA"},{"location":"build-the-doc/","text":"Building the Documentation \u00b6 Building the documentation locally can help you visualize change you are making to the docs. What you see locally should be what you see online. Building \u00b6 Documentation is built using MkDocs. You will need to latest version of Python (tested on version 3.7.1) and pip. If you're on the Windows operating system, make sure you have added Python to your Path environment variable . When executing the pip module on Windows you can prefix it with python -m . For example: python -m pip install mkdocs-material Install MkDocs pip install mkdocs==1.2.2 pip install markdown-include Install the Material theme pip install mkdocs-material==7.2.4 Serve it up mkdocs serve Open a browser to http://127.0.0.1:8000/ Deploy mkdocs gh-deploy from main branch","title":"Building documentation"},{"location":"build-the-doc/#building-the-documentation","text":"Building the documentation locally can help you visualize change you are making to the docs. What you see locally should be what you see online.","title":"Building the Documentation"},{"location":"build-the-doc/#building","text":"Documentation is built using MkDocs. You will need to latest version of Python (tested on version 3.7.1) and pip. If you're on the Windows operating system, make sure you have added Python to your Path environment variable . When executing the pip module on Windows you can prefix it with python -m . For example: python -m pip install mkdocs-material Install MkDocs pip install mkdocs==1.2.2 pip install markdown-include Install the Material theme pip install mkdocs-material==7.2.4 Serve it up mkdocs serve Open a browser to http://127.0.0.1:8000/ Deploy mkdocs gh-deploy from main branch","title":"Building"},{"location":"how-to-contribute/","text":"How to contribute? \u00b6 You can contribute to this project at multiple levels: Help us with the issues list by: Answer questions from the community Fix issues in the code Improve documentation by: Correcting typos Clarify configuration and examples Add business scenario tutorials Add new reusable components , suggestions providers or Query modifier to the extensibility library. Add Web Part translations As a result, we accept pull requests from the community. You can refer to this post to learn how to make a PR on a GitHub repository. Note Your PR must target the develop branch. Important Your PR will be automatically rejected if It alters too much of the solution core architecture or the amount of code is too substantial to be reviewed properly. You don't provide any detailled steps to test it. It contains a new feature that was not discussed previously with the maintainers. Setting up the solution locally \u00b6 Before making any PR, you need to setup this project locally on your machine. This solution is composed of three distinct parts: Project Description search-parts SPFx Web Parts code search-extensibility SPFx library component containing shared code between core Web Parts and extensibilty library. search-extensibility-demo Reusable components to extend capabilities of core Web Parts https://github.com/microsoft-search/pnp-modern-search-extensibility-samples. Setup the search-extensibility project \u00b6 Note By default, the search-parts and search-extensibility-demo projects use the npm reference @pnp/modern-search-extensibility . Follow these steps only if you intend to perform some changes on the search-extensibility project. Important Because this project is published as an npm reference, any change is critical. Please do not commit changes if you are not sure about what you are doing. The search-extensibilty project is an SPFx library component containing all the shared interfaces for the search-parts and search-extensibility-demo other SPFx projects. As a result, a symbolic link must be build to these projects first before it can be used : Open the search-extensibility project and install dependencies using npm i or your favorite package manager. Build the project using the command npm run build or gulp bundle . Run the command npm link to create a symbolic link. You can also refer to the official SPFx documentation about library component usage . A symbolic link is a shortcut that points to another directory or another project (in this case) on your system Setup the search-parts and search-extensibility-demo projects \u00b6 From the search-parts or search-extensibility-demo project, run npm i . Build the project using npm run build or gulp bundle . Note If you made local changes on the search-extensibility project, after each npm i , you must link your local search-extensibility project using the command npm link @pnp/modern-search-extensibility Debug the solution \u00b6 From Visual Studio Code console or any other console, from the search-parts folder, use the npm run serve command to start the server. We use SPFx Fast Serve Tool from Sergei Sergeev to speed up development process. From Visual Studio Code, use the 'Hosted Workbench' debug configuration with your URL to debug the Web Parts. Any changes to the code will trigger a new build and refresh your page automatically within seconds.","title":"How to contribute?"},{"location":"how-to-contribute/#how-to-contribute","text":"You can contribute to this project at multiple levels: Help us with the issues list by: Answer questions from the community Fix issues in the code Improve documentation by: Correcting typos Clarify configuration and examples Add business scenario tutorials Add new reusable components , suggestions providers or Query modifier to the extensibility library. Add Web Part translations As a result, we accept pull requests from the community. You can refer to this post to learn how to make a PR on a GitHub repository. Note Your PR must target the develop branch. Important Your PR will be automatically rejected if It alters too much of the solution core architecture or the amount of code is too substantial to be reviewed properly. You don't provide any detailled steps to test it. It contains a new feature that was not discussed previously with the maintainers.","title":"How to contribute?"},{"location":"how-to-contribute/#setting-up-the-solution-locally","text":"Before making any PR, you need to setup this project locally on your machine. This solution is composed of three distinct parts: Project Description search-parts SPFx Web Parts code search-extensibility SPFx library component containing shared code between core Web Parts and extensibilty library. search-extensibility-demo Reusable components to extend capabilities of core Web Parts https://github.com/microsoft-search/pnp-modern-search-extensibility-samples.","title":"Setting up the solution locally"},{"location":"how-to-contribute/#setup-the-search-extensibility-project","text":"Note By default, the search-parts and search-extensibility-demo projects use the npm reference @pnp/modern-search-extensibility . Follow these steps only if you intend to perform some changes on the search-extensibility project. Important Because this project is published as an npm reference, any change is critical. Please do not commit changes if you are not sure about what you are doing. The search-extensibilty project is an SPFx library component containing all the shared interfaces for the search-parts and search-extensibility-demo other SPFx projects. As a result, a symbolic link must be build to these projects first before it can be used : Open the search-extensibility project and install dependencies using npm i or your favorite package manager. Build the project using the command npm run build or gulp bundle . Run the command npm link to create a symbolic link. You can also refer to the official SPFx documentation about library component usage . A symbolic link is a shortcut that points to another directory or another project (in this case) on your system","title":"Setup the search-extensibility project"},{"location":"how-to-contribute/#setup-the-search-parts-and-search-extensibility-demo-projects","text":"From the search-parts or search-extensibility-demo project, run npm i . Build the project using npm run build or gulp bundle . Note If you made local changes on the search-extensibility project, after each npm i , you must link your local search-extensibility project using the command npm link @pnp/modern-search-extensibility","title":"Setup the search-parts and search-extensibility-demo projects"},{"location":"how-to-contribute/#debug-the-solution","text":"From Visual Studio Code console or any other console, from the search-parts folder, use the npm run serve command to start the server. We use SPFx Fast Serve Tool from Sergei Sergeev to speed up development process. From Visual Studio Code, use the 'Hosted Workbench' debug configuration with your URL to debug the Web Parts. Any changes to the code will trigger a new build and refresh your page automatically within seconds.","title":"Debug the solution"},{"location":"installation/","text":"Installation \u00b6 Download the latest SharePoint Framework packages pnp-modern-search-parts-v4.sppkg from the GitHub repository . Add pnp-modern-search-parts-v4.sppkg to the global tenant app catalog or a site collection app catalog. If you don't have an app catalog, follow this procedure to create one. The packages are deployed in the general Office 365 CDN meaning we don't host any code . For the pnp-modern-search-parts-v4.sppkg package, you can choose to make the solution available in all sites or force to install an app to the site every time. The solution asks the following API permissions by default to enhance the experience. These permissions are not mandatory . If you don't accept them, you will simply have less available features. You can approve scopes from the API Access screen in the SharePoint Admin Center: https://-admin.sharepoint.com/_layouts/15/online/AdminHome.aspx#/webApiPermissionManagement If you'd like more details on this step, please see the Approving Scopes section below. Requested API permission Used for Presence.Read.All Read presence information of all users in your organization. User.Read The Microsoft Graph Toolkit persona card in the people layout. People.Read Same as above. Contacts.Read Same as above. User.Read.All Same as above. Files.Read.All Allow search for files using Graph API (Drive / Drive Items). Mail.Read Allow search for user's e-mail using Graph API (Messages). Calendars.Read Allow search for user's calendar appointments using Graph API (Events). Sites.Read.All Allow search for sites using Graph API (Sites / List Items). ExternalItem.Read.All Allow search for connector items using Graph API (External Items). Bookmark.Read.All Allow search for Bookmarks in Microsoft Search in your organization. Acronym.Read.All Allow search for Acronyms in Microsoft Search in your organization. Chat.Read Allow search for Teams messages. ChannelMessage.Read.All Read user channel messages. Add the Web Parts to a SharePoint and start building! Approving Scopes \u00b6 You can approve the required scopes in the SharePoint Admin Center on the API Access page. When you visit that page, you will see any pending requests. The screenshot below shows the pending requests for the v4 solution. You'll need to approve each request one at a time. If you have questions about what the requested scopes mean and what permissions they provide, check the article Manage access to Azure AD-secured APIs . After you approve each request your view will be as shown in the screenshot below. Note about Guest users \u00b6 By default guest users do not have access to the App Catalog. So if you are not using the CDN option, any SPFx web part from the App Catalog will show an error message for guest users: There are basicly two options to solve this issue, give guest users access to the App Catalog (read) or use the CDN option. see this for more information.","title":"Installation"},{"location":"installation/#installation","text":"Download the latest SharePoint Framework packages pnp-modern-search-parts-v4.sppkg from the GitHub repository . Add pnp-modern-search-parts-v4.sppkg to the global tenant app catalog or a site collection app catalog. If you don't have an app catalog, follow this procedure to create one. The packages are deployed in the general Office 365 CDN meaning we don't host any code . For the pnp-modern-search-parts-v4.sppkg package, you can choose to make the solution available in all sites or force to install an app to the site every time. The solution asks the following API permissions by default to enhance the experience. These permissions are not mandatory . If you don't accept them, you will simply have less available features. You can approve scopes from the API Access screen in the SharePoint Admin Center: https://-admin.sharepoint.com/_layouts/15/online/AdminHome.aspx#/webApiPermissionManagement If you'd like more details on this step, please see the Approving Scopes section below. Requested API permission Used for Presence.Read.All Read presence information of all users in your organization. User.Read The Microsoft Graph Toolkit persona card in the people layout. People.Read Same as above. Contacts.Read Same as above. User.Read.All Same as above. Files.Read.All Allow search for files using Graph API (Drive / Drive Items). Mail.Read Allow search for user's e-mail using Graph API (Messages). Calendars.Read Allow search for user's calendar appointments using Graph API (Events). Sites.Read.All Allow search for sites using Graph API (Sites / List Items). ExternalItem.Read.All Allow search for connector items using Graph API (External Items). Bookmark.Read.All Allow search for Bookmarks in Microsoft Search in your organization. Acronym.Read.All Allow search for Acronyms in Microsoft Search in your organization. Chat.Read Allow search for Teams messages. ChannelMessage.Read.All Read user channel messages. Add the Web Parts to a SharePoint and start building!","title":"Installation"},{"location":"installation/#approving-scopes","text":"You can approve the required scopes in the SharePoint Admin Center on the API Access page. When you visit that page, you will see any pending requests. The screenshot below shows the pending requests for the v4 solution. You'll need to approve each request one at a time. If you have questions about what the requested scopes mean and what permissions they provide, check the article Manage access to Azure AD-secured APIs . After you approve each request your view will be as shown in the screenshot below.","title":"Approving Scopes"},{"location":"installation/#note-about-guest-users","text":"By default guest users do not have access to the App Catalog. So if you are not using the CDN option, any SPFx web part from the App Catalog will show an error message for guest users: There are basicly two options to solve this issue, give guest users access to the App Catalog (read) or use the CDN option. see this for more information.","title":"Note about Guest users"},{"location":"using-query-tools-to-verify-issues/","text":"Please verify your issue using these tools and methods before creating an Issue in the repository We DO value your questions and loves to see more and more people starting using PnP Modern Search, however we often see issues raised that has nothing to do with PnP Modern Search but the fact that the problem becomes visible here. The most common errors \u00b6 Typos or badly formed KQL queries Values not showing up on Managed Properties as expected Errors in mapping of Crawled Properties to Managed Properties (especially for Refiners) Custom User Profile Properties are mapped incorrectly We would therefore ask you to verify that the API delivers the results you are expecting before we starting looking for bugs in PnP Modern Search. Suggested tools SharePoint Search Query Tool (stand alone application) Guides for how to use the Query tool are available on the net, see for instance The Must Have Tool While Working with Search and SharePoint Online (Jasper Oosterveld) or Using SharePoint Search Query Tool (Antti Koskela) Video: Useful tools when working with Search Episode 2 Using the SP Query Tool (Kasper Larsen) An older video is also availble: SharePoint Power Hour: Search Query Tool - YouTube (Laura Rogers) Chrome extention SP Editor. (Chrome extention) Guide: SharePoint Search Console \u2013 Now available inside Chrome SP Editor! (Antti Koskela) Video: Useful tools when working with Search - Episode 1: Using the SP Editor (Kasper Larsen) SP Editor Chrome Extension for SharePoint Administrators and Developers (Denis Molodtsov) These tools gives you an exellent option to tinker with the search query and inspect the results. This will VERY often give you a clue where the issue is. Generic query errors \u00b6 In this case the query yields no result but you are certain that the name of the library is correct, what gives? Turns out that the Library has been renamed but the URL is still https://m365b839353.sharepoint.com/sites/NW-B2000eBike (note the dash) The Data is missing \u00b6 You set up a query on a few specific libraries and knows they contains 30 documents, but only 20 shows up, weird. There are a number of reasons: - One of the libraries is set as not to be searchable - Some of the documents have broken permissions and the user account you are using in the Search Tool doesn't have access - Only checked in and published files will be indexed - and the dreaded: the documents haven't been indexed yet The three first reasons are fairly easy to check and correct, but that last one is a bit tricky. The first step should be to prove or disprove the suspicion that the documents hasn't been indexed yet. One option is to use PnP PowerShell to query the CrawlLog, see Get-PnPSearchCrawlLog . Setting the -Filter to the URL of one of the missing documents should resolve that question. If the problem IS that the documents haven't been indexed yet, you can request a reindexing, either on a List/Library level or on a Site level. A forced Full Index as known from On-Premises is not available in SharePoint Online. People Search \u00b6 Your company has added a new property to the User Properties in SharePoint and you are responsible for implementing it in search. You have found the crawled property and mapped it to a RefinableString in order to use it as a filter. You have waited the required 24 hours but the RefinableString is still not showing up. What is wrong? Most likely you have either forgotten or didn't knew that you MUST change the full-text-index from Default to PeopleIdx in your custom Managed Property and/or RefinableString otherwise it will show up in the wrong index, and be of no use. Follow this guide (SearchExplained) and you shouldn't encounter this problem anymore.","title":"Using query tools to verify issues"},{"location":"using-query-tools-to-verify-issues/#the-most-common-errors","text":"Typos or badly formed KQL queries Values not showing up on Managed Properties as expected Errors in mapping of Crawled Properties to Managed Properties (especially for Refiners) Custom User Profile Properties are mapped incorrectly We would therefore ask you to verify that the API delivers the results you are expecting before we starting looking for bugs in PnP Modern Search.","title":"The most common errors"},{"location":"using-query-tools-to-verify-issues/#generic-query-errors","text":"In this case the query yields no result but you are certain that the name of the library is correct, what gives? Turns out that the Library has been renamed but the URL is still https://m365b839353.sharepoint.com/sites/NW-B2000eBike (note the dash)","title":"Generic query errors"},{"location":"using-query-tools-to-verify-issues/#the-data-is-missing","text":"You set up a query on a few specific libraries and knows they contains 30 documents, but only 20 shows up, weird. There are a number of reasons: - One of the libraries is set as not to be searchable - Some of the documents have broken permissions and the user account you are using in the Search Tool doesn't have access - Only checked in and published files will be indexed - and the dreaded: the documents haven't been indexed yet The three first reasons are fairly easy to check and correct, but that last one is a bit tricky. The first step should be to prove or disprove the suspicion that the documents hasn't been indexed yet. One option is to use PnP PowerShell to query the CrawlLog, see Get-PnPSearchCrawlLog . Setting the -Filter to the URL of one of the missing documents should resolve that question. If the problem IS that the documents haven't been indexed yet, you can request a reindexing, either on a List/Library level or on a Site level. A forced Full Index as known from On-Premises is not available in SharePoint Online.","title":"The Data is missing"},{"location":"using-query-tools-to-verify-issues/#people-search","text":"Your company has added a new property to the User Properties in SharePoint and you are responsible for implementing it in search. You have found the crawled property and mapped it to a RefinableString in order to use it as a filter. You have waited the required 24 hours but the RefinableString is still not showing up. What is wrong? Most likely you have either forgotten or didn't knew that you MUST change the full-text-index from Default to PeopleIdx in your custom Managed Property and/or RefinableString otherwise it will show up in the wrong index, and be of no use. Follow this guide (SearchExplained) and you shouldn't encounter this problem anymore.","title":"People Search"},{"location":"create-custom-layouts/","text":"If you are looking for inspiration, you can find a selection of custom layouts in the Custom layouts repository If you have a custom layout you want to share, please submit a PR to the repository. Scenario Create your first custom template \u00b6 Create your first custom template Store custom templates in SharePoint \u00b6 Storing custom templates as files in a SharePoint site, is great when you want to use them across sites and want some control. Edit custom templates in SharePoint \u00b6 Storing custom templates as files in a SharePoint site, is great when you want to use them across sites and want some control. Edit custom templates locally in Visual Studio Code \u00b6 When you have your templates in SharePoint, it is easy to setup a way to edit locally on your computer and still get the result in SharePoint almost instantly.","title":"Create custom layouts"},{"location":"create-custom-layouts/#scenario-create-your-first-custom-template","text":"Create your first custom template","title":"Scenario Create your first custom template"},{"location":"create-custom-layouts/#store-custom-templates-in-sharepoint","text":"Storing custom templates as files in a SharePoint site, is great when you want to use them across sites and want some control.","title":"Store custom templates in SharePoint"},{"location":"create-custom-layouts/#edit-custom-templates-in-sharepoint","text":"Storing custom templates as files in a SharePoint site, is great when you want to use them across sites and want some control.","title":"Edit custom templates in SharePoint"},{"location":"create-custom-layouts/#edit-custom-templates-locally-in-visual-studio-code","text":"When you have your templates in SharePoint, it is easy to setup a way to edit locally on your computer and still get the result in SharePoint almost instantly.","title":"Edit custom templates locally in Visual Studio Code"},{"location":"create-custom-layouts/create-your-first-custom-template/","text":"Create your first custom template \u00b6 You can make the results from a search, look like you want to by using a custom template. This article will get you started by creating a simple custom template. Steps \u00b6 Create a file Add content to the file Do a small change to the content. Store your file in SharePoint Create a file \u00b6 On your computer, create a file with the name: mycustomtemplate.html Note Make sure you name the file with the extension html Open this file in a text editor, like Notepad, VS Code or whatnot. The file is empty, so we need to add some basic needed html code. Copy the code below and paste it into the file and save. < content id = \"data-content\" > < style > /* Insert your CSS overrides here */ . example-themePrimary a { color : { { @ root . theme . palette . themeprimary } } } . myfirstcss { font-size : 30 px ; font-weight : 600 ; } . icon { width : 20 px ; height : 16 px ; } ul { list-style : none ; display : flex ; flex-wrap : wrap ; } ul li { display : flex ; padding : 8 px ; flex : 1 1 100 % ; } < div class = \"template\" > {{#if @root.properties.showSelectedFilters}} < pnp-selectedfilters data-filters = \"{{JSONstringify filters.selectedFilters 2}}\" data-filters-configuration = \"{{JSONstringify filters.filtersConfiguration 2}}\" data-instance-id = \"{{filters.instanceId}}\" data-operator = \"{{filters.filterOperator}}\" data-theme-variant = \"{{JSONstringify @root.theme}}\" > {{/if}} {{#if @root.properties.showResultsCount}} < div class = \"template--resultCount\" > < label class = \"ms-fontWeight-semibold\" > {{getCountMessage @root.data.totalItemsCount @root.inputQueryText}} {{/if}} < ul class = \"template--custom\" > {{!-- This div below can be deleted. it is just for showing that is is your first template.--}} < div class = \"myfirstcss\" > This is my custom template!!!! {{#each data.items as |item|}} {{#> resultTypes item=item}} {{!-- The block below will be used as default item template if no result types matched --}} < li > < pnp-iconfile class = \"icon\" data-extension = \"{{slot item @root.slots.FileType}}\" data-theme-variant = \"{{JSONstringify @root.theme}}\" > < span class = \"example-themePrimary\" >< a href = \"{{slot item @root.slots.Path}}\" > {{slot item @root.slots.Title}} {{/resultTypes}} {{/each}} You now have your first custom template!! Change the content of your new template file \u00b6 You are going to make a small change to your template so you can check if it is working. Find the line where the text says:
This is my custom template!!!!
Change the text \"This is my custom template!!!!\" to \"This is my first awesome custom template that rocks!!!!\". Maybe you want to add your name to? Upload the custom template to a SharePoint library \u00b6 Open a SharePoint site you use for development. Or create one. Open Shared Documents Create a folder called \"PnPSearchTemplates\" Upload the file to this folder. The URL to this template is: /Shared Documents/PnPSearchTemplates/mycustomtemplate.html Change with the URL of the site you created/are using. Will be something like this: https://contoso.sharepoint.com/sites/mydevsite You will need this when you are configuring the PnP Search Template web part. Test the custom template \u00b6 Open a SharePoint site you use for development. Create a new page, call it what you want and use whatever template you want (Blank is fine) In a section add the \"PnP Search Results\" web part Choose to configure the web part Select SharePoint search Make sure you get some results by typing in a search term in the Query template, like \"{searchTerms} *\" Go to page 2 Select \"Custom\" layout Under \"Use an external template URL\", type in the URL for the custom template file. You cannot type in a URL that does not exist, if so you get an error message. Click outside the URL text box. Publish the page Did the results change layout? If so you have succeeded! Troubleshooting \u00b6 If not, check if you copied all the text into the file, and that you did not change anything else than the text. Pictures \u00b6 Insert the URL in \"Use an external template URL\" What the custom search result looks like","title":"Create your first custom template"},{"location":"create-custom-layouts/create-your-first-custom-template/#create-your-first-custom-template","text":"You can make the results from a search, look like you want to by using a custom template. This article will get you started by creating a simple custom template.","title":"Create your first custom template"},{"location":"create-custom-layouts/create-your-first-custom-template/#steps","text":"Create a file Add content to the file Do a small change to the content. Store your file in SharePoint","title":"Steps"},{"location":"create-custom-layouts/create-your-first-custom-template/#create-a-file","text":"On your computer, create a file with the name: mycustomtemplate.html Note Make sure you name the file with the extension html Open this file in a text editor, like Notepad, VS Code or whatnot. The file is empty, so we need to add some basic needed html code. Copy the code below and paste it into the file and save. < content id = \"data-content\" > < style > /* Insert your CSS overrides here */ . example-themePrimary a { color : { { @ root . theme . palette . themeprimary } } } . myfirstcss { font-size : 30 px ; font-weight : 600 ; } . icon { width : 20 px ; height : 16 px ; } ul { list-style : none ; display : flex ; flex-wrap : wrap ; } ul li { display : flex ; padding : 8 px ; flex : 1 1 100 % ; } < div class = \"template\" > {{#if @root.properties.showSelectedFilters}} < pnp-selectedfilters data-filters = \"{{JSONstringify filters.selectedFilters 2}}\" data-filters-configuration = \"{{JSONstringify filters.filtersConfiguration 2}}\" data-instance-id = \"{{filters.instanceId}}\" data-operator = \"{{filters.filterOperator}}\" data-theme-variant = \"{{JSONstringify @root.theme}}\" > {{/if}} {{#if @root.properties.showResultsCount}} < div class = \"template--resultCount\" > < label class = \"ms-fontWeight-semibold\" > {{getCountMessage @root.data.totalItemsCount @root.inputQueryText}} {{/if}} < ul class = \"template--custom\" > {{!-- This div below can be deleted. it is just for showing that is is your first template.--}} < div class = \"myfirstcss\" > This is my custom template!!!! {{#each data.items as |item|}} {{#> resultTypes item=item}} {{!-- The block below will be used as default item template if no result types matched --}} < li > < pnp-iconfile class = \"icon\" data-extension = \"{{slot item @root.slots.FileType}}\" data-theme-variant = \"{{JSONstringify @root.theme}}\" > < span class = \"example-themePrimary\" >< a href = \"{{slot item @root.slots.Path}}\" > {{slot item @root.slots.Title}} {{/resultTypes}} {{/each}} You now have your first custom template!!","title":"Create a file"},{"location":"create-custom-layouts/create-your-first-custom-template/#change-the-content-of-your-new-template-file","text":"You are going to make a small change to your template so you can check if it is working. Find the line where the text says:
This is my custom template!!!!
Change the text \"This is my custom template!!!!\" to \"This is my first awesome custom template that rocks!!!!\". Maybe you want to add your name to?","title":"Change the content of your new template file"},{"location":"create-custom-layouts/create-your-first-custom-template/#upload-the-custom-template-to-a-sharepoint-library","text":"Open a SharePoint site you use for development. Or create one. Open Shared Documents Create a folder called \"PnPSearchTemplates\" Upload the file to this folder. The URL to this template is: /Shared Documents/PnPSearchTemplates/mycustomtemplate.html Change with the URL of the site you created/are using. Will be something like this: https://contoso.sharepoint.com/sites/mydevsite You will need this when you are configuring the PnP Search Template web part.","title":"Upload the custom template to a SharePoint library"},{"location":"create-custom-layouts/create-your-first-custom-template/#test-the-custom-template","text":"Open a SharePoint site you use for development. Create a new page, call it what you want and use whatever template you want (Blank is fine) In a section add the \"PnP Search Results\" web part Choose to configure the web part Select SharePoint search Make sure you get some results by typing in a search term in the Query template, like \"{searchTerms} *\" Go to page 2 Select \"Custom\" layout Under \"Use an external template URL\", type in the URL for the custom template file. You cannot type in a URL that does not exist, if so you get an error message. Click outside the URL text box. Publish the page Did the results change layout? If so you have succeeded!","title":"Test the custom template"},{"location":"create-custom-layouts/create-your-first-custom-template/#troubleshooting","text":"If not, check if you copied all the text into the file, and that you did not change anything else than the text.","title":"Troubleshooting"},{"location":"create-custom-layouts/create-your-first-custom-template/#pictures","text":"Insert the URL in \"Use an external template URL\" What the custom search result looks like","title":"Pictures"},{"location":"create-custom-layouts/edit-custom-templates-in-sharepoint/","text":"Edit a custom web part in SharePoint \u00b6 You can edit a custom template directly in SharePoint. Steps \u00b6 Create a custom template Store the template in a SharePoint library Edit the template in directly in SharePoint Note Step 1 The article Create your first custom template , explains how you can create a template and upload it to SharePoint. Note Step 2 The article Store custom templates in SharePoint , explains how you can create a central repository for templates in SharePoint. Edit the template in SharePoint \u00b6 Prerequisites: You have created your first template and it is stored in SharePoint. (Step 1 and Step 2) Open your SharePoint site and navigate to the SharePoint library and folder where the template is stored. Click the template Title to open it. Open the menu \"Open\" and select \"Open in Text Editor\" Do your changes and Click Save. Close the Tab. Check your changes \u00b6 To look at your changes, open a page where you have used the custom template, and refresh the page. The changes you made should now be effective.","title":"Edit a custom web part in SharePoint"},{"location":"create-custom-layouts/edit-custom-templates-in-sharepoint/#edit-a-custom-web-part-in-sharepoint","text":"You can edit a custom template directly in SharePoint.","title":"Edit a custom web part in SharePoint"},{"location":"create-custom-layouts/edit-custom-templates-in-sharepoint/#steps","text":"Create a custom template Store the template in a SharePoint library Edit the template in directly in SharePoint Note Step 1 The article Create your first custom template , explains how you can create a template and upload it to SharePoint. Note Step 2 The article Store custom templates in SharePoint , explains how you can create a central repository for templates in SharePoint.","title":"Steps"},{"location":"create-custom-layouts/edit-custom-templates-in-sharepoint/#edit-the-template-in-sharepoint","text":"Prerequisites: You have created your first template and it is stored in SharePoint. (Step 1 and Step 2) Open your SharePoint site and navigate to the SharePoint library and folder where the template is stored. Click the template Title to open it. Open the menu \"Open\" and select \"Open in Text Editor\" Do your changes and Click Save. Close the Tab.","title":"Edit the template in SharePoint"},{"location":"create-custom-layouts/edit-custom-templates-in-sharepoint/#check-your-changes","text":"To look at your changes, open a page where you have used the custom template, and refresh the page. The changes you made should now be effective.","title":"Check your changes"},{"location":"create-custom-layouts/edit-templates-using-vscode-and-onedrive/","text":"Edit custom templates using Visual Studio Code and OneDrive \u00b6 If you want to achieve the following list, don't stop reading. You have a local development environment for editing your templates. You can see your changes as fast as you can hit the refresh button in your browser. You can see how the template looks in your SharePoint environment. You can use real search results as data. You can use Extensions in VSCode to make it easier to edit html files. We are going to use Visual Studio Code (VSCode) and OneDrive to reach our goal. You do NOT have to use VSCode, you can use any text editor you want. If you need to install VSCode, you can get it here: Visual Studio Code I guess you already have OneDrive, if not, here is the instructions to get started. Sync files with OneDrive (Windows) Sync files with OneDrive on Mac OS X Steps \u00b6 Create the SharePoint environment for developing templates. \u00b6 Setup a SharePoint site, where you want to store your templates. Store custom templates in SharePoint Create a page using PnP Search Results. Create a simple search page Create a custom template and store it in SharePoint. Create your first custom template Configure your search web part to use the new template. (See article in step 3) Sync the template locally and edit using a local editor \u00b6 Open the SharePoint site and library where you have stored your template. Choose \"Sync\" or \"Add Shortcut to OneDrive\", whatever you prefer. Open VSCode (or any other editor you prefer). Open the synced folder in your OneDrive. File - Open Folder Select the folder you just synced You will now see all your templates in this folder. Edit a template. Save a template. Wait a moment till OneDrive is finished syncing. (Should be almost instant) Refresh the Browser window where you have the search web part that is using your template. You should now see the change you made. Repeat 9 - 12, until you are happy with your awesome custom template. Troubleshoot \u00b6 You cannot see any changes on the webpage Check the sync status in OneDrive Make a change that is noticeable Check if the custom template has been set as the template for the search part you are looking at. Did you refresh the page? Is your file-extension .html? Check the code in the template. Tips \u00b6 In VS Code you can format the code by selecting the language mode for the document you are editing. For files with extension .HTML, they are automatically formatted by a rule set for HTML. Since you are going to use Handlebar you should change the language mode to \"Handlebars\".","title":"Edit custom templates using Visual Studio Code and OneDrive"},{"location":"create-custom-layouts/edit-templates-using-vscode-and-onedrive/#edit-custom-templates-using-visual-studio-code-and-onedrive","text":"If you want to achieve the following list, don't stop reading. You have a local development environment for editing your templates. You can see your changes as fast as you can hit the refresh button in your browser. You can see how the template looks in your SharePoint environment. You can use real search results as data. You can use Extensions in VSCode to make it easier to edit html files. We are going to use Visual Studio Code (VSCode) and OneDrive to reach our goal. You do NOT have to use VSCode, you can use any text editor you want. If you need to install VSCode, you can get it here: Visual Studio Code I guess you already have OneDrive, if not, here is the instructions to get started. Sync files with OneDrive (Windows) Sync files with OneDrive on Mac OS X","title":"Edit custom templates using Visual Studio Code and OneDrive"},{"location":"create-custom-layouts/edit-templates-using-vscode-and-onedrive/#steps","text":"","title":"Steps"},{"location":"create-custom-layouts/edit-templates-using-vscode-and-onedrive/#create-the-sharepoint-environment-for-developing-templates","text":"Setup a SharePoint site, where you want to store your templates. Store custom templates in SharePoint Create a page using PnP Search Results. Create a simple search page Create a custom template and store it in SharePoint. Create your first custom template Configure your search web part to use the new template. (See article in step 3)","title":"Create the SharePoint environment for developing templates."},{"location":"create-custom-layouts/edit-templates-using-vscode-and-onedrive/#sync-the-template-locally-and-edit-using-a-local-editor","text":"Open the SharePoint site and library where you have stored your template. Choose \"Sync\" or \"Add Shortcut to OneDrive\", whatever you prefer. Open VSCode (or any other editor you prefer). Open the synced folder in your OneDrive. File - Open Folder Select the folder you just synced You will now see all your templates in this folder. Edit a template. Save a template. Wait a moment till OneDrive is finished syncing. (Should be almost instant) Refresh the Browser window where you have the search web part that is using your template. You should now see the change you made. Repeat 9 - 12, until you are happy with your awesome custom template.","title":"Sync the template locally and edit using a local editor"},{"location":"create-custom-layouts/edit-templates-using-vscode-and-onedrive/#troubleshoot","text":"You cannot see any changes on the webpage Check the sync status in OneDrive Make a change that is noticeable Check if the custom template has been set as the template for the search part you are looking at. Did you refresh the page? Is your file-extension .html? Check the code in the template.","title":"Troubleshoot"},{"location":"create-custom-layouts/edit-templates-using-vscode-and-onedrive/#tips","text":"In VS Code you can format the code by selecting the language mode for the document you are editing. For files with extension .HTML, they are automatically formatted by a rule set for HTML. Since you are going to use Handlebar you should change the language mode to \"Handlebars\".","title":"Tips"},{"location":"create-custom-layouts/howto-store-custom-templates-in-sharepoint/","text":"Store custom templates in SharePoint \u00b6 You can create your own custom templates. If you store them in a central repository, they can be used across PnP Search Result web parts across sites in your tenant. Requirements for using a custom template, referenced by URL are: All users of the PnP Search Result web part, must be able to read those templates. Templates need to have their own URL. A SharePoint site within your tenant is perfect for this scenario, because you get some additional features: Central repository Version control No public access Secure change access Share to others outside of your company Use OneDrive for sync Approval flows (if you want) 5 steps to create a central repository in SharePoint \u00b6 Create a SharePoint site Give \"Everyone except externals\" read access to the site Create a Library Create a Templates Folder Add your custom templates to this folder. Now you can reference these templates using the URL to the file. https://.sharepoint.com/sites////.html Example for the a tenant named Contoso \u00b6 Site title Library name Folder name Template names SharePointResources PnPSearch Templates mytemplate.html, mytemplate2.html URLs for the templates in this example: mytemplate.html: https://contoso.sharepoint.com/sites/SharePointResources/PnPSearch/Templates/mytemplate.html mytemplate2.html: https://contoso.sharepoint.com/sites/SharePointResources/PnPSearch/Templates/mytemplate2.html Use the links in a PnP Search Results web part \u00b6 Edit the settings for the PnP Search Results web part. On page 2, under Available Layouts, choose Custom. Type the URL to your custom template in \"Use an external template URL. Republish the page When you edit the custom template, you just have to refresh the page where the web part is embedded to see the changes you made.","title":"Store custom templates in SharePoint"},{"location":"create-custom-layouts/howto-store-custom-templates-in-sharepoint/#store-custom-templates-in-sharepoint","text":"You can create your own custom templates. If you store them in a central repository, they can be used across PnP Search Result web parts across sites in your tenant. Requirements for using a custom template, referenced by URL are: All users of the PnP Search Result web part, must be able to read those templates. Templates need to have their own URL. A SharePoint site within your tenant is perfect for this scenario, because you get some additional features: Central repository Version control No public access Secure change access Share to others outside of your company Use OneDrive for sync Approval flows (if you want)","title":"Store custom templates in SharePoint"},{"location":"create-custom-layouts/howto-store-custom-templates-in-sharepoint/#5-steps-to-create-a-central-repository-in-sharepoint","text":"Create a SharePoint site Give \"Everyone except externals\" read access to the site Create a Library Create a Templates Folder Add your custom templates to this folder. Now you can reference these templates using the URL to the file. https://.sharepoint.com/sites////.html","title":"5 steps to create a central repository in SharePoint"},{"location":"create-custom-layouts/howto-store-custom-templates-in-sharepoint/#example-for-the-a-tenant-named-contoso","text":"Site title Library name Folder name Template names SharePointResources PnPSearch Templates mytemplate.html, mytemplate2.html URLs for the templates in this example: mytemplate.html: https://contoso.sharepoint.com/sites/SharePointResources/PnPSearch/Templates/mytemplate.html mytemplate2.html: https://contoso.sharepoint.com/sites/SharePointResources/PnPSearch/Templates/mytemplate2.html","title":"Example for the a tenant named Contoso"},{"location":"create-custom-layouts/howto-store-custom-templates-in-sharepoint/#use-the-links-in-a-pnp-search-results-web-part","text":"Edit the settings for the PnP Search Results web part. On page 2, under Available Layouts, choose Custom. Type the URL to your custom template in \"Use an external template URL. Republish the page When you edit the custom template, you just have to refresh the page where the web part is embedded to see the changes you made.","title":"Use the links in a PnP Search Results web part"},{"location":"extensibility/","text":"Extensibility possibilities \u00b6 This solution supports different levels of customizations depending your requirements: 'Basic' customizations : these include custom settings for data sources, search box, verticals and filters Web Parts + minor updates to existing layouts by adding custom HTML markup (ex: add a custom field in the UI from a data source), updates to builtin layouts fields ('Cards','Details List' and 'People'), etc. They only require HTML, CSS and Handlebars skills to be done . Typically a super user or a webmaster could do that. 'Advanced' customizations : these include major updates like adding a new data source, layout, component or suggestions provider. These are build from scratch and require SharePoint Framework development skills to be done . Typically, a front-end/SharePoint developer could do that. Note Extensibility samples are centralized in a dedicated repository: https://github.com/microsoft-search/pnp-modern-search-extensibility-samples/tree/main Basic customizations \u00b6 'Basic' customizations cover the layout templates updates with HTML, CSS and Handlebars. Refer to the templating documentation to know more. Advanced customizations \u00b6 The solution uses the concept of 'extensibility libraries' . Basically, these are SharePoint Framework library components you put in the global or site collection app catalog that will be loaded automatically by Web Parts to enhance the experience and options (ex: new data source with new options, custom layout, etc.). Simple as that! As a demonstration of capabilities, all builtin data sources, layouts, web components or suggestions providers are built using the same exact interfaces and methods that are publicly available in the @pnp/modern-search-extensibility SPFx library project. All documentation procedures for extensions are based on the demo extensibility library available in the same repository that you can use as reference. Prerequistes \u00b6 For your project to be a valid extensibility library, you must have the following prerequisites: Your project must be an SPFx library component . The main entry point of your library must implement the IExtensibilityLibrary interface from the @pnp/modern-search-extensibility library. You library manifest ID must be registered in the Web Part where you want to use the extension. SPFx version The SPFx library project must use the same SPFx version as the main solution (check source code for current version). Owherwise you may face issues at build time. See GitHub issue #1893 Supported extensions \u00b6 Each Web Part type in the solution supports several extensions or no extension at all. It means even your extensibility library contains all possible extensions, they won't be loaded if the Web Part does not support them. Web Part type Supported extensions Search Results Custom web components. Custom Handlebars customizations (ex: helpers, partials ,etc.). Custom event handlers for adaptive cards actions Custom Data Sources Custom query modifier Search Filters Custom web components ( not directly but via the 'Search Results' Web Part extensibility library registration ). Search box Custom suggestions providers. Search Verticals None. Register your extensibility library with a Web Part \u00b6 When a Web Part type supports one or multiple extensions, you can register them going to the last property pane confguration page in the 'Extensibility configuration' section: From here, you can add the manifest IDs of your libraries and decide to enable or disabled certain libraries. The manifest ID can be found in the .manifest.json file: Multiple librairies can be registered for a single Web Part instance allowing you to split your extensions into multiple projects (in the end, they will be all concatenated). For instance, this could be convenient when extensions come from different IT providers. Create an extensibility library \u00b6 To create an extensibility library, you have the choice to reuse the one provided in the GitHub repository or start from scratch. In this case: Create a new SharePoint Framework project of type 'Library' with yo @microsoft/sharepoint . Add an npm reference to @pnp/modern-search-extensibility library using npm i @pnp/modern-search-extensibility --save cmd. In the main entry point, implement the IExtensibilityLibrary interface. Provide all method implementations (return empty arrays if you don't implement specific extensions). Implement your extension(s) depending of the type: Layout Web component Suggestions providers Handlebars customizations Adaptive Cards Actions handlers Query modifier Data Sources Creation process always follows more or less the same pattern: Create the extension data logic or render logic. Register the information about the extension to be discovered and instanciated by the target Web Part by implementing the corresponding method according to the IExtensibilityLibrary interface. Bundle gulp bundle --ship and package gulp package-solution --ship and add the solution to the global or site collection catalog (for this one, it must be the same site collection where the Web Part loading that extension(s) is present). Register your manifest ID in the target Web Part instance . Enjoy! Debug a library component \u00b6 Debugging a library component is exactly the same as debugging an SPFx Web Part. Run gulp serve in the hosted workbench and put a 'Search Results' , 'Search Filters' or 'Search Box' Web Part depending the extension you want to test. If registered correctly, your breakpoints will be triggerred by the main Web Part loading your extension. Accessing the SharePoint Framework context and services in a library component \u00b6 In case you need to access the SharePoint Framework context and services, within your custom library component, you can easily do that by relying on the Service Locator pattern available in SPFx. You simply need to declare a public static property with name serviceKey in your library component and provide a constructor that accepts a ServiceScope instance as input argument. For example, here you can see a code excerpt of such a library component that handles custom actions for Adaptive Cards rendering: import { IAdaptiveCardAction , IComponentDefinition , IExtensibilityLibrary , ILayoutDefinition , ISuggestionProviderDefinition , IQueryModifierDefinition } from '@pnp/modern-search-extensibility' ; import { ServiceKey , ServiceScope } from '@microsoft/sp-core-library' ; import { SPHttpClient , SPHttpClientResponse } from '@microsoft/sp-http' ; import { PageContext } from '@microsoft/sp-page-context' ; export class MyCustomLibraryComponent implements IExtensibilityLibrary { public static readonly serviceKey : ServiceKey < MyCustomLibraryComponent > = ServiceKey . create < MyCustomLibraryComponent > ( 'SPFx:MyCustomLibraryComponent' , MyCustomLibraryComponent ); private _spHttpClient : SPHttpClient ; private _pageContext : PageContext ; private _currentWebUrl : string ; constructor ( serviceScope : ServiceScope ) { serviceScope . whenFinished (() => { this . _spHttpClient = serviceScope . consume ( SPHttpClient . serviceKey ); this . _pageContext = serviceScope . consume ( PageContext . serviceKey ); this . _currentWebUrl = this . _pageContext . web . absoluteUrl ; }); } public getCustomLayouts () : ILayoutDefinition [] { return []; } public getCustomWebComponents () : IComponentDefinition < any > [] { return []; } public getCustomSuggestionProviders () : ISuggestionProviderDefinition [] { return []; } public registerHandlebarsCustomizations ? ( handlebarsNamespace : typeof Handlebars ) : void { } public getCustomQueryModifiers ? () : IQueryModifierDefinition []{ } public invokeCardAction ( action : IAdaptiveCardAction ) : void { // Process the action based on type if ( action . type == \"Action.OpenUrl\" ) { window . open ( action . url , \"_blank\" ); } else if ( action . type == \"Action.Submit\" ) { // Process the Submit action based on title switch ( action . title . toLowerCase ()) { case \"user\" : // Invoke the currentUser endpoint this . _spHttpClient . get ( ` ${ this . _currentWebUrl } /_api/web/currentUser` , SPHttpClient . configurations . v1 , null ). then (( response : SPHttpClientResponse ) => { return response . json (); }); break ; default : console.log ( 'Action not supported!' ); break ; } } } public getCustomDataSources () : IDataSourceDefinition [] { return [ { name : 'Custom Data Source' , iconName : 'Database' , key : 'CustomDataSource' , serviceKey : ServiceKey.create < IDataSource > ( 'CustomDataSource' , CustomDataSource ) } ]; } public name () : string { return 'MyCustomLibraryComponent' ; } } In order to run the above sample code, you will need to import in your library the following npm packages: @microsoft/sp-component-base , @microsoft/sp-core-library , and @microsoft/sp-webpart-base .","title":"Extensibility"},{"location":"extensibility/#extensibility-possibilities","text":"This solution supports different levels of customizations depending your requirements: 'Basic' customizations : these include custom settings for data sources, search box, verticals and filters Web Parts + minor updates to existing layouts by adding custom HTML markup (ex: add a custom field in the UI from a data source), updates to builtin layouts fields ('Cards','Details List' and 'People'), etc. They only require HTML, CSS and Handlebars skills to be done . Typically a super user or a webmaster could do that. 'Advanced' customizations : these include major updates like adding a new data source, layout, component or suggestions provider. These are build from scratch and require SharePoint Framework development skills to be done . Typically, a front-end/SharePoint developer could do that. Note Extensibility samples are centralized in a dedicated repository: https://github.com/microsoft-search/pnp-modern-search-extensibility-samples/tree/main","title":"Extensibility possibilities"},{"location":"extensibility/#basic-customizations","text":"'Basic' customizations cover the layout templates updates with HTML, CSS and Handlebars. Refer to the templating documentation to know more.","title":"Basic customizations"},{"location":"extensibility/#advanced-customizations","text":"The solution uses the concept of 'extensibility libraries' . Basically, these are SharePoint Framework library components you put in the global or site collection app catalog that will be loaded automatically by Web Parts to enhance the experience and options (ex: new data source with new options, custom layout, etc.). Simple as that! As a demonstration of capabilities, all builtin data sources, layouts, web components or suggestions providers are built using the same exact interfaces and methods that are publicly available in the @pnp/modern-search-extensibility SPFx library project. All documentation procedures for extensions are based on the demo extensibility library available in the same repository that you can use as reference.","title":"Advanced customizations"},{"location":"extensibility/#prerequistes","text":"For your project to be a valid extensibility library, you must have the following prerequisites: Your project must be an SPFx library component . The main entry point of your library must implement the IExtensibilityLibrary interface from the @pnp/modern-search-extensibility library. You library manifest ID must be registered in the Web Part where you want to use the extension. SPFx version The SPFx library project must use the same SPFx version as the main solution (check source code for current version). Owherwise you may face issues at build time. See GitHub issue #1893","title":"Prerequistes"},{"location":"extensibility/#supported-extensions","text":"Each Web Part type in the solution supports several extensions or no extension at all. It means even your extensibility library contains all possible extensions, they won't be loaded if the Web Part does not support them. Web Part type Supported extensions Search Results Custom web components. Custom Handlebars customizations (ex: helpers, partials ,etc.). Custom event handlers for adaptive cards actions Custom Data Sources Custom query modifier Search Filters Custom web components ( not directly but via the 'Search Results' Web Part extensibility library registration ). Search box Custom suggestions providers. Search Verticals None.","title":"Supported extensions"},{"location":"extensibility/#register-your-extensibility-library-with-a-web-part","text":"When a Web Part type supports one or multiple extensions, you can register them going to the last property pane confguration page in the 'Extensibility configuration' section: From here, you can add the manifest IDs of your libraries and decide to enable or disabled certain libraries. The manifest ID can be found in the .manifest.json file: Multiple librairies can be registered for a single Web Part instance allowing you to split your extensions into multiple projects (in the end, they will be all concatenated). For instance, this could be convenient when extensions come from different IT providers.","title":"Register your extensibility library with a Web Part"},{"location":"extensibility/#create-an-extensibility-library","text":"To create an extensibility library, you have the choice to reuse the one provided in the GitHub repository or start from scratch. In this case: Create a new SharePoint Framework project of type 'Library' with yo @microsoft/sharepoint . Add an npm reference to @pnp/modern-search-extensibility library using npm i @pnp/modern-search-extensibility --save cmd. In the main entry point, implement the IExtensibilityLibrary interface. Provide all method implementations (return empty arrays if you don't implement specific extensions). Implement your extension(s) depending of the type: Layout Web component Suggestions providers Handlebars customizations Adaptive Cards Actions handlers Query modifier Data Sources Creation process always follows more or less the same pattern: Create the extension data logic or render logic. Register the information about the extension to be discovered and instanciated by the target Web Part by implementing the corresponding method according to the IExtensibilityLibrary interface. Bundle gulp bundle --ship and package gulp package-solution --ship and add the solution to the global or site collection catalog (for this one, it must be the same site collection where the Web Part loading that extension(s) is present). Register your manifest ID in the target Web Part instance . Enjoy!","title":"Create an extensibility library"},{"location":"extensibility/#debug-a-library-component","text":"Debugging a library component is exactly the same as debugging an SPFx Web Part. Run gulp serve in the hosted workbench and put a 'Search Results' , 'Search Filters' or 'Search Box' Web Part depending the extension you want to test. If registered correctly, your breakpoints will be triggerred by the main Web Part loading your extension.","title":"Debug a library component"},{"location":"extensibility/#accessing-the-sharepoint-framework-context-and-services-in-a-library-component","text":"In case you need to access the SharePoint Framework context and services, within your custom library component, you can easily do that by relying on the Service Locator pattern available in SPFx. You simply need to declare a public static property with name serviceKey in your library component and provide a constructor that accepts a ServiceScope instance as input argument. For example, here you can see a code excerpt of such a library component that handles custom actions for Adaptive Cards rendering: import { IAdaptiveCardAction , IComponentDefinition , IExtensibilityLibrary , ILayoutDefinition , ISuggestionProviderDefinition , IQueryModifierDefinition } from '@pnp/modern-search-extensibility' ; import { ServiceKey , ServiceScope } from '@microsoft/sp-core-library' ; import { SPHttpClient , SPHttpClientResponse } from '@microsoft/sp-http' ; import { PageContext } from '@microsoft/sp-page-context' ; export class MyCustomLibraryComponent implements IExtensibilityLibrary { public static readonly serviceKey : ServiceKey < MyCustomLibraryComponent > = ServiceKey . create < MyCustomLibraryComponent > ( 'SPFx:MyCustomLibraryComponent' , MyCustomLibraryComponent ); private _spHttpClient : SPHttpClient ; private _pageContext : PageContext ; private _currentWebUrl : string ; constructor ( serviceScope : ServiceScope ) { serviceScope . whenFinished (() => { this . _spHttpClient = serviceScope . consume ( SPHttpClient . serviceKey ); this . _pageContext = serviceScope . consume ( PageContext . serviceKey ); this . _currentWebUrl = this . _pageContext . web . absoluteUrl ; }); } public getCustomLayouts () : ILayoutDefinition [] { return []; } public getCustomWebComponents () : IComponentDefinition < any > [] { return []; } public getCustomSuggestionProviders () : ISuggestionProviderDefinition [] { return []; } public registerHandlebarsCustomizations ? ( handlebarsNamespace : typeof Handlebars ) : void { } public getCustomQueryModifiers ? () : IQueryModifierDefinition []{ } public invokeCardAction ( action : IAdaptiveCardAction ) : void { // Process the action based on type if ( action . type == \"Action.OpenUrl\" ) { window . open ( action . url , \"_blank\" ); } else if ( action . type == \"Action.Submit\" ) { // Process the Submit action based on title switch ( action . title . toLowerCase ()) { case \"user\" : // Invoke the currentUser endpoint this . _spHttpClient . get ( ` ${ this . _currentWebUrl } /_api/web/currentUser` , SPHttpClient . configurations . v1 , null ). then (( response : SPHttpClientResponse ) => { return response . json (); }); break ; default : console.log ( 'Action not supported!' ); break ; } } } public getCustomDataSources () : IDataSourceDefinition [] { return [ { name : 'Custom Data Source' , iconName : 'Database' , key : 'CustomDataSource' , serviceKey : ServiceKey.create < IDataSource > ( 'CustomDataSource' , CustomDataSource ) } ]; } public name () : string { return 'MyCustomLibraryComponent' ; } } In order to run the above sample code, you will need to import in your library the following npm packages: @microsoft/sp-component-base , @microsoft/sp-core-library , and @microsoft/sp-webpart-base .","title":"Accessing the SharePoint Framework context and services in a library component"},{"location":"extensibility/adaptivecards_customizations/","text":"Register Adaptive Cards Actions handlers customizations \u00b6 If you want to render the search results using a custom Adaptive Card, you might also want to handle custom events upon actions happening in the Adaptive Cards instances. To register a new Adaptive Cards Actions handler customization for the targeted Web Part (i.e. the Web Part instances where the extensibility library is registered and enabled): In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface), implement the invokeCardAction(action: any): void method. From within the method write your own implementation of any of the custom actions that you want to handle. public invokeCardAction ( action : any ) : void { // Process the action based on type if ( action . type == \"Action.OpenUrl\" ) { window . open ( action . url , \"_blank\" ); } else if ( action . type == \"Action.Submit\" ) { // Process the action based on title switch ( action . title ) { case 'Click on item' : console . log ( action . data ); break ; case 'Global click' : alert ( action ); break ; default : console.log ( 'Action not supported!' ); break ; } } } In the JSON of the custom Adaptive Card, you can define the custom actions, like in the following code excerpt: { \"$schema\" : \"http://adaptivecards.io/schemas/adaptive-card.json\" , \"type\" : \"AdaptiveCard\" , \"version\" : \"1.3\" , \"body\" : [ { \"type\" : \"TextBlock\" , \"text\" : \"**${$root.data.totalItemsCount}** results\" , \"size\" : \"Medium\" , \"wrap\" : true , \"$when\" : \"${$root.properties.showResultsCount == true}\" }, { \"type\" : \"Container\" , \"$data\" : \"${data.items}\" , \"items\" : [ { \"type\" : \"ColumnSet\" , \"id\" : \"${hitId}\" , \"columns\" : [ { \"type\" : \"Column\" , \"items\" : [ { \"type\" : \"TextBlock\" , \"wrap\" : true , \"text\" : \"\" } ], \"width\" : \"auto\" }, { \"type\" : \"Column\" , \"items\" : [ { \"type\" : \"TextBlock\" , \"wrap\" : true , \"text\" : \"[${string(jPath($data, concat('.',$root.slots['Title']))[0])}](${string(jPath($data, concat('.',$root.slots['Path']))[0])})\" } ], \"width\" : \"auto\" }, { \"type\" : \"Column\" , \"items\" : [ { \"type\" : \"ActionSet\" , \"actions\" : [ { \"type\" : \"Action.Submit\" , \"title\" : \"Click on item\" , \"style\" : \"positive\" , \"data\" : { \"id\" : \"123\" } } ], \"spacing\" : \"medium\" } ], \"width\" : \"auto\" } ] } ] } ], \"actions\" : [ { \"type\" : \"Action.Submit\" , \"title\" : \"Global click\" , \"data\" : { \"id\" : \"456\" } }, { \"type\" : \"Action.OpenUrl\" , \"title\" : \"Open URL\" , \"url\" : \"https://pnp.github.io/\" } ] }","title":"Custom event handlers for adaptive cards actions"},{"location":"extensibility/adaptivecards_customizations/#register-adaptive-cards-actions-handlers-customizations","text":"If you want to render the search results using a custom Adaptive Card, you might also want to handle custom events upon actions happening in the Adaptive Cards instances. To register a new Adaptive Cards Actions handler customization for the targeted Web Part (i.e. the Web Part instances where the extensibility library is registered and enabled): In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface), implement the invokeCardAction(action: any): void method. From within the method write your own implementation of any of the custom actions that you want to handle. public invokeCardAction ( action : any ) : void { // Process the action based on type if ( action . type == \"Action.OpenUrl\" ) { window . open ( action . url , \"_blank\" ); } else if ( action . type == \"Action.Submit\" ) { // Process the action based on title switch ( action . title ) { case 'Click on item' : console . log ( action . data ); break ; case 'Global click' : alert ( action ); break ; default : console.log ( 'Action not supported!' ); break ; } } } In the JSON of the custom Adaptive Card, you can define the custom actions, like in the following code excerpt: { \"$schema\" : \"http://adaptivecards.io/schemas/adaptive-card.json\" , \"type\" : \"AdaptiveCard\" , \"version\" : \"1.3\" , \"body\" : [ { \"type\" : \"TextBlock\" , \"text\" : \"**${$root.data.totalItemsCount}** results\" , \"size\" : \"Medium\" , \"wrap\" : true , \"$when\" : \"${$root.properties.showResultsCount == true}\" }, { \"type\" : \"Container\" , \"$data\" : \"${data.items}\" , \"items\" : [ { \"type\" : \"ColumnSet\" , \"id\" : \"${hitId}\" , \"columns\" : [ { \"type\" : \"Column\" , \"items\" : [ { \"type\" : \"TextBlock\" , \"wrap\" : true , \"text\" : \"\" } ], \"width\" : \"auto\" }, { \"type\" : \"Column\" , \"items\" : [ { \"type\" : \"TextBlock\" , \"wrap\" : true , \"text\" : \"[${string(jPath($data, concat('.',$root.slots['Title']))[0])}](${string(jPath($data, concat('.',$root.slots['Path']))[0])})\" } ], \"width\" : \"auto\" }, { \"type\" : \"Column\" , \"items\" : [ { \"type\" : \"ActionSet\" , \"actions\" : [ { \"type\" : \"Action.Submit\" , \"title\" : \"Click on item\" , \"style\" : \"positive\" , \"data\" : { \"id\" : \"123\" } } ], \"spacing\" : \"medium\" } ], \"width\" : \"auto\" } ] } ] } ], \"actions\" : [ { \"type\" : \"Action.Submit\" , \"title\" : \"Global click\" , \"data\" : { \"id\" : \"456\" } }, { \"type\" : \"Action.OpenUrl\" , \"title\" : \"Open URL\" , \"url\" : \"https://pnp.github.io/\" } ] }","title":"Register Adaptive Cards Actions handlers customizations"},{"location":"extensibility/custom_data_sources/","text":"Create a custom data source \u00b6 Custom data sources can be added to a search results Web Part to get results from your custom source. Custom data source creation process \u00b6 Custom data source creation process comes in two distinct steps: Create the data source logic . Register the data source information for discovery . Create the data source logic \u00b6 In your extensibility library project, create a new CustomDataSource.ts TypeScript file. Create an interface for your data source properties, typically the ones you want to persist in the Web Part property bag. Data source properties are isolated from the other general Web Part properties under the property dataSourceProperties in the property bag object. export interface ICustomDataSourceProperties { possibleResults : string ; } Implement the BaseDataSource abstract class using your properties interface: export class CustomDataSource extends BaseDataSource < ICustomDataSourceProperties > { ... } Implement your data source logic according to the available methods and properties. BaseDataSource - Methods \u00b6 Method Description onInit() The initialization method of your data source (ex: initialize your properties, etc.). You can perform asynchronous calls here. This method will be called when the data source is instantiated by the main Web Part. getPropertyPaneGroupsConfiguration() Returns the property pane fields to display when your data source is selected. These are regular SPFx property fields and groups. Data source properties are isolated from the other general Web Part properties under the property dataSourceProperties . It means you must include that path in your property pane controls to get the value persisted. Defining fields or groups is not mandatory for a provider. If you don't want to expose any option, just return an empty array. onPropertyUpdate() The method will be called when a property pane value is updated. The main Web Part in Reactive mode for property pane fields. getPagingBehavior() The method should return the desired paging behavior for the data source. Will be 'None' if not specified. getFilterBehavior() The method should return the desired filter behavior for the data source. Will be 'Static' if not specified. getAppliedFilters() If any, this method should return the list of filters (i.e data source fields) applied by the data source to filter results. getItemCount() The method should return the total number of items. This information will be used to generate page numbers. getTemplateSlots() The method should return the available template slots for this data source. getSortableFields() The method should return the list of sortable fields for the data source if applicable BaseDataSource - Properties \u00b6 Property Description properties The Web Part properties in the property bag. Corresponds to the isolated dataSourceProperties property in the global property bag. You won't be able to access any other general properties of the Web Part. Register provider information \u00b6 The next step is to provide information about your new data source. In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface) return a new IDataSourceDefinition array in the getCustomDataSources() method using these properties: Property Description name The friendly name of your data source that will show up in the configuration panel. iconName The name of an icon from Office UI Fabric/Fluent UI that will be shown in the data source options. key An unique internal key for your data source. serviceKey A service key used to instantiate your data source class. Builtin or custom data sources are instantiated dynamically using SPFx service scopes . public getCustomDataSources () : IDataSourceDefinition [] { return [ { name : 'Custom Data Source' , iconName : 'Database' , key : 'CustomDataSource' , serviceKey : ServiceKey.create < IDataSource > ( 'CustomDataSource' , CustomDataSource ) } ]; }","title":"Custom data sources"},{"location":"extensibility/custom_data_sources/#create-a-custom-data-source","text":"Custom data sources can be added to a search results Web Part to get results from your custom source.","title":"Create a custom data source"},{"location":"extensibility/custom_data_sources/#custom-data-source-creation-process","text":"Custom data source creation process comes in two distinct steps: Create the data source logic . Register the data source information for discovery .","title":"Custom data source creation process"},{"location":"extensibility/custom_data_sources/#create-the-data-source-logic","text":"In your extensibility library project, create a new CustomDataSource.ts TypeScript file. Create an interface for your data source properties, typically the ones you want to persist in the Web Part property bag. Data source properties are isolated from the other general Web Part properties under the property dataSourceProperties in the property bag object. export interface ICustomDataSourceProperties { possibleResults : string ; } Implement the BaseDataSource abstract class using your properties interface: export class CustomDataSource extends BaseDataSource < ICustomDataSourceProperties > { ... } Implement your data source logic according to the available methods and properties.","title":"Create the data source logic"},{"location":"extensibility/custom_data_sources/#basedatasource-methods","text":"Method Description onInit() The initialization method of your data source (ex: initialize your properties, etc.). You can perform asynchronous calls here. This method will be called when the data source is instantiated by the main Web Part. getPropertyPaneGroupsConfiguration() Returns the property pane fields to display when your data source is selected. These are regular SPFx property fields and groups. Data source properties are isolated from the other general Web Part properties under the property dataSourceProperties . It means you must include that path in your property pane controls to get the value persisted. Defining fields or groups is not mandatory for a provider. If you don't want to expose any option, just return an empty array. onPropertyUpdate() The method will be called when a property pane value is updated. The main Web Part in Reactive mode for property pane fields. getPagingBehavior() The method should return the desired paging behavior for the data source. Will be 'None' if not specified. getFilterBehavior() The method should return the desired filter behavior for the data source. Will be 'Static' if not specified. getAppliedFilters() If any, this method should return the list of filters (i.e data source fields) applied by the data source to filter results. getItemCount() The method should return the total number of items. This information will be used to generate page numbers. getTemplateSlots() The method should return the available template slots for this data source. getSortableFields() The method should return the list of sortable fields for the data source if applicable","title":"BaseDataSource - Methods"},{"location":"extensibility/custom_data_sources/#basedatasource-properties","text":"Property Description properties The Web Part properties in the property bag. Corresponds to the isolated dataSourceProperties property in the global property bag. You won't be able to access any other general properties of the Web Part.","title":"BaseDataSource - Properties"},{"location":"extensibility/custom_data_sources/#register-provider-information","text":"The next step is to provide information about your new data source. In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface) return a new IDataSourceDefinition array in the getCustomDataSources() method using these properties: Property Description name The friendly name of your data source that will show up in the configuration panel. iconName The name of an icon from Office UI Fabric/Fluent UI that will be shown in the data source options. key An unique internal key for your data source. serviceKey A service key used to instantiate your data source class. Builtin or custom data sources are instantiated dynamically using SPFx service scopes . public getCustomDataSources () : IDataSourceDefinition [] { return [ { name : 'Custom Data Source' , iconName : 'Database' , key : 'CustomDataSource' , serviceKey : ServiceKey.create < IDataSource > ( 'CustomDataSource' , CustomDataSource ) } ]; }","title":"Register provider information"},{"location":"extensibility/custom_layout/","text":"Create a custom layout \u00b6 Custom layouts are only supported for the 'Search Results' Web Part. You can't add custom layout for the 'Search Filters' Web Part. Layout creation process \u00b6 Same as data source, the layout creation process comes in three distinct steps: Create the layout class (i.e. define the property pane options) . Create the HTML template associated to that layout . Register the layout information for discovery . Create the layout \u00b6 In your extensibility library project, create a new MyLayout.ts TypeScript file. Create an interface for your layout properties, typically the ones you want to persist in the Web Part property bag. Layout properties are isolated from the other general Web Part properties under the property layoutProperties in the property bag object. export interface ICustomLayoutProperties { myTextProperty : string ; } Implement the BaseLayout abstract class using your properties interface: export class Customlayout extends BaseLayout < ICustomLayoutProperties > { ... } Implement your layout logic according to the available methods and properties. BaseLayout - Methods \u00b6 Method Description onInit() The initialization method of your layout (ex: initialize your properties, etc.). You can perform asynchronous calls here. This method will be called when the layout is instanciated by the main Web Part. getPropertyPaneGroupsConfiguration() Returns the property pane fields to display when your layout is selected. These are regular SPFx property fields and groups. Layout properties are isolated from the other general Web Part properties under the property layoutProperties . It means you must include that path in your property pane controls get the value persisted (same thing as custom data source). Defining fields or groups is not mandatory for a layout. If you don't want to expose any option, just return an empty array. onPropertyUpdate() The method will be called when a property pane value is updated. The main Web Part in Reactive mode for property pane fields. BaseLayout - Properties \u00b6 Property Description properties The Web Part properties in the property bag. Corresponds to the isolated layoutProperties property in the global property bag. You won't be able to access any other general properties of the Web Part. Create the HTML template file (Handlebars) \u00b6 In your extensibility library project, create a new custom-layout.html HTML file. A layout template is split into two distinct parts: A template part, containing the HTML markup to display your data once fetched . This part is mandatory to display your data. < content id = \"template\" > A placeholder part, containing the HTML markup to display as placeholder while the data are getting fetched . This part is optional. < content id = \"placeholder\" > In a template, you must use Handlebars expressions to access and display your data. Example: iterating through all items {{ #each data.items as | item | }} {{ /each }} Register layout information \u00b6 The next step is to fill information about your new layout. In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface) return a new ILayoutDefinition object in the getCustomLayouts() method using these properties: Property Description name The friendly name of your layout that will show up in tiles. iconName An Office UI Fabric icon for your layout. key An unique internal key for your layout. type The layout type ( LayoutType.Results is for the 'Data Visualizer' Web Part, LayoutType.Filter for the 'Data Filter' Web Part). Only LayoutType.Results is supported for now. You can't add custom layout for the 'Data Filter' Web Part. templateContent The template HTML content as string. Use a require statement to get the string content from your HTML file. If you reference a JSON file, you must use the stringified value (ex: JSON.stringify(require('../custom-layout.json'), null, \"\\t\") ) serviceKey A service key used to instanciate your layout class. Builtin or custom data layouts are instanciated dynamically using SPFx service scopes . public getCustomLayouts () : ILayoutDefinition [] { return [ { name : 'My custom layout' , iconName : 'Color' , key : 'CustomLayout' , type : LayoutType . Results , templateContent : require ( '../custom-layout.html' ), serviceKey : ServiceKey.create < ILayout > ( 'MyCompany:CustomLayout' , Customlayout ) } ]; }","title":"Custom layout"},{"location":"extensibility/custom_layout/#create-a-custom-layout","text":"Custom layouts are only supported for the 'Search Results' Web Part. You can't add custom layout for the 'Search Filters' Web Part.","title":"Create a custom layout"},{"location":"extensibility/custom_layout/#layout-creation-process","text":"Same as data source, the layout creation process comes in three distinct steps: Create the layout class (i.e. define the property pane options) . Create the HTML template associated to that layout . Register the layout information for discovery .","title":"Layout creation process"},{"location":"extensibility/custom_layout/#create-the-layout","text":"In your extensibility library project, create a new MyLayout.ts TypeScript file. Create an interface for your layout properties, typically the ones you want to persist in the Web Part property bag. Layout properties are isolated from the other general Web Part properties under the property layoutProperties in the property bag object. export interface ICustomLayoutProperties { myTextProperty : string ; } Implement the BaseLayout abstract class using your properties interface: export class Customlayout extends BaseLayout < ICustomLayoutProperties > { ... } Implement your layout logic according to the available methods and properties.","title":"Create the layout"},{"location":"extensibility/custom_layout/#baselayout-methods","text":"Method Description onInit() The initialization method of your layout (ex: initialize your properties, etc.). You can perform asynchronous calls here. This method will be called when the layout is instanciated by the main Web Part. getPropertyPaneGroupsConfiguration() Returns the property pane fields to display when your layout is selected. These are regular SPFx property fields and groups. Layout properties are isolated from the other general Web Part properties under the property layoutProperties . It means you must include that path in your property pane controls get the value persisted (same thing as custom data source). Defining fields or groups is not mandatory for a layout. If you don't want to expose any option, just return an empty array. onPropertyUpdate() The method will be called when a property pane value is updated. The main Web Part in Reactive mode for property pane fields.","title":"BaseLayout - Methods"},{"location":"extensibility/custom_layout/#baselayout-properties","text":"Property Description properties The Web Part properties in the property bag. Corresponds to the isolated layoutProperties property in the global property bag. You won't be able to access any other general properties of the Web Part.","title":"BaseLayout - Properties"},{"location":"extensibility/custom_layout/#create-the-html-template-file-handlebars","text":"In your extensibility library project, create a new custom-layout.html HTML file. A layout template is split into two distinct parts: A template part, containing the HTML markup to display your data once fetched . This part is mandatory to display your data. < content id = \"template\" > A placeholder part, containing the HTML markup to display as placeholder while the data are getting fetched . This part is optional. < content id = \"placeholder\" > In a template, you must use Handlebars expressions to access and display your data. Example: iterating through all items {{ #each data.items as | item | }} {{ /each }}","title":"Create the HTML template file (Handlebars)"},{"location":"extensibility/custom_layout/#register-layout-information","text":"The next step is to fill information about your new layout. In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface) return a new ILayoutDefinition object in the getCustomLayouts() method using these properties: Property Description name The friendly name of your layout that will show up in tiles. iconName An Office UI Fabric icon for your layout. key An unique internal key for your layout. type The layout type ( LayoutType.Results is for the 'Data Visualizer' Web Part, LayoutType.Filter for the 'Data Filter' Web Part). Only LayoutType.Results is supported for now. You can't add custom layout for the 'Data Filter' Web Part. templateContent The template HTML content as string. Use a require statement to get the string content from your HTML file. If you reference a JSON file, you must use the stringified value (ex: JSON.stringify(require('../custom-layout.json'), null, \"\\t\") ) serviceKey A service key used to instanciate your layout class. Builtin or custom data layouts are instanciated dynamically using SPFx service scopes . public getCustomLayouts () : ILayoutDefinition [] { return [ { name : 'My custom layout' , iconName : 'Color' , key : 'CustomLayout' , type : LayoutType . Results , templateContent : require ( '../custom-layout.html' ), serviceKey : ServiceKey.create < ILayout > ( 'MyCompany:CustomLayout' , Customlayout ) } ]; }","title":"Register layout information"},{"location":"extensibility/custom_query_modifications/","text":"Create a custom query modifier \u00b6 Custom query modifier can be added to a search result Web Part to modify search requests before they are sent to the server. A query modifier supports: Modification of query text : a query modifier can alter the query text. Sorted modifications : modifier can be sorted and are executed in order - but you can set a modifier to stop further modifications. Custom modifier creation process \u00b6 Custom modifier creation process comes in two distinct steps: Create the modifier logic . Register the modifier information for discovery . Create the provider logic \u00b6 In your extensibility library project, create a new CustomQueryModifier.ts TypeScript file. Create an interface for your modifier properties, typically the ones you want to persist in the Web Part property bag. Modifier properties are isolated from the other general Web Part properties under the property queryModifierProperties in the property bag object. export interface ICustomQueryModifierProperties { myProperty : string ; } Implement the BaseQueryModifier abstract class using your properties interface: export class CustomQueryModifier extends BaseQueryModifier < ICustomQueryModifierProperties > { ... } Implement your query modifier logic according to the available methods and properties. BaseQueryModifier - Methods \u00b6 Method Description onInit() The initialization method of your query modifier (ex: initialize your properties, etc.). You can perform asynchronous calls here. This method will be called when the provider is instanciated by the main Web Part. This is a good place to initialize any consumed services if any. modifyQuery() Method called to get a query modification when a search is requested. getPropertyPaneGroupsConfiguration() Returns the property pane fields to display when your query modifier is selected. These are regular SPFx property fields and groups. Query modifier properties are isolated from the other general Web Part properties under the property queryModifierProperties . It means you must include that path in your property pane controls get the value persisted. Defining fields or groups is not mandatory for a provider. If you don't want to expose any option, just return an empty array. onPropertyUpdate() The method will be called when a property pane value is updated. The main Web Part is in Reactive mode for property pane fields. BaseQueryModifier - Properties \u00b6 Property Description properties The Web Part properties in the property bag. Corresponds to the isolated queryModifierProperties property in the global property bag. You won't be able to access any other general properties of the Web Part. endWhenSuccessfull Flag indicating if this should be the last query modification when the query was modified - can be switched in the query modifier list overview. Register provider information \u00b6 The next step is to fill information about your new query modifier. In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface) return a new IQueryModifierDefinition object in the getCustomQueryModifiers() method using these properties: Property Description name The friendly name of your query modifier that will show up in the configuration panel. key An unique internal key for your data source. description A meaningful description of your query modifier. serviceKey A service key used to instanciate your query modifier class. Builtin or custom query modifiers are instanciated dynamically using SPFx service scopes . public getCustomQueryModifiers () : IQueryModifierDefinition [] { return [ { name : 'Custom Query Modifier' , key : 'CustomQueryModifier' , description : 'A demo custom query modifier from the extensibility library' , serviceKey : ServiceKey.create < IQueryModifier > ( 'MyCompany:CustomQueryModifier' , CustomQueryModifier ) } ]; }","title":"Custom query modifier"},{"location":"extensibility/custom_query_modifications/#create-a-custom-query-modifier","text":"Custom query modifier can be added to a search result Web Part to modify search requests before they are sent to the server. A query modifier supports: Modification of query text : a query modifier can alter the query text. Sorted modifications : modifier can be sorted and are executed in order - but you can set a modifier to stop further modifications.","title":"Create a custom query modifier"},{"location":"extensibility/custom_query_modifications/#custom-modifier-creation-process","text":"Custom modifier creation process comes in two distinct steps: Create the modifier logic . Register the modifier information for discovery .","title":"Custom modifier creation process"},{"location":"extensibility/custom_query_modifications/#create-the-provider-logic","text":"In your extensibility library project, create a new CustomQueryModifier.ts TypeScript file. Create an interface for your modifier properties, typically the ones you want to persist in the Web Part property bag. Modifier properties are isolated from the other general Web Part properties under the property queryModifierProperties in the property bag object. export interface ICustomQueryModifierProperties { myProperty : string ; } Implement the BaseQueryModifier abstract class using your properties interface: export class CustomQueryModifier extends BaseQueryModifier < ICustomQueryModifierProperties > { ... } Implement your query modifier logic according to the available methods and properties.","title":"Create the provider logic"},{"location":"extensibility/custom_query_modifications/#basequerymodifier-methods","text":"Method Description onInit() The initialization method of your query modifier (ex: initialize your properties, etc.). You can perform asynchronous calls here. This method will be called when the provider is instanciated by the main Web Part. This is a good place to initialize any consumed services if any. modifyQuery() Method called to get a query modification when a search is requested. getPropertyPaneGroupsConfiguration() Returns the property pane fields to display when your query modifier is selected. These are regular SPFx property fields and groups. Query modifier properties are isolated from the other general Web Part properties under the property queryModifierProperties . It means you must include that path in your property pane controls get the value persisted. Defining fields or groups is not mandatory for a provider. If you don't want to expose any option, just return an empty array. onPropertyUpdate() The method will be called when a property pane value is updated. The main Web Part is in Reactive mode for property pane fields.","title":"BaseQueryModifier - Methods"},{"location":"extensibility/custom_query_modifications/#basequerymodifier-properties","text":"Property Description properties The Web Part properties in the property bag. Corresponds to the isolated queryModifierProperties property in the global property bag. You won't be able to access any other general properties of the Web Part. endWhenSuccessfull Flag indicating if this should be the last query modification when the query was modified - can be switched in the query modifier list overview.","title":"BaseQueryModifier - Properties"},{"location":"extensibility/custom_query_modifications/#register-provider-information","text":"The next step is to fill information about your new query modifier. In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface) return a new IQueryModifierDefinition object in the getCustomQueryModifiers() method using these properties: Property Description name The friendly name of your query modifier that will show up in the configuration panel. key An unique internal key for your data source. description A meaningful description of your query modifier. serviceKey A service key used to instanciate your query modifier class. Builtin or custom query modifiers are instanciated dynamically using SPFx service scopes . public getCustomQueryModifiers () : IQueryModifierDefinition [] { return [ { name : 'Custom Query Modifier' , key : 'CustomQueryModifier' , description : 'A demo custom query modifier from the extensibility library' , serviceKey : ServiceKey.create < IQueryModifier > ( 'MyCompany:CustomQueryModifier' , CustomQueryModifier ) } ]; }","title":"Register provider information"},{"location":"extensibility/custom_suggestions_provider/","text":"Create a custom suggestions providers \u00b6 Custom suggestions providers can be added to a search box Web Part to get normalized keywords during search. A suggestions provider supports: Zero term suggestions : suggestions displayed when the search box get the initial focus and no term is provided. Suggestions based on a keywords : suggestions matching specific keywords provided in the search box. Custom suggestions provider creation process \u00b6 Suggestions provider creation process comes in two distinct steps: Create the provider logic . Register the provider information for discovery . Create the provider logic \u00b6 In your extensibility library project, create a new MyProvider.ts TypeScript file. Create an interface for your provider properties, typically the ones you want to persist in the Web Part property bag. Providers properties are isolated from the other general Web Part properties under the property providerProperties in the property bag object. export interface ICustomSuggestionProviderProperties { myProperty : string ; } Implement the BaseSuggestionProvider abstract class using your properties interface: export class CustomSuggestionProvider extends BaseSuggestionProvider < ICustomSuggestionProviderProperties > { ... } Implement your provider logic according to the available methods and properties. BaseSuggestionProvider - Methods \u00b6 Method Description onInit() The initialization method of your provider (ex: initialize your properties, etc.). You can perform asynchronous calls here. This method will be called when the provider is instanciated by the main Web Part. This is a good place to fetch any zero term suggestions if any. getSuggestions() Method called to retrieve suggestions when a keyword is entered (in paramter). getZeroTermSuggestions() Method called to retrieve the zero term suggestions (i.e. when the search box gets initial focus). getPropertyPaneGroupsConfiguration() Returns the property pane fields to display when your provider is selected. These are regular SPFx property fields and groups. PRovider properties are isolated from the other general Web Part properties under the property providerProperties . It means you must include that path in your property pane controls get the value persisted. Defining fields or groups is not mandatory for a provider. If you don't want to expose any option, just return an empty array. onPropertyUpdate() The method will be called when a property pane value is updated. The main Web Part in Reactive mode for property pane fields. BaseSuggestionProvider - Properties \u00b6 Property Description properties The Web Part properties in the property bag. Corresponds to the isolated providerProperties property in the global property bag. You won't be able to access any other general properties of the Web Part. isZeroTermSuggestionsEnabled Flag indicating if the provider supports zero term suggestions or not. Register provider information \u00b6 The next step is to fill information about your new suggestions provider. In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface) return a new ISuggestionProviderDefinition object in the getCustomSuggestionProviders() method using these properties: Property Description name The friendly name of your provider that will show up in the configuration panel. key An unique internal key for your data source. description A meaningful description of your provider. serviceKey A service key used to instanciate your provider class. Builtin or custom providers are instanciated dynamically using SPFx service scopes . public getCustomSuggestionProviders () : ISuggestionProviderDefinition [] { return [ { name : 'Custom Suggestions Provider' , key : 'CustomSuggestionsProvider' , description : 'A demo custom suggestions provider from the extensibility library' , serviceKey : ServiceKey.create < ISuggestionProvider > ( 'MyCompany:CustomSuggestionsProvider' , CustomSuggestionProvider ) } ]; }","title":"Custom suggestions provider"},{"location":"extensibility/custom_suggestions_provider/#create-a-custom-suggestions-providers","text":"Custom suggestions providers can be added to a search box Web Part to get normalized keywords during search. A suggestions provider supports: Zero term suggestions : suggestions displayed when the search box get the initial focus and no term is provided. Suggestions based on a keywords : suggestions matching specific keywords provided in the search box.","title":"Create a custom suggestions providers"},{"location":"extensibility/custom_suggestions_provider/#custom-suggestions-provider-creation-process","text":"Suggestions provider creation process comes in two distinct steps: Create the provider logic . Register the provider information for discovery .","title":"Custom suggestions provider creation process"},{"location":"extensibility/custom_suggestions_provider/#create-the-provider-logic","text":"In your extensibility library project, create a new MyProvider.ts TypeScript file. Create an interface for your provider properties, typically the ones you want to persist in the Web Part property bag. Providers properties are isolated from the other general Web Part properties under the property providerProperties in the property bag object. export interface ICustomSuggestionProviderProperties { myProperty : string ; } Implement the BaseSuggestionProvider abstract class using your properties interface: export class CustomSuggestionProvider extends BaseSuggestionProvider < ICustomSuggestionProviderProperties > { ... } Implement your provider logic according to the available methods and properties.","title":"Create the provider logic"},{"location":"extensibility/custom_suggestions_provider/#basesuggestionprovider-methods","text":"Method Description onInit() The initialization method of your provider (ex: initialize your properties, etc.). You can perform asynchronous calls here. This method will be called when the provider is instanciated by the main Web Part. This is a good place to fetch any zero term suggestions if any. getSuggestions() Method called to retrieve suggestions when a keyword is entered (in paramter). getZeroTermSuggestions() Method called to retrieve the zero term suggestions (i.e. when the search box gets initial focus). getPropertyPaneGroupsConfiguration() Returns the property pane fields to display when your provider is selected. These are regular SPFx property fields and groups. PRovider properties are isolated from the other general Web Part properties under the property providerProperties . It means you must include that path in your property pane controls get the value persisted. Defining fields or groups is not mandatory for a provider. If you don't want to expose any option, just return an empty array. onPropertyUpdate() The method will be called when a property pane value is updated. The main Web Part in Reactive mode for property pane fields.","title":"BaseSuggestionProvider - Methods"},{"location":"extensibility/custom_suggestions_provider/#basesuggestionprovider-properties","text":"Property Description properties The Web Part properties in the property bag. Corresponds to the isolated providerProperties property in the global property bag. You won't be able to access any other general properties of the Web Part. isZeroTermSuggestionsEnabled Flag indicating if the provider supports zero term suggestions or not.","title":"BaseSuggestionProvider - Properties"},{"location":"extensibility/custom_suggestions_provider/#register-provider-information","text":"The next step is to fill information about your new suggestions provider. In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface) return a new ISuggestionProviderDefinition object in the getCustomSuggestionProviders() method using these properties: Property Description name The friendly name of your provider that will show up in the configuration panel. key An unique internal key for your data source. description A meaningful description of your provider. serviceKey A service key used to instanciate your provider class. Builtin or custom providers are instanciated dynamically using SPFx service scopes . public getCustomSuggestionProviders () : ISuggestionProviderDefinition [] { return [ { name : 'Custom Suggestions Provider' , key : 'CustomSuggestionsProvider' , description : 'A demo custom suggestions provider from the extensibility library' , serviceKey : ServiceKey.create < ISuggestionProvider > ( 'MyCompany:CustomSuggestionsProvider' , CustomSuggestionProvider ) } ]; }","title":"Register provider information"},{"location":"extensibility/custom_web_component/","text":"Create a custom web component \u00b6 What is a web component? \u00b6 A web component is a custom HTML element that can be used in your templates to implement complex behaviors. In the solution we used them here as \"wrappers\" for React components to be able to use them with Handlebars. More information about web components in general can be found here . By default, several components are available ( see the complete list ). If these does not fit your requirement, you can still create your own. Web component creation process \u00b6 Web component creation process comes in two distinct steps: Create the component class and its React sub components . Register the component information for discovery . Create the component logic and sub components \u00b6 A web component is typically composed of these parts: A web component class derived from the native HTMLElement class. A main React component to be rendered inside the web component. To create new component: In your extensibility library project, create a new MyComponent.ts JSX file. Create a new class extending the abstract class BaseWebComponent . This class must have at least the connectedCallback() method from base interface HTMLElement . export class MyCustomComponentWebComponent extends BaseWebComponent { public constructor () { super (); } public async connectedCallback () { ... } } Create a new regular React component (in the same file or a separate file and as class or hook): export interface IObjectParam { myProperty : string ; } export interface ICustomComponentProps { /** * A sample string param */ myStringParam? : string ; /*** * A sample object param */ myObjectParam? : IObjectParam ; } export interface ICustomComponentState { } export class CustomComponent extends React . Component < ICustomComponentProps , ICustomComponentState > { public render () { // Parse custom object const myObject : IObjectParam = this . props . myObjectParam ; return < div > { this . props . myStringParam } { myObject . myProperty } < /div>; } } In this solution, web components are considered stateless , meaning they will be entirely recreated when an attribute is changed (coming from the property pane). It means you can still use an internal state in your React components but not rely on the parent context (props) since it will be recreated every time by the Handlebars template if a property pane value is updated. The componentDidMount() method will be called every time in this case (not componentDidUpdate() ). In your web component class, render your React component: public async connectedCallback () { let props = this . resolveAttributes (); const customComponent = < CustomComponent {... props } /> ; ReactDOM . render ( customComponent , this ); } The resolveAttributes() method will look at all data-* HTML attributes in your web component custom element node and return a corresponding key/value pair object with values in their guessed type that you can pass directly to your React component as props. By convention, web component attributes have to be passed using camel case to be tranformed into React component props. For instance: a data-my-string-param HTML attribute becomes myStringParam prop. Supported guessed types for attributes are boolean , string , date and object . All non supported types will be passed a string . HTML attributes must use the data- prefix to be retrieved correctly. To pass JSON objects, you can use the JSONstringify Handlebars helper. If valid JSON, they will be returned as objects by the resolveAttributes() method. Example < my-custom-component data-my-string-param = \"Default value\" data-my-object-param = \"{{JSONstringify this 2}}\" data-my-date-param = \"01/01/2020\" data-my-boolean-param = \"true\" > Register component information \u00b6 The next step is to fill information about your new component. In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface) return a new IComponentDefinition object in the getCustomWebComponents() method using these properties: Property Description componentName The name for your component. This name will be used as the custom HTML element name (ex: ). componentClass The web component class for that component. public getCustomWebComponents () : IComponentDefinition < any > [] { return [ { componentName : 'my-custom-component' , componentClass : MyCustomComponentWebComponent } ]; } Consume services from BaseWebComponent \u00b6 const msGraphClientFactory = this . _serviceScope . consume < MSGraphClientFactory > ( MSGraphClientFactory . serviceKey ); const msGraphClient = await msGraphClientFactory . getClient ();","title":"Custom web component"},{"location":"extensibility/custom_web_component/#create-a-custom-web-component","text":"","title":"Create a custom web component"},{"location":"extensibility/custom_web_component/#what-is-a-web-component","text":"A web component is a custom HTML element that can be used in your templates to implement complex behaviors. In the solution we used them here as \"wrappers\" for React components to be able to use them with Handlebars. More information about web components in general can be found here . By default, several components are available ( see the complete list ). If these does not fit your requirement, you can still create your own.","title":"What is a web component?"},{"location":"extensibility/custom_web_component/#web-component-creation-process","text":"Web component creation process comes in two distinct steps: Create the component class and its React sub components . Register the component information for discovery .","title":"Web component creation process"},{"location":"extensibility/custom_web_component/#create-the-component-logic-and-sub-components","text":"A web component is typically composed of these parts: A web component class derived from the native HTMLElement class. A main React component to be rendered inside the web component. To create new component: In your extensibility library project, create a new MyComponent.ts JSX file. Create a new class extending the abstract class BaseWebComponent . This class must have at least the connectedCallback() method from base interface HTMLElement . export class MyCustomComponentWebComponent extends BaseWebComponent { public constructor () { super (); } public async connectedCallback () { ... } } Create a new regular React component (in the same file or a separate file and as class or hook): export interface IObjectParam { myProperty : string ; } export interface ICustomComponentProps { /** * A sample string param */ myStringParam? : string ; /*** * A sample object param */ myObjectParam? : IObjectParam ; } export interface ICustomComponentState { } export class CustomComponent extends React . Component < ICustomComponentProps , ICustomComponentState > { public render () { // Parse custom object const myObject : IObjectParam = this . props . myObjectParam ; return < div > { this . props . myStringParam } { myObject . myProperty } < /div>; } } In this solution, web components are considered stateless , meaning they will be entirely recreated when an attribute is changed (coming from the property pane). It means you can still use an internal state in your React components but not rely on the parent context (props) since it will be recreated every time by the Handlebars template if a property pane value is updated. The componentDidMount() method will be called every time in this case (not componentDidUpdate() ). In your web component class, render your React component: public async connectedCallback () { let props = this . resolveAttributes (); const customComponent = < CustomComponent {... props } /> ; ReactDOM . render ( customComponent , this ); } The resolveAttributes() method will look at all data-* HTML attributes in your web component custom element node and return a corresponding key/value pair object with values in their guessed type that you can pass directly to your React component as props. By convention, web component attributes have to be passed using camel case to be tranformed into React component props. For instance: a data-my-string-param HTML attribute becomes myStringParam prop. Supported guessed types for attributes are boolean , string , date and object . All non supported types will be passed a string . HTML attributes must use the data- prefix to be retrieved correctly. To pass JSON objects, you can use the JSONstringify Handlebars helper. If valid JSON, they will be returned as objects by the resolveAttributes() method. Example < my-custom-component data-my-string-param = \"Default value\" data-my-object-param = \"{{JSONstringify this 2}}\" data-my-date-param = \"01/01/2020\" data-my-boolean-param = \"true\" > ","title":"Create the component logic and sub components"},{"location":"extensibility/custom_web_component/#register-component-information","text":"The next step is to fill information about your new component. In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface) return a new IComponentDefinition object in the getCustomWebComponents() method using these properties: Property Description componentName The name for your component. This name will be used as the custom HTML element name (ex: ). componentClass The web component class for that component. public getCustomWebComponents () : IComponentDefinition < any > [] { return [ { componentName : 'my-custom-component' , componentClass : MyCustomComponentWebComponent } ]; }","title":"Register component information"},{"location":"extensibility/custom_web_component/#consume-services-from-basewebcomponent","text":"const msGraphClientFactory = this . _serviceScope . consume < MSGraphClientFactory > ( MSGraphClientFactory . serviceKey ); const msGraphClient = await msGraphClientFactory . getClient ();","title":"Consume services from BaseWebComponent"},{"location":"extensibility/handlebars_customizations/","text":"Register Handlebars customizations \u00b6 By default, builtin helpers and open-source Handlebars helpers are available. If these don't fit your requirements, you can still create your own custom helper or partial that you can use in your HTML templates or layout fields (ex: 'Cards' or 'Details List' layouts). To avoid any conflict, each Web Part instance gets its own Handlebars isolated namespace (i.e. using Handlebars.create() ) meaning registering customizations in the global Handlebars namespace won't work (ex: using Handlebars.registerHelper() directly). To register a new Handlebars customization for the targeted Web Part (i.e. the Web Part instances where the extensibility library is registered and enabled): In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface), register your customization using the registerHandlebarsCustomizations() method. The namespace parameter corresponds to the targeted Web Part Handlebars isolated namespace: From here, use the Handlebars API to add your customizations to this specific namespace. They will be availabe in templates for registered Web Part instances: public registerHandlebarsCustomizations ( namespace : typeof Handlebars ) { // Register custom Handlebars helpers // Usage {{myHelper 'value'}} namespace . registerHelper ( 'myHelper' , ( value : string ) => { return new namespace . SafeString ( value . toUpperCase ()); }); } To reference the deployed manifest id of your extension in the search web part see the Introduction .","title":"Custom Handlebars customizations"},{"location":"extensibility/handlebars_customizations/#register-handlebars-customizations","text":"By default, builtin helpers and open-source Handlebars helpers are available. If these don't fit your requirements, you can still create your own custom helper or partial that you can use in your HTML templates or layout fields (ex: 'Cards' or 'Details List' layouts). To avoid any conflict, each Web Part instance gets its own Handlebars isolated namespace (i.e. using Handlebars.create() ) meaning registering customizations in the global Handlebars namespace won't work (ex: using Handlebars.registerHelper() directly). To register a new Handlebars customization for the targeted Web Part (i.e. the Web Part instances where the extensibility library is registered and enabled): In the library main entry point (i.e. the class implementing the IExtensibilityLibrary in interface), register your customization using the registerHandlebarsCustomizations() method. The namespace parameter corresponds to the targeted Web Part Handlebars isolated namespace: From here, use the Handlebars API to add your customizations to this specific namespace. They will be availabe in templates for registered Web Part instances: public registerHandlebarsCustomizations ( namespace : typeof Handlebars ) { // Register custom Handlebars helpers // Usage {{myHelper 'value'}} namespace . registerHelper ( 'myHelper' , ( value : string ) => { return new namespace . SafeString ( value . toUpperCase ()); }); } To reference the deployed manifest id of your extension in the search web part see the Introduction .","title":"Register Handlebars customizations"},{"location":"extensibility/templating/","text":"Customize layout templates \u00b6 In a basic customization scenario, super users and webmasters can customize existing templates or start from a blank template to adapt the UI to their requirements. Templates can use either Handlebars or Adaptive cards templates to display data retrieved from the data source. Depdending of the template type, there are several options to customize a template: Handlebars \u00b6 Use regular HTML markup, Handlebars syntax and helpers . Write custom CSS styles . Adaptive cards \u00b6 Use declarative Adaptive Cards JSON templates with data. Both techniques \u00b6 Use data sources slots Use default web components provided by the solution. Use Microsoft Graph Toolkit components . Handlebars, HTML and CSS customizations \u00b6 The templates and fields HTML markup is sanitized automatically preventing XSS attacks. We used DOMPurify to do so. It means for instance, you cannot add your own