Go to Slack

Paginating through collections

Throughout the Slack platform you'll often encounter collections of things. Lists of users. Arrays of channels. A pride of lion emoji.

Very few of these collections are paginated today. Instead, you're likely to receive everything in the collection. Some collections are enormous and instead of receiving all of it, you'll receive it truncated instead.

Start paginating now

This new form of pagination will spread across the platform quickly and eventually become mandatory on some methods.

Please paginate along with us.

A unified cursor-based pagination system

A few Web API methods already support a rudimentary pagination pattern using a page parameter. This form of pagination works in humble scenarios but for larger collections like channel and user lists, navigating through a collection is made more scalable with a cursor-based approach.

Cursors, if you're not familiar, are like pointers. Pointers point at things, they reference a specific iota, a place in the list where your last request left off. A bookmark with breadcrumbs built in.

They help us avoid loading an entire set just to give you a slice.

Just the factoids

  • Paginated methods accept limit and cursor parameters.
  • The limit parameter sets a maximum number of results to return per call.
  • Provide sensible limit values. We recommend 100-200 results at a time.
  • The limit parameter maximum is 1000 and subject to change and may vary per method.
  • It's possible to receive less results than your specified limit, even when there are additional results to retrieve.
  • Paginated requests include a top-level response_metadata object including a next_cursor when additional results can be retrieved.
  • Present the cursor parameter on subsequent requests to navigate the collection.
  • An empty, null, or non-existent¬†next_cursor indicates no further results.

Walkthrough

When accessing the first virtual page of a paginated collection — for instance making a users.list request for the first time while using a limit parameter, you'll receive a response_metadata attribute containing a cursor for your next request.

Let's limit our list of users to 2 users per "page" as it will make it easier to test on a workspace with a low number of users.

GET slack.com/api/users.list?limit=2&token=xoxp-1234-5678-90123

In response we get our typical users.list response, but limited to 2 results. Skip down to the bottom to see the response_metadata, containing information on the next page of results.

{
    "ok": true,
    "members": [
        {
            "id": "USLACKBOT",
            "team_id": "T0G9PQBBK",
            "name": "slackbot",
            "deleted": false,
            "color": "757575",
            "real_name": "slackbot",
            "tz": null,
            "tz_label": "Pacific Daylight Time",
            "tz_offset": -25200,
            "profile": {
                "first_name": "slackbot",
                "last_name": "",
                "image_24": "https:\/\/a.slack-edge.com...png",
                "image_32": "https:\/\/a.slack-edge.com...png",
                "image_48": "https:\/\/a.slack-edge.com...png",
                "image_72": "https:\/\/a.slack-edge.com...png",
                "image_192": "https:\/\/a.slack-edge.com...png",
                "image_512": "https:\/\/a.slack-edge.com...png",
                "avatar_hash": "sv1444671949",
                "always_active": true,
                "real_name": "slackbot",
                "real_name_normalized": "slackbot",
                "fields": null
            },
            "is_admin": false,
            "is_owner": false,
            "is_primary_owner": false,
            "is_restricted": false,
            "is_ultra_restricted": false,
            "is_bot": false,
            "updated": 0
        },
        {
            "id": "W07QCRPA4",
            "team_id": "T0G9PQBBK",
            "name": "glinda",
            "deleted": false,
            "color": "9f69e7",
            "real_name": "Glinda Southgood",
            "tz": "America\/Los_Angeles",
            "tz_label": "Pacific Daylight Time",
            "tz_offset": -25200,
            "profile": {
                "avatar_hash": "8fbdd10b41c6",
                "image_24": "https:\/\/a.slack-edge.com...png",
                "image_32": "https:\/\/a.slack-edge.com...png",
                "image_48": "https:\/\/a.slack-edge.com...png",
                "image_72": "https:\/\/a.slack-edge.com...png",
                "image_192": "https:\/\/a.slack-edge.com...png",
                "image_512": "https:\/\/a.slack-edge.com...png",
                "image_1024": "https:\/\/a.slack-edge.com...png",
                "image_original": "https:\/\/a.slack-edge.com...png",
                "first_name": "Glinda",
                "last_name": "Southgood",
                "title": "Glinda the Good",
                "phone": "",
                "skype": "",
                "real_name": "Glinda Southgood",
                "real_name_normalized": "Glinda Southgood",
                "email": "glenda@south.oz.coven"
            },
            "is_admin": true,
            "is_owner": true,
            "is_primary_owner": true,
            "is_restricted": false,
            "is_ultra_restricted": false,
            "is_bot": false,
            "updated": 1480527098,
            "has_2fa": false
        }
    ],
    "cache_ts": 1498777272,
    "response_metadata": {
        "next_cursor": "dXNlcjpVMEc5V0ZYTlo="
    }
}

Within response_metadata you'll find next_cursor, an inscrutable string pointing at the next page of results. To retrieve the next page of results, provide this value as the cursor parameter to the paginated method.

Cursors typically end with the = character. When presenting this value as a URL or POST parameter, it must be encoded as %3D.

We issue our request for the next page of no more than 2 results like this:

GET slack.com/api/users.list?limit=2&cursor=dXNlcjpVMEc5V0ZYTlo%3D&token=xoxp-1234-5678-90123

And (spoiler alert): we only get one result back. This workspace actually only has three users. We've reached the end of our pagination journey and there are no more results to retrieve, so our next_cursor becomes but an empty string:

{
    "ok": true
    "members": [
        // that one last member
    ],
    "cache_ts": 1498777272,
    "response_metadata": {
        "next_cursor": ""
    }
}

There are no further results to retrieve when presented a next_cursor field that is contains an empty string (""). You're not even paginating at all if you receive no response_metadata or its next_cursor value.

Cursors can expire and are meant to be used within a reasonable amount of time. You should have no trouble pausing between rate limiting windows but do not persist cursors for hours or days.

Error conditions

Typically you won't run into error conditions specifically borne from pagination, except this one:

  • invalid_cursor - When navigating a paginated collection and providing a cursor value that just does not computer — whether it's gibberish, somehow encoded wrong, or of too great a vintage.

Invalid limit values are currently magically adjusted to something sensible. We recommend providing reasonable values for best results, as with most parameters.

Methods supporting cursor-based pagination

We plan to add this same pagination scheme to many of our collections, especially those that tend to yield a higher number of items.

Today, cursor-based pagination is supported by these methods:

Please note that when you are trying to page through the channels.list, the exclude_members argument won't work too well with pagination at this time. Channels with very large memberships and teams with many channels may cause the method to throw HTTP 500 errors. Please exclude memberships with exclude_members and look them up atomically with channels.info instead.

Classic pagination

You'll find a variety of other pseudo and real pagination schemes through a few other Web API methods.

Each of those API methods detail their pagination strategy.

Timeline methods

These methods are more positional than page oriented and allow you to navigate through time with oldest, latest, and a special inclusive parameter.

Traditional paging

These methods use some form of archaic numeric-based page and count or other limiting parameters.