diff --git a/bootstrap.php b/bootstrap.php
index 6d7acf23f8152980feabb54d00ffdf3cb0d53b6a..8a68c184342105d0bb5d3583a8a842d389de3426 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -135,6 +135,8 @@ class Bootstrap {
         define('QUEUE_TABLE', $prefix.'queue');
         define('COLUMN_TABLE', $prefix.'queue_column');
         define('QUEUE_COLUMN_TABLE', $prefix.'queue_columns');
+        define('QUEUE_SORT_TABLE', $prefix.'queue_sort');
+        define('QUEUE_SORTING_TABLE', $prefix.'queue_sorts');
 
         define('API_KEY_TABLE',$prefix.'api_key');
         define('TIMEZONE_TABLE',$prefix.'timezone');
diff --git a/include/ajax.admin.php b/include/ajax.admin.php
index 6869eaad000070c8bb1c2569f7d51a6daa77ff4a..1047b8395d101d93dac8990ce2022c4e178c6825 100644
--- a/include/ajax.admin.php
+++ b/include/ajax.admin.php
@@ -215,4 +215,29 @@ class AdminAjaxAPI extends AjaxController {
         include STAFFINC_DIR . 'templates/queue-column-add.tmpl.php';
 
     }
+
+    function addQueueSort($root='Ticket') {
+        global $ost, $thisstaff;
+
+        if (!$thisstaff)
+            Http::response(403, 'Agent login required');
+        if (!$thisstaff->isAdmin())
+            Http::response(403, 'Access denied');
+
+        $sort = new QueueSort();
+        if ($_POST) {
+            $data_form = $sort->getDataConfigForm($_POST);
+            if ($data_form->isValid()) {
+                $sort->update($data_form->getClean() + $_POST, $root);
+                if ($sort->save())
+                    Http::response(201, $this->encode(array(
+                        'id' => $sort->getId(),
+                        'name' => (string) $sort->getName(),
+                    ), 'application/json'));
+            }
+        }
+
+        include STAFFINC_DIR . 'templates/queue-sorting-add.tmpl.php';
+
+    }
 }
diff --git a/include/ajax.search.php b/include/ajax.search.php
index 4a735f304055ed761ce9c148564aef6edc6ff62f..3f78b93f1d2269db20852740362aee0931418bf4 100644
--- a/include/ajax.search.php
+++ b/include/ajax.search.php
@@ -239,6 +239,28 @@ class SearchAjaxAPI extends AjaxController {
         include STAFFINC_DIR . 'templates/queue-column-edit.tmpl.php';
     }
 
+    function editSort($sort_id) {
+        global $thisstaff;
+
+        if (!$thisstaff) {
+            Http::response(403, 'Agent login is required');
+        }
+        elseif (!($sort = QueueSort::lookup($sort_id))) {
+            Http::response(404, 'No such queue sort');
+        }
+
+        if ($_POST) {
+            $data_form = $sort->getDataConfigForm($_POST);
+            if ($data_form->isValid()) {
+                $sort->update($data_form->getClean() + $_POST);
+                if ($sort->save())
+                    Http::response(201, 'Successfully updated');
+            }
+        }
+
+        include STAFFINC_DIR . 'templates/queue-sorting-edit.tmpl.php';
+    }
+
     function previewQueue($id=false) {
         global $thisstaff;
 
diff --git a/include/class.i18n.php b/include/class.i18n.php
index b9bac6202c110bb9652ded88381e8faba3979cf8..0a08736d76d84cc4e120cd73215a48a161a659f0 100644
--- a/include/class.i18n.php
+++ b/include/class.i18n.php
@@ -67,6 +67,7 @@ class Internationalization {
             'file.yaml' =>          'AttachmentFile::__create',
             'sequence.yaml' =>      'Sequence::__create',
             'queue_column.yaml' =>  'QueueColumn::__create',
+            'queue_sort.yaml' =>    'QueueSort::__create',
             'queue.yaml' =>         'CustomQueue::__create',
         );
 
diff --git a/include/class.orm.php b/include/class.orm.php
index 8828103ad7e505dce76330bb9081b149db2836dd..7756fec739ccd369220fc576d3d8f2524b9ce3f1 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -502,6 +502,15 @@ class VerySimpleModel {
 
     function __onload() {}
 
+    function serialize() {
+        return $this->getPk();
+    }
+
+    function unserialize($data) {
+        $this->ht = $data;
+        $this->refetch();
+    }
+
     static function getMeta($key=false) {
         if (!static::$meta instanceof ModelMeta
             || get_called_class() != static::$meta->model
@@ -671,9 +680,7 @@ class VerySimpleModel {
         if ($refetch) {
             // Preserve non database information such as list relationships
             // across the refetch
-            $this->ht =
-                static::objects()->filter($this->getPk())->values()->one()
-                + $this->ht;
+            $this->refetch();
         }
         if ($wasnew) {
             // Attempt to update foreign, unsaved objects with the PK of
@@ -702,6 +709,12 @@ class VerySimpleModel {
         return true;
     }
 
+    private function refetch() {
+        $this->ht =
+            static::objects()->filter($this->getPk())->values()->one()
+            + $this->ht;
+    }
+
     private function getPk() {
         $pk = array();
         foreach ($this::getMeta('pk') as $f)
@@ -752,7 +765,7 @@ END_CLASS
 }
 
 trait AnnotatedModelTrait {
-    function get($what) {
+    function get($what, $default=false) {
         if (isset($this->__overlay__[$what]))
             return $this->__overlay__[$what];
         return parent::get($what);
@@ -784,7 +797,7 @@ trait AnnotatedModelTrait {
  * is, the annotated model will remain).
  */
 trait WriteableAnnotatedModelTrait {
-    function get($what) {
+    function get($what, $default=false) {
         if ($this->__overlay__->__isset($what))
             return $this->__overlay__->get($what);
         return parent::get($what);
diff --git a/include/class.queue.php b/include/class.queue.php
index be5ca24ef1b21210df9744306443c99dfa2edb1a..c57630e69f4c84d44d709b6040f41237a64098a8 100644
--- a/include/class.queue.php
+++ b/include/class.queue.php
@@ -30,6 +30,10 @@ class CustomQueue extends VerySimpleModel {
                 'reverse' => 'QueueColumnGlue.queue',
                 'broker' => 'QueueColumnListBroker',
             ),
+            'sorts' => array(
+                'reverse' => 'QueueSortGlue.queue',
+                'broker' => 'QueueSortListBroker',
+            ),
             'parent' => array(
                 'constraint' => array(
                     'parent_id' => 'CustomQueue.id',
@@ -49,6 +53,7 @@ class CustomQueue extends VerySimpleModel {
     const FLAG_CONTAINER =      0x0004; // Container for other queues ('Open')
     const FLAG_INHERIT_CRITERIA = 0x0008; // Include criteria from parent
     const FLAG_INHERIT_COLUMNS = 0x0010; // Inherit column layout from parent
+    const FLAG_INHERIT_SORTING = 0x0020; // Inherit advanced sorting from parent
 
     var $criteria;
 
@@ -524,6 +529,13 @@ class CustomQueue extends VerySimpleModel {
         $col->queue = $this;
     }
 
+    function getSortOptions() {
+        if ($this->inheritSorting() && $this->parent) {
+            return $this->parent->getSortOptions();
+        }
+        return $this->sorts;
+    }
+
     function getStatus() {
         return 'bogus';
     }
@@ -679,6 +691,10 @@ class CustomQueue extends VerySimpleModel {
         return $this->hasFlag(self::FLAG_INHERIT_COLUMNS);
     }
 
+    function inheritSorting() {
+        return $this->hasFlag(self::FLAG_INHERIT_SORTING);
+    }
+
     function buildPath() {
         if (!$this->id)
             return;
@@ -734,6 +750,8 @@ class CustomQueue extends VerySimpleModel {
             $this->parent_id > 0 && isset($vars['inherit']));
         $this->setFlag(self::FLAG_INHERIT_COLUMNS,
             $this->parent_id > 0 && isset($vars['inherit-columns']));
+        $this->setFlag(self::FLAG_INHERIT_SORTING,
+            $this->parent_id > 0 && isset($vars['inherit-sorting']));
 
         // Update queue columns (but without save)
         if (!isset($vars['columns']) && $this->parent) {
@@ -773,6 +791,32 @@ class CustomQueue extends VerySimpleModel {
             $this->columns->sort(function($c) { return $c->sort; });
         }
 
+        // Update advanced sorting options for the queue
+        if (isset($vars['sorts']) && !$this->hasFlag(self::FLAG_INHERIT_SORTING)) {
+            $new = $vars['sorts'];
+            $order = array_keys($new);
+            foreach ($this->sorts as $sort) {
+                $key = $sort->sort_id;
+                if (!in_array($key, $vars['sorts'])) {
+                    $this->sorts->remove($sort);
+                    continue;
+                }
+                $sort->set('sort', array_search($key, $order));
+                unset($new[$key]);
+            }
+            // Add new columns
+            foreach ($new as $id) {
+                $glue = new QueueSortGlue(array(
+                    'sort_id' => $id,
+                    'sort' => array_search($id, $order),
+                ));
+                $glue->queue = $this;
+                $this->sorts->add(QueueSort::lookup($id), $glue);
+            }
+            // Re-sort the in-memory columns array
+            $this->sorts->sort(function($c) { return $c->sort; });
+        }
+
         // TODO: Move this to SavedSearch::update() and adjust
         //       AjaxSearch::_saveSearch()
         $form = $form ?: $this->getForm($vars);
@@ -799,7 +843,8 @@ class CustomQueue extends VerySimpleModel {
             $this->path = $this->buildPath();
             $this->save();
         }
-        return $this->columns->saveAll();
+        return $this->columns->saveAll()
+            && $this->sorts->saveAll();
     }
 
     static function getOrmPath($name, $query=null) {
@@ -851,6 +896,13 @@ class CustomQueue extends VerySimpleModel {
             $glue->queue_id = $q->getId();
             $glue->save();
         }
+        if (isset($vars['sorts'])) {
+            foreach ($vars['sorts'] as $info) {
+                $glue = new QueueSortGlue($info);
+                $glue->queue_id = $q->getId();
+                $glue->save();
+            }
+        }
         return $q;
     }
 }
@@ -1099,7 +1151,7 @@ extends QueueColumnAnnotation {
 
 class DataSourceField
 extends ChoiceField {
-    function getChoices() {
+    function getChoices($verbose=false) {
         $config = $this->getConfiguration();
         $root = $config['root'];
         $fields = array();
@@ -1310,7 +1362,7 @@ extends ChoiceField {
             return new $choices(array('name' => $prop));
     }
 
-    function getChoices() {
+    function getChoices($verbose=false) {
         if (isset($this->property))
             return static::$properties[$this->property];
 
@@ -1722,7 +1774,7 @@ extends InstrumentedList {
         $this->queryset->select_related('column');
     }
 
-    function add($column, $glue=null) {
+    function add($column, $glue=null, $php7_is_annoying=true) {
         $glue = $glue ?: new QueueColumnGlue();
         $glue->column = $column;
         $anno = AnnotatedModel::wrap($column, $glue);
@@ -1731,6 +1783,175 @@ extends InstrumentedList {
     }
 }
 
+class QueueSort
+extends VerySimpleModel {
+    static $meta = array(
+        'table' => QUEUE_SORT_TABLE,
+        'pk' => array('id'),
+        'ordering' => array('name'),
+        'joins' => array(
+            'queue' => array(
+                'constraint' => array('queue_id' => 'CustomQueue.id'),
+            ),
+        ),
+    );
+
+    var $_columns;
+
+    function getRoot($hint=false) {
+        switch ($hint ?: $this->root) {
+        case 'T':
+        default:
+            return 'Ticket';
+        }
+    }
+
+    function getName() {
+        return $this->name;
+    }
+
+    function getId() {
+        return $this->id;
+    }
+
+    function applySort(QuerySet $query, $reverse=false, $root=false) {
+        $fields = CustomQueue::getSearchableFields($this->getRoot($root));
+        foreach ($this->getColumnPaths() as $path=>$descending) {
+            $descending = $reverse ? !$descending : $descending;
+            if (isset($fields[$path])) {
+                list(,$field) = $fields[$path];
+                $query = $field->applyOrderBy($query, $descending,
+                    CustomQueue::getOrmPath($path, $query));
+            }
+        }
+        return $query;
+    }
+
+    function getColumnPaths() {
+        if (!isset($this->_columns)) {
+            $columns = array();
+            foreach (JsonDataParser::decode($this->columns) as $path) {
+                if ($descending = $path[0] == '-')
+                    $path = substr($path, 1);
+                $columns[$path] = $descending;
+            }
+            $this->_columns = $columns;
+        }
+        return $this->_columns;
+    }
+
+    function getColumns() {
+        $columns = array();
+        $paths = $this->getColumnPaths();
+        $everything = CustomQueue::getSearchableFields($this->getRoot());
+        foreach ($paths as $p=>$descending) {
+            if (isset($everything[$p])) {
+                $columns[$p] = array($everything[$p], $descending);
+            }
+        }
+        return $columns;
+    }
+
+    function getDataConfigForm($source=false) {
+        return new QueueSortDataConfigForm($source ?: $this->getDbFields(),
+            array('id' => $this->id));
+    }
+
+    static function forQueue(CustomQueue $queue) {
+        return static::objects()->filter([
+            'root' => $queue->getRoot(),
+        ]);
+    }
+
+    function save($refetch=false) {
+        if ($this->dirty)
+            $this->updated = SqlFunction::NOW();
+        return parent::save($refetch || $this->dirty);
+    }
+
+    function update($vars, &$errors=array()) {
+        if (!isset($vars['name']))
+            $errors['name'] = __('A title is required');
+
+        $this->name = $vars['name'];
+        if (isset($vars['root']))
+            $this->root = $vars['root'];
+        elseif (!isset($this->root))
+            $this->root = 'T';
+
+        $fields = CustomQueue::getSearchableFields($this->getRoot($vars['root']));
+        $columns = array();
+        if (@is_array($vars['columns'])) {
+            foreach ($vars['columns']as $path=>$info) {
+                $descending = (int) @$info['descending'];
+                // TODO: Check if column is valid, stash in $columns
+                if (!isset($fields[$path]))
+                    continue;
+                $columns[] = ($descending ? '-' : '') . $path;
+            }
+            $this->columns = JsonDataEncoder::encode($columns);
+        }
+
+        if (count($errors))
+            return false;
+
+        return $this->save();
+    }
+
+    static function __create($vars) {
+        $c = new static($vars);
+        $c->save();
+        return $c;
+    }
+}
+
+class QueueSortGlue
+extends VerySimpleModel {
+    static $meta = array(
+        'table' => QUEUE_SORTING_TABLE,
+        'pk' => array('sort_id', 'queue_id'),
+        'joins' => array(
+            'ordering' => array(
+                'constraint' => array('sort_id' => 'QueueSort.id'),
+            ),
+            'queue' => array(
+                'constraint' => array('queue_id' => 'CustomQueue.id'),
+            ),
+        ),
+        'select_related' => array('ordering', 'queue'),
+        'ordering' => array('sort'),
+    );
+}
+
+class QueueSortGlueMIM
+extends ModelInstanceManager {
+    function getOrBuild($modelClass, $fields, $cache=true) {
+        $m = parent::getOrBuild($modelClass, $fields, $cache);
+        if ($m && $modelClass === 'QueueSortGlue') {
+            // Instead, yield the QueueColumn instance with the local fields
+            // in the association table as annotations
+            $m = AnnotatedModel::wrap($m->ordering, $m, 'QueueSort');
+        }
+        return $m;
+    }
+}
+
+class QueueSortListBroker
+extends InstrumentedList {
+    function __construct($fkey, $queryset=false) {
+        parent::__construct($fkey, $queryset, 'QueueSortGlueMIM');
+        $this->queryset->select_related('ordering');
+    }
+
+    function add($ordering, $glue=null, $php7_is_annoying=true) {
+        $glue = $glue ?: new QueueSortGlue();
+        $glue->ordering = $ordering;
+        $anno = AnnotatedModel::wrap($ordering, $glue);
+        parent::add($anno, false);
+        return $anno;
+    }
+}
+
 abstract class QueueColumnFilter {
     static $registry;
 
@@ -1907,3 +2128,24 @@ extends AbstractForm {
         );
     }
 }
+
+class QueueSortDataConfigForm
+extends AbstractForm {
+    function getInstructions() {
+        return __('Add, and remove the fields in this list using the options below. Sorting is priortized in ascending order.');
+    }
+
+    function buildFields() {
+        return array(
+            'name' => new TextboxField(array(
+                'required' => true,
+                'layout' => new GridFluidCell(12),
+                'translatable' => isset($this->options['id'])
+                    ? _H('queuesort.name.'.$this->options['id']) : false,
+                'configuration' => array(
+                    'placeholder' => __('Sort Criteria Title'),
+                ),
+            )),
+        );
+    }
+}
diff --git a/include/class.search.php b/include/class.search.php
index 7dc62a5144c0c917eb392ba02dc109aaa49f9c66..1cb8ac3d1356d4bb7fb2f365cad90c84c0f8573e 100644
--- a/include/class.search.php
+++ b/include/class.search.php
@@ -670,8 +670,8 @@ class SavedSearch extends CustomQueue {
         return count($errors) === 0;
     }
 
-    static function create() {
-        $search = parent::create();
+    static function create($vars=false) {
+        $search = parent::create($vars);
         $search->clearFlag(self::FLAG_QUEUE);
         return $search;
     }
@@ -833,7 +833,7 @@ class AssigneeChoiceField extends ChoiceField {
 }
 
 class AgentSelectionField extends ChoiceField {
-    function getChoices() {
+    function getChoices($verbose=false) {
         return Staff::getStaffMembers();
     }
 
@@ -858,7 +858,7 @@ class AgentSelectionField extends ChoiceField {
 }
 
 class TeamSelectionField extends ChoiceField {
-    function getChoices() {
+    function getChoices($verbose=false) {
         return Team::getTeams();
     }
 
diff --git a/include/i18n/en_US/queue.yaml b/include/i18n/en_US/queue.yaml
index 4d01f5fbb8c57c8b9ab1add551e075a98ae25535..678575ba2846e5de8e35efca2e70e785fa253e4b 100644
--- a/include/i18n/en_US/queue.yaml
+++ b/include/i18n/en_US/queue.yaml
@@ -15,8 +15,8 @@
 #   0x02:   FLAG_QUEUE (should be set for everything here)
 #   0x04:   FLAG_CONTAINER (should be set for top-level queues)
 #   0x08:   FLAG_INHERIT (inherit criteria from parent)
-#   0x10:   FLAG_DEFAULT (default queue for parent container)
-#   0x20:   FLAG_DRAFT
+#   0x10:   FLAG_INHERIT_COLUMNS
+#   0x20:   FLAG_INHERIT_SORTING
 # staff_id: User owner of the queue
 # sort:     Manual sort order
 # title:    Display name of the queue
@@ -58,6 +58,11 @@
       sort: 6
       width: 160
       heading: Assigned To
+  sorts:
+    - sort_id: 1
+    - sort_id: 2
+    - sort_id: 3
+    - sort_id: 4
 
 - id: 2
   title: Closed
@@ -93,7 +98,7 @@
 
 - title: Unanswered
   parent_id: 1
-  flags: 0x0b
+  flags: 0x2b
   root: T
   sort: 1
   config: '[["isanswered","nset",null]]'
@@ -125,7 +130,7 @@
 
 - title: Unassigned
   parent_id: 1
-  flags: 0x0b
+  flags: 0x2b
   root: T
   sort: 2
   config: '[["assignee","!assigned",null]]'
@@ -157,7 +162,7 @@
 
 - title: My Tickets
   parent_id: 1
-  flags: 0x0b
+  flags: 0x2b
   root: T
   sort: 4
   config: '[["assignee","includes",{"M":"Me", "T":"One of my teams"}]]'
diff --git a/include/i18n/en_US/queue_sort.yaml b/include/i18n/en_US/queue_sort.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..34133fc7a532804b42073cb066119abca969e0f4
--- /dev/null
+++ b/include/i18n/en_US/queue_sort.yaml
@@ -0,0 +1,21 @@
+# Columns are not necessary and a default list is used if no columns are
+# specified.
+#
+# Fields: 
+#   id:
+---
+- id: 1
+  name: Priority + Most Recently Updated
+  columns: '["-cdata__priority","-lastupdate"]'
+
+- id: 2
+  name: Priority + Most Recently Created
+  columns: '["-cdata__priority","-created"]'
+
+- id: 3
+  name: Priority + Due Date
+  columns: '["-cdata__priority","-est_duedate"]'
+
+- id: 4
+  name: Due Date
+  columns: '["-est_duedate"]'
diff --git a/include/staff/queue.inc.php b/include/staff/queue.inc.php
index 39f0f8007539e5c36eef6f1cd1c828c179aabfa5..63db07b90cf9687c8b21646acd33283087ecfcdd 100644
--- a/include/staff/queue.inc.php
+++ b/include/staff/queue.inc.php
@@ -146,52 +146,126 @@ else {
             <br><?php echo __("Add, edit or remove the sorting criteria for this custom queue using the options below. Sorting is priortized in ascending order."); ?></p>
         </div>
         <table class="queue-sort table">
-            <tbody class="sortable-rows ui-sortable">
-                <tr style="display: table-row;">
-                    <td>
-                        <i class="faded-more icon-sort"></i>
-                         <a class="inline"
-                href="#" onclick="javascript:
-                var colid = $(this).closest('tr').find('[data-name=sorting_id]').val();
-                $.dialog('ajax.php/tickets/search/sorting/edit/' + colid, 201);
-                return false;"><?php echo __('This is sort criteria title 1'); ?></a>
-                    </td>
-                    <td>
-                        <a href="#" class="pull-right drop-column" title="Delete"><i class="icon-trash"></i></a>
-                    </td>
-                </tr>
-                <tr style="display: table-row;">
-                    <td>
-                        <i class="faded-more icon-sort"></i>
-                        <a class="inline"
-                href="#" onclick="javascript:
-                var colid = $(this).closest('tr').find('[data-name=sorting_id]').val();
-                $.dialog('ajax.php/tickets/search/sorting/edit/' + colid, 201);
-                return false;
-                "><?php echo __('This is sort criteria title 2'); ?></a>
-                    </td>
-                    <td>
-                        <a href="#" class="pull-right drop-column" title="Delete"><i class="icon-trash"></i></a>
-                    </td>
-                </tr>
-            </tbody>
-            <tbody>
-                <tr class="header">
-                    <td colspan="3"></td>
-                </tr>
-                <tr>
-                    <td colspan="3" id="append-sort">
-                        <i class="icon-plus-sign"></i>
-                        <select id="add-sort" data-quick-add="queue-column">
-                            <option value="">— Add Sort Criteria —</option>
-                            <option value="">Sort Option 1</option>
-                            <option value="">Sort Option 2</option>
-                            <option value="0" data-quick-add>&mdash; <?php echo __('Add New Sort Criteria');?> &mdash;</option>
-                        </select>
-                        <button type="button" class="green button">Add</button>
-                    </td>
-                </tr>
+<?php
+if ($queue->parent) { ?>
+          <tbody>
+            <tr>
+              <td colspan="3">
+                <input type="checkbox" name="inherit-sorting" <?php
+                  if ($queue->inheritSorting()) echo 'checked="checked"'; ?>
+                  onchange="javascript:$(this).closest('table').find('.if-not-inherited').toggle(!$(this).prop('checked'));" />
+                <?php echo __('Inherit sorting from the parent queue'); ?>
+                <br /><br />
+              </td>
+            </tr>
+          </tbody>
+<?php } ?>
+          <tbody class="if-not-inherited <?php if ($queue->inheritSorting()) echo 'hidden'; ?>">
+            <tr class="header">
+              <td nowrap><small><b><?php echo __('Name'); ?></b></small></td>
+              <td><small><b><?php echo __('Details'); ?></b></small></td>
+              <td/>
+            </tr>
+          </tbody>
+          <tbody class="sortable-rows if-not-inherited <?php
+            if ($queue->inheritSorting()) echo 'hidden'; ?>">
+            <tr id="sort-template" class="hidden">
+              <td nowrap>
+                <i class="faded-more icon-sort"></i>
+                <input type="hidden" data-name="sorts[]" />
+                <span data-name="name"></span>
+              </td>
+              <td>
+                <div>
+                <a class="inline action-button"
+                    href="#" onclick="javascript:
+                    var colid = $(this).closest('tr').find('[data-name^=sorts]').val();
+                    $.dialog('ajax.php/tickets/search/sort/edit/' + colid, 201);
+                    return false;
+                    "><i class="icon-cog"></i> <?php echo __('Config'); ?></a>
+                </div>
+              </td>
+              <td>
+                <a href="#" class="pull-right drop-sort" title="<?php echo __('Delete');
+                  ?>"><i class="icon-trash"></i></a>
+              </td>
+            <tr>
+          </tbody>
+            <tbody class="if-not-inherited <?php
+            if ($queue->inheritSorting()) echo 'hidden'; ?>">
+              <tr class="header">
+                  <td colspan="3"></td>
+              </tr>
+              <tr>
+                  <td colspan="3" id="append-sort">
+                      <i class="icon-plus-sign"></i>
+                      <select id="add-sort" data-quick-add="queue-sort">
+                          <option value="">— <?php
+                            echo __('Add Sort Criteria'); ?> —</option>
+<?php foreach (QueueSort::forQueue($queue) as $QS) { ?>
+                          <option value="<?php echo $QS->id; ?>"><?php
+                            echo Format::htmlchars($QS->name); ?></option>
+<?php } ?>
+                          <option value="0" data-quick-add>&mdash; <?php
+                            echo __('Add New Sort Criteria');?> &mdash;</option>
+                      </select>
+                      <button type="button" class="green button"><?php
+                        echo __('Add'); ?></button>
+                  </td>
+              </tr>
             </tbody>
+<script>
++function() {
+var Q = setInterval(function() {
+  if ($('#append-sort').length == 0)
+    return;
+  clearInterval(Q);
+
+  var addSortOption = function(sortid, info) {
+    if (!sortid) return;
+    var copy = $('#sort-template').clone();
+    info['sorts[]'] = sortid;
+    copy.find('input[data-name]').each(function() {
+      var $this = $(this),
+          name = $this.data('name');
+      if (info[name] !== undefined) {
+        $this.val(info[name]);
+      }
+      $this.attr('name', name);
+    });
+    copy.find('span').text(info['name']);
+    copy.attr('id', '').show().insertBefore($('#sort-template'));
+    copy.removeClass('hidden');
+    copy.find('a.drop-sort').click(function() {
+      $('<option>')
+        .attr('value', copy.find('input[data-name^=sorts]').val())
+        .text(info.name)
+        .insertBefore($('#add-sort')
+          .find('[data-quick-add]')
+        );
+      copy.fadeOut(function() { $(this).remove(); });
+      return false;
+    });
+    var selected = $('#add-sort').find('option[value=' + sortid + ']');
+    selected.remove();
+  };
+
+  $('#append-sort').find('button').on('click', function() {
+    var selected = $('#add-sort').find(':selected'),
+        id = parseInt(selected.val());
+    if (!id)
+        return;
+    addSortOption(id, {name: selected.text()});
+    return false;
+  });
+<?php foreach ($queue->getSortOptions() as $C) {
+  echo sprintf('addSortOption(%d, {name: %s});',
+    $C->sort_id, JsonDataEncoder::encode($C->getName())
+  );
+} ?>
+}, 25);
+}();
+</script>
         </table>
     </div>    
     
diff --git a/include/staff/templates/queue-sort.tmpl.php b/include/staff/templates/queue-sort.tmpl.php
index 42b56437057c208450bfbc3c29ffc89e2732ced6..5c2a14e63b2d4045f62b56daeb8fbdd5201214e3 100644
--- a/include/staff/templates/queue-sort.tmpl.php
+++ b/include/staff/templates/queue-sort.tmpl.php
@@ -1,29 +1,43 @@
+<?php
+if (count($queue->getSortOptions()) === 0)
+    return;
 
-<span class="action-button muted" data-dropdown="#sort-dropdown" data-toggle="tooltip" title="<?php echo $sort_options[$sort_cols]; ?>">
+if (strpos($_GET['sort'], 'qs-') === 0) {
+    $sort_id = substr($_GET['sort'], 3);
+    $queuesort = QueueSort::lookup($sort_id);
+}
+
+$sort_dir = $_GET['dir'];
+?>
+
+<span class="action-button muted" data-dropdown="#sort-dropdown"
+  data-toggle="tooltip" title="<?php
+    if (is_object($queuesort)) echo Format::htmlchars($queuesort->getName()); ?>">
   <i class="icon-caret-down pull-right"></i>
   <span><i class="icon-sort-by-attributes-alt <?php if ($sort_dir) echo 'icon-flip-vertical'; ?>"></i> <?php echo __('Sort');?></span>
 </span>
 <div id="sort-dropdown" class="action-dropdown anchor-right"
 onclick="javascript:
-var query = addSearchParam({'sort': $(event.target).data('mode'), 'dir': $(event.target).data('dir')});
+var $et = $(event.target),
+    query = addSearchParam({'sort': $et.data('mode'), 'dir': $et.data('dir')});
 $.pjax({
     url: '?' + query,
     timeout: 2000,
     container: '#pjax-container'});">
   <ul class="bleed-left">
-    <?php foreach ($queue_sort_options as $mode) {
-    $desc = $sort_options[$mode];
+    <?php foreach ($queue->getSortOptions() as $qs) {
+    $desc = $qs->getName();
     $icon = '';
     $dir = '0';
-    $selected = $sort_cols == $mode; ?>
+    $selected = isset($queuesort) && $queuesort->id == $qs->id; ?>
     <li <?php
     if ($selected) {
-    echo 'class="active"';
-    $dir = ($sort_dir == '1') ? '0' : '1'; // Flip the direction
-    $icon = ($sort_dir == '1') ? 'icon-hand-up' : 'icon-hand-down';
+      echo 'class="active"';
+      $dir = ($sort_dir == '1') ? '0' : '1'; // Flip the direction
+      $icon = ($sort_dir == '1') ? 'icon-hand-up' : 'icon-hand-down';
     }
     ?>>
-        <a href="#" data-mode="<?php echo $mode; ?>" data-dir="<?php echo $dir; ?>">
+        <a href="#" data-mode="qs-<?php echo $qs->id; ?>" data-dir="<?php echo $dir; ?>">
           <i class="icon-fixed-width <?php echo $icon; ?>"
           ></i> <?php echo Format::htmlchars($desc); ?></a>
       </li>
diff --git a/include/staff/templates/queue-sorting-add.tmpl.php b/include/staff/templates/queue-sorting-add.tmpl.php
index ab5c345a8a907a4bc37a2e64350bd099a840f37e..4fcc3b8ff1dcbb73374cae050390821cee698068 100644
--- a/include/staff/templates/queue-sorting-add.tmpl.php
+++ b/include/staff/templates/queue-sorting-add.tmpl.php
@@ -6,11 +6,11 @@
  */
 $colid = 0;
 ?>
-<h3 class="drag-handle"><?php echo __('Add Sort Options'); ?></h3>
+<h3 class="drag-handle"><?php echo __('Add Sort Option'); ?></h3>
 <a class="close" href=""><i class="icon-remove-circle"></i></a>
 <hr/>
 
-<form method="post" action="#admin/quick-add/queue-column">
+<form method="post" action="#admin/quick-add/queue-sort">
 
 <?php
 include 'queue-sorting.tmpl.php';
diff --git a/include/staff/templates/queue-sorting-edit.tmpl.php b/include/staff/templates/queue-sorting-edit.tmpl.php
index 0e2734d4822d618842fcaacbd49770685db099d0..0a2d98b0246433b59ea0f9cc1d575deb3a4ed41b 100644
--- a/include/staff/templates/queue-sorting-edit.tmpl.php
+++ b/include/staff/templates/queue-sorting-edit.tmpl.php
@@ -4,15 +4,15 @@
  *
  * $column - <QueueColumn> instance for this column
  */
-$colid = $column->getId();
+$sortid = $sort->getId();
 ?>
 <h3 class="drag-handle"><?php echo __('Manage Sort Options'); ?> &mdash;
-    <?php echo $column->get('name') ?></h3>
+    <?php echo $sort->get('name') ?></h3>
 <a class="close" href=""><i class="icon-remove-circle"></i></a>
 <hr/>
 
-<form method="post" action="#tickets/search/column/edit/<?php
-    echo $colid; ?>">
+<form method="post" action="#tickets/search/sort/edit/<?php
+    echo $sortid; ?>">
 
 <?php
 include 'queue-sorting.tmpl.php';
diff --git a/include/staff/templates/queue-sorting.tmpl.php b/include/staff/templates/queue-sorting.tmpl.php
index d73c0f9c2bb6b7adc3871e22cf7bb927fb1b90b3..2640e43acfed9c1c9ad917e2d632cf57dec63b1b 100644
--- a/include/staff/templates/queue-sorting.tmpl.php
+++ b/include/staff/templates/queue-sorting.tmpl.php
@@ -1,67 +1,106 @@
-<div class="tab-desc">
-    <p><b>Manage Custom Sorting</b>
-    <br>Add, and remove the fields in this list using the options below. Sorting is priortized in ascending order.</p>
-</div>
+<?php echo $sort->getDataConfigForm()->asTable(); ?>
+
 <table class="table">
-    <tbody>
-        <tr>
-            <td colspan="3" style="border-bottom:1px">
-                <input type="text" name="name" value="" style="width:100%" placeholder="<?php echo __('Sort Criteria Title');?>" />
-            </td>
-        </tr>
-    </tbody>
-    <tbody class="sortable-rows ui-sortable">
-        <tr style="display: table-row;">
-            <td>
-                <i class="faded-more icon-sort"></i>
-                <span><?php echo __('Sort field 0'); ?></span>
-            </td>
-            <td>
-                <select>
-                    <option value="0">
-                        <?php echo __('Ascending');?>
-                    </option>
-                    <option value="1">
-                        <?php echo __('Descending');?>
-                    </option>
-                </select>
-            </td>
-            <td>
-                <a href="#" class="pull-right drop-column" title="Delete"><i class="icon-trash"></i></a>
-            </td>
-        </tr>
-        <tr style="display: table-row;">
-            <td>
-                <i class="faded-more icon-sort"></i>
-                <span><?php echo __('Sort field 1'); ?></span>
-            </td>
-            <td>
-                <select>
-                    <option value="0">
-                        <?php echo __('Ascending');?>
-                    </option>
-                    <option value="1">
-                        <?php echo __('Descending');?>
-                    </option>
-                </select>
-            </td>
-            <td>
-                <a href="#" class="pull-right drop-column" title="Delete"><i class="icon-trash"></i></a>
-            </td>
-        </tr>
+    <tbody class="sortable-rows">
+      <tr id="sort-column-template" class="hidden">
+        <td nowrap>
+          <i class="faded-more icon-sort"></i>
+          <span data-name="label"></span>
+        </td>
+        <td>
+          <select data-name="descending">
+            <option value="0">
+              <?php echo __('Ascending');?>
+            </option>
+            <option value="1">
+              <?php echo __('Descending');?>
+            </option>
+          </select>
+        </td>
+        <td>
+          <a href="#" class="pull-right drop-column" title="Delete"><i class="icon-trash"></i></a>
+        </td>
+      </tr>
     </tbody>
     <tbody>
         <tr class="header">
             <td colspan="3"></td>
         </tr>
         <tr>
-            <td colspan="3" id="append-sort">
+            <td colspan="3" id="append-sort-column">
                 <i class="icon-plus-sign"></i>
-                <select id="add-sort">
-                    <option value="">— Add Field —</option>
+                <select id="add-sort-column">
+                    <option value="">— <?php echo __("Add Field"); ?> —</option>
+<?php foreach (CustomQueue::getSearchableFields($sort->getRoot()) as $path=>$F) {
+    list($label,) = $F;
+?>
+                    <option value="<?php echo Format::htmlchars($path); ?>"><?php
+                        echo Format::htmlchars($label);
+                    ?></option>
+<?php } ?>
                 </select>
-                <button type="button" class="green button">Add</button>
+                <button type="button" class="green button"><?php
+                  echo __('Add'); ?></button>
             </td>
         </tr>
     </tbody>
-</table>
\ No newline at end of file
+<script>
++function() {
+var Q = setInterval(function() {
+  if ($('#append-sort-column').length == 0)
+    return;
+  clearInterval(Q);
+
+  var addSortColumn = function(info) {
+    if (!info.path) return;
+    var copy = $('#sort-column-template').clone(),
+        name_prefix = 'columns[' + info.path + ']';
+    copy.find(':input[data-name]').each(function() {
+      var $this = $(this),
+          name = $this.data('name');
+
+      if (info[name] !== undefined) {
+        if ($this.is(':checkbox'))
+          $this.prop('checked', info[name]);
+        else
+          $this.val(info[name]);
+      }
+      $this.attr('name', name_prefix + '[' + name + ']');
+    });
+    copy.find('span').text(info['name']);
+    copy.attr('id', '').show().insertBefore($('#sort-column-template'));
+    copy.removeClass('hidden');
+    copy.find('a.drop-column').click(function() {
+      $('<option>')
+        .attr('value', copy.find('input[data-name=column_id]').val())
+        .text(info.name)
+        .insertBefore($('#add-column')
+          .find('[data-quick-add]')
+        );
+      copy.fadeOut(function() { $(this).remove(); });
+      return false;
+    });
+    var selected = $('#add-sort-column').find('option[value=' + info.path + ']');
+    selected.remove();
+  };
+
+  $('#append-sort-column').find('button').on('click', function() {
+    var selected = $('#add-sort-column').find(':selected'),
+        path = selected.val();
+    if (!path)
+        return;
+    addSortColumn({path: path, name: selected.text(), descending: 0});
+    return false;
+  });
+<?php foreach ($sort->getColumns() as $path=>$C) {
+  list(list($label,), $descending) = $C;
+  echo sprintf('addSortColumn({path: %s, name: %s, descending: %d});',
+    JsonDataEncoder::encode($path),
+    JsonDataEncoder::encode($label),
+    $descending ? 1 : 0
+  );
+} ?>
+}, 25);
+}();
+</script>
+</table>
diff --git a/include/staff/templates/queue-tickets.tmpl.php b/include/staff/templates/queue-tickets.tmpl.php
index 50f94fab984f046d999ea614c2c36a48b02b1443..83ff38e0a07a91a744a7851c3691ae4800174c72 100644
--- a/include/staff/templates/queue-tickets.tmpl.php
+++ b/include/staff/templates/queue-tickets.tmpl.php
@@ -162,12 +162,24 @@ if ($canManageTickets) { ?>
         <th style="width:12px"></th>
 <?php 
 }
-if (isset($_GET['sort'])) {
+if (isset($_GET['sort']) && is_numeric($_GET['sort'])) {
     $sort = $_SESSION['sort'][$queue->getId()] = array(
         'col' => (int) $_GET['sort'],
         'dir' => (int) $_GET['dir'],
     );
 }
+elseif (isset($_GET['sort'])
+    // Drop the leading `qs-`
+    && (strpos($_GET['sort'], 'qs-') === 0)
+    && ($sort_id = substr($_GET['sort'], 3))
+    && is_numeric($sort_id)
+    && ($sort = QueueSort::lookup($sort_id))
+) {
+    $sort = $_SESSION['sort'][$queue->getId()] = array(
+        'queuesort' => $sort,
+        'dir' => (int) $_GET['dir'],
+    );
+}
 else {
     $sort = $_SESSION['sort'][$queue->getId()];
 }
@@ -188,7 +200,11 @@ foreach ($columns as $C) {
     if (isset($sort['col']) && $sort['col'] == $C->id) {
         $tickets = $C->applySort($tickets, $sort['dir']);
     }
-} ?>
+}
+if (isset($sort['queuesort'])) {
+    $sort['queuesort']->applySort($tickets, $sort['dir']);
+}
+?>
     </tr>
   </thead>
   <tbody>
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 5c87bd1bf8de944497a1e5a4357e9a26722bdeb9..482fa87699383c4d90d2424748e47fd0344c75c0 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-934b8db8f97d6859d013b6219957724f
+a099b35b5ed141de6213f165b197e623
diff --git a/include/upgrader/streams/core/934b8db8-a099b35b.patch.sql b/include/upgrader/streams/core/934b8db8-a099b35b.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..4a3b26006f6fd73d539580fef381b16dbfe1ea2e
--- /dev/null
+++ b/include/upgrader/streams/core/934b8db8-a099b35b.patch.sql
@@ -0,0 +1,35 @@
+/**
+ * @version v1.11
+ * @signature a099b35b5ed141de6213f165b197e623
+ * @title Custom Queues, Advanced Sorting
+ *
+ * Add advanced sorting configuration to custom queues
+ */
+
+ALTER TABLE `%TABLE_PREFIX%queue`
+  ADD `sort_id` int(11) unsigned AFTER `columns_id`;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_sort`;
+CREATE TABLE `%TABLE_PREFIX%queue_sort` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `root` varchar(32) DEFAULT NULL,
+  `name` varchar(64) NOT NULL DEFAULT '',
+  `columns` text,
+  `updated` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_sorts`;
+CREATE TABLE `%TABLE_PREFIX%queue_sorts` (
+  `queue_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `sort_id` int(11) unsigned NOT NULL,
+  `bits` int(11) unsigned NOT NULL DEFAULT '0',
+  `sort` int(10) unsigned NOT NULL DEFAULT '0',
+  PRIMARY KEY (`queue_id`)
+) DEFAULT CHARSET=utf8;
+
+-- Finished with patch
+UPDATE `%TABLE_PREFIX%config`
+    SET `value` = 'a099b35b5ed141de6213f165b197e623'
+    WHERE `key` = 'schema_signature' AND `namespace` = 'core';
+
diff --git a/include/upgrader/streams/core/934b8db8-a099b35b.task.php b/include/upgrader/streams/core/934b8db8-a099b35b.task.php
new file mode 100644
index 0000000000000000000000000000000000000000..aef435f94881e4fe5cb7020a0fdc5146281f0fdc
--- /dev/null
+++ b/include/upgrader/streams/core/934b8db8-a099b35b.task.php
@@ -0,0 +1,25 @@
+<?php
+
+class QueueSortCreator extends MigrationTask {
+    var $description = "Load customziable sorting for ticket queues";
+
+    function run($time) {
+        $i18n = new Internationalization('en_US');
+        $columns = $i18n->getTemplate('queue_sort.yaml')->getData();
+        foreach ($columns as $C) {
+            QueueSort::__create($C);
+        }
+
+        $open = CustomQueue::lookup(1);
+        foreach (QueueSort::forQueue($open) as $qs) {
+            $open->sorts->add($qs);
+        }
+        $open->sorts->saveAll();
+
+        foreach ($open->getChildren() as $q) {
+            $q->flags |= CustomQueue::FLAG_INHERIT_SORTING;
+            $q->save();
+        }
+    }
+}
+return 'QueueSortCreator';
diff --git a/scp/ajax.php b/scp/ajax.php
index 0b4b015a0aa2fc0b8342ca61f912f269aae0a8ba..570020809037fb6e0222d9b0717b736db4ebaf71 100644
--- a/scp/ajax.php
+++ b/scp/ajax.php
@@ -176,6 +176,7 @@ $dispatcher = patterns('',
             url_post('^/create$', 'createSearch'),
             url_get('^/field/(?P<id>[\w_!:]+)$', 'addField'),
             url('^/column/edit/(?P<id>\d+)$', 'editColumn'),
+            url('^/sort/edit/(?P<id>\d+)$', 'editSort'),
             url_post('^(?P<id>\d+)/delete$', 'deleteQueues'),
             url_post('^(?P<id>\d+)/disable$', 'disableQueues'),
             url_post('^(?P<id>\d+)/enable$', 'undisableQueues')
@@ -247,7 +248,8 @@ $dispatcher = patterns('',
             url('^/team$', 'addTeam'),
             url('^/role$', 'addRole'),
             url('^/staff$', 'addStaff'),
-            url('^/queue-column$', 'addQueueColumn')
+            url('^/queue-column$', 'addQueueColumn'),
+            url('^/queue-sort$', 'addQueueSort')
         )),
         url_get('^/role/(?P<id>\d+)/perms', 'getRolePerms')
     )),
diff --git a/scp/css/scp.css b/scp/css/scp.css
index 3a6b184c749aafa8c96cef18a8d3f40d68062027..9df71e3cc556af2852c3949d9f8e699d66a1b9c4 100644
--- a/scp/css/scp.css
+++ b/scp/css/scp.css
@@ -1269,9 +1269,6 @@ a.print {
     padding: 4px;
     background-color:#fff;
 }
-.queue-sort.table td:not(:empty) {
-    padding: 10px;
-}
 .table.two-column tbody tr td:first-child {
     width: 25%;
 }
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index 28ed813984c86f132f73275f61182a7323ac7637..78c51e1b7cda3e370bba3ab0fdb291f1976d6be8 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -828,6 +828,7 @@ CREATE TABLE `%TABLE_PREFIX%queue` (
   `id` int(11) unsigned not null auto_increment,
   `parent_id` int(11) unsigned not null default 0,
   `columns_id` int(11) unsigned default null,
+  `sort_id` int(11) unsigned default null,
   `flags` int(11) unsigned not null default 0,
   `staff_id` int(11) unsigned not null default 0,
   `sort` int(11) unsigned not null default 0,
@@ -867,6 +868,25 @@ CREATE TABLE `%TABLE_PREFIX%queue_columns` (
   PRIMARY KEY (`queue_id`, `column_id`)
 ) DEFAULT CHARSET=utf8;
 
+DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_sort`;
+CREATE TABLE `%TABLE_PREFIX%queue_sort` (
+  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `root` varchar(32) DEFAULT NULL,
+  `name` varchar(64) NOT NULL DEFAULT '',
+  `columns` text,
+  `updated` datetime DEFAULT NULL,
+  PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+DROP TABLE IF EXISTS `%TABLE_PREFIX%queue_sorts`;
+CREATE TABLE `%TABLE_PREFIX%queue_sorts` (
+  `queue_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
+  `sort_id` int(11) unsigned NOT NULL,
+  `bits` int(11) unsigned NOT NULL DEFAULT '0',
+  `sort` int(10) unsigned NOT NULL DEFAULT '0',
+  PRIMARY KEY (`queue_id`)
+) DEFAULT CHARSET=utf8;
+
 DROP TABLE IF EXISTS `%TABLE_PREFIX%translation`;
 CREATE TABLE `%TABLE_PREFIX%translation` (
   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,