Folder Path Length
This PnP PowerShell script analyses the folder and page structure of a SharePoint Online Site Pages library, calculating the character length of every path in the hierarchy. It exports a full report to CSV, sorted by path length, so you can quickly identify items that are approaching or exceeding SharePoint's URL limits.
Purpose
SharePoint enforces a 400-character limit on encoded paths (starting from sites/...). Deeply nested folder structures with long names can silently cause issues — pages may fail to load, move, or be indexed correctly. This script helps you:
- Audit path lengths across the entire Site Pages library before a migration
- Identify folders and pages that are at risk of hitting the 400-character limit
- Understand the difference between decoded and URL-encoded path lengths
- Plan folder restructuring to avoid path length violations
- Produce a documented baseline of your site's folder hierarchy
What the script reports
The exported CSV includes the following columns for every folder and page found:
- Type — whether the item is a Folder or a Page
- Name — the item's file or folder name
- PathForLimit — the decoded path from
sites/onwards - EncodedPathForLimit — the URL-encoded path (spaces become
%20, brackets become%28, etc.) - ActualPathLength — the encoded path length; this is what SharePoint measures against the 400-character limit
- DecodedPathLength — the plain-text path length, for comparison
- ServerRelativeUrl — the full server-relative path including the leading slash
- FullUrl — the complete URL including the domain
- Level — the folder depth within the library
Results are sorted with the longest paths first, making it easy to focus on the items most at risk.
Prerequisites
- PnP PowerShell module installed
- Site collection administrator permissions
- Access to your PnP app Client Id
PowerShell Script
param(
[Parameter(Mandatory=$false)]
[string]$ClientId = "",
[Parameter(Mandatory=$false)]
[string]$SiteUrl = "https://tenantName.sharepoint.com/sites/siteName",
[Parameter(Mandatory=$false)]
[string]$OutputPath = ".\SharePoint_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
$SiteUrland$ClientIdbefore running - The
$OutputPathdefaults to the current directory — set it to a specific folder if needed - SharePoint's 400-character limit applies to the URL-encoded path starting from
sites/— spaces and special characters count as 3 characters each (e.g.%20) - The script uses recursion to traverse all subfolder levels, so large or deeply nested libraries may take a few minutes to complete
- A processed-paths hashtable prevents duplicate entries if items appear in multiple query results
- Run this script before a migration to identify any paths that need shortening or restructuring