SharePoint Online: Default New, Edit, and Display List Forms are missing

Summary

In SharePoint Online (SPO), the default new item, edit item, and display item forms for a SharePoint list may be missing, blank, deleted, or null. This can cause a number of symptoms, such as the following:

  • When trying to create list items the following error occurs:
    yourDomain.sharepoint.com redirected you too many times.
    ERR_TOO_MANY_REDIRECTS
  • The link created by the “Copy Link” feature does not work.
  • The list-level search feature does not return any results.
  • When trying to create a new item in a list:
    Unable to find the default new form for list (listName)
  • When trying to edit an existing item in the list:
    Unable to find the default edit form for list (listName)
  • When trying to view an existing item in the list:
    Unable to find the default display form for list (listName)

Cause

This can happen if the default new item form (NewForm.aspx), edit form (EditForm.aspx), or display form (dispform.aspx) have been deleted. Admins can do this mistakenly using PowerShell or SharePoint Designer, or sometimes it can occur as part of a site migration. This results in a missing / blank / empty / null default form values for the list.

You can verify this problem for your list by using REST.

Call the Lists REST API for the problem list to return the default form values by entering a sting like the following in a browser. For example, list name “SP1”:

https://contoso.sharepoint.com/sites/Team2/_api/web/lists/getbytitle('SP1')?$select=ID,DefaultDisplayFormUrl,DefaultEditFormUrl,DefaultNewFormUrl

In this example, DefaultDisplayFormUrl, DefaultEditFormUrl, and DefaultNewFormUrl are all Null:

They should be set to DispForm.aspx, EditForm.aspx, and NewForm.aspx, respectively. For comparison, here’s a “good” list named “L7”.

Resolution

To resolve this issue, the default forms for the problem list must be recreated. This can be done with a PowerShell script.

Enable Custom Script for the Site


In order to create new forms in a list or library (which is what the “Create-DefaultListForms-Interactive.ps1” PowerShell script does), Custom Script must be enabled for the site, which means the DenyAddAndCustomizePages site property must be set to 0.
Example:

# Set Parameters
$AdminSiteURL="https://contoso-admin.sharepoint.com/"
$SiteURL="https://contoso.sharepoint.com/sites/Team2"
# Connect to SharePoint Online Admin Center
Connect-SPOService -Url $AdminSiteURL 
# Enable Custom Script
Set-SPOSite -Identity $SiteURL -DenyAddAndCustomizePages 0

Important! Failure to complete this step will result in an Access Denied error when you run the “Create-DefaultListForms-Interactive.ps1” script, even for a site collection administrator.

Run the Create-DefaultListForms-Interactive.ps1 Script

The following “Create-DefaultListForms-Interactive.ps1” script is an updated version of the script found at Create-DefaultListForms.ps1. It has been enhanced to allow the use of an Entra ID application registration (ClientID) to connect to the SharePoint site, and to allows you to supply a single site URL and list name to fix a single list.

The script can either take a single site URL and list name (-SiteURL and -ListName parameters), or if you use the -CSVPath parameter, it can fix multiple sites and lists at a time. It does that by reading the site URL and list name from a CSV file. You can find a sample CSV here: Sample CSV, or just create your own. It should look something like this:

Create-DefaultListForms-Interactive.ps1

### Create-DefaultListForms-Interactive.ps1 ###


<#
 Microsoft provides programming examples for illustration only, without warranty either expressed or
 implied, including, but not limited to, the implied warranties of merchantability and/or fitness
 for a particular purpose. We grant You a nonexclusive, royalty-free right to use and modify the
 Sample Code and to reproduce and distribute the object code form of the Sample Code, provided that
 You agree: (i) to not use Our name, logo, or trademarks to market Your software product in which the
 Sample Code is embedded; (ii) to include a valid copyright notice on Your software product in which
 the Sample Code is embedded; and (iii) to indemnify, hold harmless, and defend Us and Our suppliers
 from and against any claims or lawsuits, including attorneys' fees, that arise or result from the
 use or distribution of the Sample Code.

 This sample assumes that you are familiar with the programming language being demonstrated and the
 tools used to create and debug procedures. Microsoft support professionals can help explain the
 functionality of a particular procedure, but they will not modify these examples to provide added
 functionality or construct procedures to meet your specific needs. if you have limited programming
 experience, you may want to contact a Microsoft Certified Partner or the Microsoft fee-based consulting
 line at (800) 936-5200.
#>

<#
  .SYNOPSIS
  Script to create default list forms (New, Edit, Display) if they are missing.
  Will also re-add missing Form Webpart to existing list forms if they are missing.

 .DESCRIPTION
  Script to create default list forms (New, Edit, Display) if they are missing.
  Will also re-add missing Form Webpart to existing list forms if they are missing.
  Supply SiteURL and ListName to process a single list, or CSVPath to process multiple lists.
  These input modes cannot be combined. If neither mode is specified, prompts for
  SiteURL and ListName. There is no default CSV path.
  CSV must have siteUrl and listTitle columns with nonblank values in each row.

  Tested with List Templates: 100, 101, 102, 103, 104, 105, 106, 107, 108

  Requires PowerShell 7 compatible with the installed PnP.PowerShell module.
  If PnP.PowerShell is missing, the script will attempt to install it.
  Connections always use interactive authentication with the specified client ID.
  The client ID must identify an Entra ID application configured for PnP interactive
  authentication with appropriate SharePoint delegated permissions and consent.

  .PARAMETER CSVPath
  Specify an explicit path to a CSV file containing siteUrl and listTitle fields.
  Cannot be combined with SiteURL or ListName.

  .PARAMETER SiteURL
  Site URL for a single list. Required with ListName when CSVPath is not supplied.
  Prompts for this value if omitted in single-list mode.

  .PARAMETER ListName
  List name to process at SiteURL. Required with SiteURL when CSVPath is not supplied.
  Prompts for this value if omitted in single-list mode.

  .PARAMETER ClientId
  Required Entra ID application client ID used for interactive authentication.
  You must supply your own client ID; there is no default value.

 .EXAMPLE
  .\Create-DefaultListForms-Interactive.ps1 -ClientId "00000000-0000-0000-0000-000000000001"

  Prompts for SiteURL and ListName, then creates list forms using interactive login.

 .EXAMPLE
  .\Create-DefaultListForms-Interactive.ps1 -CSVPath C:\temp\targetlists.csv -ClientId "00000000-0000-0000-0000-000000000001"

  Creates list forms for rows in the specified CSV using interactive login and your supplied client ID.

 .EXAMPLE
  .\Create-DefaultListForms-Interactive.ps1 -SiteURL "https://contoso.sharepoint.com/sites/team" -ListName "Tasks" -ClientId "00000000-0000-0000-0000-000000000001"

  Creates list forms for the Tasks list at the specified site using interactive login.
#>

[CmdletBinding(DefaultParameterSetName="SingleList")]
param(
    [parameter(Mandatory=$true, ParameterSetName="CSV")]
    [ValidateNotNullOrEmpty()]
    [string]$CSVPath,
    [parameter(Mandatory=$true, ParameterSetName="SingleList")]
    [ValidateNotNullOrEmpty()]
    [ValidatePattern('\S')]
    [string]$SiteURL,
    [parameter(Mandatory=$true, ParameterSetName="SingleList")]
    [ValidateNotNullOrEmpty()]
    [ValidatePattern('\S')]
    [string]$ListName,
    [parameter(Mandatory=$true)]
    [ValidateNotNullOrEmpty()]
    [guid]$ClientId
    )

#############################################

if ($PSCmdlet.ParameterSetName -eq "SingleList") {
    $targets = @([pscustomobject]@{
        siteUrl = $SiteURL
        listTitle = $ListName
    })
}
else {
    $targets = @(Import-Csv -LiteralPath $CSVPath -ErrorAction Stop)
    if ($targets.Count -eq 0) {
        throw "CSV at path '$CSVPath' must contain at least one row with siteUrl and listTitle columns."
    }

    for ($index = 0; $index -lt $targets.Count; $index++) {
        if ([string]::IsNullOrWhiteSpace($targets[$index].siteUrl) -or
            [string]::IsNullOrWhiteSpace($targets[$index].listTitle)) {
            throw "CSV at path '$CSVPath' has missing or blank siteUrl or listTitle values in data row $($index + 1)."
        }
    }
}

#############################################

$module = Get-Module PnP.PowerShell -ListAvailable
if ($null -eq $module) {
    Write-Output "Installing PowerShell Module: PnP.PowerShell"
    Install-Module PnP.PowerShell -Force -AllowClobber -Confirm:$false -ErrorAction Stop
}
Import-Module PnP.PowerShell -ErrorAction Stop

#############################################

$webpartTemplate = @"
<WebPart xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.microsoft.com/WebPart/v2">
    <Assembly>Microsoft.SharePoint, Version=16.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
    <TypeName>Microsoft.SharePoint.WebPartPages.ListFormWebPart</TypeName>
    <ListName xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">{{{LIST_ID}}}</ListName>
    <ListId xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">{{LIST_ID}}</ListId>
    <PageType xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">{{PAGE_TYPE}}</PageType>
    <FormType xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">{{FORM_TYPE}}</FormType>
    <ControlMode xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">{{CONTROL_MODE}}</ControlMode>
    <ViewFlag xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">1048576</ViewFlag>
    <ViewFlags xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">Default</ViewFlags>
    <ListItemId xmlns="http://schemas.microsoft.com/WebPart/v2/ListForm">0</ListItemId>
</WebPart>
"@

Function Create-DefaultListForm
{
    param(
        [parameter(Mandatory=$true)]$List,
        [parameter(Mandatory=$true)][string]$FormUrl,
        [parameter(Mandatory=$true)][ValidateSet("Display", "Edit", "New")]$FormType
    )

    begin { }
    process
    {

        $webpartXml = $webpartTemplate -replace "{{LIST_ID}}", $List.Id.ToString()

        switch ($FormType)
        {
            "Display" {
                $webpartXml = $webpartXml -replace "{{PAGE_TYPE}}", "PAGE_DISPLAYFORM"
                $webpartXml = $webpartXml -replace "{{FORM_TYPE}}", "4"
                $webpartXml = $webpartXml -replace "{{CONTROL_MODE}}", "Display"
                break;
            }
            "Edit" {
                $webpartXml = $webpartXml -replace "{{PAGE_TYPE}}", "PAGE_EDITFORM"
                $webpartXml = $webpartXml -replace "{{FORM_TYPE}}", "6"
                $webpartXml = $webpartXml -replace "{{CONTROL_MODE}}", "Edit"
                break;
            }
            "New" {
                $webpartXml = $webpartXml -replace "{{PAGE_TYPE}}", "PAGE_NEWFORM"
                $webpartXml = $webpartXml -replace "{{FORM_TYPE}}", "8"
                $webpartXml = $webpartXml -replace "{{CONTROL_MODE}}", "New"
                break;
            }
        }

        try
        {
            #Check if form page already exists
            $listPages = Get-PnPProperty -ClientObject $List.RootFolder -Property Files
            $formPage = $listPages | Where-Object { $_.ServerRelativeUrl.ToLower() -eq $FormUrl.ToLower() }

            if ($null -eq $formPage) {
                Write-Output "  [Creating Form Page] $FormUrl"

                #Create Form
                $formPage = $List.RootFolder.Files.AddTemplateFile($FormUrl, [Microsoft.SharePoint.Client.TemplateFileType]::FormPage)
            }
            else {
                #Form page exists, check if form is recognized by list (i.e. form page has a form webpart on it)
                $listForms = Get-PnPProperty -ClientObject $List -Property Forms

                if ($null -ne $listForms -and $listForms.Count -gt 0) {
                    $existingForm = $list.Forms | Where-Object { $_.ServerRelativeUrl.ToLower() -eq $FormUrl.ToLower() }
                    if ($null -ne $existingForm) {
                        Write-Warning "  [Form Already Exists] $FormUrl"
                        return;
                    }
                }
            }

            Write-Output "  [Adding Form Webpart] $FormUrl"
            #Get Webpart Manager for Form
            $wpm = $formPage.GetLimitedWebPartManager([Microsoft.SharePoint.Client.WebParts.PersonalizationScope]::Shared)

            #Import Webpart on page
            $wp = $wpm.ImportWebPart($webpartXml)

            #Add webpart to Form
            $wpm.AddWebPart($wp.WebPart, "Main", 1) | Out-Null

            #Execute changes
            $List.Context.ExecuteQuery()
        }
        catch
        {
            Write-Error "Error creating form $FormType at $FormUrl. Error: $($_.Exception)"
        }
    }
    end { }
}

#############################################

foreach ($row in $targets)
{
    Write-Output "[$($row.siteUrl)] [$($row.listTitle)]"

    Connect-PnPOnline -Url $row.siteUrl -Interactive -ClientId $ClientId.ToString() -ErrorAction Stop
    $list = Get-PnPList $row.listTitle
    $listUrl = $list.RootFolder.ServerRelativeUrl

    # Handle Document Library Types
    if ($list.BaseType -eq [Microsoft.SharePoint.Client.BaseType]::DocumentLibrary) {
        Write-Host " > Processing Library: $listUrl"
        Create-DefaultListForm -List $list -FormUrl "$listUrl/Forms/Upload.aspx"   -FormType New
        Create-DefaultListForm -List $list -FormUrl "$listUrl/Forms/DispForm.aspx" -FormType Display
        Create-DefaultListForm -List $list -FormUrl "$listUrl/Forms/EditForm.aspx" -FormType Edit
    }

    # Handle Generic List Types
    else {
        Write-Host " > Processing List: $listUrl"
        Create-DefaultListForm -List $list -FormUrl "$listUrl/NewForm.aspx"  -FormType New
        Create-DefaultListForm -List $list -FormUrl "$listUrl/DispForm.aspx" -FormType Display
        Create-DefaultListForm -List $list -FormUrl "$listUrl/EditForm.aspx" -FormType Edit
    }
}


Add a Comment