diff --git a/core/modules/user/src/Form/UserPermissionsForm.php b/core/modules/user/src/Form/UserPermissionsForm.php
index 1ce0e117e0..4fc71500aa 100644
--- a/core/modules/user/src/Form/UserPermissionsForm.php
+++ b/core/modules/user/src/Form/UserPermissionsForm.php
@@ -109,6 +109,26 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#type' => 'system_compact_link',
     ];
 
+    $form['filters'] = [
+      '#type' => 'container',
+      '#attributes' => [
+        'class' => ['table-filter', 'js-show'],
+      ],
+    ];
+
+    $form['filters']['text'] = [
+      '#type' => 'search',
+      '#title' => $this->t('Filter permissions'),
+      '#size' => 30,
+      '#placeholder' => $this->t('Filter by name'),
+      '#description' => $this->t('Enter a part of the permission name'),
+      '#attributes' => [
+        'class' => ['table-filter-text'],
+        'data-table' => '#permissions',
+        'autocomplete' => 'off',
+      ],
+    ];
+
     $form['permissions'] = [
       '#type' => 'table',
       '#header' => [$this->t('Permission')],
@@ -116,6 +136,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#attributes' => ['class' => ['permissions', 'js-permissions']],
       '#sticky' => TRUE,
     ];
+
     foreach ($role_names as $name) {
       $form['permissions']['#header'][] = [
         'data' => $name,
@@ -165,7 +186,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         ];
         $form['permissions'][$perm]['description'] = [
           '#type' => 'inline_template',
-          '#template' => '<div class="permission"><span class="title">{{ title }}</span>{% if description or warning %}<div class="description">{% if warning %}<em class="permission-warning">{{ warning }}</em> {% endif %}{{ description }}</div>{% endif %}</div>',
+          '#template' => '<div class="permission"><span class="title table-filter-text-source">{{ title }}</span>{% if description or warning %}<div class="description">{% if warning %}<em class="permission-warning">{{ warning }}</em> {% endif %}{{ description }}</div>{% endif %}</div>',
           '#context' => [
             'title' => $perm_item['title'],
           ],
diff --git a/core/modules/user/user.permissions.es6.js b/core/modules/user/user.permissions.es6.js
index 7332ea35d0..db2f698253 100644
--- a/core/modules/user/user.permissions.es6.js
+++ b/core/modules/user/user.permissions.es6.js
@@ -68,6 +68,65 @@
           // Re-insert the table into the DOM.
           $ancestor[method]($table);
         });
+
+      const $input = $('input.table-filter-text').once('table-filter-text');
+      const $table = $($input.attr('data-table'));
+      let $rowsAndDetails;
+      let $rows;
+      let $details;
+      let searching = false;
+
+      function filterPermissionList(e) {
+        const query = $(e.target)
+          .val()
+          .toLowerCase();
+
+        function showPermissionRow(index, row) {
+          const $row = $(row);
+          const $sources = $row.find('.table-filter-text-source');
+          const textMatch =
+            $sources
+              .text()
+              .toLowerCase()
+              .indexOf(query) !== -1;
+          $row.closest('tr').toggle(textMatch);
+        }
+        // Search over all rows and packages.
+        $rowsAndDetails.show();
+
+        // Filter if the length of the query is at least 2 characters.
+        if (query.length >= 2) {
+          searching = true;
+          $rows.each(showPermissionRow);
+
+          // Note that we first open all <details> to be able to use ':visible'.
+          // Mark the <details> elements that were closed before filtering, so
+          // they can be closed again when filtering is removed.
+          $details
+            .not('[open]')
+            .attr('data-drupal-system-state', 'forced-open');
+
+          // Hide the package <details> if they don't have any visible rows.
+          // Note that we first show() all <details> to be able to use ':visible'.
+          $details.attr('open', true).each(hidePackageDetails);
+        } else if (searching) {
+          searching = false;
+          $rowsAndDetails.show();
+          // Return <details> elements that had been closed before filtering
+          // to a closed state.
+          $details
+            .filter('[data-drupal-system-state="forced-open"]')
+            .removeAttr('data-drupal-system-state')
+            .attr('open', false);
+        }
+      }
+
+      if ($table.length) {
+        $rowsAndDetails = $table.find('tr, details');
+        $rows = $table.find('tbody tr');
+        $details = $rowsAndDetails.filter('.package-listing');
+        $input.on('keyup', filterPermissionList);
+      }
     },
 
     /**
diff --git a/core/modules/user/user.permissions.js b/core/modules/user/user.permissions.js
index f0e6a2ac71..c3beb07f24 100644
--- a/core/modules/user/user.permissions.js
+++ b/core/modules/user/user.permissions.js
@@ -30,6 +30,47 @@
 
         $ancestor[method]($table);
       });
+
+      var $input = $('input.table-filter-text').once('table-filter-text');
+      var $table = $($input.attr('data-table'));
+      var $rowsAndDetails = void 0;
+      var $rows = void 0;
+      var $details = void 0;
+      var searching = false;
+
+      function filterPermissionList(e) {
+        var query = $(e.target).val().toLowerCase();
+
+        function showPermissionRow(index, row) {
+          var $row = $(row);
+          var $sources = $row.find('.table-filter-text-source');
+          var textMatch = $sources.text().toLowerCase().indexOf(query) !== -1;
+          $row.closest('tr').toggle(textMatch);
+        }
+
+        $rowsAndDetails.show();
+
+        if (query.length >= 2) {
+          searching = true;
+          $rows.each(showPermissionRow);
+
+          $details.not('[open]').attr('data-drupal-system-state', 'forced-open');
+
+          $details.attr('open', true).each(hidePackageDetails);
+        } else if (searching) {
+          searching = false;
+          $rowsAndDetails.show();
+
+          $details.filter('[data-drupal-system-state="forced-open"]').removeAttr('data-drupal-system-state').attr('open', false);
+        }
+      }
+
+      if ($table.length) {
+        $rowsAndDetails = $table.find('tr, details');
+        $rows = $table.find('tbody tr');
+        $details = $rowsAndDetails.filter('.package-listing');
+        $input.on('keyup', filterPermissionList);
+      }
     },
     toggle: function toggle() {
       var authCheckbox = this;
