diff --git a/include/class.i18n.php b/include/class.i18n.php
index 884dc7cb9863f51e796e5fe812c03eeb67d697ab..a5d9e7c7b1ab36672dcb944da414b89224310a21 100644
--- a/include/class.i18n.php
+++ b/include/class.i18n.php
@@ -57,6 +57,8 @@ class Internationalization {
             'team.yaml' =>          'Team::create',
             // Organization
             'organization.yaml' =>  'Organization::__create',
+            // Ticket
+            'ticket_status.yaml' =>  'TicketStatus::__create',
             // Note that group requires department
             'group.yaml' =>         'Group::create',
             'file.yaml' =>          'AttachmentFile::create',
diff --git a/include/class.list.php b/include/class.list.php
index 2ba87622560907893962a6e62656310df4c21860..293165bbd285920408e9882f43cd687945370aa6 100644
--- a/include/class.list.php
+++ b/include/class.list.php
@@ -618,23 +618,52 @@ class TicketStatusList extends BuiltInCustomList {
     }
 
     function getNumItems() {
-        return 0;
+        return TicketStatus::objects()->count();
     }
 
     function getAllItems() {
-
+         return TicketStatus::objects()->order_by($this->getListOrderBy());
     }
 
     function getItems($criteria) {
 
+        if (!$this->_items) {
+            $this->_items = TicketStatus::objects()->filter(
+                array('flags__hasbit' => TicketStatus::ENABLED))
+                ->order_by($this->getListOrderBy());
+            if ($criteria['limit'])
+                $this->_items->limit($criteria['limit']);
+            if ($criteria['offset'])
+                $this->_items->offset($criteria['offset']);
+        }
+
+        return $this->_items;
     }
 
+    function getItem($id) {
+         return TicketStatus::lookup($id);
+    }
+
+    function addItem($vars, &$errors) {
+        $item = TicketStatus::create(array(
+            'flags' => 0, //Disable  until configured.
+            'sort'  => $vars['sort'],
+            'name' => $vars['value'],
+        ));
+        $item->save();
+
+        $this->_items = false;
+
+        return $item;
+    }
+
+
     function hasProperties() {
-        return true;
+        return ($this->getForm());
     }
 
     function getListOrderBy() {
-        switch ($this->setSortMode()) {
+        switch ($this->getSortMode()) {
             case 'Alpha':
                 return 'name';
             case '-Alpha':
@@ -645,20 +674,304 @@ class TicketStatusList extends BuiltInCustomList {
         }
     }
 
-    function getForm() {
-       return null;
-    }
-
     function update($vars, &$errors) {
 
         foreach (static::$config_fields as $f) {
             if (!isset($vars[$f])) continue;
 
             if (parent::set($f, $vars[$f]))
-                $this->ht[$field] = $vars[$f];
+                $this->ht[$f] = $vars[$f];
         }
 
         return true;
     }
+
+    function getForm() {
+
+        if (!isset($this->_form)) {
+            $o = DynamicForm::objects()->filter(array('type'=>'S'));
+            if ($o && $o[0])
+                $this->_form =  $o[0];
+            else // Auto-load the data
+                $this->_form = self::__load();
+        }
+
+        return $this->_form;
+    }
+
+
+    static function __load() {
+        require_once(INCLUDE_DIR.'class.i18n.php');
+
+        $i18n = new Internationalization();
+        $tpl = $i18n->getTemplate('form.yaml');
+        foreach ($tpl->getData() as $f) {
+            if ($f['type'] == 'S') {
+                $form = DynamicForm::create($f);
+                $form->save();
+                break;
+            }
+        }
+
+        $o = DynamicForm::objects()->filter(array('type'=>'S'));
+        if (!$form || !$o)
+            return false;
+
+        // Create default statuses
+        if (($statuses = $i18n->getTemplate('ticket_status.yaml')->getData()))
+            foreach ($statuses as $status)
+                TicketStatus::__create($status);
+
+        return $o[0];
+    }
 }
+
+class TicketStatus  extends VerySimpleModel implements CustomListItem {
+
+    static $meta = array(
+        'table' => TICKET_STATUS_TABLE,
+        'ordering' => array('name'),
+        'pk' => array('id'),
+    );
+
+    // Major statuses (states)
+    static $_states = array( 1 => 'open', 'closed', 'archived', 'deleted');
+
+    // Supported flags (TODO: move to configurable custom list)
+    static $_flags = array(
+            'onhold' => array(
+                'flag' => 1,
+                'name' => 'Onhold',
+                'states' => array('open'),
+                ),
+            'overdue' => array(
+                'flag' => 2,
+                'name' => 'Overdue',
+                'states' => array('open'),
+                ),
+            'answered' => array(
+                'flag' => 4,
+                'name' => 'Answered',
+                'states' => array('open'),
+                )
+            );
+
+    var $_form;
+    var $_config;
+    var $_settings;
+
+
+    const ENABLED = 0x0001;
+    const INTERNAL = 0x0002; // Forbid deletion or name and status change.
+
+
+
+    function __construct() {
+        call_user_func_array(array('parent', '__construct'), func_get_args());
+        $this->_config = new Config('TS.'.$this->getId());
+    }
+
+    protected function hasFlag($field, $flag) {
+        return 0 !== ($this->get($field) & $flag);
+    }
+
+    protected function clearFlag($field, $flag) {
+        return $this->set($field, $this->get($field) & ~$flag);
+    }
+
+    protected function setFlag($field, $flag) {
+        return $this->set($field, $this->get($field) | $flag);
+    }
+
+    function getForm() {
+        return $this->getConfigurationForm();
+    }
+
+    function getConfigurationForm() {
+
+        if (!$this->_form) {
+            $this->_form = DynamicForm::lookup(array('type'=>'S'));
+        }
+
+        return $this->_form;
+    }
+
+    function isEnabled() {
+        return $this->hasFlag('mode', self::ENABLED);
+    }
+
+    function enable() {
+
+        // Ticket status without properties cannot be enabled!
+        if (!$this->isEnableable())
+            return false;
+
+        return $this->setFlag('mode', self::ENABLED);
+    }
+
+    function disable() {
+        return (!$this->isInternal()
+                && $this->clearFlag('mode', self::ENABLED));
+    }
+
+    function isEnableable() {
+        return ($this->getForm());
+    }
+
+    function isDeletable() {
+        return !$this->isInternal();
+    }
+
+    function isInternal() {
+        return ($this->hasFlag('mode', self::INTERNAL));
+    }
+
+    function getId() {
+        return $this->get('id');
+    }
+
+    function getName() {
+        return $this->get('name');
+    }
+
+    function getValue() {
+        return $this->getName();
+    }
+
+    function getAbbrev() {
+        return '';
+    }
+
+    function getSortOrder() {
+        return $this->get('sort');
+    }
+
+    function getConfiguration() {
+
+        if (!$this->_settings) {
+             $this->_settings = $this->_config->get('properties');
+             if (is_string($this->_settings))
+                 $this->_settings = JsonDataParser::parse($this->_settings);
+             elseif (!$this->_settings)
+                 $this->_settings = array();
+
+            if ($this->getConfigurationForm()) {
+                foreach ($this->getConfigurationForm()->getFields() as $f)  {
+                    $name = mb_strtolower($f->get('name'));
+                    $id = $f->get('id');
+                    switch($name) {
+                        case 'flags':
+                            foreach (static::$_flags as $k => $v)
+                                if ($this->hasFlag('flags', $v['flag']))
+                                    $this->_settings[$id][] = $k;
+                            break;
+                        case 'state':
+                            $this->_settings[$id] = $this->get('state');
+                            break;
+                        default:
+                            if (!$this->_settings[$id] && $this->_settings[$name])
+                                $this->_settings[$id] = $this->_settings[$name];
+                    }
+                }
+            }
+        }
+
+        return $this->_settings;
+    }
+
+    function setConfiguration(&$errors=array()) {
+        $properties = array();
+        foreach ($this->getConfigurationForm()->getFields() as $f) {
+            $val = $f->to_database($f->getClean());
+            $name = mb_strtolower($f->get('name'));
+            switch ($name) {
+                case 'flags':
+                    $val = $f->getClean();
+                    if ($val && is_array($val)) {
+                        $flags = 0;
+                        foreach ($val as $v) {
+                            if (isset(static::$_flags[$v]))
+                                $flags += static::$_flags[$v]['flag'];
+                            elseif (!$f->errors())
+                                $f->addError('Unknown or invalid flag', $name);
+                        }
+                        $this->set('flags', $flags);
+                    } elseif ($val && !$f->errors()) {
+                        $f->addError('Unknown or invalid flag format', $name);
+                    }
+                    break;
+                case 'state':
+                    if ($val && in_array($val, static::$_states))
+                        $this->set('state', $val);
+                    else
+                        $f->addError('Unknown or invalid state', $name);
+                    break;
+                default: //Custom properties the user might add.
+                    $properties[$f->get('id')] = $val;
+            }
+            $errors = array_merge($errors, $f->errors());
+        }
+
+        if (count($errors) === 0) {
+            $this->save(true);
+            $this->setProperties($properties);
+        }
+
+        return count($errors) === 0;
+    }
+
+    function setProperties($properties) {
+        if ($properties && is_array($properties))
+            $properties = JsonDataEncoder::encode($properties);
+
+        $this->_config->set('properties', $properties);
+    }
+
+    function update($vars, &$errors) {
+
+        $fields = array('value' => 'name', 'sort' => 'sort');
+        foreach($fields as $k => $v) {
+            if (isset($vars[$k]))
+                $this->set($v, $vars[$k]);
+        }
+
+        return $this->save(true);
+    }
+
+    function delete() {
+
+        if (!$this->isDeletable())
+            return false;
+
+        // TODO: Delete and do house cleaning (move tickets..etc)
+
+    }
+
+    function toString() {
+        return $this->getValue();
+    }
+
+    function __toString() {
+        return $this->toString();
+    }
+
+    static function __create($ht, &$error=false) {
+        global $ost;
+
+        $properties = JsonDataEncoder::encode($ht['properties']);
+        unset($ht['properties']);
+        $ht['created'] = new SqlFunction('NOW');
+        if ($status = TicketStatus::create($ht)) {
+            $status->save(true);
+            $status->_config = new Config('TS.'.$status->getId());
+            $status->_config->set('properties', $properties);
+        }
+
+        return $status;
+    }
+}
+
+
+
+
 ?>
diff --git a/include/i18n/en_US/form.yaml b/include/i18n/en_US/form.yaml
index fe4be2f66098d0c8de9daee25c785ed3d04260f9..cefe3a072e12747748bb601540b6a7aefac0a71b 100644
--- a/include/i18n/en_US/form.yaml
+++ b/include/i18n/en_US/form.yaml
@@ -198,3 +198,46 @@
       configuration:
         rows: 4
         cols: 40
+
+- type: S # notrans
+  title: Ticket Status Properties
+  instructions: Properties that can be set on a ticket status.
+  deletable: false
+  fields:
+    - type: choices # notrans
+      name: state # notrans
+      label: State
+      required: true
+      sort: 1
+      edit_mask: 63
+      configuration:
+        choices:|
+            open: Open
+            closed: Closed
+            archived: Archived
+            deleted: Deleted
+        prompt: State of a ticket
+    - type: choices # notrans
+      name: flags # notrans
+      label: Flags
+      required: false
+      sort: 2
+      edit_mask: 63
+      configuration:
+        choices:|
+            onhold: Onhold (Disable SLA)
+            overdue: Mark as Overdue
+            answered: Mark as Answered
+        prompt: Status flags
+        multiselect: true
+    - type: memo # notrans
+      name: description
+      label: Description
+      required: false
+      sort: 3
+      edit_mask: 15
+      configuration:
+        rows: 2
+        cols: 40
+        html: false
+        length: 100
diff --git a/include/i18n/en_US/ticket_status.yaml b/include/i18n/en_US/ticket_status.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3c41d326f8a5e4aa8151f62f1dc89a2ae806bcb6
--- /dev/null
+++ b/include/i18n/en_US/ticket_status.yaml
@@ -0,0 +1,64 @@
+#
+# Default system data for ticket statuses
+#
+# Fields:
+#  id - (int:optional) id number in the database
+#  name - (string) descriptive name of the status
+#  state - (string) Main status of a ticket
+#  (open, resolved, closed, archived, deleted)
+#  mode - (bit) access mask (1 - enabled, 2 - internal)
+#  flags - (bit) flags that can be set on a ticket
+#  properties:
+#   description - (string) Description of the status
+#
+---
+- id: 1
+  name: Open
+  state: open
+  mode: 3
+  sort: 1
+  flags: 0
+  properties:
+    description: >
+        Open tickets.
+
+- id: 2
+  name: Resolved
+  state: closed
+  mode: 3
+  sort: 2
+  flags: 0
+  properties:
+    description: >
+        Resolved tickets are closed tickets that can be reopened by the end user. This might be useful
+        when a trigger is used to close resolved tickets with notice sent to end user.
+
+- id: 3
+  name: Closed
+  state: closed
+  mode: 3
+  sort: 3
+  flags: 0
+  properties:
+    description: >
+        Tickets marked as closed cannot be reopened by the end user. Tickets will still be accessible on client and staff panels.
+
+- id: 4
+  name: Archived
+  state:  archived
+  mode: 3
+  sort: 4
+  flags: 0
+  properties:
+    description: >
+        Tickets only adminstratively available but nolonger accessible on ticket queues.
+
+- id: 5
+  name: Deleted
+  state: deleted
+  mode: 3
+  sort: 5
+  flags: 0
+  properties:
+    description: >
+        Tickets queued for deletion. Not accessible on ticket queues.
diff --git a/include/upgrader/streams/core.sig b/include/upgrader/streams/core.sig
index 324095cf42b6af08769dcb485913dacba7cc301d..f3561a24cf47f64ff653740c9d2de13c3cadcd56 100644
--- a/include/upgrader/streams/core.sig
+++ b/include/upgrader/streams/core.sig
@@ -1 +1 @@
-8f99b8bf9bee63c8e4dc274ffbdda383
+9b9e2dc4551d448f081f180ca3829fa8
diff --git a/include/upgrader/streams/core/8f99b8bf-9b9e2dc4.patch.sql b/include/upgrader/streams/core/8f99b8bf-9b9e2dc4.patch.sql
new file mode 100644
index 0000000000000000000000000000000000000000..b08c0a972266e4aa677d38abf5b2d30fae8d7ba4
--- /dev/null
+++ b/include/upgrader/streams/core/8f99b8bf-9b9e2dc4.patch.sql
@@ -0,0 +1,24 @@
+/**
+ * @version v1.9.3
+ * @signature 9b9e2dc4551d448f081f180ca3829fa8
+ * @title Add custom ticket status support
+ *
+ */
+
+CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%ticket_status` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(60) NOT NULL DEFAULT '',
+  `state` varchar(16) NOT NULL DEFAULT 'open',
+  `mode` int(11) unsigned NOT NULL DEFAULT '0',
+  `flags` int(10) unsigned NOT NULL DEFAULT '0',
+  `sort` int(11) unsigned NOT NULL DEFAULT '0',
+  `notes` text NOT NULL,
+  `created` datetime NOT NULL,
+  `updated` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) DEFAULT CHARSET=utf8;
+
+UPDATE `%TABLE_PREFIX%config`
+    SET `value` = '9b9e2dc4551d448f081f180ca3829fa8'
+    WHERE `key` = 'schema_signature' AND `namespace` = 'core';
diff --git a/setup/inc/streams/core/install-mysql.sql b/setup/inc/streams/core/install-mysql.sql
index 20c3dc3460c7cb3f323d1f6425e0844d9b6ffa3d..a7c27d76aae8ca172d5d96e160be933aff64d2da 100644
--- a/setup/inc/streams/core/install-mysql.sql
+++ b/setup/inc/streams/core/install-mysql.sql
@@ -650,6 +650,22 @@ CREATE TABLE `%TABLE_PREFIX%ticket_event` (
   KEY `ticket_stats` (`timestamp`, `state`)
 ) DEFAULT CHARSET=utf8;
 
+DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_status`;
+CREATE TABLE IF NOT EXISTS `%TABLE_PREFIX%ticket_status` (
+  `id` int(11) NOT NULL AUTO_INCREMENT,
+  `name` varchar(60) NOT NULL DEFAULT '',
+  `state` varchar(16) NOT NULL DEFAULT 'open',
+  `mode` int(11) unsigned NOT NULL DEFAULT '0',
+  `flags` int(10) unsigned NOT NULL DEFAULT '0',
+  `sort` int(11) unsigned NOT NULL DEFAULT '0',
+  `notes` text NOT NULL,
+  `created` datetime NOT NULL,
+  `updated` datetime NOT NULL,
+  PRIMARY KEY (`id`),
+  UNIQUE KEY `name` (`name`)
+) DEFAULT CHARSET=utf8;
+
+
 DROP TABLE IF EXISTS `%TABLE_PREFIX%ticket_priority`;
 CREATE TABLE `%TABLE_PREFIX%ticket_priority` (
   `priority_id` tinyint(4) NOT NULL auto_increment,