Our IT team tracks every laptop, monitor and headset in an Excel workbook, and they asked me to move it into a SharePoint list. Pasting rows into grid view works once, but not when the import repeats every month.
In this tutorial, we’ll add items from an Excel to SharePoint list using PowerShell: read the workbook with ImportExcel, create a matching list, add the rows with Add-PnPListItem, switch to a batch, report bad rows and prove the count matches.
Here’s what we’re building: the SPGDemo Equipment list, filled from the workbook with text, choice, currency, date, yes/no and person columns.

Before You Start
You need PowerShell 7.4 or later, PnP PowerShell with your own Entra ID app and edit rights on the site. New to PnP? Start with installing PnP PowerShell for SharePoint Online.
The workbook has one sheet, Equipment, with 20 rows. The keys we pass to Add-PnPListItem are the internal names:
| Excel header | Internal name | Type |
|---|---|---|
| Asset Name | Title | Single line of text |
| Asset Tag | AssetTag | Single line of text (unique key) |
| Category | Category | Choice |
| Purchase Price | PurchasePrice | Currency |
| Purchase Date | PurchaseDate | Date only |
| Under Warranty | UnderWarranty | Yes/No |
| Assigned To | AssignedTo | Person (email, e.g. miller@spguides.onmicrosoft.com) |
Step 1: Install ImportExcel and Read the Workbook
Goal: get the rows into PowerShell without Excel installed.
- Open PowerShell 7.
- Install the module once with
-Scope CurrentUser(no admin rights needed). - Read the sheet with Import-Excel.
Install-Module -Name ImportExcel -Scope CurrentUser # one time, no admin rights
$rows = Import-Excel -Path "C:\SPGuidesDemo\SPGDemo Equipment.xlsx" -WorksheetName "Equipment"
$rows | Select-Object -First 8 | Format-Table -AutoSize

Checkpoint: Rows found: 20, one object per row. Purchase Date shows 46034 instead of a date: that’s the Excel serial number for 01/12/2026, fixed in Step 3.
Step 2: Connect and Create the SharePoint List
Goal: a list whose columns match the workbook. Using an existing list? Skip this and look up its internal names with SharePoint list column details in PnP PowerShell.
$siteUrl = "https://spguides.sharepoint.com/sites/SPGuides"
$clientId = "00000000-0000-0000-0000-000000000000" # your Entra ID app's client ID
Connect-PnPOnline -Url $siteUrl -ClientId $clientId -Interactive
$listName = "SPGDemo Equipment"
New-PnPList -Title $listName -Template GenericList -OnQuickLaunch | Out-Null
# Title becomes "Asset Name"; the other columns match the Excel headers
Set-PnPField -List $listName -Identity "Title" -Values @{ Title = "Asset Name" }
Add-PnPField -List $listName -DisplayName "Asset Tag" -InternalName "AssetTag" -Type Text | Out-Null
Add-PnPField -List $listName -DisplayName "Category" -InternalName "Category" -Type Choice `
-Choices "Laptop", "Monitor", "Docking Station", "Phone", "Headset" | Out-Null
Add-PnPField -List $listName -DisplayName "Purchase Price" -InternalName "PurchasePrice" -Type Currency | Out-Null
Add-PnPFieldFromXml -List $listName -FieldXml '<Field Type="DateTime" DisplayName="Purchase Date" Name="PurchaseDate" Format="DateOnly" />' | Out-Null
Add-PnPField -List $listName -DisplayName "Under Warranty" -InternalName "UnderWarranty" -Type Boolean | Out-Null
Add-PnPField -List $listName -DisplayName "Assigned To" -InternalName "AssignedTo" -Type User | Out-Null
# show the new columns in the default view
Set-PnPView -List $listName -Identity "All Items" `
-Fields "LinkTitle", "AssetTag", "Category", "PurchasePrice", "PurchaseDate", "UnderWarranty", "AssignedTo" | Out-Null
Add-PnPFieldFromXml sets the date-only format in one line. To build columns from the headers automatically, see adding SharePoint list fields from Excel.

Checkpoint: Get-PnPField lists seven columns with types Text, Text, Choice, Currency, DateTime, Boolean and User.
Step 3: Add the Excel Rows With Add-PnPListItem
Goal: one item per row, with failures reported, not fatal.
- Read the sheet with
-AsDate "Purchase Date"so dates arrive as DateTime values. - Load the Category field’s real Choices so typos never reach the list.
- Wrap Add-PnPListItem in
try/catchand collect skipped rows.
$listName = "SPGDemo Equipment"
$path = "C:\SPGuidesDemo\SPGDemo Equipment.xlsx"
$rows = Import-Excel -Path $path -WorksheetName "Equipment" -AsDate "Purchase Date"
$choices = (Get-PnPField -List $listName -Identity "Category").Choices
$added = 0; $skipped = @()
$timer = [Diagnostics.Stopwatch]::StartNew()
foreach ($row in $rows) {
$tag = $row.'Asset Tag'
# validate before SharePoint sees the row
if ($row.'Purchase Date' -isnot [datetime]) { $skipped += "$tag : bad date '$($row.'Purchase Date')'"; continue }
if ($row.Category -notin $choices) { $skipped += "$tag : unknown category '$($row.Category)'"; continue }
try {
Add-PnPListItem -List $listName -Values @{
Title = $row.'Asset Name'
AssetTag = $tag
Category = $row.Category
PurchasePrice = [double]$row.'Purchase Price'
PurchaseDate = $row.'Purchase Date'
UnderWarranty = [bool]$row.'Under Warranty'
AssignedTo = $row.'Assigned To'
} -ErrorAction Stop | Out-Null
$added++
}
catch { $skipped += "$tag : $($_.Exception.Message)" }
}
"Rows in Excel : $($rows.Count)"
"Items added : $added"
"Rows skipped : $($skipped.Count)"
$skipped | ForEach-Object { " - $_" }
"Time taken : {0:N1} seconds" -f $timer.Elapsed.TotalSeconds
The person column takes an email address, Yes/No takes $true/$false, and outside a batch, numbers use US notation.

Checkpoint: Items added: 17, Rows skipped: 3 in 14.7 seconds. EQ-1013 says Monitors, EQ-1014 says TBD, and EQ-1015’s misspelled email failed with The specified user … could not be found.
For lookup and multi-choice values, see adding SharePoint list items with PnP PowerShell.
Step 4: Bulk Upload Faster With New-PnPBatch
Goal: one batch and safe reruns. I fixed the three bad rows and added five purchases (25 rows now).
- Read the existing AssetTag values and skip those rows.
- Create a batch with New-PnPBatch and pass
-Batch $batch. - Send it with Invoke-PnPBatch.
# same $listName, $path, $rows and $choices as in Step 3, plus the tags already in the list
$existing = Get-PnPListItem -List $listName -PageSize 500 -Fields "AssetTag" | ForEach-Object { $_["AssetTag"] }
$batch = New-PnPBatch
$queued = 0; $already = 0
foreach ($row in $rows) {
if ($row.'Asset Tag' -in $existing) { $already++; continue }
if ($row.'Purchase Date' -isnot [datetime] -or $row.Category -notin $choices) { continue }
Add-PnPListItem -List $listName -Batch $batch -Values @{
Title = $row.'Asset Name'; AssetTag = $row.'Asset Tag'; Category = $row.Category
PurchasePrice = [double]$row.'Purchase Price'; PurchaseDate = $row.'Purchase Date'
UnderWarranty = [bool]$row.'Under Warranty'; AssignedTo = $row.'Assigned To'
}
$queued++
}
Invoke-PnPBatch -Batch $batch
"Already in the list: $already Added in one batch: $queued"

Checkpoint: Already in the list: 17, Added in one batch: 8, Time taken: 2.0 seconds.
Invoke-PnPBatch runs the whole batch and lists errors at the end; -StopOnException stops at the first. Inside a batch, numbers use the site’s regional separators. For thousands of rows, read adding more than 5000 items to a SharePoint list.
Test the Import
Compare the Excel row count with the list’s item count:
$excelRows = (Import-Excel -Path $path -WorksheetName "Equipment").Count
$listItems = (Get-PnPList -Identity $listName).ItemCount
"Excel rows: $excelRows List items: $listItems Match: $($excelRows -eq $listItems)"
Both are 25. EQ-1014 now shows 04/06/2026, prices read as currency like $1,199.99, and the person column resolved to real users. To change rows later, use list item CRUD with PnP PowerShell.
No ImportExcel? Save the Sheet as CSV
Can’t install modules? Save the sheet as CSV UTF-8 and use Import-Csv, converting the text values yourself:
$rows = Import-Csv -Path "C:\SPGuidesDemo\SPGDemo Equipment.csv" # every value is text
$date = [datetime]::ParseExact($row.'Purchase Date', "MM/dd/yyyy", [cultureinfo]"en-US")
Make It Production-Ready
- Unique key: the AssetTag check makes reruns safe; index it as the list grows.
- Unattended runs: use certificate-based app-only sign-in instead of
-Interactive. - Test on a copy: keep deleting all items from a SharePoint list handy for a clean retry.
Troubleshooting
- Dates show as 46034: add
-AsDateor convert with[datetime]::FromOADate(). - The specified user could not be found: fix the email and rerun; existing rows are skipped.
- Column does not exist: use the internal name (
PurchaseDate), not the display name. - Connect-PnPOnline not recognized: see fixing Connect-PnPOnline is not recognized.
Where to Take It Next
- No code: create a SharePoint list from Excel using Power Automate.
- Reverse trip: export SharePoint list items to Excel for reports.
- More scripts: the SharePoint PowerShell tutorials hub.
A few more guides that pair well with an Excel-to-SharePoint import:
Frequently Asked Questions
How do I import Excel data into a SharePoint list using PnP PowerShell?
Read the sheet with Import-Excel, connect with Connect-PnPOnline, then call Add-PnPListItem for each row with internal column names as keys. The Add-PnPListItem reference shows each column’s value format.
How do I read an Excel file in PowerShell without Excel installed?
Install the ImportExcel module and run Import-Excel -Path file.xlsx; you get one object per row.
What is the fastest way to bulk upload to a SharePoint list from Excel?
Queue rows with Add-PnPListItem -Batch and send them with Invoke-PnPBatch. In my test, 8 rows took 2 seconds in one batch versus 14.7 seconds for 17 rows one by one. See New-PnPBatch.
Can Microsoft Lists import from Excel without PowerShell?
Yes, Microsoft Lists can create a new list from an Excel table, but it can’t add validated rows to an existing list.
Why does Import-Excel return numbers instead of dates?
Excel stores dates as serial numbers. Add -AsDate with the column name to get DateTime values.

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.