Live training: next batches coming soon. Join the waitlist:Waitlists open:Developer Training →Developer →Copilot Studio Training →Copilot Studio →

Create, Read, Update and Delete SharePoint List Items with PnP PowerShell

A client’s operations team recently needed 300 task rows loaded into a SharePoint list, half of them updated a week later, and the finished ones cleaned out before quarter close. By hand, that’s an afternoon. With PnP PowerShell, it’s seconds.

Here’s how I create, read, update and delete SharePoint Online list items with PnP PowerShell. Every command below ran against a demo task list on my own Microsoft 365 tenant in PowerShell 7, and the screenshots show the real output.

Quick answer: Connect with Connect-PnPOnline, then use Add-PnPListItem -List "Tasks" -Values @{Title="New task"} to create, Get-PnPListItem -List "Tasks" -PageSize 500 to read, Set-PnPListItem -List "Tasks" -Identity 8 -Values @{TaskStatus="Completed"} to update, and Remove-PnPListItem -List "Tasks" -Identity 8 -Recycle -Force to delete. For many items, pass -Batch from New-PnPBatch and run Invoke-PnPBatch.

Before you start: connection and internal column names

You need PnP PowerShell 3.x in PowerShell 7.4 or later, plus your own Entra ID app registration for -ClientId. If that’s new to you, start with installing PnP PowerShell for SharePoint Online, then connect to the site that holds your list.

Also, -Values takes internal column names. In my demo list “Contoso Demo Tasks”, the Status column is TaskStatus and Due Date is DueDate. Run Get-PnPField -List "Contoso Demo Tasks" to look them up, or see how to get SharePoint list column details with PnP PowerShell.

Create list items with Add-PnPListItem

Add-PnPListItem takes the list name and a hashtable of column values, and returns the new item, so you can grab its ID right away. Below I add one task, then six more in a single batch:

Add-PnPListItem creating one SharePoint list item and six more with New-PnPBatch in PowerShell 7

Value formats that save trial and error:

  • Numbers and currency: pass them as strings, like "2500". Without a batch they use US notation; inside a batch they follow the site’s regional settings.
  • Dates: a string such as "11/16/2026" or "11/16/2026 14:30".
  • Person or group: the user’s email (or the site user ID). Lookup: the ID of the item in the source list.
  • Yes/No: $true or $false. Multi-choice: an array. Managed metadata: a term path like "CORPORATE|FINANCE" or the term GUID.
# Person column: email; lookup column: the ID of the item in the other list
Add-PnPListItem -List "Projects" -Values @{
    Title       = "Quarterly security review"
    AssignedTo  = "megan@contoso.com"
    Department  = 3
    Tags        = "Finance", "Audit"          # multi-choice column: an array
}

Use -Folder "2026/Q4" to drop the item into a list folder (PnP creates it if it’s missing), and -ContentType when the list has more than one. If your list doesn’t exist yet, create the SharePoint list and its columns with PnP PowerShell first.

Read list items with Get-PnPListItem

Get-PnPListItem with no ID returns every item. I add -PageSize so items come back in pages, and -Fields so SharePoint only sends the columns I need. Values live in the item’s indexer, so $_["TaskStatus"] reads the Status column:

Get-PnPListItem reading SharePoint list items with PageSize, Fields and a CAML query in PowerShell

SharePoint returns dates in UTC, hence .ToLocalTime(). For a single item, use Get-PnPListItem -List $list -Id 8, which returns all of its fields.

For server-side filtering, pass a CAML query with -Query. You can’t combine it with -Fields; put a <ViewFields> element inside the CAML instead. One more limit: if you ask for more than 12 lookup-type columns (lookup, person, managed metadata, Created By, Modified By) at once, you’ll hit the lookup column threshold error.

Update list items with Set-PnPListItem

Set-PnPListItem finds the item by -Identity (its ID) and changes only the columns you pass. The -UpdateType parameter decides what happens to the version history, and this run shows the difference on a list with versioning on:

Set-PnPListItem Update versus SystemUpdate showing the SharePoint item version staying at 2.0
UpdateTypeNew version?Modified / Modified ByTriggers Power Automate?
Update (default)YesUpdatedYes
SystemUpdateNo, changes go into the last versionNot updatedNo
UpdateOverwriteVersionNo, overwrites the last versionModified date updatedYes

I use SystemUpdate for data fixes so the Modified column and any Power Automate flows aren’t disturbed. Add -Force when you want to fire flows or webhooks even though no value changed.

A common request is “update the row if it exists, otherwise add it.” Add-PnPListItem always creates a new item, so look it up first:

$title = "Update vendor contracts"
$match = Get-PnPListItem -List $list -PageSize 500 | Where-Object { $_["Title"] -eq $title }

if ($match) {
    Set-PnPListItem -List $list -Identity $match[0].Id -Values @{ TaskStatus = "Completed" } | Out-Null
} else {
    Add-PnPListItem -List $list -Values @{ Title = $title; TaskStatus = "Not Started" } | Out-Null
}

Delete list items with Remove-PnPListItem

Remove-PnPListItem asks for confirmation unless you add -Force. Here’s the part older scripts get wrong: without -Recycle, the item is permanently deleted, with no way back from the recycle bin. I use -Recycle unless I’m sure:

Remove-PnPListItem with Recycle and Force deleting one item and a batch of completed SharePoint list items

-Recycle also works inside a batch, as shown above. To empty a whole list, see removing all items from a SharePoint list.

Work with hundreds or thousands of items

All three write cmdlets (Add, Set and Remove) accept -Batch. PnP queues the operations and sends them in chunks of 100 when you run Invoke-PnPBatch. That’s about 6 requests for 100 new items instead of roughly 600. Here’s a bulk status change:

$batch = New-PnPBatch
Get-PnPListItem -List $list -PageSize 500 |
    Where-Object { $_["TaskStatus"] -eq "Not Started" } |
    ForEach-Object { Set-PnPListItem -List $list -Identity $_.Id -Values @{ TaskStatus = "In Progress" } -Batch $batch }
Invoke-PnPBatch -Batch $batch -StopOnException

By default a batch keeps going after an error; -StopOnException stops at the first one. For a worked example at scale, see adding 5,000 items to a SharePoint list with PnP PowerShell, or bulk-loading rows from an Excel file.

Large lists need one more habit. SharePoint Online’s list view threshold is 5,000 items, so keep -PageSize well under it and filter CAML queries on indexed columns. My guide to the SharePoint list view threshold explains the indexing side.

To move items between lists instead, see how to copy items from one list to another using PnP PowerShell.

Common errors and fixes

  • “Column ‘Status’ does not exist”: you used the display name. Switch to the internal name (TaskStatus in my list).
  • Number or date saved wrong inside a batch: batched values follow the site’s regional settings. On a site that uses a comma as the decimal separator, "2500.50" needs to be "2500,50".
  • Add-PnPListItem is not recognized: you’re in Windows PowerShell 5.1 or the ISE. PnP PowerShell 3.x runs in PowerShell 7.4 or later.
  • Person column fails: the user must exist on the site. Use their email, or add them first with New-PnPUser.
  • The attempted operation is prohibited because it exceeds the list view threshold: add -PageSize, and index the column your CAML query filters on.

For more ready-to-use examples, see my PnP PowerShell scripts for SharePoint.

If you’re working with SharePoint lists from PowerShell, these guides pick up where this one ends:

Frequently Asked Questions

How do I add an item to a SharePoint list using PnP PowerShell?

Connect with Connect-PnPOnline, then run Add-PnPListItem -List “Tasks” -Values @{Title=”New task”}, using internal column names in the hashtable.

How do I update a SharePoint list item without creating a new version?

Run Set-PnPListItem with -UpdateType SystemUpdate. It keeps the version number and Modified By, and doesn’t trigger Power Automate flows. The Set-PnPListItem reference lists all three update types.

Does Remove-PnPListItem send items to the recycle bin?

Only with -Recycle. Without it, the item is permanently deleted. Add -Force to skip the confirmation prompt.

How do I get all items from a large SharePoint list with PnP PowerShell?

Use Get-PnPListItem -List “Tasks” -PageSize 500 so the items come back in pages below the 5,000-item list view threshold, and add -Fields to return only the columns you need.

How do I add or update many SharePoint list items quickly?

Create a batch with New-PnPBatch, pass -Batch to each Add, Set or Remove-PnPListItem call, then run Invoke-PnPBatch. See the PnP PowerShell batching article for details.