Since ordering of /sites is not guaranteed, how do i effectively order sites in a user-friendly way?

General requirements:

  1. Trilogy must stay on the top of the list

    [stackoverflow.com,meta.stackoverflow.com,serverfault.com,meta.serverfault.com,superuser.com,meta.superuser.com, ...]

  2. StackApps should stay on the bottom of the list or just below the Trilogy.

    [...., stackapps] or [..meta.superuser.com, stackapps, ...]

  3. New beta Stack Exchange sites must be grouped with their meta site.

    [.., webapps.stackexchange.com, meta.webapps.stackexchange.com, gaming.stackexchange.com, meta.stackexchange.com,..]

  4. Official Stack Exchange sites (out of beta) should compare below the Trilogy.

any ideas, thoughts, hints?

  • Take a look at what StackMobile does. (Except that it considers StackApps part of the trilogy.) I can't share code with you but if your interested, I might share pseudo-code. Let me know. Commented Jul 24, 2010 at 16:42
  • @George feel free to share what you like to share, this dev-tip might be helpful for other developer someday. Commented Jul 24, 2010 at 20:07
  • There. Added. Commented Jul 24, 2010 at 20:17

4 Answers 4


Here is my take on kevin's sort using Soapi.JS

  • get a list of sites from stackauth /sites
  • get the community user from each site to get creation date
  • attach each meta to it's parent
  • sort by date
  • group by 'state' or type.
  • render grouped or as a single list.

Live Demo HERE

p.s. - i will be tightening this code up and merging it into Soapi.JS for those that use it.

alt text http://img121.imageshack.us/img121/451/pantheon.png

This is the money shot

// get a list of sites from stackauth
Soapi.RouteFactory("", apiKey).Sites()
.getResponse(function(sitesResponse) {

    // count down so we know when we are done
    var count = sitesResponse.items.length;

    // determine the increment of the progress bar for each operation
    progressStep = document.getElementById("progressOuter").clientWidth / count;

    function checkCount() {

        if (--count == 0) {
            // all sites reported in

            // perform meta assocations

            while (sitesResponse.items.length > 0) {

                // take the site of the bottom
                var item = sitesResponse.items.shift();

                // if it is not a meta site, add it to the site list
                // cannot depend on linked_meta right not
                if (item.api_endpoint.toLowerCase().indexOf(".meta.") == -1) {
                else {
                    // check the site list to see if the parent has been seen yet
                    var parent = sites.tryGetParentByEndpoint(item.api_endpoint);
                    if (parent) {
                        // attach the meta to the parent
                        parent.linked_meta = item;
                    else {
                        // parent not seen yet, stick it back on top

            // do sorting and arrangement

            // now we have a list of sites similar to this
            // stackoverflow.com
            //    meta.stackoverflow.com
            // stackapps.com
            // gaming.stackexchange.com
            //    meta.gaming.stackexchange.com

            // lets sort by date
            sites = sites.sort(function(a, b) {
                return a.creation_date - b.creation_date;

            // and segregate the sites by state as we 
            sites.normal = [];
            sites.open_beta = [];
            sites.closed_beta = [];
            sites.linked_meta = [];

            // empty the array
            var tmp = sites.splice(0, sites.length);

            // and push them in order of appearance to maintain date sort
            // into both the parent array and the group arrays
            while (tmp.length > 0) {
                var site = tmp.shift();
                if (site.linked_meta) {

            // return results

    // get community user from each site to get creation date

    for (var i = 0; i < sitesResponse.items.length; i++) {

        var site = sitesResponse.items[i];

        Soapi.RouteFactory(site.api_endpoint, apiKey)
        .UsersById(-1).getResponse(function(usersResponse, context) {

            // add the date to the site so we can sort
            context.creation_date = usersResponse.items[0].creation_date;
        }, function(error, context) {

            // probably timed out. set now as creation date
            // attach the error
            context.creation_date = new Date();
            context.error = error.message;
            // still need to decrement count so we don't get stuck
        }, 10000, site);
        // we want to give a reasonable timeout value because
        // we are doing a bunch of stuff here. it should not 
        // take this long, just being safe

  • 1
    +1 for integrating this into the library layer at least in order to prevent anybody reinventing the wheel here - I really hope issues like this will get 'sorted' at the API level soon, i.e. we don't have to wait for the currently pretty distant 2.0 API to get some of those easy and mostly non breaking improvements available cross client ... Commented Jul 25, 2010 at 15:09

[thinking out loud]

Well, the sites we want to pin should be a constant and, as you describe, are.

  1. I would have 3 arrays, head, body and tail.
  2. I would statically populate head and tail with the 'pinned' sites.
  3. I would sort the response from /sites, keyed by reversed api_endpoint in descending order


  1. I would then iterate the sorted results, discard those found in head and tail and add the rest to body.

    // head
    api.stackoverflow.com - Stack Overflow 
    api.meta.stackoverflow.com - Stack Overflow Meta
    // body
    api.gaming.stackexchange.com - Gamers
    api.meta.gaming.stackexchange.com - Gamers Meta
    api.food.stackexchange.com - Foodies
    api.meta.food.stackexchange.com - Foodies Meta
    // tail
    api.stackapps.com - Stack Apps


I am not suggesting that you store all of the data for your 'pinned' or 'constantly positioned' sites, just the endpoint as a key, which is not likely to change, and populate any volitile data from the /sites and/or /stats query.

  • 1
    +1 thanks for your time poet; i read this hints in a comment somewhere and i thought that it could\should be shared in a dev-tip for reference. Yep, i just need to order my endpoint list on get_supported_services() method github.com/systempuntoout/stackprinter/blob/master/app/utility/…. Anyway, i really don't understand why /sites can't be ordered on API side (and i'm not talking about a custom tailored app ordering but an order with a common sense and not with the Meta Super User and Meta Server Fault at random spot) Commented Jul 24, 2010 at 19:41

Try this:

  1. Split off meta sites (using linked_meta status)
  2. Order by age
  3. Move StackApps to the bottom (as its a special case)
  4. Re-insert meta sites below their linked site (as determined by address)

Of course, you should cache this aggressively. In particular, the site age can be cached indefinitely which will save you #(site) requests on each recalc.

  • 1
    youtube.com/watch?v=dCud8H7z7vU ;-) but good anyway. Commented Jul 24, 2010 at 19:57
  • @Kevin Thanks for the hints.Why not add this as default ordering for /sites response?Hehehe, ok ok..i give up again :). Commented Jul 24, 2010 at 20:04
  • @systempuntoout - I'm not opposed to adding a sort in the future for the /sites route. I am opposed to adding any the_way_app_num_4159_wants_to_display_it sort options though. Commented Jul 24, 2010 at 20:13
  • of course, i was being facetious. this is actually quite an elegant solution, but does required at least 30+ requests to compile. I realize my solution reeks of goldberg but requires 1 request. I think I prefer this (your) technique. Commented Jul 24, 2010 at 20:16
  • Why does StackApps get moved to the bottom? It's probably my favorite trilogy site! (And certainly the one I have the most rep on.) Commented Jul 24, 2010 at 20:16
  • 2
    @geo - because that is the story posited by OP. Question followed by Answer relevant to Question. Interesting concept. ;-) Commented Jul 24, 2010 at 20:23
  • @code: K, what you just said made no sense. I was just criticizing (in a joking way) the decision to always put StackApps at the bottom of the list. Commented Jul 24, 2010 at 20:30
  • @geo i've changed the requirements, Stack Apps could stay just belowe the Trilogy :). Commented Jul 24, 2010 at 20:37
  • @kevin actually i just need a sorted list of sites. A sort that is good to Stack Exchange is good to me and to my app; and, at the moment, what /sites returns is not sorted. Commented Jul 24, 2010 at 20:39
  • @kevin, kinda like this? stackapps.com/questions/1203/… Commented Jul 25, 2010 at 9:21

Okay, here is sort of what happens with StackMobile:

  1. The list of sites is retrieved and stored in an associative array with the name of the site as the key.
  2. A new list is created by enumerating over the array:
    • If the item is of the type 'linked_meta', then see if another site with the same name is present.
      • If so, add it to that site. (its parent)
      • If not, add a separate entry.
    • If not, just add it to the list.
  3. The trilogy sites are removed from the list. (The only hard-coded part of this method!)
  4. The remaining items are echo'd to the page.

Note: This isn't quite true because StackMobile caches the StackAuth data in a database. But for the sake of simplicity, pretend it doesn't.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .