diff --git a/src/applications/feed/query/PhabricatorFeedTransactionQuery.php b/src/applications/feed/query/PhabricatorFeedTransactionQuery.php
index 00da566532..e7ab4cc55d 100644
--- a/src/applications/feed/query/PhabricatorFeedTransactionQuery.php
+++ b/src/applications/feed/query/PhabricatorFeedTransactionQuery.php
@@ -1,178 +1,189 @@
 <?php
 
 final class PhabricatorFeedTransactionQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
   private $phids;
+  private $authorPHIDs;
   private $createdMin;
   private $createdMax;
 
   public function withPHIDs(array $phids) {
     $this->phids = $phids;
     return $this;
   }
 
+  public function withAuthorPHIDs(array $phids) {
+    $this->authorPHIDs = $phids;
+    return $this;
+  }
+
   public function withDateCreatedBetween($min, $max) {
     $this->createdMin = $min;
     $this->createdMax = $max;
     return $this;
   }
 
   protected function loadPage() {
     $queries = $this->newTransactionQueries();
 
     $xactions = array();
 
     if ($this->shouldLimitResults()) {
       $limit = $this->getRawResultLimit();
       if (!$limit) {
         $limit = null;
       }
     } else {
       $limit = null;
     }
 
     // We're doing a bit of manual work to get paging working, because this
     // query aggregates the results of a large number of subqueries.
 
     // Overall, we're ordering transactions by "<dateCreated, phid>". Ordering
     // by PHID is not very meaningful, but we don't need the ordering to be
     // especially meaningful, just consistent. Using PHIDs is easy and does
     // everything we need it to technically.
 
     // To actually configure paging, if we have an external cursor, we load
     // the internal cursor first. Then we pass it to each subquery and the
     // subqueries pretend they just loaded a page where it was the last object.
     // This configures their queries properly and we can aggregate a cohesive
     // set of results by combining all the queries.
 
     $cursor = $this->getExternalCursorString();
     if ($cursor !== null) {
       $cursor_object = $this->newInternalCursorFromExternalCursor($cursor);
     } else {
       $cursor_object = null;
     }
 
     $is_reversed = $this->getIsQueryOrderReversed();
 
     $created_min = $this->createdMin;
     $created_max = $this->createdMax;
 
     $xaction_phids = $this->phids;
+    $author_phids = $this->authorPHIDs;
 
     foreach ($queries as $query) {
       $query->withDateCreatedBetween($created_min, $created_max);
 
       if ($xaction_phids !== null) {
         $query->withPHIDs($xaction_phids);
       }
 
+      if ($author_phids !== null) {
+        $query->withAuthorPHIDs($author_phids);
+      }
+
       if ($limit !== null) {
         $query->setLimit($limit);
       }
 
       if ($cursor_object !== null) {
         $query
           ->setAggregatePagingCursor($cursor_object)
           ->setIsQueryOrderReversed($is_reversed);
       }
 
       $query->setOrder('global');
 
       $query_xactions = $query->execute();
       foreach ($query_xactions as $query_xaction) {
         $xactions[] = $query_xaction;
       }
 
       $xactions = msortv($xactions, 'newGlobalSortVector');
       if ($is_reversed) {
         $xactions = array_reverse($xactions);
       }
 
       if ($limit !== null) {
         $xactions = array_slice($xactions, 0, $limit);
 
         // If we've found enough transactions to fill up the entire requested
         // page size, we can narrow the search window: transactions after the
         // last transaction we've found so far can't possibly be part of the
         // result set.
 
         if (count($xactions) === $limit) {
           $last_date = last($xactions)->getDateCreated();
           if ($is_reversed) {
             if ($created_max === null) {
               $created_max = $last_date;
             } else {
               $created_max = min($created_max, $last_date);
             }
           } else {
             if ($created_min === null) {
               $created_min = $last_date;
             } else {
               $created_min = max($created_min, $last_date);
             }
           }
         }
       }
     }
 
     return $xactions;
   }
 
   public function getQueryApplicationClass() {
     return 'PhabricatorFeedApplication';
   }
 
   private function newTransactionQueries() {
     $viewer = $this->getViewer();
 
     $queries = id(new PhutilClassMapQuery())
       ->setAncestorClass('PhabricatorApplicationTransactionQuery')
       ->execute();
 
     $type_map = array();
 
     // If we're querying for specific transaction PHIDs, we only need to
     // consider queries which may load transactions with subtypes present
     // in the list.
 
     // For example, if we're loading Maniphest Task transaction PHIDs, we know
     // we only have to look at Maniphest Task transactions, since other types
     // of objects will never have the right transaction PHIDs.
 
     $xaction_phids = $this->phids;
     if ($xaction_phids) {
       foreach ($xaction_phids as $xaction_phid) {
         $type_map[phid_get_subtype($xaction_phid)] = true;
       }
     }
 
     $results = array();
     foreach ($queries as $query) {
       if ($type_map) {
         $type = $query->getTemplateApplicationTransaction()
           ->getApplicationTransactionType();
         if (!isset($type_map[$type])) {
           continue;
         }
       }
 
       $results[] = id(clone $query)
         ->setViewer($viewer)
         ->setParentQuery($this);
     }
 
     return $results;
   }
 
   protected function newExternalCursorStringForResult($object) {
     return (string)$object->getPHID();
   }
 
   protected function applyExternalCursorConstraintsToQuery(
     PhabricatorCursorPagedPolicyAwareQuery $subquery,
     $cursor) {
     $subquery->withPHIDs(array($cursor));
   }
 
 }
diff --git a/src/applications/feed/query/PhabricatorFeedTransactionSearchEngine.php b/src/applications/feed/query/PhabricatorFeedTransactionSearchEngine.php
index bc0d27c70c..5c73818e4d 100644
--- a/src/applications/feed/query/PhabricatorFeedTransactionSearchEngine.php
+++ b/src/applications/feed/query/PhabricatorFeedTransactionSearchEngine.php
@@ -1,113 +1,145 @@
 <?php
 
 final class PhabricatorFeedTransactionSearchEngine
   extends PhabricatorApplicationSearchEngine {
 
   public function getResultTypeDescription() {
     return pht('Transactions');
   }
 
   public function getApplicationClassName() {
     return 'PhabricatorFeedApplication';
   }
 
   public function newQuery() {
     return new PhabricatorFeedTransactionQuery();
   }
 
   protected function buildCustomSearchFields() {
-    return array();
+    return array(
+      id(new PhabricatorUsersSearchField())
+        ->setLabel(pht('Authors'))
+        ->setKey('authorPHIDs')
+        ->setAliases(array('author', 'authors')),
+      id(new PhabricatorSearchDateField())
+        ->setLabel(pht('Created After'))
+        ->setKey('createdStart'),
+      id(new PhabricatorSearchDateField())
+        ->setLabel(pht('Created Before'))
+        ->setKey('createdEnd'),
+    );
   }
 
   protected function buildQueryFromParameters(array $map) {
     $query = $this->newQuery();
 
+    if ($map['authorPHIDs']) {
+      $query->withAuthorPHIDs($map['authorPHIDs']);
+    }
+
+    $created_min = $map['createdStart'];
+    $created_max = $map['createdEnd'];
+
+    if ($created_min && $created_max) {
+      if ($created_min > $created_max) {
+        throw new PhabricatorSearchConstraintException(
+          pht(
+            'The specified "Created Before" date is earlier in time than the '.
+            'specified "Created After" date, so this query can never match '.
+            'any results.'));
+      }
+    }
+
+    if ($created_min || $created_max) {
+      $query->withDateCreatedBetween($created_min, $created_max);
+    }
+
     return $query;
   }
 
   protected function getURI($path) {
     return '/feed/transactions/'.$path;
   }
 
   protected function getBuiltinQueryNames() {
     $names = array(
       'all' => pht('All Transactions'),
     );
 
     return $names;
   }
 
   public function buildSavedQueryFromBuiltin($query_key) {
     $query = $this->newSavedQuery()
       ->setQueryKey($query_key);
 
     switch ($query_key) {
       case 'all':
         return $query;
     }
 
     return parent::buildSavedQueryFromBuiltin($query_key);
   }
 
   protected function renderResultList(
     array $objects,
     PhabricatorSavedQuery $query,
     array $handles) {
     assert_instances_of($objects, 'PhabricatorApplicationTransaction');
 
     $viewer = $this->requireViewer();
 
     $handle_phids = array();
     foreach ($objects as $object) {
       $author_phid = $object->getAuthorPHID();
       if ($author_phid !== null) {
         $handle_phids[] = $author_phid;
       }
       $object_phid = $object->getObjectPHID();
       if ($object_phid !== null) {
         $handle_phids[] = $object_phid;
       }
     }
 
     $handles = $viewer->loadHandles($handle_phids);
 
     $rows = array();
     foreach ($objects as $object) {
       $author_phid = $object->getAuthorPHID();
       $object_phid = $object->getObjectPHID();
 
       try {
         $title = $object->getTitle();
       } catch (Exception $ex) {
         $title = null;
       }
 
       $rows[] = array(
         $handles[$author_phid]->renderLink(),
         $handles[$object_phid]->renderLink(),
         AphrontTableView::renderSingleDisplayLine($title),
         phabricator_datetime($object->getDateCreated(), $viewer),
       );
     }
 
     $table = id(new AphrontTableView($rows))
       ->setHeaders(
         array(
-          pht('Actor'),
+          pht('Author'),
           pht('Object'),
           pht('Transaction'),
           pht('Date'),
         ))
       ->setColumnClasses(
         array(
           null,
           null,
           'wide',
           'right',
         ));
 
     return id(new PhabricatorApplicationSearchResultView())
       ->setTable($table);
   }
 
 }