diff --git a/app/src/Locale/en_US/default.po b/app/src/Locale/en_US/default.po
index a081e416..c2fe138a 100644
--- a/app/src/Locale/en_US/default.po
+++ b/app/src/Locale/en_US/default.po
@@ -324,6 +324,9 @@ msgstr "Requested Reference ID must already be in use, or be the keyword 'new'"
 msgid "match.fd.action"
 msgstr "Action"
 
+msgid "match.fd.all"
+msgstr "All"
+
 msgid "match.fd.alphanumeric"
 msgstr "Alphanumeric"
 
@@ -462,9 +465,15 @@ msgstr "(Please select a value)"
 msgid "match.fd.sor"
 msgstr "System of Record"
 
+msgid "match.fd.sor.abbr"
+msgstr "SOR"
+
 msgid "match.fd.sorid"
 msgstr "System of Record ID"
 
+msgid "match.fd.sorid.abbr"
+msgstr "SOR ID"
+
 msgid "match.fd.status"
 msgstr "Status"
 
@@ -502,6 +511,9 @@ msgstr "There are no matchgrids currently defined."
 msgid "match.in.pagination.format"
 msgstr "Page {{page}} of {{pages}}, Viewing {{start}}-{{end}} of {{count}}"
 
+msgid "match.in.records.none"
+msgstr "No records to display"
+
 ### Operations (Commands)
 msgid "match.op.add.a"
 msgstr "Add New {0}"
@@ -518,6 +530,12 @@ msgstr "Build"
 msgid "match.op.build.confirm"
 msgstr "Are you sure you wish to (re)build this matchgrid?"
 
+msgid "match.op.clear"
+msgstr "Clear"
+
+msgid "match.op.clear.filters"
+msgstr "{0,plural,=1{Clear Filter} other{Clear Filters}}"
+
 msgid "match.op.configure.a"
 msgstr "Configure {0}"
 
@@ -545,6 +563,9 @@ msgstr "Edit {0}"
 msgid "match.op.first"
 msgstr "First"
 
+msgid "match.op.filter"
+msgstr "Filter"
+
 msgid "match.op.go"
 msgstr "Go"
 
diff --git a/app/src/Template/Element/javascript.ctp b/app/src/Template/Element/javascript.ctp
index ab33a6fb..f02d7f71 100644
--- a/app/src/Template/Element/javascript.ctp
+++ b/app/src/Template/Element/javascript.ctp
@@ -118,6 +118,50 @@
         $("#user-panel").hide();
       }
     });
+    
+    // TOP SEARCH FILTER FORM
+    // Send only non-empty fields in the form
+    $("#top-search-form").submit(function() {
+      $("#top-search-form *").filter(':input').each(function () {
+        if($(this).val() == '') {
+          $(this).prop('disabled',true);
+        }
+      });
+    });
+
+      // Toggle the top search filter box
+    $("#top-search-toggle, #top-search-toggle button.cm-toggle").click(function(e) {
+      e.preventDefault();
+      e.stopPropagation();
+      if ($("#top-search-fields").is(":visible")) {
+        $("#top-search-fields").hide();
+        $("#top-search-toggle button.cm-toggle").attr("aria-expanded","false");
+        $("#top-search-toggle button.cm-toggle .drop-arrow").text("arrow_drop_down");
+      } else {
+        $("#top-search-fields").show();
+        $("#top-search-toggle button.cm-toggle").attr("aria-expanded","true");
+        $("#top-search-toggle button.cm-toggle .drop-arrow").text("arrow_drop_up");
+      }
+    });
+
+    // Clear a specific top search filter by clicking the filter button
+    $("#top-search-toggle button.top-search-active-filter").click(function(e) {
+      e.preventDefault();
+      e.stopPropagation();
+      $(this).hide();
+      filterId = '#' + $(this).attr("aria-controls");
+      $(filterId).val("");
+      $(this).closest('form').submit();
+    });
+
+    // Clear all top filters from the filter bar
+    $("#top-search-clear-all-button").click(function(e) {
+      e.preventDefault();
+      e.stopPropagation();
+      $(this).hide();
+      $("#top-search-toggle .top-search-active-filter").hide();
+      $("#top-search-clear").click();
+    });
 
     // Accordion
     $(".accordion").accordion();
diff --git a/app/src/Template/Element/search.ctp b/app/src/Template/Element/search.ctp
new file mode 100644
index 00000000..3fcfc066
--- /dev/null
+++ b/app/src/Template/Element/search.ctp
@@ -0,0 +1,178 @@
+<?php
+/**
+ * COmanage Match Search Element
+ *
+ * Portions licensed to the University Corporation for Advanced Internet
+ * Development, Inc. ("UCAID") under one or more contributor license agreements.
+ * See the NOTICE file distributed with this work for additional information
+ * regarding copyright ownership.
+ *
+ * UCAID licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @link          http://www.internet2.edu/comanage COmanage Project
+ * @package       match
+ * @since         COmanage Match v1.0.0
+ * @license       Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
+ */
+
+use \Cake\Utility\Inflector;
+
+// Globals
+global $cm_lang, $cm_texts;
+
+// Get a pointer to our controller
+$controller = $this->request->getParam('controller');
+$req = Inflector::singularize($controller);
+  
+// Get the query string and separate the search params from the non-search params  
+$query = $this->request->getQueryParams();
+$non_search_params = array_diff_key($query, $vv_search_fields);
+$search_params = array_intersect_key($query, $vv_search_fields);
+     
+// Begin the form 
+print $this->Form->create($req, [
+  'id'   => 'top-search-form',
+  'type' => 'get',
+  'url'  => ['action' => $this->request->action]
+]);
+
+// Pass back the non-search params as hidden fields, but always exclude the page parameter
+// because we need to start new searches on page one (or we're likely to end up with a 404). 
+if(!empty($non_search_params)) {
+  foreach ($non_search_params as $param => $value) {
+    if($param != 'page') {
+      print $this->Form->hidden(filter_var($param, FILTER_SANITIZE_SPECIAL_CHARS), array('default' => filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS))) . "\n";
+    }  
+  }
+}  
+  
+// Boolean to distinguish between search filters and sort parameters
+// XXX may no longer need this 
+$hasActiveFilters = false;
+?>
+
+<div id="<?php print $req . ucfirst($this->request->action); ?>Search" class="top-search">
+  <fieldset onclick="event.stopPropagation();">
+    <legend id="top-search-toggle">
+      <em class="material-icons">search</em>
+      <?= __('match.op.filter');?>
+
+      <?php if(!empty($search_params)):?>
+        <span id="top-search-active-filters">
+        <?php foreach($search_params as $key => $params): ?>
+          <?php
+          // Construct aria-controls string
+          $key_fields = explode('.', $key);
+          $aria_controls = $key_fields[0] . ucfirst($key_fields[1]);
+
+          // We have active filters - not just a sort.
+          $hasActiveFilters = true;
+          ?>
+          <button class="top-search-active-filter deletebutton spin" type="button" aria-controls="<?php print $aria_controls; ?>" title="<?= __('match.op.clear.filters',[1]);?>">
+             <span class="top-search-active-filter-title"><?php print $vv_search_fields[$key]['label']; ?></span>
+             <span class="top-search-active-filter-value">
+               <?php
+                 $value = $params;
+                 // Get user friendly name from an Enumerator Class - XXX will need updating for Match if it becomes used
+                 // XXX How should we handle dynamic Enumerator lists?
+                 if(isset($vv_search_fields[$key]['enum'])
+                   && isset($cm_texts[ $cm_lang ][$vv_search_fields[$key]['enum']][$params])) {
+                   $value = $cm_texts[ $cm_lang ][$vv_search_fields[$key]['enum']][$params];
+                   print filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
+                   continue;
+                 }
+                 // Get user friendly name from the dropdown Select List
+                 // XXX Currently we do not have a use case where the grouping name would create a namespace
+                 if (isset($vv_search_fields[$key]['options'])) {
+                   // Outside of any groups
+                   if (isset($vv_search_fields[$key]['options'][$value])) {
+                     print filter_var($vv_search_fields[$key]['options'][$value], FILTER_SANITIZE_SPECIAL_CHARS);
+                   } else {
+                     // Inside a group
+                     foreach(array_keys($vv_search_fields[$key]['options']) as $optgroup) {
+                       if( is_array($vv_search_fields[$key]['options'][$optgroup])
+                         && isset($vv_search_fields[$key]['options'][$optgroup][$value]) ) {
+                         print filter_var($vv_search_fields[$key]['options'][$optgroup][$value], FILTER_SANITIZE_SPECIAL_CHARS);
+                         print $this->Html->tag('span','(' . $optgroup . ')', array('class' => 'ml-1') );
+                         break;
+                       }
+                     }
+                   }
+                 } else {
+                   print filter_var($value, FILTER_SANITIZE_SPECIAL_CHARS);
+                 }
+               ?>
+             </span>
+          </button>
+        <?php endforeach; ?>
+          <?php if($hasActiveFilters): ?>
+            <button id="top-search-clear-all-button" class="filter-clear-all-button spin btn" type="button" aria-controls="top-search-clear" onclick="event.stopPropagation()">
+              <?= __('match.op.clear.filters',[2]);?>
+           </button>
+          <?php endif; ?>
+      </span>
+      <?php endif; ?>
+      <button class="cm-toggle" aria-expanded="false" aria-controls="top-search-fields" type="button"><em class="material-icons drop-arrow">arrow_drop_down</em></button>
+    </legend>
+    <div id="top-search-fields">
+      <div id="top-search-fields-subgroups">
+      <?php
+        $i = 0;
+        $field_subgroup_columns = array();
+        $skippedFields = 0;
+        foreach($vv_search_fields as $key => $options) {
+          // Exclude explicitly skipped fields
+          if(array_key_exists('searchSkip',$options)) {
+            $skippedFields++;
+            continue;
+          }
+          $formParams = array(
+            'label' => !empty($options['searchLabel']) ? $options['searchLabel'] : $options['label'],
+            'type' => !empty($options['searchType']) ? $options['searchType'] : 'text',
+            'value' => (!empty($query[$key]) ? $query[$key] : ''),
+            'required' => false,
+          );
+          if(isset($options['searchEmpty'])) {
+            $formParams['empty'] = $options['searchEmpty'];
+          }
+          if(isset($options['searchOptions'])) {
+            $formParams['options'] = $options['searchOptions'];
+          }
+          
+          print $this->Form->input($key, $formParams);
+        }
+      ?>
+      </div>
+      <?php $rebalanceColumns = ((count($vv_search_fields)-$skippedFields) % 2 != 0) ? ' class="tss-rebalance"' : ''; ?>
+      <div id="top-search-submit"<?php print $rebalanceColumns ?>>
+        <?php
+          $args = array();
+          // search button (submit)
+          $args['id'] = 'top-search-filter-button';
+          $args['aria-label'] = __('match.op.filter');
+          $args['class'] = 'submit-button spin btn btn-primary';
+          print $this->Form->submit(__('match.op.filter'),$args);
+
+          // clear button
+          $args['id'] = 'top-search-clear';
+          $args['class'] = 'clear-button spin btn btn-default';
+          $args['aria-label'] = __('match.op.clear');
+          $args['onclick'] = 'clearTopSearch(this.form)';
+          print $this->Form->button(__('match.op.clear'),$args);
+        ?>
+      </div>
+    </div>
+  </fieldset>
+</div>
+
+<?php print $this->Form->end();?>
\ No newline at end of file
diff --git a/app/src/Template/MatchgridRecords/columns.inc b/app/src/Template/MatchgridRecords/columns.inc
index 3c7b4225..1a1f5f4e 100644
--- a/app/src/Template/MatchgridRecords/columns.inc
+++ b/app/src/Template/MatchgridRecords/columns.inc
@@ -28,21 +28,38 @@
 // We need to add matchgrid ID to all links (since ID is unique only within a matchgrid)
 // eg: matchgrid-records/edit/48?matchgrid_id=3
 $forcePrimaryLink = true;
+  
+// Turn on the search/filter box for this index view  
+// If $enableSearch is true, search input labels will be 'label' unless overridden by 'searchLabel'.
+// If $enableSearch is true, a specific field can be excluded from the search by adding 'searchSkip' => true.
+$enableSearch = true;
 
 // Note we don't need to do any special filtering, since MatchgridRecordsController will
 // force the model's table only to the one associated with the matchgrid
 $indexColumns = [
   'id' => [
-    'type' => 'echo'
+    'label'      => __('match.fd.id'),
+    'type'       => 'echo',
+    'searchSkip'  => true
   ],
   'sor' => [
-    'type' => 'echo'
+    'label'         => __('match.fd.sor.abbr'),
+    'type'          => 'echo',
+    'searchType'    => 'select',
+    'searchLabel'   => __('match.fd.sor'),
+    'searchEmpty'   => __('match.fd.all'),
+    'searchOptions' => $sor
   ],
   'sorid' => [
-    'type' => 'echo'
+    'label'      => __('match.fd.sorid.abbr'),
+    'type'       => 'echo',
+    'searchType' => 'text',
+    'searchLabel'   => __('match.fd.sorid')
   ],
   'referenceid' => [
-    'type' => 'echo'
+    'label'       => __('match.fd.referenceid'),
+    'type'        => 'echo',
+    'searchType'  => 'text'
   ]
 ];
 
@@ -51,7 +68,8 @@ foreach($attributes as $attr) {
   if($attr->index_display === true) {
     $indexColumns[$attr->name] = [
       'label' => $attr->name,
-      'type' => 'echo'
+      'type' => 'echo',
+      'searchType'  => 'text'
     ];
   }
 }
\ No newline at end of file
diff --git a/app/src/Template/Standard/index.ctp b/app/src/Template/Standard/index.ctp
index 64e5f20a..dc963384 100644
--- a/app/src/Template/Standard/index.ctp
+++ b/app/src/Template/Standard/index.ctp
@@ -42,6 +42,10 @@ $modelsName = $this->name;
 // $tablefk = model_id
 $tableFK = Inflector::singularize($vv_tablename) . "_id";
 
+// Do we have records for this index? This will be set to true during render if we do.
+// Otherwise, we'll print out a "no records" message.
+$recordsExist = false;  
+
 // Our default link actions, in order of preference, unless the column config overrides it
 $linkActions = ['edit', 'view'];
 
@@ -132,7 +136,13 @@ function _column_key($modelsName, $c, $tz=null) {
     </div>
   <?php endforeach; // $banners ?>
 <?php endif; // $banners ?>
-
+  
+<!-- Search block -->
+<?php if(!empty($enableSearch)): ?>  
+  <?= $this->element('search', ['vv_search_fields' => $indexColumns]); ?>
+<?php endif; // $enableSearch ?>
+  
+<!-- Index table -->  
 <div class="table-container">
   <table id="<?= $vv_tablename . '-table'; ?>">
     <tr>
@@ -316,7 +326,11 @@ function _column_key($modelsName, $c, $tz=null) {
         ?>
       </td>
     </tr>
+    <?php $recordsExist = true; ?>
   <?php endforeach; // $$vv_tablename ?>
+  <?php if(!$recordsExist): ?>
+    <tr><td colspan="<?= count($indexColumns); ?>"><?= __('match.in.records.none') ?></td></tr>
+  <?php endif; ?>
   </table>
 </div>
 
diff --git a/app/webroot/css/co-base.css b/app/webroot/css/co-base.css
index fe0576df..17f100ca 100644
--- a/app/webroot/css/co-base.css
+++ b/app/webroot/css/co-base.css
@@ -507,6 +507,135 @@ body.logged-in #top-menu {
   clear: both;
   margin-top: 1em;
 }
+/* TOP SEARCH */
+.top-search {
+  margin-top: 0.5em;
+  padding: 0;
+}
+.top-search legend .material-icons {
+  vertical-align: middle;
+}
+.top-search legend button.cm-toggle {
+  position: absolute;
+  right: 0.5em;
+  border: none;
+  background-color: transparent;
+}
+#content .top-search legend .material-icons {
+  font-size: 20px;
+}
+#content .top-search legend button.cm-toggle .material-icons {
+  font-size: 34px;
+  line-height: 17px;
+}
+.top-search fieldset {
+  clear: both;
+  position: relative;
+  padding: 0.5em 0.5em 0;
+  margin: 0;
+  background-color: #f5f5f5;
+}
+.top-search.open fieldset {
+  padding-bottom: 0.5em;
+}
+.top-search legend {
+  width: 100%;
+  background-color: #f5f5f5;
+  margin: 0 -0.5em;
+  padding: 0.5em 0.5em 0;
+  line-height: 1.8em;
+  cursor: pointer;
+  font-size: 1em;
+  box-sizing: content-box;
+}
+#top-search-fields {
+  display: none;
+  padding: 0.25em 0.5em;
+}
+#top-search-submit {
+  float: right;
+  width: 200px;
+  margin-bottom: 0.75em;
+}
+.top-search input[type=text],
+.top-search select,
+.side-search input[type=text],
+.side-search select {
+  width: 100%;
+  box-sizing: border-box;
+  margin: 0 0 0.5em 0;
+  height: 28px;
+  padding: 2px 4px;
+  border: 1px solid #ddd;
+  background-color: #fff;
+}
+.top-search label {
+  margin-bottom: 0;
+}
+::-webkit-input-placeholder,
+::-moz-placeholder,
+:-ms-input-placeholder,
+:-moz-placeholder {
+  opacity: 0.2;
+}
+.top-search input[type=text]:focus,
+.side-search input[type=text]:focus {
+  background-color: #ffd;
+}
+.top-search .submit-button,
+.top-search .clear-button,
+.side-search .submit-button,
+.side-search .clear-button {
+  float: right;
+  font-size: 0.9em;
+  width: 80px;
+  line-height: 28px;
+  height: 28px;
+  margin: 1em 0.5em;
+  padding: 0;
+}
+.top-search .top-search-checkboxes {
+  margin-top: 0.5em;
+}
+.top-search .filter-clear-all-button {
+  font-size: 0.9em;
+  width: auto;
+  height: auto;
+  line-height: unset;
+  margin: 0;
+  padding: 0 1em;
+}
+.top-search .filter-clear-all-button:hover {
+  background-color: unset;
+}
+.top-search.top-search-hide-fields label,
+.side-search label {
+  display: none;
+}
+#top-search-active-filters {
+  margin-left: 1em;
+  padding: 0.5em 0;
+}
+.top-search-active-filter {
+  margin: 0 0.25em 0 0;
+  white-space: nowrap;
+  /*color: #b00;*/
+  font-style: italic;
+}
+.top-search-active-filter-title::after {
+  content: ": ";
+}
+.top-search-active-filter-title.no-value::after {
+  content: none;
+}
+.top-search-active-filters-remove button {
+  margin-left: 2em;
+  font-size: 0.9em;
+  background-color: #f5f5f5;
+}
+.top-search-active-filters-remove button:hover {
+  background-color: #eee;
+}
 /* RECONCILE TABLE */
 #reconcile-table {
   width: auto;
diff --git a/app/webroot/css/co-responsive.css b/app/webroot/css/co-responsive.css
index 03cfd923..dfadf3ad 100644
--- a/app/webroot/css/co-responsive.css
+++ b/app/webroot/css/co-responsive.css
@@ -191,6 +191,17 @@
   .call-to-action {
     margin-bottom: 0;
   }
+  /* TOP SEARCH */
+  #top-search-fields-subgroups {
+    display: grid;
+    grid-template-columns: 1fr 1fr;
+    column-gap: 1em;
+    padding: 0 0.5em;
+  }
+  #top-search-submit.tss-rebalance {
+    margin-top: -3.5em;
+    position: relative;
+  }
   /* MATCHGRID MANAGEMENT */
   #matchgrid-config-menu {
     display: grid;
diff --git a/app/webroot/js/comanage.js b/app/webroot/js/comanage.js
index 34a55626..213c7fd1 100644
--- a/app/webroot/js/comanage.js
+++ b/app/webroot/js/comanage.js
@@ -226,3 +226,13 @@ function limitPage(pageLimit,recordCount,currentPage) {
   window.location = currentUrl;
 }
 
+// Clear the top search form for index views
+// formObj         - form object (DOM form obj, required)
+function clearTopSearch(formObj) {
+  for (var i=0; i<formObj.elements.length; i++) {
+    if(formObj.elements[i].type != 'hidden') {
+      formObj.elements[i].disabled = true;  
+    }
+  }
+  formObj.submit();
+}