diff --git a/project_browser.routing.yml b/project_browser.routing.yml
index 1529306129146c832815d6a2af46926080d9bdcf..5c15191d0a7ff7c65b3a6d3e180c7251cc90ebf1 100644
--- a/project_browser.routing.yml
+++ b/project_browser.routing.yml
@@ -19,11 +19,11 @@ project_browser.api_project_get_all:
   #options:
   #  no_cache: 'TRUE'
 project_browser.browse:
-  path: '/admin/modules/browse/{module_name}'
+  path: '/admin/modules/browse/{id}'
   defaults:
     _controller: '\Drupal\project_browser\Controller\BrowserController::browse'
     _title: 'Browse projects'
-    module_name: ''
+    id: null
   requirements:
     _permission: 'administer modules'
 project_browser.settings:
diff --git a/src/Controller/BrowserController.php b/src/Controller/BrowserController.php
index 7ddf97e6c7d2196ee223bac4a306c5c85794bc32..da1d0fbcd57543857734e36d8e2dbc7b2d1c4acd 100644
--- a/src/Controller/BrowserController.php
+++ b/src/Controller/BrowserController.php
@@ -63,18 +63,18 @@ class BrowserController extends ControllerBase {
    * rendered. For example, 'https//drupal-site/admin/modules/browse/ctools'
    * will display the details for ctools.
    *
-   * @param string $module_name
-   *   Module for which the detailed page is built.
+   * @param string|null $id
+   *   The project ID, if any.
    *
    * @return array
    *   A render array.
    */
-  public function browse($module_name) {
+  public function browse(?string $id = NULL) {
     $request = $this->requestStack->getCurrentRequest();
     $current_sources = $this->enabledSource->getCurrentSources();
     $ui_install_enabled = (bool) $this->config('project_browser.admin_settings')->get('allow_ui_install') && (bool) $this->installReadiness;
 
-    if (!empty($current_sources['drupalorg_mockapi']) && !$module_name) {
+    if (array_key_exists('drupalorg_mockapi', $current_sources) && empty($id)) {
       $this->messenger()
         ->addStatus($this->t('Project Browser is currently a prototype, and the projects listed may not be up to date with Drupal.org. For the most updated list of projects, visit <a href=":url">:url</a>', [':url' => 'https://www.drupal.org/project/project_module']))
         ->addStatus($this->t('Your feedback and input are welcome at <a href=":url">:url</a>', [':url' => 'https://www.drupal.org/project/issues/project_browser']));
diff --git a/src/Controller/ProjectBrowserEndpointController.php b/src/Controller/ProjectBrowserEndpointController.php
index 3d0bcfcfd492a494be2a5fd73e1b5a5a7ec4a9e3..e0798b29608c5c1763f45781e470af261cd8bef3 100644
--- a/src/Controller/ProjectBrowserEndpointController.php
+++ b/src/Controller/ProjectBrowserEndpointController.php
@@ -45,11 +45,30 @@ class ProjectBrowserEndpointController extends ControllerBase {
    *   Typically a project listing.
    */
   public function getAllProjects(Request $request) {
+    $id = $request->query->get('id');
+    if ($id) {
+      return new JsonResponse($this->enabledSource->getStoredProject($id));
+    }
+
     $current_sources = $this->enabledSource->getCurrentSources();
     if (!$current_sources) {
       return new JsonResponse([], Response::HTTP_ACCEPTED);
     }
 
+    $query = $this->buildQuery($request);
+    return new JsonResponse($this->enabledSource->getProjects($query));
+  }
+
+  /**
+   * Builds the query based on the current request.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request.
+   *
+   * @return array
+   *   See \Drupal\project_browser\EnabledSourceHandler::getProjects().
+   */
+  private function buildQuery(Request $request): array {
     // Validate and build query.
     $query = [
       'page' => (int) $request->query->get('page', 0),
@@ -101,7 +120,7 @@ class ProjectBrowserEndpointController extends ControllerBase {
       $query['tabwise_categories'] = $tabwise_categories;
     }
 
-    return new JsonResponse($this->enabledSource->getProjects($query));
+    return $query;
   }
 
   /**
diff --git a/src/EnabledSourceHandler.php b/src/EnabledSourceHandler.php
index 79779f855449e11ff9bf4d0c24e9380779fbb445..2a56fac2041c0c798a7bf7e49e9522142ccccd55 100644
--- a/src/EnabledSourceHandler.php
+++ b/src/EnabledSourceHandler.php
@@ -119,8 +119,13 @@ class EnabledSourceHandler implements LoggerAwareInterface, EventSubscriberInter
           // name, which are unlikely to change. This isn't security-sensitive,
           // so SHA1 is okay for this purpose.
           $project->id = sha1($source_id . $project->packageName . $project->machineName);
+          // Remember the ID of the source plugin that exposed this project,
+          // since that information might be needed by the front-end.
+          $project->source = $source_id;
+
           $this->keyValue->setIfNotExists($project->id, $project);
-          // Add activation data to the project.
+          // Add activation data to the project. This is volatile and should not
+          // be changed.
           $this->getActivationData($project);
         }
         // Store each source's results for this query as a set of arguments to
@@ -189,10 +194,10 @@ class EnabledSourceHandler implements LoggerAwareInterface, EventSubscriberInter
   }
 
   /**
-   * Looks up a previously stored project by its UUID.
+   * Looks up a previously stored project by its ID.
    *
-   * @param string $uuid
-   *   The project UUID. See ::getProjects() for where this is set.
+   * @param string $id
+   *   The project ID. See ::getProjects() for where this is set.
    *
    * @return \Drupal\project_browser\ProjectBrowser\Project
    *   The project object, with activation status and commands added.
@@ -200,8 +205,8 @@ class EnabledSourceHandler implements LoggerAwareInterface, EventSubscriberInter
    * @throws \RuntimeException
    *   Thrown if the project is not found in the non-volatile data store.
    */
-  public function getStoredProject(string $uuid): Project {
-    $project = $this->keyValue->get($uuid) ?? throw new \RuntimeException("Project '$uuid' was not found in non-volatile storage.");
+  public function getStoredProject(string $id): Project {
+    $project = $this->keyValue->get($id) ?? throw new \RuntimeException("Project '$id' was not found in non-volatile storage.");
     $this->getActivationData($project);
     return $project;
   }
diff --git a/src/ProjectBrowser/Project.php b/src/ProjectBrowser/Project.php
index fc8370593af593a262d1f99eb710312f4d4634d0..f861e4ed31755df5f7a33c82e6df931da1597e91 100644
--- a/src/ProjectBrowser/Project.php
+++ b/src/ProjectBrowser/Project.php
@@ -17,15 +17,30 @@ class Project implements \JsonSerializable {
   /**
    * A persistent ID for this project in non-volatile storage.
    *
+   * This property is internal and should be ignored by source plugins.
+   *
    * @var string
    *
    * @see \Drupal\project_browser\EnabledSourceHandler::getProjects()
    */
   public string $id;
 
+  /**
+   * The ID of the source plugin which exposed this project.
+   *
+   * This property is internal and should be ignored by source plugins.
+   *
+   * @var string
+   *
+   * @see \Drupal\project_browser\EnabledSourceHandler::getProjects()
+   */
+  public string $source;
+
   /**
    * The status of this project in the current site.
    *
+   * This property is internal and should be ignored by source plugins.
+   *
    * @var \Drupal\project_browser\ActivationStatus
    */
   public ActivationStatus $status;
@@ -33,6 +48,8 @@ class Project implements \JsonSerializable {
   /**
    * The instructions, if any, to activate this project.
    *
+   * This property is internal and should be ignored by source plugins.
+   *
    * @var string|\Drupal\Core\Url|null
    *
    * @see \Drupal\project_browser\ActivatorInterface::getInstructions()
@@ -187,6 +204,7 @@ class Project implements \JsonSerializable {
       'selector_id' => $this->getSelectorId(),
       'commands' => $commands,
       'id' => $this->id,
+      'source' => $this->source,
     ];
   }
 
diff --git a/sveltejs/public/build/bundle.js b/sveltejs/public/build/bundle.js
index 62ff98b2c5c813145a0aa6005f6d239811e13236..cb9f528c49d6673100aae26249b18ef014e4a50a 100644
Binary files a/sveltejs/public/build/bundle.js and b/sveltejs/public/build/bundle.js differ
diff --git a/sveltejs/public/build/bundle.js.map b/sveltejs/public/build/bundle.js.map
index f3937ede71587d42c8dff0624832aee1c020be67..aa1574549a7b322482df69a1a5d9b7d3642cb6b2 100644
Binary files a/sveltejs/public/build/bundle.js.map and b/sveltejs/public/build/bundle.js.map differ
diff --git a/sveltejs/src/App.svelte b/sveltejs/src/App.svelte
index 63d31fa3e58bb83a669a5a67bdce2f319bf8adab..6bcf837daf555fbf980e872527a9a47a1ed27bb3 100644
--- a/sveltejs/src/App.svelte
+++ b/sveltejs/src/App.svelte
@@ -8,7 +8,7 @@
   const matches = window.location.pathname.match(
     /\/admin\/modules\/browse\/([^/]+)/,
   );
-  const moduleName = matches ? matches[1] : null;
+  const projectId = matches ? matches[1] : null;
 
   let loading = true;
   let data;
@@ -18,20 +18,11 @@
     loading = true;
     const res = await fetch(url);
     if (res.ok) {
-      data = await res.json();
-      Object.entries(data).forEach((item) => {
-        const [source, result] = item;
-        if (result.totalResults !== 0) {
-          $activeTab = source;
-          [project] = result.list;
-          projectExists = true;
-        }
-      });
+      project = await res.json();
+      $activeTab = project.source;
+      projectExists = true;
     }
     loading = false;
-    if (!projectExists) {
-      $searchString = moduleName;
-    }
     return project;
   }
 
@@ -42,10 +33,10 @@
   }
 </script>
 
-{#if !moduleName}
+{#if !projectId}
   <ProjectBrowser />
 {:else}
-  {#await load(`${ORIGIN_URL}/drupal-org-proxy/project?machine_name=${moduleName}`)}
+  {#await load(`${ORIGIN_URL}/drupal-org-proxy/project?id=${projectId}`)}
     {#if loading}
       <Loading />
     {/if}
diff --git a/sveltejs/src/Project/Project.svelte b/sveltejs/src/Project/Project.svelte
index 56af1bf24b711195ada1d3b23dc415dc2f948128..bc0fd76b5b78b5b65b92751c90424cdee9ffcad0 100644
--- a/sveltejs/src/Project/Project.svelte
+++ b/sveltejs/src/Project/Project.svelte
@@ -33,7 +33,7 @@
       <a
         id="{project.project_machine_name}_title"
         class="pb-project__link"
-        href="{ORIGIN_URL}/admin/modules/browse/{project.project_machine_name}"
+        href="{ORIGIN_URL}/admin/modules/browse/{project.id}"
         rel="noreferrer">{project.title}</a
       >
     </h3>
diff --git a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
index 4064035b2a7be0115cb5b208fc3e35598856bd7f..c230f9781a58d5e8418fc4e3db2b05e170b62855 100644
--- a/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
+++ b/tests/src/FunctionalJavascript/ProjectBrowserUiTest.php
@@ -590,7 +590,8 @@ class ProjectBrowserUiTest extends WebDriverTestBase {
     $this->svelteInitHelper('text', 'Helvetica');
     $page->clickLink('Helvetica');
     $this->assertTrue($assert_session->waitForText('By Hel Vetica'));
-    $assert_session->addressEquals('admin/modules/browse/helvetica');
+    // cspell:disable-next-line
+    $assert_session->addressEquals('/admin/modules/browse/' . sha1('drupalorg_mockapidrupal/helveticahelvetica'));
     $page->clickLink('Back to Browsing');
     $assert_session->addressEquals('admin/modules/browse');
   }