SharePoint REST API and Lists with Folders

Posted by: Rob Windsor on July 19, 2017

The SharePoint REST API support for folders is limited. The support that is there is tailored to work with documents in document libraries. Because of this I see lots of questions on how to use the REST API to perform operations on lists with folders. My suggestion is... don't. The Client Object Model has full support for folders so I strongly suggest that you use it instead.

Now, this would be a very short blog post if I ended with that advice. So, while I don't think it's a good practice, I realize some developers will insist on using the REST API. With that in mind I've prepared these examples demonstrating how to perform several common actions against lists with folders using the REST API. 

December 2018 update:
In the time since the original publishing of this blog post I've found alternative techniques to both add a folder and to add an item to a folder.  I've updated the blog post below accordingly. 

Demo list
The demo code shown in this post targets a custom list that has two columns: Title and Number. I've populated the list with some data. Here are some screen shots of the contents of the list.

Getting all list items or folders
Requesting the Items collection for the list will bring back all items and folders regardless of what folder they are in. You can use the file system object type (FSObjType) property in the filter to only get list items or to only get folders.

function getItemsOrFolders(fileSystemObjectType) {
    var message = jQuery("#message");
    message.text("Working on it...");

    var url = _spPageContextInfo.webAbsoluteUrl +
        "/_api/Web/Lists/getByTitle('FolderTest')/Items?" +
        "$select=*,FileDirRef&" +
        "$filter=FSObjType eq " + fileSystemObjectType;

    var call = jQuery.ajax({
        url: url,
        type: "GET",
        dataType: "json",
        headers: {
            Accept: "application/json;odata=nometadata"
        }
    });
    call.done(function (data, textStatus, jqXHR) {
        var fileOrFolderText = fileSystemObjectType ==
            SP.FileSystemObjectType.file ? "list items" : "folders";
        message.text("All " + fileOrFolderText + ":");
        message.append("<br>");
        jQuery.each(data.value, function (index, item) {
            message.append(String.format(
                "Title: {0};  Number: {1};  Id: {2}; Path: {3}",
                item["Title"], item["Number"], item["Id"], item["FileDirRef"])
            );
            message.append("<br>");
        });
    });
    call.fail(failHandler);
}

Screen capture of all list items

Getting all the items in a folder

There are a couple ways you can do this. You can get the Files collection for the folder and expand ListItemAllFields or you can use a CAML query. I'm an old school SharePoint developer so I'll go with the CAML query. Note that I'm setting the FolderServerRelativeUrl property in the query to restrict the results to the target folder. 

In the sample below I'm assuming that there are only items in the folder. If the folder had child folders I would need to filter them out if I didn't want to process them.

function getItemsInFolder() {
    var message = jQuery("#message");
    message.text("Working on it...");

    var url = _spPageContextInfo.webAbsoluteUrl +
        "/_api/Web/Lists/getByTitle('FolderTest')/getItems?" +
        "$select=*,FileDirRef";

    var call = jQuery.ajax({
        url: url,
        type: "POST",
        data: JSON.stringify({
            query: {
                ViewXml: "",
                FolderServerRelativeUrl: _spPageContextInfo.webAbsoluteUrl +
                    "/Lists/FolderTest/Folder 1"
            }
        }),
        headers: {
            Accept: "application/json;odata=nometadata",
            "Content-Type": "application/json;odata=nometadata",
            "X-RequestDigest": jQuery("#__REQUESTDIGEST").val()
        }
    });
    call.done(function (data, textStatus, jqXHR) {
        message.text("All list items in Folder 1:");
        message.append("<br>");
        jQuery.each(data.value, function (index, item) {
            message.append(String.format(
                "Title: {0};  Number: {1};  Id: {2}; Path: {3}",
                item["Title"], item["Number"], item["Id"], item["FileDirRef"]));
            message.append("<br>");
        });
    });
    call.fail(failHandler);
}

Screen capture of all list items in Folder 1 

Updating an item

This is a standard REST API update. If you know the Id of the item you can do the update in a single call. If you only know the value of a property of the item (e.g. the Title) then you need two calls: one call to find the item and get the Id and one call to do the update. The code sample below shows how to do the update using two calls. It adds 5 to the Number property for the item with the Title 'Item C'.

function updateItem() {
    var message = jQuery("#message");
    message.text("Working on it...");

    var url = _spPageContextInfo.webAbsoluteUrl +
        "/_api/Web/Lists/getByTitle('FolderTest')/Items?" +
        "$filter=(Title eq 'Item C')";

    var call = jQuery.ajax({
        url: url,
        type: "GET",
        dataType: "json",
        headers: {
            Accept: "application/json;odata=nometadata"
        }
    });
    call.done(function (data, textStatus, jqXHR) {
        var items = data.value;
        if (items.length > 0) {
            var item = items[0];
            updateItem(item);
        }
    });
    call.fail(failHandler);

    function updateItem(item) {
        var url = _spPageContextInfo.webAbsoluteUrl +
            "/_api/Web/Lists/getByTitle('FolderTest')/Items(" + item.Id + ")";

        var call = jQuery.ajax({
            url: url,
            type: "POST",
            data: JSON.stringify({
                Number: item["Number"] + 5
            }),
            headers: {
                Accept: "application/json;odata=nometadata",
                "Content-Type": "application/json;odata=nometadata",
                "X-RequestDigest": jQuery("#__REQUESTDIGEST").val(),
                "IF-MATCH": "*",
                "X-Http-Method": "PATCH"
            }
        });
        call.done(function (data, textStatus, jqXHR) {
            message.text("Number field value increased by 5 for Item C");
        });
        call.fail(failHandler);
    }
}

Adding an item to a folder

As far as I know knew at the time of the original publishing of this blog post there is was no way to add an item to a folder that is not the root folder using the REST API. The workaround is to add the item to the root folder and then move it to the child folder.

This is a three step process when using the REST API. The first call adds the item to the root folder. The second call gets the values of the FileLeafRef and FileRef properties of the item that was just added. These property values are needed for the third call which uses the moveTo operation to move the item to the desired folder. 

In the sample below we are adding an item with the Title 'Item Z' to the folder with the Title 'Folder 2'.

function addItemToFolder1() {
    function addItemToRootFolder() {
        var addItemUrl = _spPageContextInfo.webAbsoluteUrl +
            "/_api/Web/Lists/getByTitle('FolderTest')/Items";

        var addItemCall = jQuery.ajax({
            url: addItemUrl,
            type: "POST",
            data: JSON.stringify({
                Title: "Item Z",
                Number: 40
            }),
            headers: {
                Accept: "application/json;odata=nometadata",
                "Content-Type": "application/json;odata=nometadata",
                "X-RequestDigest": jQuery("#__REQUESTDIGEST").val()
            }
        });

        return addItemCall;
    }

    function getItemFileDetails(data) {
        var fileDetailsUrl = _spPageContextInfo.webAbsoluteUrl +
            "/_api/Web/Lists/getByTitle('FolderTest')/Items(" + data.Id + ")?" +
            "$select=FileLeafRef,FileRef";

        var fileDetailsCall = jQuery.ajax({
            url: fileDetailsUrl,
            type: "GET",
            dataType: "json",
            headers: {
                Accept: "application/json;odata=nometadata"
            }
        });

        return fileDetailsCall;
    }

    function moveItemToFolder(data) {
        var serverRelativeHostUrl = _spPageContextInfo.webAbsoluteUrl;
        var parts = serverRelativeHostUrl.replace("://", "").split("/");
        parts.shift();
        serverRelativeHostUrl = "/" + parts.join("/");

        var moveToUrl = serverRelativeHostUrl + "/Lists/FolderTest/Folder 2/" +
            data.FileLeafRef;
        var moveItemUrl = _spPageContextInfo.webAbsoluteUrl +
            "/_api/Web/getFileByServerRelativeURL('" + data.FileRef + "')/" +
            "moveTo(newurl='" + moveToUrl + "',flags=1)";

        var moveItemCall = jQuery.ajax({
            url: moveItemUrl,
            type: "POST",
            headers: {
                Accept: "application/json;odata=nometadata",
                "X-RequestDigest": jQuery("#__REQUESTDIGEST").val()
            }
        });

        return moveItemCall;
    }

    var message = jQuery("#message");
    message.text("Working on it...");

    var call = addItemToRootFolder()
        .then(getItemFileDetails)
        .then(moveItemToFolder);
    call.done(function (data, textStatus, jqXHR) {
        message.text("Item Z added to Folder 2");
    });
    call.fail(failHandler);
}

December 2018 Update:
Since the time this blog post was originally published Microsoft added the somewhat obscure AddValidateUpdateItemUsingPath method which is a cousin of the ValidateUpdateListItem method. Generally I’ve seen these methods used to update a list item or document without adding a new version of the item or document. However in this case we can use the method to add a new item.

The FolderPath.DecodedUrl property of the body contains the server relative URL to the target folder. The form values property is an array of objects which each have two properties, FieldName and FieldValue. The syntax is a little bit odd but it gets the job done. Finally, setting the bNewDocumentUpdate property to false indicates that we want to add a new item rather than updating an existing item.

Please note that this code will work in a SharePoint Online tenant but it may or may not work in a SharePoint on-premises farm.

function addItemToFolder2() {
    var call = jQuery.ajax({
        url: _spPageContextInfo.webAbsoluteUrl +
            "/_api/Web/Lists/GetByTitle('FolderTest')/" +
            "AddValidateUpdateItemUsingPath",
        type: "POST",
        data: JSON.stringify({
            "listItemCreateInfo": {
                "FolderPath": {
                    "DecodedUrl":
                        _spPageContextInfo.webServerRelativeUrl +
                        "/Lists/FolderTest/Folder 2"
                },
                "UnderlyingObjectType": 0
            },
            "formValues": [
                {
                    "FieldName": "Title",
                    "FieldValue": "Item Y"
                },
                {
                    "FieldName": "Number",
                    "FieldValue": "50"
                }
            ],
            "bNewDocumentUpdate": false
        }),
        dataType: "json",
        headers: {
            Accept: "application/json;odata=nometadata",
            "Content-Type": "application/json;odata=nometadata",
            "X-RequestDigest": jQuery("#__REQUESTDIGEST").val()
        }
    });
    call.done(function (data, textStatus, jqXHR) {
        var message = jQuery("#message");
        message.text("Item Y added to Folder 2");
    });
    call.fail(failHandler);
}

December 2018 Update:
There is a third option to add an item to a folder. One that is, in my humble opinion, cleaner than the other two options. You can use the SharePoint 2010 REST API. When using this option you use the Path folder of the message body to indicate the target folder.

function addItemToFolder3() {
    var call = jQuery.ajax({
        url: _spPageContextInfo.webAbsoluteUrl +
            "/_vti_bin/ListData.svc/FolderTest",
        type: "POST",
        data: JSON.stringify({
            Title: "Item X",
            Number: 60,
            Path: _spPageContextInfo.webServerRelativeUrl +
                "/Lists/FolderTest/Folder 2"
        }),
        dataType: "json",
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json"
        }
    });
    call.done(function (data, textStatus, jqXHR) {
        var message = jQuery("#message");
        message.text("Item X added to Folder 2");
    });
    call.fail(failHandler);
}

Adding a folder

When you add a folder in SharePoint using the Web UI it actually adds two things: the folder and a list item. It's the list item that shows up in the List View. Also, there are two names. The name of the list item and the name of the folder. You set the name of the list item by setting the Title field value and you set the name of the folder by setting the FileLeafRef field value. When you add a folder using the REST API, it just adds the folder. The workaround is to add the list item and set the content type to folder. This adds the list item and the folder. The problem is that trying to set the name of the folder on creation does not work. You need to create the list item and then set the name of the folder as a second step.

In the sample below we are creating a new folder with the Title 'Folder 99'. This folder will be a child of the root folder.

function addFolder1() {
    function addFolderInternal() {
        var addFolderUrl = _spPageContextInfo.webAbsoluteUrl +
            "/_api/Web/Lists/getByTitle('FolderTest')/Items";

        var addFolderCall = jQuery.ajax({
            url: addFolderUrl,
            type: "POST",
            data: JSON.stringify({
                Title: "Folder 99",
                FileLeafRef: "Folder 99",     // No effect here
                FileSystemObjectType: SP.FileSystemObjectType.folder,
                ContentTypeId: "0x0120"
            }),
            headers: {
                Accept: "application/json;odata=nometadata",
                "Content-Type": "application/json;odata=nometadata",
                "X-RequestDigest": jQuery("#__REQUESTDIGEST").val()
            }
        });

        return addFolderCall;
    }

    function renameFolder(data) {
        var renameFolderUrl = _spPageContextInfo.webAbsoluteUrl +
            "/_api/Web/Lists/getByTitle('FolderTest')/Items('" + data.Id + "')";

        var renameFolderCall = jQuery.ajax({
            url: renameFolderUrl,
            type: "POST",
            data: JSON.stringify({
                Title: "Folder 99",
                FileLeafRef: "Folder 99"
            }),
            headers: {
                Accept: "application/json;odata=nometadata",
                "Content-Type": "application/json;odata=nometadata",
                "X-RequestDigest": jQuery("#__REQUESTDIGEST").val(),
                "IF-MATCH": "*",
                "X-Http-Method": "PATCH"
            }
        });

        return renameFolderCall;
    }

    var message = jQuery("#message");
    message.text("Working on it...");

    var call = addFolderInternal().then(renameFolder);
    call.done(function (data, textStatus, jqXHR) {
        message.text("Folder 99 added");
    });
    call.fail(failHandler);
}

December 2018 Update:
Another option is to use the SharePoint 2010 REST API to add the folder. In the request the ContentTypeID property of the message body indicates that you want to create a folder and the Path property of the message body identifies the parent folder by its server relative URL.

function addFolder2() {
    var call = jQuery.ajax({
        url: _spPageContextInfo.webAbsoluteUrl +
            "/_vti_bin/ListData.svc/FolderTest",
        type: "POST",
        data: JSON.stringify({
            ContentTypeID: "0x0120",
            Title: "Folder 98",
            Path: _spPageContextInfo.webServerRelativeUrl + "/Lists/FolderTest"
        }),
        dataType: "json",
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json"
        }
    });
    call.done(function (data, textStatus, jqXHR) {
        var message = jQuery("#message");
        message.text("Folder 98 added");
    });
    call.fail(failHandler);
}

Topics: SharePoint, SharePoint REST API

    Recent Posts

    Categories