SharePoint Online: Copy a Document Library to another Site using PowerShell

Requirement: Copy a document library to another SharePoint Online site using PowerShell.

sharepoint online copy document library to another site collection

How to copy a Document Library to another site in SharePoint Online?

Have you ever needed to copy a document library from one SharePoint Online site to another? Maybe you needed to move content from an old site to a new one, or create a “test” environment for development work. In this article, I’ll show you how to use PowerShell to copy a document library between two sites. Let’s get started!

Copy a Document Library in SharePoint Online

The latest document library creation feature introduced in SharePoint Online lets you copy an existing library from any existing site. Here is how it works:

  1. Login to the destination SharePoint site where the document library is to be copied
  2. Click on the Settings gear icon >> Click on “Site Contents”
  3. Click on the “New” button on the toolbar >> Choose “Document Library”
  4. Select the “From existing library” option on the Create new document library popup.
    copy document library sharepoint
  5. Select the site where the source library exists, and select the document library to copy. Click on the “Next” button at the bottom of the page.
    sharepoint online copy document library to another site
  6. Provide a name for your new library and, optionally, a description. Finally, click on the “Create” button to create the library.
    copy document library to another sharepoint site

This will copy the source list along with all its columns and settings (but not any contents of the library). Once created, You can use the “Copy to” option from the source library to copy its content to the target document library created.

Copy a Document Library to another site using Copy-PnPList cmdlet in PnP PowerShell

We can use the PnP PowerShell cmdlet Copy-PnPList to copy a document library to another site. Here is how:

#Variables
$SourceSiteURL = "https://crescent.sharepoint.com/sites/Operations"
$TargetSiteURL = "https://crescent.sharepoint.com/sites/Sales"
$LibraryName = "Invoices"

#Connect to Pnp Online
Connect-PnPOnline -Url $SourceSiteURL -Interactive

#Copy the document library to destination site
Copy-PnPList -Identity $LibraryName -DestinationWebUrl $TargetSiteURL -Title $LibraryName

Now, the next part: Copy all files and folders from the source to the destination document library!

#Parameters
$SourceSiteURL = "https://crescent.sharepoint.com/sites/Operations"
$SourceLibrarySiteRelativeUrl = "/Invoices"
$TargetLibraryServerRelativeUrl = "/sites/Migration/Invoices"

#Connect to the Source site
Connect-PnPOnline -Url $SourceSiteURL -Interactive

#Get All Files and Subfolders from Source Library's Root Folder 
$RootFolderItems = Get-PnPFolderItem -FolderSiteRelativeUrl $SourceLibrarySiteRelativeUrl | Where {($_.Name -ne "Forms") -and (-Not($_.Name.StartsWith("_")))}
         
#Copy Items to the Destination
$RootFolderItems | ForEach-Object {
    Copy-PnPFile -SourceUrl $_.ServerRelativeUrl -TargetUrl $TargetLibraryServerRelativeUrl -Force -OverwriteIfAlreadyExists
    Write-host "`tCopied '$($_.ServerRelativeUrl)'" -f Green    
}

The “Save List as Template” Method to Copy a Document Library

In classic sites, (or an alternative approach) you can use this method to copy a document library:

  1. Save the source document library as a template – Go to the source library >> Click on Settings gear >> Choose Library Settings >> Click on “Save as Template”. More info: How to Save List as Template in SharePoint Online?
  2. Download the List Template and Upload it to the Destination Site
  3. Create a new document library from the list template – Login to your destination SharePoint Online site >> Click on the Settings gear icon and click “Add an app”. You can use the pagination at the bottom of the page to find your custom list template, or the “Find an App” search box and pick your list template. More info: SharePoint Online: Create List from Custom Template using PowerShell
  4. Copy all items from the source document library to the new document library – Once you created a new instance of the document library from the existing list template, you can copy all files and folders from the source library to the destination library. Select all items in the source library, choose the Copy to button in the toolbar and then specify the document library created in step 1. 3.

Sounds like a lot of manual work? Well, If you need to copy a document library from one SharePoint Online site to another, PowerShell is the way to go. Let me show you how to use PowerShell to copy a document library, including all its contents, from one site to another.

Custom script must be enabled both in source and the destination sites, before running this script! How to Enable Custom Script in SharePoint Online?

SharePoint Online: Copy document library to another site collection using PowerShell

Use this PowerShell script to copy a document library with its files and folders to either the same or a different site.

#Function to Copy library to Another site
Function Copy-PnPLibrary
{
    param (
    [parameter(Mandatory=$true, ValueFromPipeline=$true)][string]$SourceSiteURL,
    [parameter(Mandatory=$true, ValueFromPipeline=$true)][string]$DestinationSiteURL,
    [parameter(Mandatory=$true, ValueFromPipeline=$true)][string]$SourceLibraryName,
    [parameter(Mandatory=$true, ValueFromPipeline=$true)][string]$DestinationLibraryName
    )
  
    Try {
    #Connect to the Source Site
    $SourceConn = Connect-PnPOnline -URL $SourceSiteURL -Interactive -ReturnConnection
    $SourceCtx = $SourceConn.Context
  
    #Get the Source library
    $SourceLibrary =  Get-PnPList -Identity $SourceLibraryName -Includes RootFolder -Connection $SourceConn
  
    #Get the List Template
    $SourceRootWeb = $SourceCtx.Site.RootWeb
    $SourceListTemplates = $SourceCtx.Site.GetCustomListTemplates($SourceRootWeb)
    $SourceCtx.Load($SourceRootWeb)
    $SourceCtx.Load($SourceListTemplates)
    $SourceCtx.ExecuteQuery()
    $SourceListTemplate = $SourceListTemplates | Where {$_.Name -eq $SourceLibrary.id.Guid}
    $SourceListTemplateURL = $SourceRootWeb.ServerRelativeUrl+"/_catalogs/lt/"+$SourceLibrary.id.Guid+".stp"
  
    #Remove the List template if exists    
    If($SourceListTemplate)
    {
        #Remove-PnPFile -ServerRelativeUrl $SourceListTemplateURL -Recycle -Force -Connection $SourceConn
        $SourceListTemplate = Get-PnPFile -Url $SourceListTemplateURL -Connection $SourceConn
        $SourceListTemplate.DeleteObject()
        $SourceCtx.ExecuteQuery()
    }
    Write-host "Creating List Template from Source Library..." -f Yellow -NoNewline
    $SourceLibrary.SaveAsTemplate($SourceLibrary.id.Guid, $SourceLibrary.id.Guid, [string]::Empty, $False)
    $SourceCtx.ExecuteQuery()
    Write-host "Done!" -f Green
  
    #Reload List Templates to Get Newly created List Template
    $SourceListTemplates = $SourceCtx.Site.GetCustomListTemplates($SourceRootWeb)
    $SourceCtx.Load($SourceListTemplates)
    $SourceCtx.ExecuteQuery()
    $SourceListTemplate = $SourceListTemplates | Where {$_.Name -eq $SourceLibrary.id.Guid}     
  
    #Connect to the Destination Site
    $DestinationConn = Connect-PnPOnline -URL $DestinationSiteURL -Interactive -ReturnConnection
    $DestinationCtx = $DestinationConn.Context
    $DestinationRootWeb = $DestinationCtx.Site.RootWeb
    $DestinationListTemplates = $DestinationCtx.Site.GetCustomListTemplates($DestinationRootWeb)
    $DestinationCtx.Load($DestinationRootWeb)
    $DestinationCtx.Load($DestinationListTemplates)
    $DestinationCtx.ExecuteQuery()    
    $DestinationListTemplate = $DestinationListTemplates | Where {$_.Name -eq $SourceLibrary.id.Guid}
    $DestinationListTemplateURL = $DestinationRootWeb.ServerRelativeUrl+"/_catalogs/lt/"+$SourceLibrary.id.Guid+".stp"
  
    #Remove the List template if exists    
    If($DestinationListTemplate)
    {
        #Remove-PnPFile -ServerRelativeUrl $DestinationListTemplateURL -Recycle -Force -Connection $DestinationConn
        $DestinationListTemplate = Get-PnPFile -Url $DestinationListTemplateURL -Connection $DestinationConn
        $DestinationListTemplate.DeleteObject()
        $DestinationCtx.ExecuteQuery()        
    }
  
    #Copy List Template from source to the destination site
    Write-host "Copying List Template from Source to Destination Site..." -f Yellow -NoNewline
    Copy-PnPFile -SourceUrl $SourceListTemplateURL -TargetUrl ($DestinationRootWeb.ServerRelativeUrl+"/_catalogs/lt") -Force -OverwriteIfAlreadyExists -Connection $SourceConn
    Write-host "Done!" -f Green
  
    #Reload List Templates to Get Newly created List Template
    $DestinationListTemplates = $DestinationCtx.Site.GetCustomListTemplates($DestinationRootWeb)
    $DestinationCtx.Load($DestinationListTemplates)
    $DestinationCtx.ExecuteQuery()
    $DestinationListTemplate = $DestinationListTemplates | Where {$_.Name -eq $SourceLibrary.id.Guid}
  
    #Create the destination library from the list template
    Write-host "Creating New Library in the Destination Site..." -f Yellow -NoNewline
    If(!(Get-PnPList -Identity $DestinationLibraryName -Connection $DestinationConn -ErrorAction SilentlyContinue))
    {
        #Create the destination library
        $ListCreation = New-Object Microsoft.SharePoint.Client.ListCreationInformation
        $ListCreation.Title = $DestinationLibraryName
        $ListCreation.ListTemplate = $DestinationListTemplate
        $DestinationList = $DestinationCtx.Web.Lists.Add($ListCreation)
        $DestinationCtx.ExecuteQuery()
        Write-host "Library '$DestinationLibraryName' created successfully!" -f Green
    }
    Else
    {
        Write-host "Library '$DestinationLibraryName' already exists!" -f Yellow
    }
 
    Write-host "Copying Files and Folders from the Source to Destination Site..." -f Yellow    
    $DestinationLibrary = Get-PnPList $DestinationLibraryName -Includes RootFolder -Connection $DestinationConn
    #Copy All Content from Source Library's Root Folder to the Destination Library
    If($SourceLibrary.ItemCount -gt 0)
    {
        #Get All Items from the Root Folder of the Library
        $global:counter = 0
        $ListItems = Get-PnPListItem -List $SourceLibraryName -Connection $SourceConn -PageSize 500 -Fields ID -ScriptBlock {Param($items) $global:counter += $items.Count; Write-Progress -PercentComplete `
            (($global:Counter / $SourceLibrary.ItemCount) * 100) -Activity "Getting Items from List" -Status "Getting Items $global:Counter of $($SourceLibrary.ItemCount)"}
       $RootFolderItems = $ListItems | Where { ($_.FieldValues.FileRef.Substring(0,$_.FieldValues.FileRef.LastIndexOf($_.FieldValues.FileLeafRef)-1)) -eq $SourceLibrary.RootFolder.ServerRelativeUrl}
        Write-Progress -Activity "Completed Getting Items from Library $($SourceLibrary.Title)" -Completed
         
        #Copy Items to the Destination
        $RootFolderItems | ForEach-Object {
            $DestinationURL = $DestinationLibrary.RootFolder.ServerRelativeUrl
            Copy-PnPFile -SourceUrl $_.FieldValues.FileRef -TargetUrl $DestinationLibrary.RootFolder.ServerRelativeUrl -Force -OverwriteIfAlreadyExists -Connection $SourceConn
            Write-host "`tCopied $($_.FileSystemObjectType) '$($_.FieldValues.FileRef)' Successfully!" -f Green     
        }
    }
  
    #Cleanup List Templates in source and destination sites
    $SourceListTemplate = Get-PnPFile -Url $SourceListTemplateURL -Connection $SourceConn
    $DestinationListTemplate = Get-PnPFile -Url $DestinationListTemplateURL -Connection $DestinationConn
    $SourceListTemplate.DeleteObject()
    $DestinationListTemplate.DeleteObject()
    $SourceCtx.ExecuteQuery()
    $DestinationCtx.ExecuteQuery()
    #Remove-PnPFile -ServerRelativeUrl $SourceListTemplateURL -Recycle -Force -Connection $SourceConn
    #Remove-PnPFile -ServerRelativeUrl $DestinationListTemplateURL -Recycle -Force -Connection $DestinationConn
    }
    Catch {
        write-host -f Red "Error:" $_.Exception.Message
    }
}
  
#Parameters
$SourceSiteURL = "https://crescent.sharepoint.com/sites/Retail"
$DestinationSiteURL = "https://crescent.sharepoint.com/sites/warehouse"
$SourceLibraryName = "Invoices"
$DestinationLibraryName = "Invoices V2"
  
#Call the function to copy document library to another site
Copy-PnPLibrary -SourceSiteURL $SourceSiteURL -DestinationSiteURL $DestinationSiteURL -SourceLibraryName $SourceLibraryName -DestinationLibraryName $DestinationLibraryName

You can also use this PowerShell method to move a document library to another site! Just add a step to delete the source document library (Which we call “Move”!). Ensure you have Site Collection administrator access to both the source and destination sites, and that the custom script is enabled before running this script!

Salaudeen Rajack

Salaudeen Rajack - Information Technology Expert with Two-decades of hands-on experience, specializing in SharePoint, PowerShell, Microsoft 365, and related products. He has held various positions including SharePoint Architect, Administrator, Developer and consultant, has helped many organizations to implement and optimize SharePoint solutions. Known for his deep technical expertise, He's passionate about sharing the knowledge and insights to help others, through the real-world articles!

38 thoughts on “SharePoint Online: Copy a Document Library to another Site using PowerShell

  • When i run the “SharePoint Online: Copy document library to another site collection using PowerShell”
    code i have the error : Creating List Template from Source Library…Error: Exception calling “ExecuteQuery” with “0” argument(s): “The operation has timed out.”
    My source SP site is a big one, ‎1.46‎ TB

    Reply
    • The copy operation doesn’t support more than 100GB of content in one go. You need to use migration tools or copy data in batches.

      Reply
  • Hello Salaudeen, thanks for the script!
    I ran the script and got the error below. Not sure how to proceed. Please help.
    Get-PnPFile : File Not Found.
    At line:31 char:31
    + … tTemplate = Get-PnPFile -Url $SourceListTemplateURL -Connection $Sour …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : WriteError: (:) [Get-PnPFile], ServerException
    + FullyQualifiedErrorId : EXCEPTION,PnP.PowerShell.Commands.Files.GetFile
    Error: You cannot call a method on a null-valued expression.

    Reply
    • Hi 0ndoa,

      I have worked out that this script works with modern sharepoint sites (typically with a URL like “https://something.sharepoint.com/sites/SiteA”) but fails on classic sites with URLS like “https://something.sharepoint.com/SiteA/SubsiteX”.
      It appears to be because classic sites have a template ID like STS#3 which the script cannot handle. – I don’t have much understanding of SP structures and how PnP handles them, so I’m blundering about trying to work through this. I think your error is because your $SourceListTemplateURL is null or invalid, because of this template issue.
      Can you confirm if you are using it with a classic or modern site? If classic, try it on a modern site and see how you go.
      Ish

      Reply
      • Hello Ish Rashad, thanks for your feedback.
        I’m using a classic site. So you’re probably right.
        Did you figure out a workaround for the site template ?

        Reply
      • Thanks v much for your reply, Salaudeen.
        It’s excellent. The Copy-PnPList scripts worked for me. I will need to put some wrapping around them to work through sites & libraries, but the core copy process is working, incuding from classic to modern, which was my major concern. (Doing a small client migration, but most of their sharepoint libraries are in classic subsites.)
        A question: Do you know of any way to save and apply permissions/versioning for libraries/folders/files? Any scripts or web pages that you can point me to?
        But for now, I really appreciate your assistance with this.
        Many thanks from Down Under! 🙂
        Cheers, Ish

        Reply
      • Hi Salaudeed, thanks for the update.
        I’m having some issues with this parameter $SourceListTemplateURL. Please can you check it out?

        Reply
      • Get-PnPFile : File Not Found.
        At line:32 char:31
        + … tTemplate = Get-PnPFile -Url $SourceListTemplateURL -Connection $Sour …
        + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        + CategoryInfo : WriteError: (:) [Get-PnPFile], ServerException
        + FullyQualifiedErrorId : EXCEPTION,PnP.PowerShell.Commands.Files.GetFile
        Error: You cannot call a method on a null-valued expression.

        Reply
  • I’ve been running this script successfully for copying a library between top-level sites, with URLs like “https://xxxx.sharepoint.com/sites/yyyy”.
    Today I tested on an old Sharepoint tenant with a doc library in a subsite of the form “https://xxxx.sharepoint.com/yyyy/zzzz.
    Getting lots of errors, current issue is an empty $SourceListTemplate.
    I’m not knowledgable enough on Sharepoint structure and PnP PS to know if the script is expected to run on sites like this (subsites, I guess).
    Can anyone confirm this for me please?
    Thanks

    Reply
  • Hi,
    Thanks for your amazing body of work across SharePoint. It has been a lifesaver to many like me, I’m sure.
    I am doing battle at the moment trying to get the script to run successfully, and have got over some of the early hurdles with site-collection admin perms, etc.
    When I run this script now, it bombs out at creating a new library, with error message:
    Creating New Library in the Destination Site…Error: List ‘Documents V2’ does not exist at site with URL ‘https://xxxxx.sharepoint.com/sites/xxxxx’.

    But… isn’t that the point? That the library should *not* exist in the destination site? So it can be created there?
    I’m no SP expert, so maybe my underatdning is flawed – would appreciate any suggestions/explanations please.
    Thanks!

    Reply
    • Updating, resolution to my problem:
      Cause: The test to see if the destination library exists throws an error, messing up the logic flow.
      Resolution: Needs parameter ” -ErrorAction Ignore ” added, as below:
      #Create the destination library from the list template
      Write-host “Creating New Library in the Destination Site…” -f Yellow -NoNewline
      If(!(Get-PnPList -Identity $DestinationLibraryName -ErrorAction Ignore -Connection $DestinationConn))

      Reply
  • the script copy metadata

    Reply
  • How about if we wanted to target a source folder using -FolderServerRelativeUrl eg “/sites/random/folder/In/direcory/”

    Reply
  • Great script!

    I have one problem when:

    #Copy List Template from source to the destination site
    Write-host “Copying List Template from Source to Destination Site…” -f Yellow -NoNewline
    Copy-PnPFile -SourceUrl $SourceListTemplateURL -TargetUrl ($DestinationRootWeb.ServerRelativeUrl+”/_catalogs/lt”) -Force -OverwriteIfAlreadyExists
    Write-host “Done!” -f Green

    I got this:

    PS C:\Users\user\Desktop> .\copyLibraryContent.ps1
    Creating List Template from Source Library…Done!
    Copying List Template from Source to Destination Site…Error: The current connection holds no SharePoint context. Please use one of the Connect-PnPOnline commands which uses the -Url argument to connect.
    PS C:\Users\user\Desktop>

    Reply
      • Worked like a charm Salaudeen, is there any way to keep the Created, Modified, Author and Editor values? I need to iterate through multiple source sites to copy the libraries to multiple target sites, any hint to iterate and store the credentials so they are not prompted for each site?

        Reply
  • After running this script, I received the error below:
    Error: Exception calling “ExecuteQuery” with “0” argument(s): “The underlying connection was closed: An unexpected error occurred on a receive.”

    HELP!

    Reply
  • This script does not preserve the metadata. Can you please tell how to do the same.

    Reply
  • Creating List Template from Source Library…Error: Exception calling “ExecuteQuery” with “0” argument(s): “Access is denied. (Exception fro
    m HRESULT: 0x80070005 (E_ACCESSDENIED))”

    Is there in the meantime a solution? I am a global admin and site collection owner on both source and destination sites (same tenant)

    kind regards,

    Reply
    • Check if custom script is enabled for the site!
      Set-PnPTenantSite -Identity “Site-URL” -DenyAddAndCustomizePages:$false

      Reply
  • any idea why I get this error? I am site collection admin on both and custom scripts have been enable.

    Creating List Template from Source Library…Done!
    Copying List Template from Source to Destination Site…Copy-PnPFile : The remote server returned an error: (404) Not Found.
    At line:69 char:5
    + Copy-PnPFile -SourceUrl $SourceListTemplateURL -TargetUrl ($Desti …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : WriteError: (:) [Copy-PnPFile], WebException
    + FullyQualifiedErrorId : EXCEPTION,PnP.PowerShell.Commands.Files.CopyFile
    Done!
    Creating New Library in the Destination Site…Error: Exception calling “ExecuteQuery” with “0” argument(s): “Invalid list template.”

    Reply
  • Got an error at start : Creating List Template from Source Library…Error: Exception lors de l’appel de « ExecuteQuery » avec « 0 » argument(s) : « Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)) »
    tried with 2 accounts all admin of the collection site, any workaround ?

    Reply
  • Do you have a script that copy document library to another Site tenant
    e.g
    Source: test1.Sharepoint.com\lib1
    Destination test2.Sharepoint.com\lib1

    Reply
  • When running, it stops and I’m getting the following error:

    #Call the function to copy document library to another site
    Copy-PnPLibrary -SourceSiteURL $SourceSiteURL -DestinationSiteURL $DestinationSiteURL -SourceLibraryName $SourceLibraryName -DestinationLibraryName $DestinationLibraryName
    Creating List Template from Source Library…Error: Exception calling “ExecuteQuery” with “0” argument(s): “Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))”

    The account running the script is a Global admin. Any idea what’s wrong?

    Reply
    • Global Admin doesn’t get access to all SharePoint Online sites automatically! You have to add the Account as Site Collection Admin to the sites individually.

      Reply
      • Yes, I’ve done that. Even used your script to do this on all sites in the tenant. 🙂

        Reply
    • I found the solution:
      Just add the following to the script underneath the connect-pnp
      Set-PnPSite -Identity $DestinationSiteURL -DenyAndAddCustomizePages $false
      do the same for the source but change $DestinationSiteURL into $SourceSiteURL

      Reply
      • tried that, I have site admin and custom scripts enabled but still getting the error
        Creating List Template from Source Library…Error: Exception calling “ExecuteQuery” with “0” argument(s): “Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))”

        Reply
        • Try adding your account as Site collection Admin to both source and Destination sites!

          Reply
  • Great script, thank you. Does this preserve metadata?

    Reply
  • I want to copy to a folder with the site name in the target document and fix the variable – but when I run the script it gives an error.

    $DestinationLibraryName = “Documents\COPY”

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *