diff --git a/gertty/db.py b/gertty/db.py
index e98ade2..773889b 100644
--- a/gertty/db.py
+++ b/gertty/db.py
@@ -602,6 +602,9 @@ class DatabaseSession(object):
     def getPendingMessages(self):
         return self.session().query(Message).filter_by(pending=True).all()
 
+    def getPendingTopics(self):
+        return self.session().query(Change).filter_by(pending_topic=True).all()
+
     def getAccountByID(self, id, name=None, username=None, email=None):
         try:
             account = self.session().query(Account).filter_by(id=id).one()
diff --git a/gertty/keymap.py b/gertty/keymap.py
index 803627b..50da3d4 100644
--- a/gertty/keymap.py
+++ b/gertty/keymap.py
@@ -46,6 +46,7 @@ NEXT_CHANGE = 'next change'
 PREV_CHANGE = 'previous change'
 TOGGLE_HIDDEN_COMMENTS = 'toggle hidden comments'
 REFRESH = 'refresh'
+EDIT_TOPIC = 'edit topic'
 # Project list screen:
 TOGGLE_LIST_REVIEWED = 'toggle list reviewed'
 TOGGLE_LIST_SUBSCRIBED = 'toggle list subscribed'
@@ -83,6 +84,7 @@ DEFAULT_KEYMAP = {
     PREV_CHANGE: 'p',
     TOGGLE_HIDDEN_COMMENTS: 't',
     REFRESH: 'ctrl r',
+    EDIT_TOPIC: 'ctrl t',
 
     TOGGLE_LIST_REVIEWED: 'l',
     TOGGLE_LIST_SUBSCRIBED: 'L',
diff --git a/gertty/sync.py b/gertty/sync.py
index ec3e108..d958bd5 100644
--- a/gertty/sync.py
+++ b/gertty/sync.py
@@ -531,9 +531,30 @@ class UploadReviewsTask(Task):
     def run(self, sync):
         app = sync.app
         with app.db.getSession() as session:
+            for c in session.getPendingTopics():
+                sync.submitTask(SetTopicTask(c.key, self.priority))
             for m in session.getPendingMessages():
                 sync.submitTask(UploadReviewTask(m.key, self.priority))
 
+class SetTopicTask(Task):
+    def __init__(self, change_key, priority=NORMAL_PRIORITY):
+        super(SetTopicTask, self).__init__(priority)
+        self.change_key = change_key
+
+    def __repr__(self):
+        return '<SetTopicTask %s>' % (self.change_key,)
+
+    def run(self, sync):
+        app = sync.app
+        with app.db.getSession() as session:
+            change = session.getChange(self.change_key)
+            data = dict(topic=change.topic)
+            change.pending_topic = False
+            # Inside db session for rollback
+            sync.put('changes/%s/topic' % (change.id,),
+                     data)
+            sync.submitTask(SyncChangeTask(change.id, priority=self.priority))
+
 class UploadReviewTask(Task):
     def __init__(self, message_key, priority=NORMAL_PRIORITY):
         super(UploadReviewTask, self).__init__(priority)
@@ -667,6 +688,17 @@ class Sync(object):
                                      'User-Agent': self.user_agent})
         self.log.debug('Received: %s' % (r.text,))
 
+    def put(self, path, data):
+        url = self.url(path)
+        self.log.debug('PUT: %s' % (url,))
+        self.log.debug('data: %s' % (data,))
+        r = self.session.put(url, data=json.dumps(data).encode('utf8'),
+                             verify=self.app.config.verify_ssl,
+                             auth=self.auth,
+                             headers = {'Content-Type': 'application/json;charset=UTF-8',
+                                        'User-Agent': self.user_agent})
+        self.log.debug('Received: %s' % (r.text,))
+
     def syncSubscribedProjects(self):
         keys = []
         with self.app.db.getSession() as session:
diff --git a/gertty/view/change.py b/gertty/view/change.py
index 7f5ed6c..cce3937 100644
--- a/gertty/view/change.py
+++ b/gertty/view/change.py
@@ -25,6 +25,31 @@ from gertty.view import side_diff as view_side_diff
 from gertty.view import unified_diff as view_unified_diff
 import gertty.view
 
+class EditTopicDialog(mywid.ButtonDialog):
+    signals = ['save', 'cancel']
+    def __init__(self, app, topic):
+        self.app = app
+        save_button = mywid.FixedButton('Search')
+        cancel_button = mywid.FixedButton('Cancel')
+        urwid.connect_signal(save_button, 'click',
+                             lambda button:self._emit('save'))
+        urwid.connect_signal(cancel_button, 'click',
+                             lambda button:self._emit('cancel'))
+        super(EditTopicDialog, self).__init__("Edit Topic",
+                                              "Edit the change topic.",
+                                              entry_prompt="Topic: ",
+                                              entry_text=topic,
+                                              buttons=[save_button,
+                                                       cancel_button])
+
+    def keypress(self, size, key):
+        r = super(EditTopicDialog, self).keypress(size, key)
+        commands = self.app.config.keymap.getCommands(r)
+        if keymap.ACTIVATE in commands:
+            self._emit('save')
+            return None
+        return r
+
 class ReviewDialog(urwid.WidgetWrap):
     signals = ['save', 'cancel']
     def __init__(self, revision_row):
@@ -322,6 +347,8 @@ class ChangeView(urwid.WidgetWrap):
              "Cherry-pick the most recent revision onto the local repo"),
             (key(keymap.REFRESH),
              "Refresh this change"),
+            (key(keymap.EDIT_TOPIC),
+             "Edit the topic of this change"),
             ]
 
         for k in self.app.config.reviewkeys.values():
@@ -418,6 +445,7 @@ class ChangeView(urwid.WidgetWrap):
         change_info = []
         with self.app.db.getSession() as session:
             change = session.getChange(self.change_key)
+            self.topic = change.topic or ''
             if change.reviewed:
                 reviewed = ' (reviewed)'
             else:
@@ -435,7 +463,7 @@ class ChangeView(urwid.WidgetWrap):
             self.owner_label.set_text(('change-data', change.owner.name))
             self.project_label.set_text(('change-data', change.project.name))
             self.branch_label.set_text(('change-data', change.branch))
-            self.topic_label.set_text(('change-data', change.topic or ''))
+            self.topic_label.set_text(('change-data', self.topic))
             self.created_label.set_text(('change-data', str(change.created)))
             self.updated_label.set_text(('change-data', str(change.updated)))
             self.status_label.set_text(('change-data', change.status))
@@ -692,6 +720,9 @@ class ChangeView(urwid.WidgetWrap):
                 sync.SyncChangeTask(self.change_rest_id, priority=sync.HIGH_PRIORITY))
             self.app.status.update()
             return None
+        if keymap.EDIT_TOPIC in commands:
+            self.editTopic()
+            return None
         if r in self.app.config.reviewkeys:
             self.reviewKey(self.app.config.reviewkeys[r])
             return None
@@ -704,6 +735,27 @@ class ChangeView(urwid.WidgetWrap):
             screen = view_side_diff.SideDiffView(self.app, revision_key)
         self.app.changeScreen(screen)
 
+    def editTopic(self):
+        dialog = EditTopicDialog(self.app, self.topic)
+        urwid.connect_signal(dialog, 'save',
+            lambda button: self.closeEditTopic(dialog, True))
+        urwid.connect_signal(dialog, 'cancel',
+            lambda button: self.closeEditTopic(dialog, False))
+        self.app.popup(dialog)
+
+    def closeEditTopic(self, dialog, save):
+        if save:
+            change_key = None
+            with self.app.db.getSession() as session:
+                change = session.getChange(self.change_key)
+                change.topic = dialog.entry.edit_text
+                change.pending_topic = True
+                change_key = change.key
+            self.app.sync.submitTask(
+                sync.SetTopicTask(change_key, sync.HIGH_PRIORITY))
+        self.app.backScreen()
+        self.refresh()
+
     def reviewKey(self, reviewkey):
         approvals = {}
         for a in reviewkey['approvals']: