Copilot in SharePoint Skills are in my opinion one of the best AI features in SharePoint. However currently there is no easy way to see what a site actually has. Today you can either type /skills in the Copilot in SharePoint chat, or go hunting through the AgentAssets/Skills library. Neither is somewhere a normal user is probably going to look. So I set out to fix that, and configured a PnP Modern Search rollup web part that lists every SKILL.md on a site, with a link straight to each one, that you could then add to the homepage of your site. This rollup uses the great PnP Modern Search open source web parts, built by the community and Microsoft, and best of all they are free.

This post covers what the rollup does, where Skills live, and the exact settings to build it, with the full importable config at the end so you could even just drop it on your own sites with no need to follow the explainer steps below!

What the Skills Search Rollup Does

The rollup is a single PnP – Search Rollup web part configured to do one job: find every SKILL.md file saved in the current site and show it as a tidy list.

Each row gives you the skill name and its folder, a link that opens the skill file, a folder icon that jumps to the skill’s folder in the library, and a clickable more info icon that loads a info panel with the file details. Because it runs on SharePoint search, it is permission trimmed by default, so people only see the skills they already have access to.

Here is the part I like most. Hover over a skill, or open its More Info panel, and you see the full description of what that skill does. That is the actual instruction text from the Description column of the SKILL.md metadata, so you can tell at a glance what each skill is for without opening a single file.

It is site scoped on purpose. Skills live per site, so a per site rollup is the natural building block. A tenant wide version is a different query and a separate page, which I will come back to in a future post but a sneak peek is below!

Where Copilot in SharePoint Skills Live

A Skill is a SKILL.md markdown file that tells Copilot in SharePoint how to run a specific, repeatable task. Microsoft stores them in a fixed location: the Agent Assets library on the site, under /AgentAssets/Skills/<skill-name>/SKILL.md. Each skill gets its own folder, and the folder name is the skill name.

That fixed path is what makes the rollup possible. We know exactly where to look and exactly what the file is called, so the search query can be precise. If you want the background on Skills themselves, Microsoft has the detail in Extend Copilot in SharePoint with skills, or I walk through on this blog building one end to end in Copilot in SharePoint Skills: Building a Contracts Dashboard.

Building It in PnP Modern Search v4

There are two ways to do this, so pick whichever suits you.

The fast way: scroll down to the Full Importable Config, copy the whole JSON, then add a fresh PnP Search Rollup web part to your page then in the web part property pane open Import/Export settings, click Edit properties, paste the JSON in, and click Apply – that is it. The config works out of the box because it relies only on standard managed properties that every tenant has, with no RefinableStrings or custom search schema to set up first.

The step by step way: follow the steps below to build it yourself and understand what each setting does. Everything is done in the property pane, with no code deployment required.

Start with a fresh PnP – Search Rollup web part added to a page. Out of the box it uses the Cards layout, the SharePoint Search data source, and an empty query.

Step 1: Add the web part. Edit a page, add the PnP – Search Rollup web part, and leave the data source as SharePoint Search. Use the Rollup variant rather than PnP – Search Results here on purpose. It is the same engine, but it drops the web part connections you do not need for a standalone rollup, and it lazy loads, meaning it does not fire the search query until it is scrolled into view. That is ideal for a rollup sitting lower down a page with no search box or filters wired to it. If you later want to connect it to a search box or filters, switch it to the Search Results variant and every property and template carries straight over. You do not need to change the result source either. The default (Local SharePoint Results) is exactly what we want, because the query template does all the scoping.

Step 2: Set the query template. Open the data source settings and set the Query template to:

Path:{Site}/AgentAssets/Skills AND FileName:SKILL.MD

Two things are happening here. {Site} is a query token that resolves to the current site’s URL at runtime, so the same web part works on any site you drop it on. Path:.../AgentAssets/Skills restricts results to the Skills folder, and FileName:SKILL.MD narrows it to the skill files only.

Step 3: Add the managed properties. A new rollup already returns most of the properties you need. There are only five you have to add manually to the Selected properties list, because the columns reference them and they are not returned by default:

Filename, ParentLink, Description, EditorOWSUSER, LastModifiedTimeForRetention

Everything else the layout uses (the author, created date, file type, site URL and title, and the path) comes back by default on a new rollup, so you do not need to add those. If a property is missing it renders blank in the template, which is the usual reason a column comes up empty.

Step 4: Add the template slots. Slots map a friendly name to a managed property so your Handlebars can reference it as slot item @root.slots.Name. The SharePoint Search data source ships with a good set of defaults (Title, Path, Author, Date, FileType, PreviewUrl and so on). On top of those, add these custom slots:

Slot nameManaged property
FilenameFilename
ParentLinkParentLink
EditorEditorOWSUSER
LastModifiedTimeForRetentionLastModifiedTimeForRetention
SkillPathPath
DescriptionDescription

Step 5: Switch to the Details list layout. Change the layout from Cards to Details list. In the layout options, set Compact to On and Use alternating background colour to On. Then turn Show results count off and set the web part Title to Skills on this Site.

Step 6: Build the columns. The Details list layout lets you define columns with a name and a Handlebars value, set in the Manage columns dialog. Add three columns in this exact order: the skill link, a folder jump, and the More Info panel. Tick Use Handlebars expression on all three, since every column uses an expression rather than a plain property. Set the widths as shown in the image: a minimum of 80 and a maximum of 300 for the Available Skills column, and a minimum and maximum of 5 for both icon columns so they stay tight against the link. Leave the Column description (tooltip) empty on all three. Match the Resizable and Multiline checkboxes to the image: both on for the Available Skills column, and both off for the two icon columns.

The first column, Available Skills, is the main link. It shows the filename followed by the skill’s folder name with the hyphens swapped for spaces, links to the file, and uses the skill Description as a hover tooltip. <details> <summary><strong>Column 1: Available Skills</strong></summary>

<a href="{{slot item @root.slots.PreviewUrl}}" target="_blank" data-interception="off" style="color: {{@root.theme.semanticColors.link}}" title="{{slot item @root.slots.Description}}">
{{slot item @root.slots.Filename}} - {{replace (last (split ParentLink "/")) "-" " "}}
</a>

The second column is a narrow icon column with no header. It renders a folder icon that opens the skill’s parent folder in the library. <details> <summary><strong>Column 2: View in folder icon</strong></summary>

<a href="{{slot item @root.slots.ParentLink}}" style="text-decoration: none" data-interception="off" target="_blank" title="View Skill in Folder"><pnp-icon data-name="PreviewSideBySide" aria-hidden="true" ></pnp-icon></a>

The third column is the More Info panel. It uses the PnP pnp-panel web component to slide out a metadata card with the document link, the folder, the site, the skill description, and the created and modified detail. This is the longest bit of the build, so it lives in the accordion below.

<pnp-panel
    data-theme-variant='{{JSONstringify @root.theme}}'
    data-is-open="false"
    data-is-light-dismiss="true"
    data-is-blocking="true"
    data-size="3"
    data-panel-header-text="ā„¹ļø More Info">
    <template id="panel-open">
        <a href="#" title="More Info" style="text-decoration: none"><pnp-icon data-name="Info12" aria-hidden="true" ></pnp-icon></a>
    </template>
    <template id="panel-content">
        <div class="docMetadata">
            <div style="display: flex; flex-wrap: wrap; gap: 0; margin-top: 16px; border-collapse: collapse; width: 100%;">
                <div style="display: flex; flex-direction: row; flex: 1 1 100%; padding: 8px 9px; border-bottom: none; align-items: center; font-weight: bold; color: black; text-align: left;">
                    <pnp-iconfile data-extension="{{slot item @root.slots.FileType}}" data-is-container="false" data-size="32" style="margin-right: 5px;"></pnp-iconfile> {{slot item @root.slots.Filename}}
                </div>
                <div style="display: flex; flex-direction: row; flex: 1 1 100%; padding: 4px 9px; border-bottom: 1px solid #ddd; align-items: center;">
                    <span style="font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;">Document Link</span>
                    <span style="flex: 1; color: #333; text-align: left;"><a href="{{slot item @root.slots.PreviewUrl}}" target="_blank">{{slot item @root.slots.Filename}}</a></span>
                </div>
                <div style="display: flex; flex-direction: row; flex: 1 1 100%; padding: 4px 9px; border-bottom: 1px solid #ddd; align-items: center;">
                    <span style="font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;">View Skill in Folder</span>
                    <span style="flex: 1; color: #333; text-align: left;"><a href="{{slot item @root.slots.ParentLink}}" target="_blank" style="display:inline-flex;align-items:center;"><pnp-icon data-name="FabricFolder" aria-hidden="true"></pnp-icon><span style="margin-left:4px;">{{titleize (last (split ParentLink "/"))}}</span></a></span>
                </div>
                <div style="display: flex; flex-direction: row; flex: 1 1 100%; padding: 4px 9px; border-bottom: 1px solid #ddd; align-items: center;">
                    <span style="font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;">Site</span>
                    <span style="flex: 1; color: #333; text-align: left;"><a href="{{slot item @root.slots.SPWebURL}}" target="_blank" data-interception="off">{{slot item @root.slots.SiteTitle}}</a></span>
                </div>
            </div>
        </div>
        <div class="docMetadata">
            <div style="display: flex; flex-wrap: wrap; gap: 0; margin-top: 16px; border-collapse: collapse; width: 100%;">
                <div style="display: flex; align-items: center; font-weight: bold; padding: 16px 9px 8px 9px;"><pnp-icon data-name="TextDocument" aria-hidden="true"></pnp-icon><span style="margin-left: 6px;">File Details</span></div>
                <div style="display: flex; flex-direction: row; flex: 1 1 100%; padding: 8px 9px; border-bottom: 1px solid #ddd; align-items: flex-start;">
                    <span style="font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;">Skill Description</span>
                    <span style="flex: 1; color: #333; text-align: left; line-height: 1.5;">{{#if Description}}{{Description}}{{else}}<span style="color: #999;">No Description detected.</span>{{/if}}</span>
                </div>
                <div style="display: flex; flex-direction: row; flex: 1 1 100%; padding: 4px 9px; border-bottom: 1px solid #ddd; align-items: center;">
                    <span style="font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;">Created</span>
                    <span style="flex: 1; color: #333; text-align: left;">{{getDate (slot item @root.slots.Date) "LL"}} by {{#with (split (slot item @root.slots.Author) '|') as |values|}}{{values.[1]}}{{/with}}</span>
                </div>
                <div style="display: flex; flex-direction: row; flex: 1 1 100%; padding: 4px 9px; border-bottom: 1px solid #ddd; align-items: center;">
                    <span style="font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;">Modified</span>
                    <span style="flex: 1; color: #333; text-align: left;">{{getDate (slot item @root.slots.LastModifiedTimeForRetention) "LL"}} by {{#with (split (slot item @root.slots.Editor) '|') as |values|}}{{values.[1]}}{{/with}}</span>
                </div>
            </div>
        </div>
    </template>
</pnp-panel>

Step 7: Save and publish. Set the column widths to taste (I keep the two icon columns narrow at 5px so they sit tight against the link), save the web part, and publish the page. You should see every skill on the site listed, exactly like the screenshot above.

The Full Importable Config

Here is the finished config, exactly as I run it. This is the fast route: copy the whole thing, then add a fresh PnP Search Rollup web part, then in the web part property pane open Import/Export settings, click Edit properties, paste the JSON in, and click Apply. It works out of the box on any site with no edits. It assumes the standard AgentAssets/Skills path, and it relies only on managed properties that every tenant has by default, so there are no RefinableStrings or custom search schema to configure first.

{
	"documentationLink": "https://microsoft-search.github.io/pnp-modern-search/",
	"allowWebPartConnections": false,
	"showTitle": true,
	"selectedLayoutKey": "DetailsList",
	"resultTypes": [],
	"dataSourceProperties": {
		"queryTemplate": "Path:{Site}/AgentAssets/Skills AND FileName:SKILL.MD",
		"enableQueryRules": false,
		"enableLocalization": false,
		"refinementFilters": "",
		"selectedProperties": [
			"AuthorOWSUSER",
			"contentclass",
			"ContentTypeId",
			"Created",
			"CreatedBy",
			"DefaultEncodingURL",
			"Description",
			"DescriptionOWSMTXT",
			"EditorOWSUSER",
			"Filename",
			"FileType",
			"HitHighlightedProperties",
			"HitHighlightedSummary",
			"HtmlFileType",
			"JobTitle",
			"LastModifiedTimeForRetention",
			"NormListID",
			"NormSiteID",
			"NormUniqueID",
			"NormWebID",
			"owstaxidmetadataalltagsinfo",
			"ParentLink",
			"Path",
			"PictureThumbnailURL",
			"ServerRedirectedEmbedURL",
			"SiteLogo",
			"SiteTitle",
			"SPSiteURL",
			"SPWebUrl",
			"Title",
			"UniqueID",
			"UserName",
			"WorkPhone"
		],
		"resultSourceId": "8413cd39-2156-4e00-b54d-11efd9abdb89",
		"hitHighlightedProperties": "",
		"trimDuplicates": false,
		"sortList": []
	},
	"queryTextSource": 0,
	"layoutProperties": {
		"detailsListColumns": [
			{
				"name": "Available Skills",
				"value": "<a href=\"{{slot item @root.slots.PreviewUrl}}\" target=\"_blank\" data-interception=\"off\" style=\"color: {{@root.theme.semanticColors.link}}\" title=\"{{slot item @root.slots.Description}}\">\n{{slot item @root.slots.Filename}} - {{replace (last (split ParentLink \"/\")) \"-\" \" \"}}\n</a>",
				"useHandlebarsExpr": true,
				"minWidth": "80",
				"maxWidth": "300",
				"enableSorting": false,
				"isMultiline": true,
				"isResizable": true,
				"sortIdx": 1
			},
			{
				"uniqueId": "d8cc4817-6c9d-4895-bcc3-6affbf851f78",
				"name": " ",
				"value": "<a href=\"{{slot item @root.slots.ParentLink}}\" style=\"text-decoration: none\" data-interception=\"off\" target=\"_blank\" title=\"View Skill in Folder\"><pnp-icon data-name=\"PreviewSideBySide\" aria-hidden=\"true\" ></pnp-icon></a>",
				"useHandlebarsExpr": true,
				"minWidth": "5",
				"maxWidth": "5",
				"enableSorting": null,
				"valueSorting": null,
				"isResizable": false,
				"isMultiline": false,
				"columnDescription": "",
				"sortIdx": 2
			},
			{
				"uniqueId": "6fafc97e-5e00-4908-bdba-227175d4ca90",
				"name": " ",
				"value": "<pnp-panel\r\n    data-theme-variant='{{JSONstringify @root.theme}}'\r\n    data-is-open=\"false\" \r\n    data-is-light-dismiss=\"true\"\r\n    data-is-blocking=\"true\"\r\n    data-size=\"3\"\r\n    data-panel-header-text=\"ā„¹ļø More Info\">\r\n\r\n    <template id=\"panel-open\">\r\n        <!-- All the content here will be wrapped with an onclick event opening/hiding the panel -->\r\n\t\t<a href=\"#\" title=\"More Info\" style=\"text-decoration: none\"><pnp-icon data-name=\"Info12\" aria-hidden=\"true\" ></pnp-icon></a>\r\n    </template>\r\n\r\n    <template id=\"panel-content\">\r\n        <!-- Panel content goes here -->\r\n\r\n\r\n\t\t<div class=\"docMetadata\">\r\n\t\t\t<div style=\"display: flex; flex-wrap: wrap; gap: 0; margin-top: 16px; border-collapse: collapse; width: 100%;\">\r\n\t\t\t\t<!-- Header Row -->\r\n\t\t\t\t<div style=\"display: flex; flex-direction: row; flex: 1 1 100%; padding: 8px 9px; border-bottom: none; align-items: center; font-weight: bold; color: black; text-align: left;\">\r\n\t\t\t\t\t<pnp-iconfile data-extension=\"{{slot item @root.slots.FileType}}\" data-is-container=\"false\" data-size=\"32\" style=\"margin-right: 5px;\"></pnp-iconfile> {{slot item @root.slots.Filename}}\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<!-- Data Rows -->\r\n\t\t\t\t<div style=\"display: flex; flex-direction: row; flex: 1 1 100%; padding: 4px 9px; border-bottom: 1px solid #ddd; align-items: center;\">\r\n\t\t\t\t\t<span style=\"font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;\">Document Link</span>\r\n\t\t\t\t\t<span style=\"flex: 1; color: #333; text-align: left;\"><a href=\"{{slot item @root.slots.PreviewUrl}}\" target=\"_blank\">{{slot item @root.slots.Filename}}</a></span>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div style=\"display: flex; flex-direction: row; flex: 1 1 100%; padding: 4px 9px; border-bottom: 1px solid #ddd; align-items: center;\">\r\n\t\t\t\t\t<span style=\"font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;\">View Skill in Folder</span>\r\n\t\t\t\t\t<span style=\"flex: 1; color: #333; text-align: left;\"><a href=\"{{slot item @root.slots.ParentLink}}\" target=\"_blank\" style=\"display:inline-flex;align-items:center;\"><pnp-icon data-name=\"FabricFolder\" aria-hidden=\"true\"></pnp-icon><span style=\"margin-left:4px;\">{{titleize (last (split ParentLink \"/\"))}}</span></a></span>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div style=\"display: flex; flex-direction: row; flex: 1 1 100%; padding: 4px 9px; border-bottom: 1px solid #ddd; align-items: center;\">\r\n\t\t\t\t\t<span style=\"font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;\">Site</span>\r\n\t\t\t\t\t<span style=\"flex: 1; color: #333; text-align: left;\"><a href=\"{{slot item @root.slots.SPWebURL}}\" target=\"_blank\" data-interception=\"off\">{{slot item @root.slots.SiteTitle}}</a></span>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\r\n        <div class=\"docMetadata\">\r\n            <div style=\"display: flex; flex-wrap: wrap; gap: 0; margin-top: 16px; border-collapse: collapse; width: 100%;\">\r\n                <!-- Header Row -->\r\n\t\t<div style=\"display: flex; align-items: center; font-weight: bold; padding: 16px 9px 8px 9px;\"><pnp-icon data-name=\"TextDocument\" aria-hidden=\"true\"></pnp-icon><span style=\"margin-left: 6px;\">File Details</span>\r\n\t\t</div>\r\n\r\n                <!-- Data Rows -->\r\n                <div style=\"display: flex; flex-direction: row; flex: 1 1 100%; padding: 8px 9px; border-bottom: 1px solid #ddd; align-items: flex-start;\">\r\n                    <span style=\"font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;\">Skill Description</span>\r\n                    <span style=\"flex: 1; color: #333; text-align: left; line-height: 1.5;\">{{#if Description}}{{Description}}{{else}}<span style=\"color: #999;\">No Description detected.</span>{{/if}}</span>\r\n                </div>\r\n                <div style=\"display: flex; flex-direction: row; flex: 1 1 100%; padding: 4px 9px; border-bottom: 1px solid #ddd; align-items: center;\">\r\n                    <span style=\"font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;\">Created</span>\r\n                    <span style=\"flex: 1; color: #333; text-align: left;\">{{getDate (slot item @root.slots.Date) \"LL\"}} by {{#with (split (slot item @root.slots.Author) '|') as |values|}}{{values.[1]}}{{/with}}</span>\r\n                </div>\r\n                <div style=\"display: flex; flex-direction: row; flex: 1 1 100%; padding: 4px 9px; border-bottom: 1px solid #ddd; align-items: center;\">\r\n                    <span style=\"font-weight: bold; padding-right: 15px; flex: 0 0 110px; text-align: left; word-wrap: break-word; white-space: normal;\">Modified</span>\r\n                    <span style=\"flex: 1; color: #333; text-align: left;\">{{getDate (slot item @root.slots.LastModifiedTimeForRetention) \"LL\"}} by {{#with (split (slot item @root.slots.Editor) '|') as |values|}}{{values.[1]}}{{/with}}</span>\r\n                </div>\r\n            </div>\r\n        </div>\r\n\t</template>\r\n</pnp-panel>",
				"useHandlebarsExpr": true,
				"minWidth": "5",
				"maxWidth": "5",
				"enableSorting": null,
				"valueSorting": null,
				"isResizable": null,
				"isMultiline": null,
				"columnDescription": "",
				"sortIdx": 3
			}
		],
		"isCompact": true,
		"showFileIcon": true,
		"fieldIconExtension": "FileType",
		"enableGrouping": false,
		"groupByField": "",
		"groupByOthersGroupTitle": "No value",
		"additionalGroupByFields": [],
		"groupsCollapsed": true,
		"useAlternatingBackgroundColor": true
	},
	"showSelectedFilters": false,
	"showResultsCount": false,
	"showBlankIfNoResult": false,
	"useMicrosoftGraphToolkit": false,
	"titleLinkText": "",
	"titleLinkUrl": "",
	"titleLinkOpenInNewTab": false,
	"itemSelectionProps": {
		"allowItemSelection": false,
		"selectionMode": "AsDataFilter",
		"allowMulti": false,
		"valuesOperator": "or",
		"selectionPreservedOnEmptyClick": false
	},
	"extensibilityLibraryConfiguration": [
		{
			"name": "Default extensibility library",
			"enabled": false,
			"id": "dc4f961b-dbe0-44b4-982d-5776bf99d015"
		}
	],
	"queryModifierConfiguration": [],
	"queryModifierProperties": {
		"endWhenSuccessfull": false
	},
	"selectedVerticalKeys": [],
	"useVerticals": false,
	"paging": {
		"itemsCountPerPage": 10,
		"pagingRange": 10,
		"showPaging": true,
		"hideDisabled": true,
		"hideFirstLastPages": false,
		"hideNavigation": false,
		"useNextLinks": false,
		"enableQueryString": false
	},
	"adaptiveCardsHostConfig": "{\n\t\"fontFamily\": \"Segoe UI, Helvetica Neue, sans-serif\"\n}",
	"layoutRenderType": "Handlebars",
	"dataSourceKey": "SharePointSearch",
	"templateSlots": [
		{
			"slotName": "Title",
			"slotField": "Title",
			"sortIdx": 1
		},
		{
			"slotName": "Path",
			"slotField": "DefaultEncodingURL",
			"sortIdx": 2
		},
		{
			"slotName": "Summary",
			"slotField": "HitHighlightedSummary",
			"sortIdx": 3
		},
		{
			"slotName": "FileType",
			"slotField": "FileType",
			"sortIdx": 4
		},
		{
			"slotName": "PreviewImageUrl",
			"slotField": "AutoPreviewImageUrl",
			"sortIdx": 5
		},
		{
			"slotName": "PreviewUrl",
			"slotField": "AutoPreviewUrl",
			"sortIdx": 6
		},
		{
			"slotName": "Author",
			"slotField": "AuthorOWSUSER",
			"sortIdx": 7
		},
		{
			"slotName": "Tags",
			"slotField": "owstaxidmetadataalltagsinfo",
			"sortIdx": 8
		},
		{
			"slotName": "Date",
			"slotField": "Created",
			"sortIdx": 9
		},
		{
			"slotName": "SiteId",
			"slotField": "NormSiteID",
			"sortIdx": 10
		},
		{
			"slotName": "WebId",
			"slotField": "NormWebID",
			"sortIdx": 11
		},
		{
			"slotName": "ListId",
			"slotField": "NormListID",
			"sortIdx": 12
		},
		{
			"slotName": "ItemId",
			"slotField": "NormUniqueID",
			"sortIdx": 13
		},
		{
			"slotName": "IsFolder",
			"slotField": "ContentTypeId",
			"sortIdx": 14
		},
		{
			"slotName": "contentclass",
			"slotField": "contentclass",
			"sortIdx": 15
		},
		{
			"slotName": "PersonQuery",
			"slotField": "AADObjectID",
			"sortIdx": 16
		},
		{
			"slotName": "UserDisplayName",
			"slotField": "Title",
			"sortIdx": 17
		},
		{
			"slotName": "UserEmail",
			"slotField": "UserName",
			"sortIdx": 18
		},
		{
			"slotName": "Id",
			"slotField": "DocId",
			"sortIdx": 19
		},
		{
			"slotName": "SPWebURL",
			"slotField": "SPWebUrl",
			"sortIdx": 20
		},
		{
			"slotName": "SiteTitle",
			"slotField": "SiteTitle",
			"sortIdx": 21
		},
		{
			"uniqueId": "9115c25a-e031-4a61-a604-a5ebbeaecf5c",
			"slotName": "Filename",
			"slotField": "Filename",
			"sortIdx": 22
		},
		{
			"uniqueId": "bea3d7dc-0d60-46a4-97d3-9dc02e75907f",
			"slotName": "Editor",
			"slotField": "EditorOWSUSER",
			"sortIdx": 23
		},
		{
			"uniqueId": "6981b045-721b-4433-9179-851ad00e9962",
			"slotName": "LastModifiedTimeForRetention",
			"slotField": "LastModifiedTimeForRetention",
			"sortIdx": 24
		},
		{
			"uniqueId": "2fbba88f-b8a4-4f9d-bf63-8e7e651e7d90",
			"slotName": "ParentLink",
			"slotField": "ParentLink",
			"sortIdx": 25
		},
		{
			"uniqueId": "55013190-b654-442c-a62c-e6c7084a72a9",
			"slotName": "SkillPath",
			"slotField": "Path",
			"sortIdx": 26
		},
		{
			"uniqueId": "ef96dbc1-7c6a-4cd5-b7cd-0fca502d762c",
			"slotName": "Description",
			"slotField": "Description",
			"sortIdx": 27
		}
	],
	"inlineTemplateContent": null,
	"externalTemplateUrl": "",
	"title": "Skills on this Site",
	"filtersData": {
		"__type": "DynamicProperty",
		"value": {}
	},
	"queryText": {
		"__type": "DynamicProperty",
		"value": ""
	},
	"selectedItemFieldValue": {
		"__type": "DynamicProperty",
		"value": ""
	}
}

A Few Tips

A couple of things to know before you rely on this.

Search indexing is not instant. When you create a new skill, it will not appear in the rollup until SharePoint search has crawled and indexed the file. That is usually minutes, but it is not real time, so do not panic if a brand new skill is missing for a bit.

A skill needs a description for one to show. The description comes straight from the skill, so it appears on hover and in the More Info panel. If a skill was created without one, you get a “No Description detected” fallback instead, which is a decent nudge to go back and add a description to that skill.

It is site scoped by design. The {Site} token keeps this to the current site, which is what you want per site. If you are chasing a single page that lists skills across the whole tenant, that is a different query and a slightly different setup.

Summary

A single PnP Modern Search web part turns a folder of SKILL.md files into a proper, permission trimmed list of your Copilot in SharePoint Skills, right on the page.

I’ve been asking the Microsoft product team for an admin interface, or at least a more obvious way to see which Skills are available on a site. Hopefully this serves as a temporary workaround until Skills become easier to discover out of the box.

To install it, you can either follow the steps above or simply import the full configuration and be up and running in minutes.

I am adding one of these to the homepage of every site I build, alongside a SHAREPOINT.md context file (read my blog for more about the power of SHAREPOINT.md). In a future blog post, I’ll show how to take this tenant-wide so a single search page can surface every Skill across your tenant.

If you’re building your own Skills, the PnP community maintains a SharePoint Skills GitHub repo that’s well worth exploring for inspiration. I’d also love to see what you create.

As always, if you have any questions or ideas for improving the rollup, I’m all ears. If you deploy it in your tenant, let me know how you get on,

Leave a Reply