diff --git a/src/applications/typeahead/application/PhabricatorTypeaheadApplication.php b/src/applications/typeahead/application/PhabricatorTypeaheadApplication.php
--- a/src/applications/typeahead/application/PhabricatorTypeaheadApplication.php
+++ b/src/applications/typeahead/application/PhabricatorTypeaheadApplication.php
@@ -9,7 +9,7 @@
   public function getRoutes() {
     return array(
       '/typeahead/' => array(
-        'class/(?:(?P<class>\w+)/)?'
+        '(?P<action>browse|class)/(?:(?P<class>\w+)/)?'
           => 'PhabricatorTypeaheadModularDatasourceController',
       ),
     );
diff --git a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
--- a/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
+++ b/src/applications/typeahead/controller/PhabricatorTypeaheadModularDatasourceController.php
@@ -11,6 +11,7 @@
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $query = $request->getStr('q');
+    $is_browse = ($request->getURIData('action') == 'browse');
 
     // Default this to the query string to make debugging a little bit easier.
     $raw_query = nonempty($request->getStr('raw'), $query);
@@ -23,16 +24,11 @@
       ->loadObjects();
     if (isset($sources[$class])) {
       $source = $sources[$class];
-      if ($source->getDatasourceApplicationClass()) {
-        if (!PhabricatorApplication::isClassInstalledForViewer(
-            $source->getDatasourceApplicationClass(),
-            $viewer)) {
-          return id(new Aphront404Response());
-        }
-      }
-
       $source->setParameters($request->getRequestData());
 
+      // NOTE: Wrapping the source in a Composite datasource ensures we perform
+      // application visibility checks for the viewer, so we do not need to do
+      // those separately.
       $composite = new PhabricatorTypeaheadRuntimeCompositeDatasource();
       $composite->addDatasource($source);
 
@@ -41,7 +37,47 @@
         ->setQuery($query)
         ->setRawQuery($raw_query);
 
+      if ($is_browse) {
+        $limit = 3;
+        $offset = $request->getInt('offset');
+        $composite
+          ->setLimit($limit + 1)
+          ->setOffset($offset);
+      }
+
       $results = $composite->loadResults();
+
+      if ($is_browse) {
+        $next_link = null;
+        if (count($results) > $limit) {
+          $results = array_slice($results, 0, $limit, $preserve_keys = true);
+          $next_link = phutil_tag(
+            'a',
+            array(
+              'href' => id(new PhutilURI($request->getRequestURI()))
+                ->setQueryParam('offset', $offset + $limit),
+            ),
+            pht('Next Page'));
+        }
+
+        $rows = array();
+        foreach ($results as $result) {
+          // TODO: Render nicely.
+          $rows[] = array_slice($result->getWireFormat(), 0, 3, true);
+        }
+
+        $table = id(new AphrontTableView($rows));
+
+        return $this->newDialog()
+          ->setWidth(AphrontDialogView::WIDTH_FORM)
+          ->setTitle(get_class($source)) // TODO: Provide nice names.
+          ->appendChild($table)
+          ->appendChild($next_link)
+          ->addCancelButton('/', pht('Close'));
+      }
+
+    } else if ($is_browse) {
+      return new Aphront404Response();
     } else {
       $results = array();
     }
diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php
--- a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php
+++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php
@@ -10,17 +10,34 @@
   }
 
   public function loadResults() {
+    $offset = $this->getOffset();
+    $limit = $this->getLimit();
+
     $results = array();
     foreach ($this->getUsableDatasources() as $source) {
       $source
         ->setRawQuery($this->getRawQuery())
         ->setQuery($this->getQuery())
-        ->setViewer($this->getViewer())
-        ->setLimit($this->getLimit());
+        ->setViewer($this->getViewer());
+
+      if ($limit) {
+        $source->setLimit($offset + $limit);
+      }
 
       $results[] = $source->loadResults();
     }
-    return array_mergev($results);
+
+    $results = array_mergev($results);
+    $results = msort($results, 'getName');
+
+    if ($offset) {
+      if (!$limit) {
+        $limit = count($results);
+      }
+      $results = array_slice($results, $offset, $limit, $preserve_keys = true);
+    }
+
+    return $results;
   }
 
   private function getUsableDatasources() {
diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php
--- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php
+++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php
@@ -5,6 +5,7 @@
   private $viewer;
   private $query;
   private $rawQuery;
+  private $offset;
   private $limit;
   private $parameters = array();
 
@@ -17,6 +18,15 @@
     return $this->limit;
   }
 
+  public function setOffset($offset) {
+    $this->offset = $offset;
+    return $this;
+  }
+
+  public function getOffset() {
+    return $this->offset;
+  }
+
   public function setViewer(PhabricatorUser $viewer) {
     $this->viewer = $viewer;
     return $this;
diff --git a/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php b/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php
--- a/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php
+++ b/src/applications/typeahead/storage/PhabricatorTypeaheadResult.php
@@ -69,6 +69,10 @@
     return $this;
   }
 
+  public function getName() {
+    return $this->name;
+  }
+
   public function getWireFormat() {
     $data = array(
       $this->name,