Tuesday, April 10, 2018

Working with Hub Sites and the search API

Photo by Sunaina Kamal at Unsplash.

Hub sites are a nice way to create a virtual two level site hierarchy without using sub sites. An administrator will nominate a site as a hub site, and then other sites can associate themselves to this hub. By associating a site to a hub the site will inherit the navigation and the visual look of the hub itself, ensuring a consisting look and feel. Also items from associated sites will surface in search at the hub level (as long as you have read access to the item).


Note: The hub site feature is currently in preview and only available for target release tenant at the time of writing.

After playing with hub sites you might want your favorite developer to create something for your hub sites, and this is where the search API is handy.

Search schema

At the heart of hub sites in regards to search is the managed property named DepartmentId which is the site id of the hub site. Both the hub site and sites associated to the hub site have the same id in this property. There is also a managed property named IsHubSite which currently has no value, so you cannot use this to list the hub sites themselves. [Update 2022-02-17] You can list all hub sites with the query IsHubSite:true.

[Update 2023-02-17] Instead of DepartmentId you can use RelatedHubSites which also work for nested hub site scenarios. 

Instead of explaining it all I’ll give some sample queries on how you can use search to query for hub sites.

NOTE: If you know the hub site id, then these queries do not apply. Listing associated sites to a specific hub can be achieved via KQL:

DepartmentId=<siteid of hub/hubid> contentclass:sts_site -SiteId:<siteid of hub/hubod>

List all sites which is either a hub site or associated with a hub site

Query: contentclass=sts_site
Refinement filter: departmentid:string("{*",linguistics=off)
Trim duplicates: false
REST: /_api/search/query?querytext='contentclass%3dsts_site'&refinementfilters='departmentid:string("{*"%2clinguistics%3doff)'&trimduplicates=false

If you page through all the results you will get all sites either being a hub site or being associated to a hub site. For the curious ones the refinement filter is FQL and at the heart of the query. This piece of FQL limits results where there actually is a value in the DepartmentId managed property.

List all hub site id’s

Query: contentclass=sts_site
Refinement filter: departmentid:string("{*",linguistics=off)
Trim duplicates: false
Collapse Specification: departmentid:1
REST: /_api/search/query?querytext='contentclass%3dsts_site'&refinementfilters='departmentid:string("{*"%2clinguistics%3doff)'&collapsespecification='departmentid:1'&trimduplicates=false

This will yield one result per hub site id due to the collapse specification, but the one result being returned per hub site id could very well be a site associated to the hub site and not the hub site itself.

For results where the SiteId is not equal to DepartmentId, you need to perform one more call to fetch the actual hub site with a query similar to: ContentClass=sts_site SiteId:"<DepartmentId>". Be sure to use : and not =, as equals won’t match on the site id with a guid.

List all hub site id’s having at least one site associated with it

Query: contentclass=sts_site
Refinement filter: departmentid:string("{*",linguistics=off)
Trim duplicates: false
Row limit: 0
Refiners: departmentid(filter=1000/2/*)

For this query you need to look at the refinement results, and not the list itself, which is why I set row limit to zero. The filter will return only items where the refiner count is 2 or higher, thus eliminating orphaned hub sites.

At the writing of this post the max number of hub sites is 50, so you won’t run into an issue with too many refiner values, which means you can use 50 instead of 1000 in the filter – but better to be future proof.

If you remove the filter part, you can use this query to fetch all hub site id’s, similar to the first sample, except using refinement results instead.


PnP PowerShell code to fetch all hub sites, and any associated site, and list them by the hub site

For this one I will fetch all results, using paging if needed (the -All parameter), and then iterate over and group them by the hub site. Similar code should be pretty easy to write using both REST, CSOM and JSOM.

# List all sites being a hub site or associate to a hub site
$results = Submit-PnPSearchQuery -Query 'contentclass=sts_site' -RefinementFilters 'departmentid:string("{*",linguistics=off)' -TrimDuplicates $false -SelectProperties @("Title","Path","DepartmentId","SiteId") -All -RelevantResults

# Filter out the hub sites
$hubSites = $results |? { $_.DepartmentId.Trim('{','}') -eq $_.SiteId  }

# Loop over the hub sites
foreach( $hub in $hubSites ) {
    Write-Host $hub.Title - $hub.Path -ForegroundColor Green
    # Filter out sites associated to the current hub
    $associatedSites = ($results |? { $_.DepartmentId -eq $hub.DepartmentId -and $_.SiteId -ne $hub.SiteId })
    foreach($site in $associatedSites) {
        Write-Host "`t"$site.Title - $site.Path -ForegroundColor Yellow

Other options

There are other options to work with subsites. The CSOM Site object contains a property named IsHubSite you can check on, as does the tenant properties of a site. The site object also has a HubSiteId property which corresponds to the search managed property DepartmentId.