Moving from SPServices to REST, Part 4: Converting GetListItems to REST

Summary: Because of the significant inconsistencies in the SOAP Web Services, converting your calls to REST will be a different process for each of them. In this article, we’ll look at the real workhorse of SPServices: GetListItems.

When my mechanic works on my car, sometimes he tells me about some different tires or parts I might consider instead of what was on the car since I got it. It might be to improve performance or to give me a bit more life out of the car than I might get otherwise. (I have a good mechanic.)

Officina Meccanica by efilpera

Photo credit: Officina Meccanica by efilpera

As I’ve suggested in prior articles in the series, you can start moving individual calls from SOAP to REST anytime that you decide. If you’re still running an older version of SharePoint (no shame there), then it probably won’t be until you upgrade to SharePoint 2013. If you’re already on SharePoint 2010 or 2013, then you can start anytime.

When it comes time to converting some SOAP calls to REST calls, what will that process look like? In this article, we’re going to take a look at converting the workhorse of the SOAP Web Services: GetListItems.

GetListItems lets us read items from a list or library (virtually all content in SharePoint is stored in a list or library – a library is just a fancy list that is document-centric). Even basic information about users is stored in each Site Collection in the User Information List. GetListItems does a lot for us in SPServices-based development; it’s the most-commonly used function by just about every developer who uses SPServices and it sits underneath most of the value-added functions like SPCascadeDropdowns and SPDisplayRelatedItems.

The basic structure of the simplest GetListItems call is this:

  operation: "GetListItems",
  listName: "Tasks"

We make a call to SPServices, specify the GetListItems operation, and specify the list from which we want to get data. That’s about it. The results of this call will contain the items in the Tasks list (in the current site) as they are displayed in the Tasks list’s default view, which by default would be AllTasks.aspx.

Usually we’d have a bit more code wrapped around this call, maybe something like this:

var p = $().SPServices({
  operation: "GetListItems",
  listName: "Tasks"

p.done(function() {

  // Process the response


Note that I’m using a promise above so that I know when the call has completed rather than making a synchronous call. More on that in a later article.

So what does the analogous REST call look like? It would be something like this:

  url: _spPageContextInfo.webAbsoluteUrl +
  method: "GET",
  headers: {
    "Accept": "application/json; odata=verbose"
  success: function(data) {
    success(data); // If the call is successful, process the response
  error: function(data) {
    failure(data); // Do something with the error

Not so different when you get past the sides and trimming, right? The specifics of what we’re asking for in the REST call are all in the url object. The way REST works is that we pass information about what we want the server to do for us as parameters on the url. In this case, we have:

url: _spPageContextInfo.webAbsoluteUrl +

This means:

  • Go to the context of _spPageContextInfo.webAbsoluteUrl, which is the current site. In SPServices, I figure this out for you, or you can choose to specify a different site with the webURL parameter.
  • Call the lists service: /_api/web/lists
  • Select the Tasks list using its name: /getbytitle(‘Tasks’)
  • And finally, get the items: /items

The rest of the REST call is just packaging and what we want to do with the result.

Where the two methods start to diverge more is when we start to request things in more specific ways, for example all of the outstanding tasks for a particular user. But there’s an imperfect isomorphism here. For virtually everything we can do with the SOAP Web Services, we can do the same thing in REST just by adding the analogous parameters to the REST call.

Here’s how some of the most common parameters in the request payload map from SOAP to REST:

SOAP Option(SPServices synonym) REST Comments
ViewFields(CAMLViewFields) $select Choose the columns you would like to retrieve. With both SOAP and REST we get some data we don’t explicitly request, but by specifying only the columns we need we can reduce the payload sizes.
Query(CAMLQuery) $filter, $orderby Specify which items in the list we would like to return and how we would like them sorted.
RowLimit(CAMLRowLimit) $limit Say how many items matching the Query we would like to receive. In SOAP we can specify 0 to get all matching items; in REST we can omit the parameter to get all the matching items. Otherwise, we can specify any integer as the limit.
ViewName(CAMLViewName) NA ViewName lets you choose the view you would like to get in the response. There’s no REST equivalent here. I’ve always discouraged using this option in SOAP because it’s too easy to change the view settings and cause unintended consequences.
QueryOptions (CAMLQueryOptions) NA In SOAP, this lets us specify some options that change how the data is returned to us. For example, we can ask for all of the attachment URLs rather than just a Boolean which tells us that there are attachments.
NA $expand This option in REST has no direct equivalent in SOAP. $expand allows us to indicate that we would like the values for a relationship – rather than just the indices – using a projection. This is important with Lookup columns and Person or Group columns.


As you can see, it’s really not that scary. It’s the equivalent of translating Spanish into French, but even easier since all of the words are in English (sorry rest of the world – I’m going to play American on this one).

Here’s a slightly more complicated example where I’m asking for items from a Workflow Tasks list.

First, the SOAP request:


And then the equivalent REST request:

REST request for items from a Workflow Tasks list

Here’s how the two calls break down based on the table I showed above:

CAMLViewFields <Viewfields><FieldRef Name=’ID’/><FieldRef Name=’Title’/><FieldRef Name=’AssignedTo’/><FieldRef Name=’RelatedItems’/>

<FieldRef Name=’Priority’/>

<FieldRef Name=’Status’/>

<FieldRef Name=’DueDate’/>

<FieldRef Name=’Author’/>


$select $select=ID, Title, AssignedTo/Id, AssignedTo/Title, RelatedItems, Priority, Status, DueDate, Author/Id, Author/Title
CAMLQuery <OrderBy><FieldRef Name=’Created’ Ascending=’False’/></OrderBy> $filter, $orderby $orderby=Created Desc
NA $expand $expand=AssignedTo,Author

Note that in the SOAP call, I get the ID and Title (name) of each person or group column automatically when I request the column value. If I needed any more information about those people, I’d have to read it from another source, like the User Information List or User Profile. I also can specify a CAMLQueryOption that returns more of the basic information about users, like email address, SIP, etc.:

CAMLQueryOptions: "<QueryOptions><ExpandUserField>True</ExpandUserField></QueryOptions>"

In SOAP, most options that we pass go in the request payload. With REST, some of the options – for instance, what format we’d like the response to take – go in the request header.

If we don’t specify an accept parameter in the header, then we get XML in the response. If you’re used to parsing XML, then that might make you happy, but we usually want JSON these days.

To get JSON, we specify:

accept: application/json; odata=verbose

On premises in SharePoint 2013, this is the only option at the moment. In SharePoint Online (Office 365), if we don’t want all the metadata that comes along with the verbose option, we have a couple other options.

accept: application/json; odata=minimalmetadata
accept: application/json; odata=nometadata

The best way to understand the differences between these options is to try them out. When payload size matters (and it usually does), you should think about receiving less metadata to reduce the size of the responses.


It’s the nuances that will trip us up. In SOAP, we can’t join lists together; we have to pull the data from each list and do the “join” on the client side. In REST we have what are called projections – using $expand – that let us do some lookups into other lists, for example to get information from a lookup list.

But back to GetListItems. Here are some examples of REST calls to get data back from a Tasks list showing some common parameter settings.

Get all tasks assigned to me

/_api/web/lists/getbytitle('Development Tasks')/items?$expand=AssignedTo&amp;$select=Title,AssignedTo/Title&amp;$filter=AssignedTo/Title eq 'Anderson, Marc'

Note that the format of your username will vary based on how you have things set up. Using an account may be a better choice, but I wanted to make the example clear.

Get all tasks that have not been completed, showing the status and the % complete

/_api/web/lists/getbytitle('Development Tasks')/items?$expand=AssignedTo&amp;$select=Title,AssignedTo/Title,Status,PercentComplete&amp;$filter=Status ne 'Completed'

Getting the data in these various ways is of course just the first step. Once you have the data, you’ll have to process it in some way in order to display it to the user, do calculations on it or whatever you’re trying to accomplish. That part of the exercise isn’t in the scope of this article, so you’ll have to look elsewhere for tricks for those pieces.

In the next article, we’ll look at how JavaScript promises come into play with SOAP and REST. I hope that you’ve been using promises with SPServices calls since I added support for them in version 2013.01, but if you haven’t, moving to REST is an excellent time to start doing so.

This article was first published on IT Unity on July 23, 2015. Visit the post there to read additional comments.

Retrieving Expanded Calendar Events with REST vs. SOAP

There is very little now in 2013 for which you need SPservices [sic]

from /r/sharepoint

It wouldn’t surprise you to hear that my answer to this is “it depends”. SPServices is nice in that it works the same way across SharePoint versions from 2007-2013, which might be enough reason to use it. But there are still cases where the SOAP services do something that REST doesn’t [yet] provide.

The other day I ran across an example of this. I wanted to pull events from a SharePoint calendar from today forward and I wanted to expand all of the recurring events. Since I was working on SharePoint 2013, I immediately tried to make this work using a REST call.

The first hurdle I faced was that it wasn’t possible to filter on a data like StartDate in a REST query. I know that sounds crazy, but if it’s possible I couldn’t find any way to do it.

The other challenge was the expansion of recurring events. It turned out that this was only possible using SOAP. REST doesn’t know anything about SharePoint calendar events, especially recurrence or all-day events.

I ended going back to an old thread in the SPServices discussions started by Jim Bob Howard (@jbhoward) which gives the CAML to make this work. The code I ended up with is below. In this case, I was using KnockoutJS to populate a customized view of multiple SharePoint calendars – the “rollup” idea. We’ve the various calendar locations stored in a list, and when we get to this code we have an array of calendars to work with.

// Calendar rollup
var calendars = [];
var events = [];
var calendarPromises = [];

// For each calendar, go and get the events
for (var i = 0; i < calendars.length; i++) {

  var thisCalendar = calendars[i];
  var today = moment().format();

  calendarPromises[i] = $().SPServices.SPGetListItemsJson({
      listName : thisCalendar.title,
      webURL : thisCalendar.listUrl,
      CAMLViewFields : "<ViewFields>" +
			  "<FieldRef Name='ID' />" +
			  "<FieldRef Name='Title' />" +
			  "<FieldRef Name='EventDate' />" +
			  "<FieldRef Name='EndDate' />" +
			  "<FieldRef Name='Location' />" +
			  "<FieldRef Name='Description' />" +
			  "<FieldRef Name='Category' />" +
			  "<FieldRef Name='fRecurrence' />" +
			  "<FieldRef Name='RecurrenceData' />" +
			  "<FieldRef Name='fAllDayEvent' />" +
      CAMLQuery : "<Query>" +
			  "<Where>" +
				  "<And>" +
					  "<DateRangesOverlap>" +
						  "<FieldRef Name='EventDate' />" +
						  "<FieldRef Name='EndDate' />" +
						  "<FieldRef Name='RecurrenceID' />" +
							  "<Value Type='DateTime'>" +
							  "<Year />" +
						  "</Value>" +
					  "</DateRangesOverlap>" +
					  "<Or>" +
						  "<Eq>" +
							  "<FieldRef Name='HomePageHide' />" +
							  "<Value Type='Bool'>False</Value>" +
						  "</Eq>" +
						  "<IsNull>" +
							  "<FieldRef Name='HomePageHide' />" +
						  "</IsNull>" +
					  "</Or>" +
				  "</And>" +
			  "</Where>" +
			  "<OrderBy>" +
				  "<FieldRef Name='EventDate' />" +
			  "</OrderBy>" +
      CAMLQueryOptions : "<QueryOptions>" +
			  "<CalendarDate>" + today + "</CalendarDate>" +
			  "<ExpandRecurrence>TRUE</ExpandRecurrence>" +
			  "<RecurrenceOrderBy>TRUE</RecurrenceOrderBy>" +
			  "<ViewAttributes Scope='RecursiveAll'/>" +
      mappingOverrides : {
        "ows_fAllDayEvent" : {
          "mappedName" : "fAllDayEvent",
          "objectType" : "Boolean"
        "ows_fRecurrence" : {
          "mappedName" : "fRecurrence",
          "objectType" : "Boolean"


$.when.apply($, calendarPromises).then(function () {

  var calendarEvents = this;

  // For each calendar, go and get the items and push them into an array
  for (var i = 0; i < calendars.length; i++) {

    $(calendarEvents[i].data).each(function () {
        calendar : calendars[i].title,
        id : this.ID,
        title : this.Title,
        location : this.Location != null ? this.Location : "",
        category : this.Category,
        eventDate : moment(this.EventDate),
        endDate : this.EndDate,
        fAllDayEvent : this.fAllDayEvent,
        fRecurrence : this.fRecurrence,
        RecurrenceData : this.RecurrenceData


  events.sort(function (a, b) {
    return a.eventDate.isAfter(b.eventDate) ? 1 : (a.eventDate.isBefore(b.eventDate) ? -1 : 0);

  ko.applyBindings(new calendarViewModel(), document.getElementById("calendar-rollup"));


The code is written to handle a variable number of calendars (we started with four). Because I’m putting the promise for each call to GetListItems in the calendarPromises array, I can use

$.when.apply($, calendarPromises)

to ensure that all of the promises have been fulfilled before processing the results.

I’m also using the fantastic MomentJS library to work with dates and times. Don’t ever try to do that manually again.

I’d love to hear that I don’t need to use SOAP for this anymore, but this seems to be a case where SOAP still wins out. Maybe there’s a place for SPServices in 2013, after all.

SPTechCon Developer Days Follow Up

SPTechCon Dev DaysI got lucky on the scheduling for SPTechCon Developer Days and did both of my sessions on the first day. So today I get to attend sessions and just learn, learn, learn!

As is true of every conference the BZMedia folks put on, this one is well run and packed with excellent sessions. If you’d like to stay on top of what’s happening in the SharePoint world, sign up for their weekly SPTechReport newsletter.

Since this is a conference focused on developers, both my sessions were developer-focused. If you attended either session and have questions or feedback, feel free to get in touch.

Simple Expand/Collapse for the Quick Launch in SharePoint 2013 or SharePoint Online

Yesterday I helped my pal Marcel Meth (@marcelmeth) with a pretty simple little task, but everything’s simple if you know how to do it! We needed to add simple accordion-like expand/collapse functionality to the Quick Launch on the pages in a Site Collection. Because there were going to be a lot of links in the Quick Launch (mostly links to different views of lists), it was getting pretty cumbersome to wade through. By adding this expand/collapse capability, we could improve the user experience (UX). It’s little stuff like this that makes SharePoint usable. Never underestimate the power of a small tweak like this.

This code works on a Team Site on SharePoint Online in Office365 with its Quick Launch exposed in the normal way. Any other customization you have in place may interfere with this working. (Yadda, yadda, disclaimer, YMMV, etc.) I’m sure it will work for you – possibly with some tweaks – in any SharePoint 2013 or SharePoint Online site. You can add this script to a single page or to the master page if you’d like it to work on all pages in the Site Collection.

// Find all the top level links in the Quick Launch that have children
var topLevelLinks = $("div[id$='QuickLaunchMenu'] > ul > li:has('ul') > a");

// Prepend the little "twiddle" icon to each top level link
topLevelLinks.prepend("<span class='ms-commentexpand-iconouter ql-icon'><img alt='expand' src='/Comm/Comms2/_themes/1/spcommon-B35BB0A9.themedpng?ctag=2' class='ms-commentexpand-icon'></span>");

// We're starting with all of the sections collapsed. If you want them expanded, comment this out.
topLevelLinks.closest("li").find("> ul").hide();

// Set up for the click even of on the top level links {

  // We're going to stop the default behavior

  // Find the elements we need to work with
  var childUl = $(this).closest("li").find("> ul");
  var isVisible =":visible")

  // If the section is visible, hide it, and vice versa
  if(isVisible) {  

    // Replace the icon with its antitheses
    $(this).find(".ql-icon").replaceWith("<span class='ms-commentexpand-iconouter ql-icon'><img alt='expand' src='/Comm/Comms2/_themes/1/spcommon-B35BB0A9.themedpng?ctag=2' class='ms-commentexpand-icon'></span>");
    // Hide the child links by sliding up. Note: You could change the effect here.

  } else {

    // Replace the icon with its antitheses
    $(this).find(".ql-icon").replaceWith("<span class='ms-commentcollapse-iconouter ql-icon'><img alt='expand' src='/Comm/Comms2/_themes/1/spcommon-B35BB0A9.themedpng?ctag=2' class='ms-commentcollapse-icon'></span>");
    // Show the child links by sliding down. Note: You could change the effect here.



The Medium Has the Message and My Blog Is Trying to Catch Up

I’ve really been digging the content I’ve been finding through Medium for the last few months or so. The Web is ever-evolving and to me Medium represents a new paradigm for Web publishing and content consumption. It’s sort of blogging combined with content crowd-sourcing combined with highlighting like I have on my Kindle combined with… Well, it’s unique, so it’s not really just the sum of those parts.

To me, one of the best parts of Medium is the Staff Picks and Top Stories. These two categories give me solid content to read that is either vetted by the platform owners or popular with the platform readers. One of the cool things about this is that it cuts across all topic areas. I’m as likely to want to read one of Eric Elliott’s (@_ericelliott) excellent posts about JavaScript and its communities like this or this or something like When the heck did learning to code become cool? And why it sucks to be a beginner today… which are squarely in my own wheelhouse to interesting takes on Web design like Optical Adjustment – Logic vs. Designers.

But what makes it even more fun is the stuff I run across that to many people would seem sort of random. None of us is so one-dimensional that we cannot enjoy reading something completely new to us. Medium ensures that when we go off on one of those mental tangents, that it’s a higher quality one.

When I spot one of these seemingly random posts, I can see about how long it might take me to read it, so it’s clear going in if how much time I’ll need to invest. (I’m a pretty fast reader, so for me the number is usually an upper bound – YMMV.) Those times must be built up magically based on how long people really spend and the posts and seem pretty accurate to me.

How many minutes?

The biggest thing I notice as I read posts on Medium is that I feel a need to up my own game, to write more pithy and valuable posts. Ever since I started blogging, I’ve had a little voice in my head that writes posts that come out of ordinary, every day experiences. These aren’t the “here’s a piece of code to fix a particular problem” posts, but often the softer stuff that makes the fact that the code exists have more purpose. It might have nothing to do with my professional life at all. (Maybe it’s hard to believe, but I do have a few other interests besides SharePoint.) I wish I actually spent the time to get more of those posts written.

I found an article that shows how to make your own blog a bit more “Medium-like” and I’ve added some new plugins to my blog here based on those suggestions. (See WordPressium: Creating a Medium-like Experience)

As of today, you can do a few new things on my blog…

Selection Sharer

Courtesy of Hans van Gent’s Selection Sharer plugin, you can now highlight any piece of text in one of my posts and send it as a Tweet, post it to LinkedIn, or send it in an email. (This assumes I ever say anything quotable, which I know is a huge assumption.) I would expect that he’ll add some more sharing options over time.

Reading Time

The Reading Time plugin adds some info on how long a post might take to read. You’ll see the estimate based on an average reading speed at the top of each post as you visit it. As you stay on the page, a little progress bar shows how much of that time has elapsed. Of course, reading a post full of code is different than just reading prose, so time will tell how useful this is. It can at least be a little game; can you beat the progress bar?

I hope that you find these two new capabilities useful. Let me know what you think!