Dragging files into a SharePoint library in the browser is fine for a handful of documents. When you need to move a few hundred reports from a file share, rebuild a folder tree, or set columns like Department on every file, a script is faster and you can run it again next month.
Here’s how I upload files to a SharePoint Online document library with PnP PowerShell: one file, a file with metadata, every file in a folder, and a whole folder with its subfolders. I ran every script in PowerShell 7 with PnP PowerShell 3.3.0 against my own Microsoft 365 tenant, and the screenshots show the real output.
Quick answer: Connect with Connect-PnPOnline, then run Add-PnPFile -Path "C:\Reports\Report.pdf" -Folder "Shared Documents". Use the library’s URL name in -Folder (Shared Documents, not Documents) and add a subfolder path like Shared Documents/Sales/2026 to upload into a folder; missing folders are created for you. Add -Values @{Department="Sales"} to set columns.
Before you start: connect and find the library’s URL name
You need the PnP.PowerShell module, an Entra ID app registration for sign-in, and at least Edit (Contribute) permission on the library. If the module isn’t set up yet, follow installing PnP PowerShell for SharePoint Online first, because current versions require your own app’s client ID.
Install-Module PnP.PowerShell -Scope CurrentUser # once, PowerShell 7.4 or later
$siteUrl = "https://contoso.sharepoint.com/sites/Finance"
$clientId = "<your Entra ID app (client) ID>"
Connect-PnPOnline -Url $siteUrl -ClientId $clientId -Interactive
# Find the library's URL name (use this in -Folder)
(Get-PnPList -Identity "Contoso Demo Uploads").RootFolder.ServerRelativeUrl
The last line matters more than it looks. -Folder expects the library’s URL name, which often differs from the title you see in the browser. The default library is titled Documents but lives at Shared Documents. A library created as “Contoso Demo Uploads” through PnP with -Url ContosoDemoUploads lives at ContosoDemoUploads.
Upload a single file with Add-PnPFile
Add-PnPFile takes the local file in -Path and the destination in -Folder. The folder path is site-relative: the library’s URL name, optionally followed by subfolders. Use -NewFileName when the file should get a different name in SharePoint.
Add-PnPFile -Path "C:\Reports\Q3-2026-Sales-Summary.pdf" -Folder "Shared Documents"
# Upload under a different name
Add-PnPFile -Path "C:\Reports\Meeting-Notes.txt" -Folder "Shared Documents" `
-NewFileName "Ops-Meeting-Notes-10-05-2026.txt"

Look at the last line of the output. Uploading a file with the same name didn’t fail or create a copy: Add-PnPFile replaced it, and because versioning is on, SharePoint saved the old one as version 1.0. There’s no -Overwrite switch in PnP PowerShell, even though some older guides show one. Replacing is the default behavior.
Here are the parameters you’ll use most often:
| Parameter | What it does |
|---|---|
-Path | Local file to upload |
-Folder | Site-relative destination folder (required) |
-NewFileName | Name to use in SharePoint |
-Values | Hashtable of column values, keyed by internal name |
-ContentType | Content type to assign to the file |
-Checkout, -CheckInComment | Check out the existing file first, then check in with a comment |
-Publish, -Approve | Publish a major version or approve it when the library requires it |
-Stream / -Content with -FileName | Upload from a stream or create a file from text |
Upload into a folder or subfolder
Add the folder path after the library name. If any folder in the path doesn’t exist, Add-PnPFile creates it before uploading, so you don’t need a separate step.
If you want the folders in place first, Resolve-PnPFolder returns a folder and creates it, with any missing parents, when it isn’t there. To plan a structure from scratch, see creating a document library with folders using PnP PowerShell.
# Creates Sales, 2026 and Q3 if they don't exist yet
Add-PnPFile -Path "C:\Reports\East-Region-Q3-2026.csv" -Folder "Shared Documents/Sales/2026/Q3"
# Create (or just get) a folder without uploading anything
Resolve-PnPFolder -SiteRelativePath "Shared Documents/Sales/2026/Q4"
Upload a file and set metadata (column values)
Pass -Values a hashtable to fill columns during the upload. Keys are the columns’ internal names, not their display names, so a column shown as “Report Date” may be ReportDate or Report_x0020_Date. Each column type expects a specific format:
Add-PnPFile -Path "C:\Reports\East-Region-Q3-2026.csv" -Folder "Shared Documents/Sales/2026/Q3" -Values @{
Title = "East region sales, Q3 2026"
Department = "Sales" # choice: the exact choice text
ReportDate = "10/12/2026" # date: MM/DD/YYYY
Approved = $true # yes/no
Customer = "4" # lookup: ID of the item in the lookup list
Owner = "meganb@contoso.com" # person: email or site user ID
Region = "Contoso|Regions|East" # managed metadata: term path or term GUID
}
In my demo library I uploaded a CSV into a folder that didn’t exist yet and set Title, a Department choice and a ReportDate. Reading it back with Get-PnPFile -AsListItem confirms every value landed. If you aren’t sure of an internal name, adding list fields from Excel with PnP PowerShell shows how columns get their names.

When you upload hundreds of files, spot-check a few items in the library afterward to confirm the values match what you expected. For editing metadata on files already in the library, list item CRUD operations with PnP PowerShell covers Set-PnPListItem.
Upload multiple files from a local folder
For every file in one local folder, pipe Get-ChildItem -File into a loop. Since Add-PnPFile always replaces files with the same name, I check first with Get-PnPFile and skip anything that’s already in the library. Drop the check if you want re-runs to refresh every file.
$localFolder = "C:\Reports\October 2026"
$library = "Shared Documents/Monthly Reports"
Get-ChildItem -Path $localFolder -File | ForEach-Object {
$existing = Get-PnPFile -Url "$library/$($_.Name)" -AsListItem -ErrorAction SilentlyContinue
if ($existing) {
"Skipped (already there): $($_.Name)"
} else {
$null = Add-PnPFile -Path $_.FullName -Folder $library
"Uploaded: $($_.Name)"
}
}
The same pattern works for other bulk jobs. Adding bulk data from Excel to a SharePoint list uses a similar loop for list items instead of files.
Upload a folder with all its subfolders
Add-PnPFile doesn’t recurse on its own, but it does create missing folders. So the trick is to add -Recurse to Get-ChildItem and rebuild each file’s relative path under the target folder in SharePoint:
$localRoot = "C:\Reports"
$target = "Shared Documents/Imported 10-13-2026"
Get-ChildItem -Path $localRoot -File -Recurse | ForEach-Object {
$relative = $_.DirectoryName.Substring($localRoot.Length).Trim("\").Replace("\", "/")
$folder = if ($relative) { "$target/$relative" } else { $target }
$null = Add-PnPFile -Path $_.FullName -Folder $folder
"Uploaded {0} -> {1}" -f $_.Name, $folder
}

My test folder had eight files spread over four subfolders. The script recreated Contracts, Sales Reports, 2026, Q3 and Q4 in SharePoint, and Get-PnPFolderItem -Recursive counted all eight files. Empty local folders aren’t copied by this loop; create them with Resolve-PnPFolder if you need them.
For a large file share, run it in batches (one top-level folder at a time) so a network drop doesn’t send you back to the start. Once the files are in, checking the document library size is a quick way to confirm everything arrived.
Upload from a stream or create a file from text
You don’t always have a file on disk. With -Content, Add-PnPFile writes a text file straight into the library, and with -Stream it uploads anything you’ve got in memory, such as a report built by another script. Both need -FileName, and both replace an existing file with the same name.
# From text: creates or overwrites the file
Add-PnPFile -Folder "Shared Documents/Notes" -FileName "Status-10-13-2026.txt" `
-Content "All Q3 2026 reports uploaded."
# From a stream (for example, content you built in memory)
$bytes = [Text.Encoding]::UTF8.GetBytes("Region,Orders`nEast,412")
$stream = [IO.MemoryStream]::new($bytes)
Add-PnPFile -Folder "Shared Documents/Notes" -FileName "Orders.csv" -Stream $stream
Large files and SharePoint file limits
You don’t have to do anything special for big files. PnP PowerShell sends files up to 10 MB in one request and splits larger ones into 10 MB chunks automatically. SharePoint Online accepts files up to 250 GB.
For multi-GB files, though, a long script is fragile, so the OneDrive sync app or the SharePoint Migration Tool is usually the better choice.
File names are where uploads usually fail. SharePoint doesn’t allow these characters: " * : < > ? / \ |, or leading and trailing spaces. Names like CON, PRN, AUX, NUL, .lock and desktop.ini are blocked, as is _vti_ anywhere in a name and any name starting with ~$.
You also can’t create a folder called forms at the root of a library. Renaming files locally before the upload is easier than fixing them later, and clear names are one of the document library best practices worth keeping.
Check out, check in and publish while uploading
Some libraries require check-out or content approval. -Checkout checks out the existing file before replacing it and checks it in afterward; the check-in type defaults to a minor version. Add -Publish or -Approve so the new version is visible to readers:
Add-PnPFile -Path "C:\Policies\Travel-Policy-2026.docx" -Folder "Policies" `
-Checkout -CheckInComment "Updated per-diem rates for 2026" -CheckinType MajorCheckIn -Publish
Common errors and fixes
- Access denied: your account (or the app’s permissions) needs Edit or Contribute on the library. Reading isn’t enough.
- Folder path not found or files in an unexpected folder: you probably used the display name. Check the URL name with
(Get-PnPList "Documents").RootFolder.ServerRelativeUrl. - Column values are empty: a key in
-Valuesis a display name or the value is in the wrong format. Use internal names and MM/DD/YYYY dates. - Files were overwritten: Add-PnPFile replaces same-name files. Add the Get-PnPFile check from the multiple-files script.
- The file is locked or checked out: someone has it checked out. Use
-Checkoutor ask them to check it in.
You’ll find more ready-to-run examples in the PnP PowerShell scripts for SharePoint collection, and the reverse job is covered in downloading all files from a SharePoint library.
If you upload files to SharePoint from apps and flows as well, these guides help:
Frequently Asked Questions
How do I upload a file to SharePoint Online with PnP PowerShell?
Connect with Connect-PnPOnline, then run Add-PnPFile -Path with the local file and -Folder with the library’s URL name, such as Shared Documents. The Add-PnPFile reference lists every parameter.
Does Add-PnPFile overwrite existing files?
Yes. If a file with the same name is in the folder, Add-PnPFile replaces it, and SharePoint keeps the old one as a previous version when versioning is on. There’s no -Overwrite switch; check with Get-PnPFile first if you want to skip existing files.
How do I upload a whole folder with subfolders to SharePoint?
Loop through Get-ChildItem -File -Recurse, rebuild each file’s relative folder path under your target folder, and call Add-PnPFile for each file. Add-PnPFile creates any missing folders.
How do I set metadata when uploading with PnP PowerShell?
Pass a hashtable to -Values with the columns’ internal names, for example @{Department=”Sales”; ReportDate=”10/12/2026″}. Lookups take the item ID, person columns take an email, and managed metadata takes a term path or GUID.
What’s the largest file I can upload to SharePoint Online?
SharePoint Online accepts files up to 250 GB. PnP PowerShell uploads files over 10 MB in chunks automatically. Microsoft’s restrictions and limitations page also lists the blocked characters and file names.

Hey! I’m Bijay Kumar, founder of SPGuides.com and a Microsoft Business Applications MVP (Power Automate, Power Apps). I launched this site in 2020 because I truly enjoy working with SharePoint, Power Platform, and SharePoint Framework (SPFx), and wanted to share that passion through step-by-step tutorials, guides, and training videos. My mission is to help you learn these technologies so you can utilize SharePoint, enhance productivity, and potentially build business solutions along the way.