diff --git a/modules/project_browser_source_example/src/Plugin/ProjectBrowserSource/ProjectBrowserSourceExample.php b/modules/project_browser_source_example/src/Plugin/ProjectBrowserSource/ProjectBrowserSourceExample.php
index 375f6b633a303d347d70cdf5738477e0de2a98f3..6b82afc8fa64317e1a42ace13d7eaa977f5523dd 100644
--- a/modules/project_browser_source_example/src/Plugin/ProjectBrowserSource/ProjectBrowserSourceExample.php
+++ b/modules/project_browser_source_example/src/Plugin/ProjectBrowserSource/ProjectBrowserSourceExample.php
@@ -6,6 +6,8 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Url;
 use Drupal\project_browser\Attribute\ProjectBrowserSource;
 use Drupal\project_browser\Plugin\ProjectBrowserSourceBase;
+use Drupal\project_browser\ProjectBrowser\Filter\MultipleChoiceFilter;
+use Drupal\project_browser\ProjectBrowser\Filter\TextFilter;
 use Drupal\project_browser\ProjectBrowser\Project;
 use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -166,21 +168,18 @@ final class ProjectBrowserSourceExample extends ProjectBrowserSourceBase {
   /**
    * {@inheritdoc}
    */
-  protected function getCategories(): array {
-    // Step 1: The list of categories that the modules can have. The array
-    // must have the "id" and "name" properties defined. If your source data
-    // does not use categories then return NULL. These can be
-    // hardcoded, come from REST or GraphQL endpoints, etc.
-    return [
-      [
-        'id' => 'cat_1',
-        'name' => 'Category 1',
-      ],
-      [
-        'id' => 'cat_2',
-        'name' => 'Category 2',
-      ],
+  public function getFilterDefinitions(): array {
+    $filters = [
+      'search' => new TextFilter('', $this->t('Search'), NULL),
     ];
+
+    $categories = [
+      'cat_1' => 'Category 1',
+      'cat_2' => 'Category 2',
+    ];
+    $filters['categories'] = new MultipleChoiceFilter($categories, [], $this->t('Categories'), $this->t('Categories'), NULL);
+
+    return $filters;
   }
 
 }
diff --git a/src/Plugin/DrupalDotOrgSourceBase.php b/src/Plugin/DrupalDotOrgSourceBase.php
index 359ec51dc7cbcd08237fb3011ce977140e4f6839..f3c3723e32920068d533ffcad7286c0013417de4 100644
--- a/src/Plugin/DrupalDotOrgSourceBase.php
+++ b/src/Plugin/DrupalDotOrgSourceBase.php
@@ -13,6 +13,8 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\Url;
 use Drupal\project_browser\ProjectBrowser\Filter\BooleanFilter;
+use Drupal\project_browser\ProjectBrowser\Filter\MultipleChoiceFilter;
+use Drupal\project_browser\ProjectBrowser\Filter\TextFilter;
 use Drupal\project_browser\ProjectBrowser\Project;
 use GuzzleHttp\ClientInterface;
 use Psr\Log\LoggerInterface;
@@ -208,8 +210,16 @@ abstract class DrupalDotOrgSourceBase extends ProjectBrowserSourceBase implement
    * {@inheritdoc}
    */
   public function getFilterDefinitions(): array {
-    $filters = parent::getFilterDefinitions();
+    $filters = [
+      'search' => new TextFilter('', $this->t('Search'), NULL),
+    ];
 
+    $filters['categories'] = new MultipleChoiceFilter(
+      $this->getCategories(),
+      [], $this->t('Categories'),
+      $this->t('Categories'),
+      NULL,
+    );
     $filters['security_advisory_coverage'] = new BooleanFilter(
       TRUE,
       $this->t('Show projects covered by a security policy'),
@@ -256,10 +266,7 @@ abstract class DrupalDotOrgSourceBase extends ProjectBrowserSourceBase implement
     $categories = [];
     if ($result['code'] == Response::HTTP_OK && !empty($result['data'])) {
       foreach ($result['data'] as $item) {
-        $categories[] = [
-          'id' => $item['id'],
-          'name' => $item['attributes']['name'],
-        ];
+        $categories[$item['id']] = $item['attributes']['name'];
       }
     }
     $this->cacheBin->set($cid, $categories);
diff --git a/src/Plugin/ProjectBrowserSource/DrupalCore.php b/src/Plugin/ProjectBrowserSource/DrupalCore.php
index d71c36c67e78af4cbc5729b25f78af05fa9a6203..0909ff80323313279892b30bb202ca070531b838 100644
--- a/src/Plugin/ProjectBrowserSource/DrupalCore.php
+++ b/src/Plugin/ProjectBrowserSource/DrupalCore.php
@@ -10,6 +10,8 @@ use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\Core\Url;
 use Drupal\project_browser\Attribute\ProjectBrowserSource;
 use Drupal\project_browser\Plugin\ProjectBrowserSourceBase;
+use Drupal\project_browser\ProjectBrowser\Filter\MultipleChoiceFilter;
+use Drupal\project_browser\ProjectBrowser\Filter\TextFilter;
 use Drupal\project_browser\ProjectBrowser\Project;
 use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -84,16 +86,20 @@ final class DrupalCore extends ProjectBrowserSourceBase {
   /**
    * {@inheritdoc}
    */
-  protected function getCategories(): array {
+  public function getFilterDefinitions(): array {
+    $filters = [
+      'search' => new TextFilter('', $this->t('Search'), NULL),
+    ];
+
     $categories = [];
     foreach ($this->getCoreModules() as $module) {
-      $categories[$module->info['package']] = [
-        'name' => $module->info['package'],
-        'id' => $module->info['package'],
-      ];
+      $package = $module->info['package'];
+      $categories[$package] = $package;
     }
-    usort($categories, fn($a, $b) => $a['id'] <=> $b['id']);
-    return $categories;
+    asort($categories, SORT_NATURAL);
+    $filters['categories'] = new MultipleChoiceFilter($categories, [], $this->t('Categories'), $this->t('Categories'), NULL);
+
+    return $filters;
   }
 
   /**
diff --git a/src/Plugin/ProjectBrowserSourceBase.php b/src/Plugin/ProjectBrowserSourceBase.php
index c51c6c1fd8b780307192316bf21ff20c9523cebf..e5b198f5e98fb3a5606d70a5fd1177df2b4f6901 100644
--- a/src/Plugin/ProjectBrowserSourceBase.php
+++ b/src/Plugin/ProjectBrowserSourceBase.php
@@ -5,8 +5,6 @@ namespace Drupal\project_browser\Plugin;
 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
 use Drupal\Core\Plugin\PluginBase;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\project_browser\ProjectBrowser\Filter\MultipleChoiceFilter;
-use Drupal\project_browser\ProjectBrowser\Filter\TextFilter;
 use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage;
 
 /**
@@ -20,35 +18,6 @@ abstract class ProjectBrowserSourceBase extends PluginBase implements ProjectBro
 
   use StringTranslationTrait;
 
-  /**
-   * {@inheritdoc}
-   */
-  public function getFilterDefinitions(): array {
-    $filters = [
-      'search' => new TextFilter('', $this->t('Search'), NULL),
-    ];
-
-    $categories = $this->getCategories();
-    if (is_array($categories)) {
-      $choices = array_combine(
-        array_column($categories, 'id'),
-        array_column($categories, 'name'),
-      );
-      $filters['categories'] = new MultipleChoiceFilter($choices, [], $this->t('Categories'), $this->t('Categories'), NULL);
-    }
-    return $filters;
-  }
-
-  /**
-   * Gets a list of all available categories.
-   *
-   * @return array
-   *   List of categories.
-   */
-  protected function getCategories(): ?array {
-    return NULL;
-  }
-
   /**
    * {@inheritdoc}
    */
diff --git a/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php b/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php
index f4f12a6cd8d8b69daf84c3e8dbd9e3e5225f60a7..ec7a7c0aa5faaf8217bfa99a2e8357c9fffe2878 100644
--- a/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php
+++ b/tests/modules/project_browser_test/src/Plugin/ProjectBrowserSource/ProjectBrowserTestMock.php
@@ -14,6 +14,8 @@ use Drupal\Core\Url;
 use Drupal\project_browser\Attribute\ProjectBrowserSource;
 use Drupal\project_browser\Plugin\ProjectBrowserSourceBase;
 use Drupal\project_browser\ProjectBrowser\Filter\BooleanFilter;
+use Drupal\project_browser\ProjectBrowser\Filter\MultipleChoiceFilter;
+use Drupal\project_browser\ProjectBrowser\Filter\TextFilter;
 use Drupal\project_browser\ProjectBrowser\Project;
 use Drupal\project_browser\ProjectBrowser\ProjectsResultsPage;
 use Psr\Log\LoggerInterface;
@@ -261,7 +263,16 @@ final class ProjectBrowserTestMock extends ProjectBrowserSourceBase {
    * {@inheritdoc}
    */
   public function getFilterDefinitions(): array {
-    $filters = parent::getFilterDefinitions();
+    $filters = [
+      'search' => new TextFilter('', $this->t('Search'), NULL),
+    ];
+
+    $categories = array_values($this->getCategoryData());
+    $categories = array_combine(
+      array_column($categories, 'id'),
+      array_column($categories, 'name'),
+    );
+    $filters['categories'] = new MultipleChoiceFilter($categories, [], $this->t('Categories'), $this->t('Categories'), NULL);
 
     $filters['security_advisory_coverage'] = new BooleanFilter(
       TRUE,
@@ -298,14 +309,6 @@ final class ProjectBrowserTestMock extends ProjectBrowserSourceBase {
     return $filters;
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function getCategories(): array {
-    // Rekey the array to avoid JSON considering it an object.
-    return array_values($this->getCategoryData());
-  }
-
   /**
    * {@inheritdoc}
    */