Find SharePoint pages Path lengths
Ever suspected some of the paths are bumping up against the 400 character limit? This PnP PowerShell script will help you confirm that suspicion. A client of mine took the idea of a folder structure and got a little carried away. Nested folder after nested folder. Often SharePoint itself doesn´t respect it´s own characater limits. But if you are working with another tool, such as Power Automate, that does respect SharePoint´s limits, then it quickly becomes important. Plus, Micrsoft itself recommends using metadata over folders, and folder use should be minimal where possible.
Purpose
This script helps you:
- Output the path and encoded path
- Count the number of characters in the path
- Order the pages and folders according to length
- Provide the business with evidence for decision making
Prerequisites
- PnP PowerShell module installed
- Site owner permissions
- Connection to your SharePoint Online site
PowerShell Script
param(
[Parameter(Mandatory=$false)]
[string]$ClientId = "",
[Parameter(Mandatory=$false)]
[string]$SiteUrl = "https://tenantName.sharepoint.com/sites/siteName",
[Parameter(Mandatory=$false)]
[string]$OutputPath = ".\SitePages_FolderStructure_Report.csv"
)
# Function to get all items recursively
function Get-AllItemsRecursive {
param(
[string]$FolderUrl = "",
[string]$LibraryName = "Site Pages",
[hashtable]$ProcessedPaths = @{}
)
$items = @()
try {
if ([string]::IsNullOrEmpty($FolderUrl)) {
# Get root level items
$listItems = Get-PnPListItem -List $LibraryName -PageSize 5000
}
else {
# Get items in specific folder
$listItems = Get-PnPListItem -List $LibraryName -FolderServerRelativeUrl $FolderUrl -PageSize 5000
}
foreach ($item in $listItems) {
$fileRef = $item.FieldValues["FileRef"]
$fileType = $item.FieldValues["FSObjType"]
$fileName = $item.FieldValues["FileLeafRef"]
# Skip if we've already processed this path
if ($ProcessedPaths.ContainsKey($fileRef)) {
continue
}
# Mark this path as processed
$ProcessedPaths[$fileRef] = $true
# Get the web's server-relative URL to avoid duplication
$webServerRelativeUrl = $web.ServerRelativeUrl
# Construct the full URL properly
# $fileRef is the full server-relative path (e.g., /sites/intranet/SitePages/Page.aspx)
# We need to combine domain + fileRef, NOT web.Url + fileRef
$domain = $web.Url.Replace($webServerRelativeUrl, "")
$fullUrl = $domain + $fileRef
# Per Microsoft documentation, the 400-char limit applies to the path starting from /sites/
# NOT including the domain (https://domain.sharepoint.com)
# Example: "sites/marketing/documents/Shared Documents/Promotion/Some File.xlsx"
# Remove leading slash from fileRef to get the path for length calculation
$pathForLimit = $fileRef.TrimStart('/')
# URL-encode the path to get the ACTUAL length as it appears in SharePoint
# This converts: spaces to %20, # to %23, & to %26, ( to %28, ) to %29, etc.
$encodedPath = [System.Uri]::EscapeDataString($pathForLimit)
$encodedUrl = [System.Uri]::EscapeUriString($fullUrl)
# FSObjType: 0 = File, 1 = Folder
if ($fileType -eq 1) {
# It's a folder
$items += [PSCustomObject]@{
Type = "Folder"
Name = $fileName
ServerRelativeUrl = $fileRef
FullUrl = $fullUrl
EncodedUrl = $encodedUrl
PathForLimit = $pathForLimit
EncodedPathForLimit = $encodedPath
ActualPathLength = $encodedPath.Length
DecodedPathLength = $pathForLimit.Length
FullUrlLength = $encodedUrl.Length
Level = ($fileRef -split '/').Count - 1
}
# Recursively get items in subfolder
$items += Get-AllItemsRecursive -FolderUrl $fileRef -LibraryName $LibraryName -ProcessedPaths $ProcessedPaths
}
else {
# It's a file/page
$items += [PSCustomObject]@{
Type = "Page"
Name = $fileName
ServerRelativeUrl = $fileRef
FullUrl = $fullUrl
EncodedUrl = $encodedUrl
PathForLimit = $pathForLimit
EncodedPathForLimit = $encodedPath
ActualPathLength = $encodedPath.Length
DecodedPathLength = $pathForLimit.Length
FullUrlLength = $encodedUrl.Length
Level = ($fileRef -split '/').Count - 1
}
}
}
}
catch {
Write-Error "Error processing folder '$FolderUrl': $_"
}
return $items
}
# Main script execution
try {
Write-Host "Connecting to SharePoint site: $SiteUrl" -ForegroundColor Cyan
# Connect to SharePoint (will prompt for authentication)
Connect-PnPOnline -Url $SiteUrl -Interactive -ClientId $ClientId
# Get web details for constructing full URLs
$web = Get-PnPWeb
Write-Host "Connected successfully. Retrieving folder structure and pages..." -ForegroundColor Green
# Create hashtable to track processed items and prevent duplicates
$processedPaths = @{}
# Get all items from Site Pages library
$allItems = Get-AllItemsRecursive -LibraryName "Site Pages" -ProcessedPaths $processedPaths
Write-Host "`nTotal items found: $($allItems.Count)" -ForegroundColor Yellow
Write-Host " - Folders: $(($allItems | Where-Object {$_.Type -eq 'Folder'}).Count)" -ForegroundColor Yellow
Write-Host " - Pages: $(($allItems | Where-Object {$_.Type -eq 'Page'}).Count)" -ForegroundColor Yellow
# Display statistics
$maxPathLength = ($allItems | Measure-Object -Property ActualPathLength -Maximum).Maximum
$avgPathLength = ($allItems | Measure-Object -Property ActualPathLength -Average).Average
Write-Host "`nPath Length Statistics (from /sites/ onwards, ENCODED):" -ForegroundColor Cyan
Write-Host " - Maximum path length: $maxPathLength characters" -ForegroundColor $(if($maxPathLength -gt 400){"Red"}else{"Green"})
Write-Host " - Average path length: $([math]::Round($avgPathLength, 2)) characters" -ForegroundColor White
Write-Host " - SharePoint limit: 400 characters" -ForegroundColor Yellow
# Show items with longest paths
Write-Host "`nTop 10 longest paths:" -ForegroundColor Cyan
$allItems | Sort-Object ActualPathLength -Descending | Select-Object -First 10 |
Format-Table Type, Name, ActualPathLength, DecodedPathLength, @{Label="PathFromSites";Expression={$_.PathForLimit};Width=60} -AutoSize
# Export to CSV - sorted by ActualPathLength (longest first)
Write-Host "`nExporting to CSV..." -ForegroundColor Cyan
$allItems | Sort-Object ActualPathLength -Descending |
Select-Object Type, Name, PathForLimit, EncodedPathForLimit, ActualPathLength, DecodedPathLength, ServerRelativeUrl, FullUrl, FullUrlLength, Level |
Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
Write-Host "`nCSV report exported to: $OutputPath" -ForegroundColor Green
Write-Host "Columns included:" -ForegroundColor Cyan
Write-Host " - Type: Folder or Page" -ForegroundColor White
Write-Host " - Name: Item name" -ForegroundColor White
Write-Host " - PathForLimit: Path from /sites/ onwards (decoded)" -ForegroundColor White
Write-Host " - EncodedPathForLimit: Path from /sites/ onwards (with %20, %28, etc.)" -ForegroundColor White
Write-Host " - ActualPathLength: Encoded path length - THE LIMIT APPLIES TO THIS!" -ForegroundColor Yellow
Write-Host " - DecodedPathLength: Decoded path length (for comparison)" -ForegroundColor White
Write-Host " - ServerRelativeUrl: Full relative path including leading /" -ForegroundColor White
Write-Host " - FullUrl: Complete URL with domain" -ForegroundColor White
Write-Host " - FullUrlLength: Full encoded URL length (for reference)" -ForegroundColor White
Write-Host " - Level: Folder depth" -ForegroundColor White
Write-Host "`nNote: Sorted by ActualPathLength (longest first)" -ForegroundColor Yellow
Write-Host "SharePoint limit: 400 characters for ENCODED path (starting from sites/...)" -ForegroundColor Yellow
# Disconnect
Disconnect-PnPOnline
Write-Host "`nScript completed successfully!" -ForegroundColor Green
}
catch {
Write-Error "An error occurred: $_"
Write-Error $_.Exception.StackTrace
}
finally {
# Ensure disconnection
try { Disconnect-PnPOnline -ErrorAction SilentlyContinue } catch {}
}
Usage Notes
- Update the Tenant name, site name, client id for your tenant
- The csv will be output to the same directory used to run the
- Check the FullUrlLength column for pth length
- This script shouldn´t impact site performance
Important Considerations
- Spaces add length to paths
- Restrict the length of page titles if you can
- Folder should not be nested