Provisioning the SharePoint Success Site

In this post we run through how to provision, deploy and troubleshoot the SharePoint Success Site offered by Microsoft.

  1. Intro
  2. How to provision
    1. Provision via the SharePoint look book
    2. Provision via the learning pathways site
  3. Issues and troubleshooting
    1. Learning pathways errors in the SharePoint Success Site

Intro

The SharePoint Success Site is an extension of the great Microsoft 365 learning pathways site, which allows you to use and create curated playlists within a SharePoint site to offer training and support for users. The SharePoint Success Site is a fairly recent introduction to the learning pathways family and is offered as a standalone deployable option from the SharePoint look book.

How to provision

There are two ways to provision the SharePoint Success Site, either directly from the SharePoint look book, or via the learning pathways site itself.

Provision via the SharePoint look book

The SharePoint Success Site is dependent on the learning pathways site, so you will need to provision that first, or make sure that it is updated to the current version. More information on how to update the learning pathways solution can be found here.

Things to note:

  • You will need to be a global administrator to provision the SharePoint Success Site (described as tenant admin in the look book)
  • You will need to have an app catalog site in your SharePoint environment. More details on how to create an app catalog site can be found here
  • You will need to be an app catalog administrator to provision the SharePoint Success Site

Provision via the learning pathways site

  • Navigate to the learning pathways site in your SharePoint environment
  • From the navigation menu > select  Learning Pathways Administration
  • Press the ellipsis … > add content pack
  • Select the SharePoint Success Site
  • This will then take you through to the SharePoint look book to follow the steps as described above

Once provisioned, the SharePoint Success Site will be deployed as a separate communication site within your environment. The site uses a content pack installed in the learning pathways site, which will be displayed within the site.

Issues and troubleshooting

Learning pathways errors in the SharePoint Success Site

When I tried to provision the SharePoint Success Site it didn’t work as seamlessly as I expected. For me, after provisioning the site via the SharePoint look book I received the following error across all pages displaying the learning pathways web part:

Microsoft 365 learning pathways has a configuration issue. Ask your administrator for assistance. [Administrators: Please see the browser console for detailed logs. For technical assistance check out the issues list at https://github.com/pnp/custom-learning-office-365/issues.%5D

Microsoft 365 learning pathways error.

To resolve this:

  • Navigate to the learning pathways administration page
  • Press the ellipsis … > add content pack
  • Select the SharePoint Success Site
  • A message will appear asking if you have already provisioned the SharePoint Success Site > press complete

This will configure your SharePoint Success Site to consume the newly added content pack and will display correctly.


Saving approvals as PDFs in Microsoft Teams

In this post we demo the ability to download approvals in Microsoft Teams as PDFs for filing, printing or transferring.

Introduction

Microsoft introduced the ability to save, print and transfer approvals as PDFs in late 2022. With this feature, approval creators are able to save a completed approval request to a PDF file and have the option to print it. At the time of writing, there was very little online about this feature, or what it looks like – so here it is in action!

Demo – save a PDF from approval

  • There are several ways to trigger a new approval, via Teams chat or channel message, the approvals app or via Power Automate to name a few. In my example, I created a Teams channel approval:

Note: There’s a brief delay after sending your approval before it appears within your activity feed, or the approvals app (approx. 5 minutes). Once it does, you are able to go ahead and approve/ reject it. You cannot save approvals as PDF whilst they are in the ‘requested’ state.

  • Once the approval has been completed, you can either view the details of the approval (if a channel message) or open the approval up from the approvals app in Teams.
Approval details via the Teams channel message.
Approval details via the Teams channel message.
Approval details via the approvals app.
Approval details via the approvals app.
  • Once your approval is open > press Save as PDF.
  • A PDF export of the approval window will download into the local downloads folder on your device. The output will look something like the below:

Current limitations

  • There is no way currently to change the download location of the PDF or change the behaviour. It would be nice it it was configurable to set it to save to a OneDrive location potentially.
  • There are no standard approval triggers related to this functionality in Power Automate. Again, for the same purpose as above, it would be good to be able to leverage this functionality via Power Automate to route the downloaded PDF to a OneDrive/ SharePoint location.

Create views in SharePoint that only show items created by current user

This is an oldie but a goodie in my opinion, this post will show you how to use library filters in SharePoint to only show items/ files created, or modified by the current user.

The steps below will guide you through the process of updating the view of a list or library to only show items created or modified by the current, logged in user.

  • Open the list or library you wish to update
  • From the library actions ribbon > select the current view (for example: all documents)
  • Press edit current view
  • Scroll down the edit view page until you get to the filter section
  • Create the following filter:

NOTE: this example will only show items that were created by OR modified by the current, logged in user. If you want your filter to only show items where both these values are true, change OR to AND.

  • Press OK to save your changes

Now your view should have updated to only show items created by, or modified by the current, logged in user.

Considerations

This example is really simple and easy to implement and there isn’t any real impact on your users as nothing is technically being restricted, just filtered out of the default view.

The flip-side of this is that in of itself could be considered a limitation if there is information stored within the list/ library that requires limited permissions. I’ll go into the options available for this later in this post.

However with that said, depending on what permissions you have in place for your list/ library, you could use this approach to secure the contents too.

Consideration #1 – Views don’t change permissions

A “back door” to gaining access to all contents stored in the list/ library would be to use a different view than the set default view to access all items (for example: if you created a new view to follow the below method, but left the default view set to show everything).

The solution to this would be to apply this filter approach to the default view, make sure any other public views follow the same approach and make sure end users don’t have elevated permissions.

Consideration #2 – Ensure permissions are set correctly

The only way an end user could bypass this approach without any “all items” views available, would be if they had the permissions to do so. The permission that controls this is called the Manage Lists permission, which is only granted via the Edit, Design, Full Control, Manage Hierarchy permission levels by default.

If end users aren’t in these groups, your in luck!

Consideration #3 – list/ library contents is not searchable

If you are using views to filter who can see contents within your list/ library then you will need to ensure this setting is updated if the contents needs to be hidden (below example is a document library):

  • Press the cog button > library settings
  • Press advanced settings
  • Under Search > change allow items from this document library to appear in search results to No
  • Press OK
Update the allow items from this document library to appear in search results advanced setting to ensure items aren’t visible in search.

Consideration #4 – If it’s a list, enable item level permissions

With lists, in advanced settings there is an additional configuration option – item level permissions which allows you to specify which items users can read and edit.

As the heading implies, this consideration only works for lists in SharePoint as the functionality isn’t available out the box for document libraries. SharePoint Maven have written up what the two different levels of item level permissions you can set mean, which you can find here.


How to rename a SharePoint Online site and re-use the URL

In this post we will explore how to rename SharePoint Online sites and re-use the original URL.

  1. Intro
  2. How to use an old site URL
  3. More information

Intro

Have you ever changed the name of a SharePoint Online site and tried to re-use the old URL for a new site? If you have you have probably encountered an issue where SharePoint admin center will tell you that URL is still in use. Well this is because the URL is, in fact still in use in the form of a redirect.

Old site URL is unavailable after renaming a SharePoint site.

In 2019, Microsoft introduced the site rename feature within the SharePoint admin center, which also updated site URLs too. Once a URL is updated for a given site, or moved to a different geo location, or as part of a site swap – a redirect is automatically created to ensure any links that were pointing to the prior URL continue to work.

How to use an old site URL

To use on old site URL you need to run the following PowerShell command. Note: You will need the SharePoint administrator role to run this command:

  • Open the SharePoint Online Management Shell and run the following command:
Connect-SPOService -Url https://[TENANT]-admin.sharepoint.com

Remove-SPOSite -Identity https://contoso.sharepoint.com/sites/OldSiteName
  • Confirm you want to delete the redirect

Check your URL has been deleted by trying to browse the URL, if you get a 404 error it has been successful.

More information

The Remove-SPO site command is actually defined as “Sends a SharePoint Online site collection to the SharePoint Online Recycle Bin” in the Microsoft documentation reference guide.

Microsoft documentation says that a redirect template (Template type: REDIRECTSITE#0) is applied when the site URL is updated, which contains special headers and logic to redirect your browser requests to the new site.


How to deploy a flow across all document libraries in SharePoint

In this post we look at a specific use case for needing a Power Automate flow to be available across all of SharePoint, some of the current limitations of Power Automate and how to overcome them using Encodian Trigr.

Intro

Most organisations will have a central location where they store policies, procedures and guidance documents. For most, this would form part of their corporate intranet solution on SharePoint/ Teams, but you also might find yourself getting asked by users “how do I get my policies from my team/ collaborative locations into the the corporate policy library?” – not such an easy answer!

The problem

Policies generally can be created by individuals and small groups of people, within the Teams/ SharePoint sites that they have access to for collaboration. For these people, having a way to publish relevant documents from their own collaboration sites to a central policy location in a consistent way makes a great deal of sense and you may think Power Automate is the way to achieve it…however this is not the case! Power Automate flows have a 1:1 relationship with the lists or libraries they are triggered from, so there is no easy way to do this with out-the-box capabilities.

Power Automate is also unable to manually trigger a flow when selecting multiple documents, something else that my scenario requires. This is where Encodian Trigr can really bridge the gap. Trigr makes Power Automate flows available across SharePoint, allowing users to access and run flows from within any library or list.

The solution

In my example, I needed to have a flow available across all SharePoint/ Team sites, which would allow all users to select one-to-many documents and publish to a central policy upload center for approval – and here is how I used Encodian Trigr to achieve it:

Install Trigr

  • There is a comprehensive guide on how to deploy the Encodian Trigr app available here. I followed these steps to get Trigr setup in my environment.

Create the flow

  • Navigate to https://make.powerautomate.com/
  • Under My flows > + New flow > Automated cloud flow
  • Give your flow a name
  • Search and select the Encodian ‘When a user runs a Trigr‘ Power Automate trigger action
  • Click create
Create an automated cloud flow and select the ‘When a user runs a Trigr’ Power Automate trigger action.

NOTE: This was my first time using Encodian Trigr, so I was prompted to provide a connection name and an API key. For the connection name I just entered ‘Encodian connection for flow’, and the API key will be in your welcome email, or available via this link: https://account.encodian.com/trigr/apikey.

  • You will now see the ‘When a user runs a Trigr‘ trigger action. Enter a title & description for your flow
Add a title and description to your ‘When a user runs a Trigr’ trigger action.

The ‘When a user runs a Trigr’ Power Automate trigger action provides all of the properties relevant to the files which were selected by the user when the Trigr action was started.

For the next step we are going to use the get file content action. Due to a current limitation of Power Automate, dynamic values are not immediately accessible by the SharePoint action. To get around this, we will just enter hard-coded values at first, then replace them with dynamic values once we have finished configuring the flow.

Get file content

  • Add a get file content action
  • Hard-code the site address of the site where the files are saved that you wish to publish
  • Hard-code the file identifier as just the ID number for now

Send an email

  • Add a send an email action with the the following confirguration:
    • To: User Email Address dynamic content from the ‘when a user runs a Trigr’ trigger action’
    • Subject: Anything you wish
    • Body: Anything you wish
Add a send an email action if required to notify the user who triggered the flow they have submitted a policy document.

Update get file content

Now we will go back to our get file content action and update the hard-coded values with dynamic content from the ‘when a user runs a Trigr’ trigger action’:

  • Open the get file content action
  • Update Site Address to Site Address dynamic content from the ‘when a user runs a Trigr’ trigger action’
  • Update File Identifier to File Identifiers

NOTE: This will create put all your actions in an apply to each loop, which is what we want if we want to be able to process multiple documents. The File Identifier dynamic content will be replaced with the current item from the apply to each loop.

Update the get file content action to include the current item from the apply to each loop.

Copy file

  • Add a copy file action after the update get file content action and configure with the following properties:
    • Current site address: site address dynamic content from the ‘when a user runs a Trigr’ trigger action
    • File to copy: Current item apply to each dynamic content
    • Destination site address: select/ hard-code the destination site address (in my case the policy site)
    • Destination folder: select/ hard-code the destination library/ folder
    • If another file is already there: replace
Add a copy file action to copy the files the flow has been ran against into the policy center.

Create the Trigr action

So far we have installed trigr in our environment, created the flow which will be used and now we need to create the Trigr action in the Encodian account portal:

  • Select the flow that you created earlier and configure with the following settings:
    • Title: This will auto-populate from the data set within the Power Automate Flow, update if required.
    • Description: This will auto-populate from the data set within the Power Automate Flow, update if required.
    • Run Message: Enter a message which will be displayed to the user once the action has been started.
    • Appear in: Tick whether you want your flow to appear in all libraries/ lists or both. You can also decide whether you wish to deploy the flow to all sites or a selection.
    • Press Create

That’s it! Your Trigr action will now be created and your flow will be available across your SharePoint environment.


Microsoft Search demystified! Provide configurable search options in SharePoint, Teams & OneDrive

Microsoft Search is the unified search experience that is prevalent across Microsoft 365. Microsoft Search gives users contextual search results depending on the app they search from, be it form Word or Excel, through to SharePoint, Teams & OneDrive.

Intro

If your familiar with SharePoint search you might think this is a doddle to set up. Know your crawled properties from content sources? Managed metadata from managed properties? Well, this might not help you for modern search as not all contextual app based search results are created equally, specifically Teams and SharePoint…

Within the settings of the M365 admin center there is section called search & intelligence where you can gain access to the following configurable options within Microsoft Search:

  • Acronyms: Admin or system curated acronyms/ abbreviations used by an organisation or team
  • Bookmarks: Helps users find important information with just a search. Each bookmark includes a title, URL,  keywords and a category.
  • Floor plans: Floor plans in Microsoft Search help users find people, rooms, and spaces within a building.
  • Locations: Helps your users find addresses and locate your organization’s buildings by providing an accurate location.
  • Power BI: Support for Power BI dashboards and reports to make it easier for users to find data & analytics
  • Q&A: Creating a Q&A is similar to creating bookmarks. Answer user’s questions instead of just providing a link to a webpage.
  • Result types & verticals: Result types define how search results are shown on the page based on conditions and rules. Search verticals are tabs on the search results page that show results of a specific type or from a select source.

The problem

For all of the above options within Microsoft 365, there is a varied amount of consistency as to how each option displays within specific apps, or in some cases do not display at all. For SharePoint all of the above search options do not display in the default search results of a given site. SharePoint communication or team sites search scope is to search just the site by default. Hub sites will search the hub, plus any associated sites to the hub.

None of the above search options display from within Teams, either via the desktop app or via the browser. There also isn’t a link to organisation, or a way to see all files etc. from the search experience within Teams.

Microsoft documentation says that Acronyms, Bookmarks, Floor plans & Location only show answers on Bing, SharePoint, and Office 365. Power BI search results display in the Windows search box, SharePoint, Office 365, and Bing.

Solutions and workarounds

I only really have a solution as such for SharePoint on this one, as for Teams and OneDrive there is very little you can do to change the search result options as far as I know.

For SharePoint, the only way currently to get these options to show is to change the scope of your site. There are three search scope parameters:

  • 0 – default scope, effectively site
  • 1 – tenant
  • 2 – hub
  • 3 – site

To change the scope of your site search, you need PowerShell/ PnP. You’ll also need to be a SharePoint admin to run it, but if you are you can run this cmdlet to change the scope to tenant wide:

Connect-PnPOnline -Url https://contosodemosg.sharepoint.com/sites/Strategy -UseWebLogin
# this will prompt you to sign into your site. Use the site owner credentials to sign in

$web = Get-PnPWeb
$web.SearchScope = 1
# 1 for Tenant, 2 for Hub, 3 for Site, 0 for default behavior

$web.Update()
Invoke-PnPQuery

Once your cmdlet has run successfully, you’ll notice the change in the site targeted as when a search is initiated it will default to the “organisation” wide search results by default, showing any of the configurable options you’ve set up within Microsoft Search.

SharePoint site configured to have a tenant-wide search scope, showing a custom configured location within the search results page.

For OneDrive on web, the search experience is subtly different than SharePoint or Teams. When you search there are the following options via a drop-down: My files, All files, or Whole organisation. All files is the default setting, but you can select the scope of your search before hitting enter, or change the scope after your results are returned.

In OneDrive for web, there is a toggle within the search bar that lets you select the search scope.

Things to consider

#1 Intranet search results will not be scoped

If you are building an intranet solution or already have one in place, changing the search scope will bring in search results from Teams, SharePoint & OneDrive, so it will not be ring fenced to just “intranet” content.

#2 All sites which require the scope change will need updating

You will need to change the scope for all the sites you wish to search tenant wide. So if you have an intranet solution, all sites will need this change applying.

#3 Ensure you have the correct admin roles

When I was creating Bookmarks/ Answers/ Locations etc. I originally gave my account the Search Administrator role. This displayed more config options within search & intelligence, but when it came to publishing, it threw up the dreaded “something went wrong” error. To be able to publish, I needed to also apply the Search Editor role.


How to create an address book in SharePoint Online

In this post we take a look at a few of the different ways an address books can be created in SharePoint Online.

  1. Intro
  2. Example 1 – dynamic list filtering
    1. Create the alphabet list
    2. Create the contacts list
    3. Create the address book page
    4. Bonus! Populate the contacts list from M365 user data
  3. Example 2 – Custom SharePoint Framework address book web parts
    1. People Directory SPFx sample
    2. Organization Directory SPFx sample

Intro

The idea for this post came from reader George’s comment asking to do a demo of how an address book can be created in SharePoint. From my research there is already quite a bit out there on how to do this already, but here are my two ways to create an address book.

Example 1 – dynamic list filtering

This example is a pure out of the box, SharePoint solution for creating an address book feature. For this example we will use dynamic filtering between two lists to show a certain set of values when an option is selected.

To start we will need to create two lists:

  1. An alphabet list that will act as our index, storing all the letters of the alphabet as list items.
  2. A contacts list that will be the list that contains all the address book data.

Create the alphabet list

  • Start by creating a new custom list > call the list Alphabet.
  • Create new items in the list for each letter of the alphabet. I used ‘edit in grid mode’ to speed this process up
Create an Alphabet list to store the A-Z letters as list items.
  • Change the view to a gallery view
  • From here, format the list view using either the example JSON list view formatting below, or create your own to style the A-Z list to display each letter as a button
{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/tile-formatting.schema.json",
  "height": 45,
  "width": 53,
  "hideSelection": false,
  "fillHorizontally": true,
  "formatter": {
    "elmType": "div",
    "attributes": {
      "class": "sp-card-container"
    },
    "children": [
      {
        "elmType": "button",
        "attributes": {
          "class": "sp-card-defaultClickButton",
          "role": "presentation"
        },
        "customRowAction": {
          "action": "defaultClick"
        }
      },
      {
        "elmType": "div",
        "attributes": {
          "class": "ms-bgColor-white sp-css-borderColor-neutralLight sp-card-borderHighlight sp-card-subContainer"
        },
        "children": [
          {
            "elmType": "div",
            "attributes": {
              "class": "sp-card-lastTextColumnContainer"
            },
            "children": [
              {
                "elmType": "p",
                "attributes": {
                  "title": "[$Title]",
                  "class": "ms-fontColor-neutralPrimary sp-card-content sp-card-highlightedContent"
                },
                "txtContent": "=[$Title]",
                "style": {
                  "text-align": "left",
                  "font-size": "0.8em",
                  "font-weight": "bold"
                }
              }
            ]
          }
        ]
      }
    ]
  }
}
  • Save your view under a new name, I called mine “alphabet filter view”. The end result will look like the below:
Example gallery list view formatting to display A-Z as buttons.

NOTE: There was an original example for this I found online many months ago, but I cant for the life of me find it. If anyone knows where this example came from, leave a message in the comments and I’ll credit it in this post.

Create the contacts list

Next, we need to create the contacts list. If you already have your user data held somewhere else, you can always create your new contacts list from Excel.

  • Once the contacts list is created, create a new lookup column with the following configuration:
    • Give your lookup column a name. I called mine “AZLookup”
    • Require that this column contains information = No
    • Enforce unique values = No
    • Get information from: Alphabet
    • In this column: Title
    • Press Save
Create a lookup column to connect to the Alphabet list.
  • Now you need to update each item in the contacts list, to ensure each item has an A-Z value in the lookup column. Again, for this I used “edit in grid view” to speed things up.
Update the contacts list to include values in the lookup column.
  • Switch to the gallery view and format the list using either the example JSON list view formatting below, or create your own to style the contacts list in a way that suits your needs.
{
  "$schema": "https://developer.microsoft.com/json-schemas/sp/v2/tile-formatting.schema.json",
  "height": 166,
  "width": 300,
  "hideSelection": false,
  "fillHorizontally": true,
  "formatter": {
    "elmType": "div",
    "attributes": {
      "class": "sp-card-container"
    },
    "children": [
      {
        "elmType": "button",
        "attributes": {
          "class": "sp-card-defaultClickButton",
          "role": "presentation"
        },
        "customRowAction": {
          "action": "defaultClick"
        }
      },
      {
        "elmType": "div",
        "attributes": {
          "class": "ms-bgColor-white sp-css-borderColor-neutralLight sp-card-borderHighlight sp-card-subContainer"
        },
        "children": [
          {
            "elmType": "div",
            "attributes": {
              "class": "sp-card-previewColumnContainer"
            },
            "children": [
              {
                "elmType": "div",
                "style": {
                  "display": "flex"
                },
                "children": [
                  {
                    "elmType": "p",
                    "attributes": {
                      "class": "sp-card-userEmptyText"
                    },
                    "txtContent": "=if(length([$DisplayName]) == 0, '–', '')"
                  },
                  {
                    "forEach": "personIterator in [$DisplayName]",
                    "elmType": "a",
                    "attributes": {
                      "class": "=if(loopIndex('personIterator') >= 5, 'sp-card-userContainer', 'sp-card-userContainer sp-card-keyboard-focusable')"
                    },
                    "style": {
                      "display": "=if(loopIndex('personIterator') >= 5, 'none', '')"
                    },
                    "children": [
                      {
                        "elmType": "img",
                        "defaultHoverField": "[$personIterator]",
                        "attributes": {
                          "src": "=getUserImage([$personIterator.email], 'S')",
                          "title": "[$personIterator.title]",
                          "class": "sp-card-userThumbnail"
                        },
                        "style": {
                          "display": "=if(length([$DisplayName]) > 5 && loopIndex('personIterator') >= 4, 'none', '')"
                        }
                      },
                      {
                        "elmType": "div",
                        "attributes": {
                          "class": "ms-bgColor-neutralLight ms-fontColor-neutralSecondary sp-card-userOthers"
                        },
                        "style": {
                          "display": "=if(length([$DisplayName]) > 5 && loopIndex('personIterator') == 4, '', 'none')"
                        },
                        "customCardProps": {
                          "formatter": {
                            "elmType": "div",
                            "attributes": {
                              "class": "sp-card-personCallout"
                            },
                            "children": [
                              {
                                "forEach": "personIterator in [$DisplayName]",
                                "elmType": "div",
                                "attributes": {
                                  "class": "sp-card-userContainer sp-card-userCustomCard"
                                },
                                "style": {
                                  "display": "=if(loopIndex('personIterator') < 4, 'none', '')"
                                },
                                "children": [
                                  {
                                    "elmType": "img",
                                    "defaultHoverField": "[$personIterator]",
                                    "attributes": {
                                      "src": "=getUserImage([$personIterator.email], 'S')",
                                      "title": "[$personIterator.title]",
                                      "class": "sp-card-userThumbnail"
                                    }
                                  }
                                ]
                              }
                            ]
                          },
                          "openOnEvent": "hover"
                        },
                        "children": [
                          {
                            "elmType": "span",
                            "txtContent": "='+' + toString(length([$DisplayName]) - (4))"
                          }
                        ]
                      }
                    ]
                  },
                  {
                    "elmType": "div",
                    "attributes": {
                      "class": "sp-card-userTitle"
                    },
                    "style": {
                      "display": "=if(length([$DisplayName]) == 1, '', 'none')"
                    },
                    "defaultHoverField": "[$personIterator]",
                    "txtContent": "[$DisplayName.title]"
                  }
                ]
              }
            ]
          },
          {
            "elmType": "div",
            "attributes": {
              "class": "sp-card-displayColumnContainer"
            },
            "children": [
              {
                "elmType": "p",
                "attributes": {
                  "title": "[$FirstName]",
                  "class": "ms-fontColor-neutralPrimary sp-card-content sp-card-highlightedContent",
                  "role": "heading",
                  "aria-level": "3"
                },
                "txtContent": "=if ([$FirstName] == '', '–', [$FirstName])"
              }
            ]
          },
          {
            "elmType": "div",
            "attributes": {
              "class": "sp-card-displayColumnContainer"
            },
            "children": [
              {
                "elmType": "p",
                "attributes": {
                  "title": "[$Surname]",
                  "class": "ms-fontColor-neutralPrimary sp-card-content "
                },
                "txtContent": "=if ([$Surname] == '', '–', [$Surname])"
              }
            ]
          },
          {
            "elmType": "div",
            "attributes": {
              "class": "sp-card-lastTextColumnContainer"
            },
            "children": [
              {
                "elmType": "p",
                "attributes": {
                  "title": "[$Email]",
                  "class": "ms-fontColor-neutralPrimary sp-card-content "
                },
                "txtContent": "=if ([$Email] == '', '–', [$Email])"
              }
            ]
          }
        ]
      }
    ]
  }
}
  • Save the view and give it a new name. I called mine “Contact cards”. The result will look like the below:
Contacts list with list view formatting applied.

Create the address book page

Now are lists are created and formatted, it’s time to create a new page, add our web parts and start dynamic filtering!

  • Create a new page, select any template you like
  • In a new one column section, add a new list web part > select the Alphabet list
  • Edit the web part and make the following changes:
    • Change the view to Alphabet Filter View
    • Set the size to small – about 5 items
    • Hide the command bar
    • Hide the see all button
  • Press Apply
  • Either in the same or new one column section, add a new list web part > select the Contacts list
  • Edit the web part and make the following changes:
    • Change the view to Contact Cards
    • Set the size to autosize – fit to number of items
    • Hide the command bar
    • Hide the see all button
    • Turn dynamic filtering on
    • Column in Contacts to filter = AZLookup
    • List or library containing the filter value: Alphabet
    • Column containing the filter values properties: Title
  • Press Apply

You will now be able to dynamically filter your contacts list using the A-Z buttons on the address book page!

Bonus! Populate the contacts list from M365 user data

When creating this demo I thought to myself “wouldn’t it be great if you could populate the contacts list from data already in Microsoft 365”. I thought it was a good idea so I created a Power Automate flow to get all the user data from M365 and add it to the contacts list.

Power automate flow to populate the contacts list with user data from Microsoft 365.

The flow uses the search for users (V2) action that, if no search terms are added, will bring back all the users within Microsoft 365 within the flow. Once that action has retrieved all the users, the create item action, wrapped within an apply to each creates a new item within the contacts list for each user the search for users action has found, populating the columns in the contacts list with dynamic content from the previous action.

NOTE: There is a pagination setting that will need updating if you have more than the threshold limit of 1000 users.

Pagination setting within the search for users (V2) action.

This flow was really just to prove the concept, so it runs on a manual basis. If you were to want to do something like this I would suggest having a one time “import of all the user profile data into the contacts list, then a separate, flow that runs on a longer schedule that only runs if certain conditions are met (for example: if the email address doesn’t already exist in the contact list).

Example 2 – Custom SharePoint Framework address book web parts

There are several SharePoint framework (SPFx) sample solutions available from GitHub that work as address books, connecting to user data in Microsoft 365 and allowing you to browse and search for users. Not all samples are offered as downloadable solutions packages, sometimes you will need to package & deploy them manually. I’ve got a post here that covers everything you need to do 🙂

People Directory SPFx sample

This sample was made available as part of the PnP starter kit (formerly the SharePoint starter kit), which I installed and have been using in my tenant since 2019 without issue. This web part lets you browse an A-Z, as well as being able to search for users.

People Directory SPFx sample.

This web part can be downloaded from GitHub and installed into your tenant.

Link to the sample: https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-people-directory

Organization Directory SPFx sample

This sample allows you to browse an A-Z, as well as being able to search for users and sort the results by job title, first name, last name etc. When a user is found, a profile hover card is displayed is the same as the rest of Microsoft 365.

Organization Directory SPFx sample.

This web part needs to be packaged into a solution before installing into your tenant.

Link to sample: https://github.com/pnp/sp-dev-fx-webparts/tree/main/samples/react-directory


Two ways to remove search results in SharePoint

In this post we will take a look at two different ways to remove search results in classic and modern SharePoint.

Introduction

There may be occasions in SharePoint where you are required to remove an item, or items from the search results. When we are talking about search results, we are referring to the items that are returned when a user initiates a search in SharePoint.

Search results in modern SharePoint.

Click on one of the buttons below to find out more about how you can remove search results in both classic and modern SharePoint: