Collaborate, Innovate, Automate

Create Folder Structure

This PnP PowerShell script automates the creation of standardized folder structures in SharePoint Online document libraries. Build consistent organizational hierarchies to improve content management and user navigation.

Purpose

This script helps you:

Prerequisites

PowerShell Script

<#
.SYNOPSIS
    Creates a folder structure in a SharePoint Online library from a JSON configuration file.
    Uses Resolve-PnPFolder which creates the folder if it doesn't exist, skips if it does.
#>

# ── Configuration — update these before running ───────────────
$SiteUrl    = "https://tenantName.sharepoint.com/sites/siteName"
$ClientId   = ""
$ConfigPath = ""
# ─────────────────────────────────────────────────────────────

# ── Load and validate configuration ──────────────────────────
if (-not (Test-Path $ConfigPath)) {
    Write-Error "Configuration file not found at: $ConfigPath"
    exit 1
}

try {
    $config = Get-Content -Path $ConfigPath -Raw | ConvertFrom-Json
}
catch {
    Write-Error "Failed to parse JSON configuration: $($_.Exception.Message)"
    exit 1
}

$targetLibrary   = $config.folderStructure.targetLibrary
$folderHierarchy = $config.folderStructure.folderHierarchy

if (-not $targetLibrary) {
    Write-Error "targetLibrary is missing from the configuration file."
    exit 1
}

# ── Connect ───────────────────────────────────────────────────
Write-Host "Connecting to SharePoint..." -ForegroundColor Cyan
Connect-PnPOnline -Url $SiteUrl -Interactive -ClientId $ClientId
Write-Host "Connected successfully" -ForegroundColor Green

# ── Verify target library exists ─────────────────────────────
$library = Get-PnPList -Identity $targetLibrary -ErrorAction SilentlyContinue
if (-not $library) {
    Write-Error "Library '$targetLibrary' was not found on this site."
    Disconnect-PnPOnline
    exit 1
}

Write-Host "Target library: $targetLibrary" -ForegroundColor Cyan

# ── Recursive folder creation using Resolve-PnPFolder ────────
function New-FolderRecursive {
    param(
        [string]$LibraryName,
        [string]$ParentSiteRelativePath,
        [PSCustomObject]$FolderNode
    )

    $folderName = $FolderNode.name
    if (-not $folderName) { return }

    # Resolve-PnPFolder takes a site-relative path
    $siteRelativePath = "$ParentSiteRelativePath/$folderName"

    try {
        Resolve-PnPFolder -SiteRelativePath $siteRelativePath -ErrorAction Stop | Out-Null
        Write-Host "  Resolved: $siteRelativePath" -ForegroundColor Green
    }
    catch {
        Write-Host "  Failed:   $siteRelativePath — $($_.Exception.Message)" -ForegroundColor Red
        return
    }

    # Recurse into sub-folders
    if ($FolderNode.subFolders) {
        foreach ($subFolder in $FolderNode.subFolders) {
            New-FolderRecursive `
                -LibraryName $LibraryName `
                -ParentSiteRelativePath $siteRelativePath `
                -FolderNode $subFolder
        }
    }
}

# ── Process top-level folders ─────────────────────────────────
Write-Host "`nProvisioning folder structure...`n" -ForegroundColor Cyan

foreach ($topFolder in $folderHierarchy) {
    New-FolderRecursive `
        -LibraryName $targetLibrary `
        -ParentSiteRelativePath $targetLibrary `
        -FolderNode $topFolder
}

# ── Summary ────────────────────────────────────────────────────
Write-Host "`n── Run complete ────────────────────────────────" -ForegroundColor Cyan
Write-Host "Library          : $targetLibrary"
Write-Host "Top-level folders: $($folderHierarchy.Count)"
Write-Host "View the library : $SiteUrl/$targetLibrary"

Disconnect-PnPOnline
Write-Host "Disconnected." -ForegroundColor Cyan

JSON Configuration File

{
  "folderStructure": {
    "targetLibrary": "SitePages",
    "createPermissions": "Inherit",
    "folderHierarchy": [
      {
        "name": "Projects",
        "description": "Project documentation and deliverables",
        "subFolders": [
          {
            "name": "2024 Projects",
            "subFolders": [
              {"name": "Project Alpha"},
              {"name": "Project Beta"},
              {"name": "Project Gamma"}
            ]
          },
          {
            "name": "2025 Projects",
            "subFolders": [
              {"name": "Planning"},
              {"name": "Active"},
              {"name": "Completed"}
            ]
          }
        ]
      },
      {
        "name": "DocTemplates",
        "description": "Document templates and standards",
        "subFolders": [
          {"name": "Proposals"},
          {"name": "Reports"},
          {"name": "Presentations"}
        ]
      },
      {
        "name": "Archive",
        "description": "Historical documents and records",
        "subFolders": [
          {"name": "2023"},
          {"name": "2022"},
          {"name": "2021"}
        ]
      },
      {
        "name": "Policies",
        "description": "Organizational policies and procedures",
        "subFolders": [
          {"name": "HR Policies"},
          {"name": "IT Policies"},
          {"name": "Financial Policies"}
        ]
      }
    ]
  },
  "folderSettings": {
    "setUniquePermissions": false,
    "inheritFromParent": true,
    "defaultContentType": "Document",
    "enableVersioning": true
  }
}

Usage Notes

Best Practices