Developer news
How-To: Paging with the Graph API and FQL

Often times, when you are getting data from the Graph API or FQL, we will only return a subset of the data. Just as you have to turn the pages of a book to read the whole book, you may need to “page” through the data returned to get more. With the Graph API, when there is more data available, you will notice that paging links are provided:

...
"paging": {
 "previous": 
   "https://graph.facebook.com/chickfila/posts?limit=5&since=1298995597",
 "next":
   "https://graph.facebook.com/chickfila/posts?limit=5&until=1293899704"
}

Here is a simple example that pages through posts on the Chick-fil-A Page:

<html>
<body onload="loadPosts()">
  <div id="fb-root"></div>
  <script>
    var graphURL = "https://graph.facebook.com/chickfila/posts?" +
                     "callback=processResult&limit=5";

    // Use JSONP to call the Graph API
    function loadPosts() {
      var script = document.createElement("script");
      script.src = graphURL;
      document.body.appendChild(script);
    }

    function processResult(posts) {
      if (posts.paging == undefined) {
        document.getElementById("loadMore").innerHTML =
          "No more results";
      }
      else {
        graphURL = posts.paging.next;
        for (var post in posts.data) {
          var message = document.createElement("div");
          message.innerHTML = posts.data[post].message;
          document.getElementById("content").appendChild(message);
        }
      }
    }
  </script>
  <div id="content"></div>
  <button id="loadMore" onclick="loadPosts()">Load more</button>
</body>
</html>

You might notice that the number of results returned is not always equal to the “limit” specified. This is expected behavior. Query parameters are applied on our end before checking to see if the results returned are visible to the viewer. Because of this, it is possible that you might get fewer results than expected.

This also means when you are manually constructing your own queries, you should be aware that with some tables and connections if you are specifying an “offset” parameter, the Nth result you are pointing to may not get returned in your results (like the 3rd result in step 2 of the image above). This can make paging difficult and confusing.

As such, when querying the following tables and connections, use time-based paging instead of “offset” to ensure you are getting back as many results as possible with each call. For these Graph API connections, use the “since” and “until” parameters (e.g. https://graph.facebook.com/chickfila/posts?until=1298508006):

checkins, feed, home, links, notes, photos, posts, statuses, tagged, videos

For these FQL tables, use a WHERE clause that paginates using their “timestamp”/”created_time” column (e.g. “SELECT message, created_time FROM stream WHERE source_id = 21543405100 AND created_time < 1299634732”):

checkin, status, stream

One way to tell if you should be using “since”/”until” vs. “offset” is to see which parameters we use in our own paging links in the Graph API (see first example). Here is that same example if we were to construct the paging links on our own:

<html>
<body onload="loadPosts()">
  <div id="fb-root"></div>
  <script>
    var graphURL = "https://graph.facebook.com/chickfila/posts?" +
                     "callback=processResult&" +
                     "date_format=U&" +
                     "limit=5";

    // Use JSONP to call the Graph API
    function loadPosts() {
      var script = document.createElement("script");
      script.src = graphURL;
      document.body.appendChild(script);
    }

    function processResult(posts) {
      if (posts.data.length == 0) {
        document.getElementById("loadMore").innerHTML =
          "No more results";
      }
      else {
        graphURL = graphURL + "&until=" +
          posts.data[posts.data.length-1].created_time;

        for (var post in posts.data) {
          var message = document.createElement("div");
          message.innerHTML = posts.data[post].message;
          document.getElementById("content").appendChild(message);
        }
      }
    }
  </script>
  <div id="content"></div>
  <button id="loadMore" onclick="loadPosts()">Load more</button>
</body>
</html>

One tricky issue is determining if you have reached the end of the result set. For example, if you specified a limit of “5” but the five posts returned are not visible to the viewer, you will get an empty result set. In the samples above, for simplicity we just assume that is the end of the results. However, you may want to add some logic of your own to check back further some arbitrary length of time with a larger limit to make sure it is in fact the end of the result set. In addition to the limits mentioned in the documentation for each of the tables and connections listed above, it is helpful to know that the maximum number of results we will fetch before running the visibility checks is 5,000.

We know the way results are returned is at times a bit unintuitive, but hope this post helps you better understand how to work with them effectively. Let us know below if you have any questions, comments, or future “how-to” articles you would like to see.

Jeff is excited Chick-fil-A is coming to the Bay Area soon!