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

- Intro
- Example 1 – dynamic list filtering
- Example 2 – Custom SharePoint Framework address book web parts
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:
- An alphabet list that will act as our index, storing all the letters of the alphabet as list items.
- 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

- 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:

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

- 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.

- 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:

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.

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.

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.

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.

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