Build dynamic SharePoint search experience using refiners and paging with SPFx, Office UI Fabric and PnP JS library

Build dynamic SharePoint search experience using refiners and paging with SPFx, Office UI Fabric and PnP JS library

Recently, I’ve submitted a SPFx Web Part sample showing how to build a dynamic search experience using Office UI fabric components and SharePoint search REST API. This sample comes directly from a real intranet project within SharePoint Online.

Why you would you like to do this?

Well, if you’re currently implementing a new intranet using SharePoint communication sites, you’ve probably noticed you can’t customize the default search experience, for instance, adding your own refiners:

It can be very frustrating, especially if you’ve built a nice information architecture you want to take advantage from via refiners. Also, you can’t even change the target result page for the global search box via search settings (it has no effect). The solution is for now to use either a classic search page with display templates (not for me anymore thanks) or build your own search experience with SPFx. To embrace the future ;), I chose the second option. However, in this post, I won’t talk about the integration of a search box in a custom header via SPFx extensions but I will focus on the search, paging and refinement experience with a static search query.

The full code sample can be retrieved from this repository:

This sample is totally generic and can be used without any additional customization in your solution. Here is a quick demo (click on the image to start the animation):

Web Part configuration

The following settings are available in the Web Part property pane:



Search query

The base search query in KQL format.

Query template

The query template in KQL format. You can use search query variables. See this post to know which ones are allowed.

Selected properties

The search managed properties to retrieve. Then you can use these properties in the code like this (item.<managedpropertyname>):


The search managed properties to use as refiners. Make sure these are refinables. With SharePoint Online, you have to reuse the default ones to do so (RefinableStringXX etc.).

The order is the same as they will appear in the refnement panel.

Number of items to retrieve per page

Quite explicit. The paging behavior is done directly by the search API (See the SearchDataProvider.ts file), not by the code on post-render.

Show paging

Indicates whether or not the component should show the paging control at the bottom.


Overall approach

This sample uses the React container component approach directly inspired by the PnP react-todo-basic sample. This approach is pretty simple and can be resumed as follow:

A container does data fetching and then renders its corresponding sub-component. That’s it.

From a personal point of view, I always use this pattern to make my code cleaner and more readable with one folder per component, one folder for data providers and one other for models (i.e business entities). To know more about this pattern, read this article or just study the react-todo-basic sample.

An other pretty convenient library if you work with React is the immuatbility helper: This library allows to mutate a copy of data without changing the original source, like this:

Search results response mapping

Because search results are by definition heterogeneous, we can’t simply use a common interface to aggregate all properties so that’s why I simply use a generic interface to build result objects dynamically:

Notice that the sp-pnp-js library already provides TypeScript typings for SharePoint search API REST response (you don’t need to do it yourself):

The icon for the result type (Word, PDF, etc.) is fetched dynamically via the native mapToIcon() REST method. You can set the icon size to retrieve as parameter (16×16 pixels = 0, 32×32 pixels = 1 (default = 0)):

Refinements handling

Refiners (i.e search filters) are retrieved from the search results of the first page via the searchQueryRefiners property (set as query parameter). It means that when an user filters the results or switches the current page, refiners are not updated according the new search results, as the default behavior of SharePoint. It means results can be empty regarding the current filters combination. We use this strategy to always have a consequent filter behavior in the UI and avoid frustration for users.

When an user applies filters, a custom refinement query is built via the searchQuery.RefinementFilters property.As a reminder, refinement filters uses the SharePoint FQL syntax. The code to build this query is availaable in the _buildRefinementQueryString() method. We do a AND condition between filter properties and a OR condition between values of a single filter property:

User Interface

I’ve used the following Office UI Fabric:

  • Panel to display refinement panel ;). Can be positioned on the left or right.
  • GroupedList: An underestimated component that can be easily used to build collapsible sections.
  • Checkbox to activate/deactivate refiners individually.
  • DocumentCard with preview to display results as tiles with a responsive design
  • Overlay, MessageBar and Spinner to handle errors/messages and waiting sequences.
  • Button to build selected refiners.


For the pagination, we rely directly on the SharePoint search API, results per pages are retrieved via the dedicated PnP library getPage() method. All we need to do, is to pass the right page number and the number of results we want:

By this way, we make the pagination dynamic instead of doing it “post-render” by taking advantage of the API.

Note: there is a bug prior to the 2.0.8 sp-pnp-js version regarding the page calculation. More info here:

Then, to build the custom pagination control, I’ve used this very handy React component (react-js-pagination):

PnP controls

This sample also showcases the use of the PnP SPFx Controls via the Placeholder. The integration is pretty easy and done at the top level class of the Web Part like this:

This control is very useful for the initial Web Part loading scenario, when the Web Part haven’t been configured yet:

Hope this sample will give you a starting point to build awesome search experiences with SPFx. See you soon for others cool SPFx components!





Add yours
    • Franck Cornu

      Hi steve,

      You can just configure the searchQuery object according to your needs in the search data provider search() method:

      let sortList: Sort[] = [
      Property: 'Created',
      Direction: SortDirection.Descending
      Property: 'Size',
      Direction: SortDirection.Ascending

      searchQuery.SortList = sortList;

      For now, there is no way to configure it through the property pane.

  1. Michał Guzowski

    Great peace of code! Thank you for that. By the way – are you planning to implement a document preview like in an old fashion Search Results (with ability to scroll thru pages)? Is there a way to implement it anyhow?

  2. Damien

    Bonjour Franck,
    Merci beaucoup pour ce développement très utile pour personnaliser le search dans la nouvelle expérience.

    Lorsque j’essaie de packager le projet, j’obtiens les erreurs/warnings suivants :
    Warning – [sass] src\webparts\searchBox\SearchBoxWebPart.scss: filename should end with module.scss
    Warning – [sass] src\webparts\searchResults\components\SearchResultsWebPart.scss: filename should end with module.scss
    Warning – [sass] src\webparts\searchResults\components\Layouts\SearchResultsTemplate.scss: filename should end with module.scss

    Et si je les renomme en module.scss, j’obtiens 1007 erreurs/warning de type
    [sass] The local CSS class .* is not camelCase and will not be type-safe

    Tu as déjà rencontré ce type d’erreur ? Tu sais comment y remédier?

  3. Fabian

    Great work!
    Is there a way to get a set of default search results (e.g. only ContentTypes XY) and once the user is searching for something, the results will narrow it down to ContentType XY & the user query?

  4. Nicolas

    Bonjour Franck,
    Un grand merci pour ce développement compatible avec les sites de communication.
    Je m’en sers pour interroger notre annuaire d’entreprise via une source de résultats sur de la recherche de personnes.
    Je ne fais pas personnellement de développement mais est-ce qu’il serait possible d’identifier que le résultat de la recherche est de type personne et faire en sorte d’afficher
    les éléments sous la forme de cartelettes “interactives” qui comme pour un contact (webpart MS) en passant dessus avec la souris permet d’avoir le popup de détails
    et enfin le panneau complet des informations de l’utilisateur ?
    Merci pour vos réponses et votre travail.

  5. Nicolas

    Bonjour Franck,
    Merci pour ce développement compatible avec les sites de communication.
    Je m’en sers pour interroger notre annuaire d’entreprise via une source de résultats de recherche de personnes.
    Personnellement je ne développe pas mais j’aurais voulu savoir s’il serait possible d’identifier que les éléments sont de type personne et de faire en sorte de les présenter
    comme un contact (webpart MS) pour avoir la possibilité quand l’on passe dessus d’avoir le popup de détails et enfin le panneau avec toutes les informations de l’utilisateur ?
    Merci de vos réponses et de votre travail.

  6. Fabian

    Unfortunately, the query template doesn’t work, really. No matter what I type in, it will not show up. The default value would be {searchTerms} Path:{Site} which should already display SOME data (if there are documents uploaded to the current site, which we have). However, it doesn’t really work. I tried to test a few settings, but it doesn’t look like it is changing anything regarding the query template. Any advice?

    • Franck Cornu

      Hi Fabian,

      The query template works in addition to a base query (i.e query query keywords). The {searchTerms} token is the link between them. If you enter “*” as query keyword, you should be able to get documents. If not, please submit a new issue here if repro steps. Thanks!

  7. Fabian

    Yeah exactly, if I use * in the search box as well as for the query template, I don’t get any results. The query template by itself (without the search box) works great. Also the search box by itself without the query template works great, but both combined won’t.

  8. Chris

    Thanks, Franck, your post has been really helpful. I know you specifically said you won’t mention “integration of a search box in a custom header via SPFx extensions” but do you have any suggestions on how to replace the default “Search this site” or perhaps hijack where it calls to use your search box

  9. Dheepa Iyer

    Hi, this seems like a really cool solution. How ever, I am getting an error trying to compile the downloaded code:
    Error – [tsc] src/services/NlpService/NlpService.ts(43,74): error TS2345: Argument of type ‘HttpClientConfiguration’ is not assignable to parameter of type ‘HttpClientConfiguration’.
    Error – [tsc] src/webparts/searchBox/SearchBoxWebPart.ts(123,45): error TS2345: Argument of type ‘HttpClient’ is not assignable to parameter of type ‘HttpClient’.
    Error – [tsc] src/webparts/searchResults/SearchResultsWebPart.ts(193,57): error TS2345: Argument of type ‘SPHttpClient’ is not assignable to parameter of type ‘SPHttpClient’.
    Error – [tsc] src/webparts/searchResults/SearchResultsWebPart.ts(534,79): error TS2345: Argument of type ‘SPHttpClientConfiguration’ is not assignable to parameter of type ‘SPHttpClientConfiguration’.

  10. Rajrup Bhattacharya

    Hi Frank, I must say this is a really cool solution. But one problem I am facing. I have used search refiner for blog widget. Every data I am getting except blog’s author pic.. I have used proper managed property which is working successfully for classing share-point but still not getting the author picture. Can you please help me out??

  11. Murphy

    We have a managed meta data column we set as a refiner. Some of our Parent terms have Children. Each show up in the refinement panel. If we select the Parent term, the rest of the check boxes disappear, same happens if we select a Child term. Is there a way to select multiple refiners? Also, if we select a Parent term is there a way to return the Parent and all of its Child items?

  12. Christer Andersson

    Hi Great Work
    I have two issues, first I’m not able to search custom columns when redirecting to a resultspage. If i have the searchbox andresults webpart on the same page it works fine. Second I get some wonky dates for Created and modified ie Created 20149-05-11 Modified 2017-02-02

  13. Adam Pope

    Hiya, Franck! Loving these webparts, so easy to use relative to value they generate, they really ought to be rolled in as default.

    I’ve a quick suggestion on the default sort order being the ‘created’ date; could/should this be by Rank instead? Not sure what that field is though, as when I try RankOWSNMBR i get a “This property is not sortable” error. After some scrolling, I did find that ‘LastModifiedTime’ was available, and think perhaps this is an improvement on the default ‘Created’ date field…? Certainly, its much harder for end users to find than the ‘created’ field if they wanted to switch between the two. Regardless, would be great if the documentation could be updated to refer to ‘LastModifiedTime’, would have saved me a long search 8^)

    • Franck Cornu

      Hi Adam, yes the Created property is used by defaut but you can simply remove it from the default sort order to get the default sorting behavior based an rank. Also, the list provided in the sort configuration includes all sortable/non sortable properties since there is no way to know this information before trying to use it. ‘LastModifiedTime’ and ‘Created’ are two distinct properties and won’t give you the same sort order. if you still have issues you can also use the issues list on GitHub

  14. anthony

    Bonjour franck,
    Bravo pour la vidéo concernant le paramétrage de webpart. Passionnant et quelque part j’ai l’impression qu’un monde nouveau s’ouvre à moi. Cela fait un peu peur tellement ce webpart est puissant.
    Dans la vidéo, tu installes le module search with refiners. De mon côté j’ai installé ce module
    et je n’ai pas le webpart qui intègre le filtrage dans la recherhe.
    Pour cela je suis obligé d’ajouter le webpart filtre de recherche et je ne trouve pas cela pratique du point de vue accessibilité.

    Que dois-je faire ?


    • Franck Cornu

      Bonjour Anthony, cette vidéo est un peu vieille ;). Maintenant les filtres et les résultats sont divisés en plusieurs Web Parts que tu peux connecter ensemble sur ta page. C’est la manière de faire la plus modulaire car les filtres ne sont pas toujours nécessaires.

+ Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.