diff --git a/experience_builder.api.php b/experience_builder.api.php
index 3713bc8a547d4f4bd6192f2b0c4143af22c6db1d..de1f1ddbf1af377861f0cb74c6dce10965ea77a6 100644
--- a/experience_builder.api.php
+++ b/experience_builder.api.php
@@ -59,6 +59,8 @@ use Drupal\experience_builder\PropShape\CandidateStorablePropShape;
  * XB Extensions makes additional functionalities and customization points
  * available for extending the Experience Builder module.
  *
+ * XB Extensions can only be defined in modules. Themes are not supported.
+ *
  * Any library with `drupalSettings.xbExtension` will be identified as an
  * Experience Builder extension and will be loaded with the UI. Be sure
  * to add `experience_builder/ui` as a dependency.
@@ -71,9 +73,14 @@ use Drupal\experience_builder\PropShape\CandidateStorablePropShape;
  *      attributes: { type: module }
  *  drupalSettings:
  *    xbExtension:
- *      testExtension: { id: 'experience-builder-test-extension', name: 'XB Test Extension' }
+ *      testExtension: {
+ *        id: 'experience-builder-test-extension',
+ *        name: 'XB Test Extension',
+ *        description: 'A test extension for Experience Builder.',
+ *        imgSrc: 'relative/path/from/your/module/optionalImage.png'
+ *      }
  *  dependencies:
- *    - experience_builder/ui
+ *    - experience_builder/xb-ui
  *
  * @see tests/modules/xb_test_extension/ui/index.jsx for how to wrap your
  * React Application so it has access to Experience Builder UI APIs
diff --git a/experience_builder.extensions.inc b/experience_builder.extensions.inc
new file mode 100644
index 0000000000000000000000000000000000000000..74457e03b7c3dd4142039ff8a8aa61abf853fb9c
--- /dev/null
+++ b/experience_builder.extensions.inc
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Hook implementations for Experience Builder extensions.
+ */
+
+use Drupal\Core\Discovery\YamlDiscovery;
+use Drupal\Core\Url;
+
+/**
+ * Adds libraries that extend Experience Builder to a centralized library.
+ *
+ * @param array $libraries
+ *   The libraries array.
+ */
+function _experience_builder_build_extension_libraries(array &$libraries): void {
+  $core_discovery = new YamlDiscovery('libraries', \Drupal::service('module_handler')->getModuleDirectories());
+
+  $xb_extensions = [];
+
+  // Get all libraries with xbExtension defined in drupalSettings.
+  foreach ($core_discovery->findAll() as $module_name => $module_libraries) {
+    foreach ($module_libraries as $library_name => $library) {
+      if (isset($library['drupalSettings']['xbExtension'])) {
+        $xb_extensions[] = $module_name . '/' . $library_name;
+      }
+    }
+  }
+
+  // Add the libraries as dependencies of experience_builder/extensions.
+  if (!empty($xb_extensions)) {
+    $libraries['extensions'] = [
+      'dependencies' => $xb_extensions,
+    ];
+  }
+}
+
+/**
+ * Process module paths for all extensions.
+ *
+ * This is called from experience_builder_library_info_alter()
+ * in the main module file.
+ */
+function _experience_builder_process_extensions_paths(array &$libraries, string $extension): void {
+  $module_path_service = \Drupal::service('extension.list.module');
+
+  // Find all libraries that specify xbExtension in drupalSettings and provide
+  // default values and image paths.
+  foreach ($libraries as &$library) {
+    if (!isset($library['drupalSettings']['xbExtension'])) {
+      continue;
+    }
+
+    foreach ($library['drupalSettings']['xbExtension'] as &$extension_settings) {
+      $module_path = $module_path_service->getPath($extension);
+      $extension_settings['modulePath'] = $module_path;
+
+      assert(!empty($extension_settings['id']), "The xbExtension config in $extension must have an 'id' property.");
+      assert(!empty($extension_settings['name']), "The xbExtension config in $extension must have a 'name' property.");
+
+      if (empty($extension_settings['description'])) {
+        $extension_settings['description'] = t('No description provided.');
+      }
+
+      if (!isset($extension_settings['imgSrc'])) {
+        continue;
+      }
+
+      $img_src = $extension_settings['imgSrc'];
+
+      // Only prepend the path if it's a relative path without a leading slash
+      if (!str_starts_with($img_src, '/') && !str_starts_with($img_src, 'http')) {
+        assert(!str_starts_with($img_src, '.'), 'The extension image path must not start with "."');
+        $extension_settings['imgSrc'] = Url::fromUri('base://' . $module_path . '/' . $img_src)->toString();
+      }
+    }
+  }
+}
diff --git a/experience_builder.module b/experience_builder.module
index 8b0e09e820ac4c311471222b1876f38f96ba1a77..23fd69b125f7cfe8343bef52fa92922c969b6fc8 100644
--- a/experience_builder.module
+++ b/experience_builder.module
@@ -22,7 +22,6 @@ use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Plugin\Discovery\YamlDiscovery;
 use Drupal\Core\Render\Markup;
 use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
 use Drupal\experience_builder\Entity\AssetLibrary;
@@ -53,6 +52,8 @@ require_once __DIR__ . '/experience_builder.auto_save.inc';
 require_once __DIR__ . '/experience_builder.force_render_with_twig.inc';
 // @see docs/components.md, section 3.3.3
 require_once __DIR__ . '/experience_builder.block_overrides.inc';
+// Experience Builder extensions support
+require_once __DIR__ . '/experience_builder.extensions.inc';
 
 /**
  * Implements hook_preprocess_HOOK() for regions.
@@ -545,33 +546,6 @@ function _experience_builder_base_library_build(array &$libraries, array $defaul
   }
 }
 
-/**
- * Adds libraries that extend Experience Builder to a centralized library.
- *
- * @param array $libraries
- *   The libraries array.
- *
- * @return void
- */
-function _experience_builder_build_extension_libraries(array &$libraries): void {
-  $libraries_discovery = new YamlDiscovery('libraries', \Drupal::service('module_handler')->getModuleDirectories());
-
-  // Any library with a 'xbExtension' drupalSettings will be added.
-  $xb_extensions = array_reduce($libraries_discovery->getDefinitions(), function ($carry, $item) {
-    if (isset($item['drupalSettings']['xbExtension'])) {
-      $carry[] = $item['provider'] . '/' . $item['id'];
-    }
-    return $carry;
-  }, []);
-
-  // Add the libraries as dependencies of experience_builder/extensions.
-  if (!empty($xb_extensions)) {
-    $libraries['extensions'] = [
-      'dependencies' => $xb_extensions,
-    ];
-  }
-}
-
 /**
  * Customize core/drupal.dialog for Experience Builder.
  *
@@ -792,6 +766,14 @@ function experience_builder_entity_form_display_alter(EntityFormDisplayInterface
   }
 }
 
+/**
+ * Implements hook_library_info_alter().
+ */
+function experience_builder_library_info_alter(array &$libraries, string $extension): void {
+  _experience_builder_process_transforms($libraries, $extension);
+  _experience_builder_process_extensions_paths($libraries, $extension);
+}
+
 /**
  * Implements hook_entity_type_alter().
  */
diff --git a/experience_builder.redux_integrated_field_widgets.inc b/experience_builder.redux_integrated_field_widgets.inc
index 3a079482536a703de32a1e2727546827a44ee72c..e5cb6a95e01bd588c59a262bce646dfb16b1ac4f 100644
--- a/experience_builder.redux_integrated_field_widgets.inc
+++ b/experience_builder.redux_integrated_field_widgets.inc
@@ -18,7 +18,7 @@ use Drupal\media_library\MediaLibraryState;
 /**
  * Implements hook_library_info_alter().
  */
-function experience_builder_library_info_alter(array &$libraries, string $extension): void {
+function _experience_builder_process_transforms(array &$libraries, string $extension): void {
   if ($extension === 'experience_builder') {
     // We need to dynamically create a 'transforms' library by compiling a list
     // of all module defined transforms - which are libraries prefixed with
diff --git a/tests/modules/xb_test_extension/xb_test_extension.libraries.yml b/tests/modules/xb_test_extension/xb_test_extension.libraries.yml
index 020c18c9196fa0f33fed302507fe7d5933215935..fad863d67b7b721f58f93fe25f80ebbf05f8508f 100644
--- a/tests/modules/xb_test_extension/xb_test_extension.libraries.yml
+++ b/tests/modules/xb_test_extension/xb_test_extension.libraries.yml
@@ -8,4 +8,4 @@ app:
     xbExtension:
       testExtension: { id: 'experience-builder-test-extension', name: 'XB Test Extension', description: 'Demonstrates many things that an XB extension can do' }
   dependencies:
-    - experience_builder/ui
+    - experience_builder/xb-ui
diff --git a/ui/assets/icons/extension-default-abstract.svg b/ui/assets/icons/extension-default-abstract.svg
new file mode 100644
index 0000000000000000000000000000000000000000..ea732874d7b96ae64635b4a487bef7b4f3732566
--- /dev/null
+++ b/ui/assets/icons/extension-default-abstract.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24" fill="currentColor">
+  <path
+    d="M120-380q-8 0-14-6t-6-14q0-8 6-14t14-6q8 0 14 6t6 14q0 8-6 14t-14 6zm0-160q-8 0-14-6t-6-14q0-8 6-14t14-6q8 0 14 6t6 14q0 8-6 14t-14 6zm120 340q-17 0-28.5-11.5T200-240q0-17 11.5-28.5T240-280q17 0 28.5 11.5T280-240q0 17-11.5 28.5T240-200zm0-160q-17 0-28.5-11.5T200-400q0-17 11.5-28.5T240-440q17 0 28.5 11.5T280-400q0 17-11.5 28.5T240-360zm0-160q-17 0-28.5-11.5T200-560q0-17 11.5-28.5T240-600q17 0 28.5 11.5T280-560q0 17-11.5 28.5T240-520zm0-160q-17 0-28.5-11.5T200-720q0-17 11.5-28.5T240-760q17 0 28.5 11.5T280-720q0 17-11.5 28.5T240-680zm160 340q-25 0-42.5-17.5T340-400q0-25 17.5-42.5T400-460q25 0 42.5 17.5T460-400q0 25-17.5 42.5T400-340zm0-160q-25 0-42.5-17.5T340-560q0-25 17.5-42.5T400-620q25 0 42.5 17.5T460-560q0 25-17.5 42.5T400-500zm0 300q-17 0-28.5-11.5T360-240q0-17 11.5-28.5T400-280q17 0 28.5 11.5T440-240q0 17-11.5 28.5T400-200zm0-480q-17 0-28.5-11.5T360-720q0-17 11.5-28.5T400-760q17 0 28.5 11.5T440-720q0 17-11.5 28.5T400-680zm0 580q-8 0-14-6t-6-14q0-8 6-14t14-6q8 0 14 6t6 14q0 8-6 14t-14 6zm0-720q-8 0-14-6t-6-14q0-8 6-14t14-6q8 0 14 6t6 14q0 8-6 14t-14 6zm160 480q-25 0-42.5-17.5T500-400q0-25 17.5-42.5T560-460q25 0 42.5 17.5T620-400q0 25-17.5 42.5T560-340zm0-160q-25 0-42.5-17.5T500-560q0-25 17.5-42.5T560-620q25 0 42.5 17.5T620-560q0 25-17.5 42.5T560-500zm0 300q-17 0-28.5-11.5T520-240q0-17 11.5-28.5T560-280q17 0 28.5 11.5T600-240q0 17-11.5 28.5T560-200zm0-480q-17 0-28.5-11.5T520-720q0-17 11.5-28.5T560-760q17 0 28.5 11.5T600-720q0 17-11.5 28.5T560-680zm0 580q-8 0-14-6t-6-14q0-8 6-14t14-6q8 0 14 6t6 14q0 8-6 14t-14 6zm0-720q-8 0-14-6t-6-14q0-8 6-14t14-6q8 0 14 6t6 14q0 8-6 14t-14 6zm160 620q-17 0-28.5-11.5T680-240q0-17 11.5-28.5T720-280q17 0 28.5 11.5T760-240q0 17-11.5 28.5T720-200zm0-160q-17 0-28.5-11.5T680-400q0-17 11.5-28.5T720-440q17 0 28.5 11.5T760-400q0 17-11.5 28.5T720-360zm0-160q-17 0-28.5-11.5T680-560q0-17 11.5-28.5T720-600q17 0 28.5 11.5T760-560q0 17-11.5 28.5T720-520zm0-160q-17 0-28.5-11.5T680-720q0-17 11.5-28.5T720-760q17 0 28.5 11.5T760-720q0 17-11.5 28.5T720-680zm120 300q-8 0-14-6t-6-14q0-8 6-14t14-6q8 0 14 6t6 14q0 8-6 14t-14 6zm0-160q-8 0-14-6t-6-14q0-8 6-14t14-6q8 0 14 6t6 14q0 8-6 14t-14 6z" />
+</svg>
\ No newline at end of file
diff --git a/ui/src/app/store.ts b/ui/src/app/store.ts
index fb6ea2e4bb1181ef79aac00bf25d5808c34264d5..3fabaa0100df53adf8facaaa7d553d0eaf98ca73 100644
--- a/ui/src/app/store.ts
+++ b/ui/src/app/store.ts
@@ -31,7 +31,6 @@ import { pageDataFormApi } from '@/services/pageDataForm';
 import { configurationSlice } from '@/features/configuration/configurationSlice';
 import { sectionApi } from '@/services/sections';
 import { extensionsSlice } from '@/features/extensions/extensionsSlice';
-import { extensionsApi } from '@/services/extensions';
 import { assetLibraryApi } from '@/services/assetLibrary';
 import { componentAndLayoutApi } from '@/services/componentAndLayout';
 import { formStateSlice } from '@/features/form/formStateSlice';
@@ -126,7 +125,6 @@ const rootReducer = combineSlices(
     ),
   },
   sectionApi,
-  extensionsApi,
   assetLibraryApi,
   componentAndLayoutApi,
   previewApi,
@@ -195,7 +193,6 @@ export const makeStore = (preloadedState?: Partial<RootState>) => {
     middleware: (getDefaultMiddleware) => {
       return getDefaultMiddleware().concat(
         sectionApi.middleware,
-        extensionsApi.middleware,
         assetLibraryApi.middleware,
         componentAndLayoutApi.middleware,
         previewApi.middleware,
diff --git a/ui/src/components/extensions/ExtensionsList.tsx b/ui/src/components/extensions/ExtensionsList.tsx
index ccd2b51971699dfd0c875a2bb84b0ef6d1cb8367..6ca53731e863dfd9be48316a8ee1083573351d16 100644
--- a/ui/src/components/extensions/ExtensionsList.tsx
+++ b/ui/src/components/extensions/ExtensionsList.tsx
@@ -1,43 +1,37 @@
-import { Flex, Heading, Link, Grid, Spinner } from '@radix-ui/themes';
+import { Flex, Heading, Link, Grid } from '@radix-ui/themes';
 import { ExternalLinkIcon } from '@radix-ui/react-icons';
 import ExtensionButton from '@/components/extensions/ExtensionButton';
 import { handleNonWorkingBtn } from '@/utils/function-utils';
 import type React from 'react';
-import { useGetExtensionsQuery } from '@/services/extensions';
-import ErrorCard from '@/components/error/ErrorCard';
 
 interface ExtensionsPopoverProps {}
 
+const { drupalSettings } = window;
+
 const ExtensionsList: React.FC<ExtensionsPopoverProps> = () => {
-  const {
-    data: extensions,
-    isError,
-    isLoading,
-    refetch,
-  } = useGetExtensionsQuery();
+  let extensionsList = [];
+  if (drupalSettings && drupalSettings.xbExtension) {
+    extensionsList = Object.values(drupalSettings.xbExtension).map((value) => {
+      return {
+        ...value,
+        imgSrc:
+          value.imgSrc ||
+          `${drupalSettings.path.baseUrl}${drupalSettings.xb.xbModulePath}/ui/assets/icons/extension-default-abstract.svg`,
+        name: value.name,
+        description: value.description,
+      };
+    });
+  }
 
-  return (
-    <ExtensionsListDisplay
-      extensions={extensions || []}
-      isLoading={isLoading}
-      isError={isError}
-      refetch={refetch}
-    />
-  );
+  return <ExtensionsListDisplay extensions={extensionsList || []} />;
 };
 
 interface ExtensionsListDisplayProps {
   extensions: Array<any>;
-  isLoading: boolean;
-  isError: boolean;
-  refetch: () => void;
 }
 
 const ExtensionsListDisplay: React.FC<ExtensionsListDisplayProps> = ({
   extensions,
-  isLoading,
-  isError,
-  refetch,
 }) => {
   return (
     <>
@@ -60,26 +54,19 @@ const ExtensionsListDisplay: React.FC<ExtensionsListDisplayProps> = ({
           </Link>
         </Flex>
       </Flex>
-      {isError && (
-        <ErrorCard
-          error="Cannot display extensions, please try again."
-          resetErrorBoundary={refetch}
-          resetButtonText="Try again"
-          title="Error loading extensions"
-        />
-      )}
-      {isLoading && (
-        <Flex justify="center">
-          <Spinner />
-        </Flex>
-      )}
-      {!isError && extensions && (
+
+      {extensions.length > 0 && (
         <Grid columns="3" gap="3">
           {extensions.map((extension) => (
             <ExtensionButton extension={extension} key={extension.id} />
           ))}
         </Grid>
       )}
+      {extensions?.length === 0 && (
+        <Flex justify="center">
+          <p>No extensions found</p>
+        </Flex>
+      )}
     </>
   );
 };
diff --git a/ui/src/components/extensions/ExtensionsListDisplay.stories.tsx b/ui/src/components/extensions/ExtensionsListDisplay.stories.tsx
index 45b4f8b9a9f6319883d96b98e4818c68e000c81f..3191e4f38e817b619b7adf12fba998def3de858f 100644
--- a/ui/src/components/extensions/ExtensionsListDisplay.stories.tsx
+++ b/ui/src/components/extensions/ExtensionsListDisplay.stories.tsx
@@ -38,20 +38,11 @@ const meta: Meta<typeof ExtensionsListDisplay> = {
   component: ExtensionsListDisplay,
   args: {
     extensions: mockExtensions,
-    isLoading: false,
-    isError: false,
-    refetch: () => {},
   },
   argTypes: {
     extensions: {
       control: { type: 'object' },
     },
-    isLoading: {
-      control: { type: 'boolean' },
-    },
-    isError: {
-      control: { type: 'boolean' },
-    },
   },
   decorators: [
     (Story) => (
@@ -73,23 +64,8 @@ export default meta;
 type Story = StoryObj<typeof ExtensionsListDisplay>;
 
 export const Default: Story = {};
-
-export const Loading: Story = {
+export const Empty: Story = {
   args: {
     extensions: [],
-    isLoading: true,
-    isError: false,
-    refetch: () => {},
-  },
-};
-
-export const Error: Story = {
-  args: {
-    extensions: [],
-    isLoading: false,
-    isError: true,
-    refetch: () => {
-      alert('Dummy refetch');
-    },
   },
 };
diff --git a/ui/src/services/extensions.ts b/ui/src/services/extensions.ts
deleted file mode 100644
index 3f29522d3ab850444fb210e1e47bfa4851b1fd01..0000000000000000000000000000000000000000
--- a/ui/src/services/extensions.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-// Need to use the React-specific entry point to import createApi
-import { createApi } from '@reduxjs/toolkit/query/react';
-import { baseQuery } from '@/services/baseQuery';
-import type { ExtensionsList } from '@/types/Extensions';
-const { drupalSettings } = window;
-const kittenBase64 =
-  /* cspell:disable-next-line */
-  'data:image/jpeg;base64, /9j/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAAYABgDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAMGBwX/xAAlEAABBAEDBAIDAAAAAAAAAAABAgMEEQAFEiEGBzFBE1EUFSL/xAAYAQADAQEAAAAAAAAAAAAAAAACAwQBBf/EAB4RAAICAgIDAAAAAAAAAAAAAAECAAMRIQQSIlJx/9oADAMBAAIRAxEAPwDPOopUeU604VNEPLKFniwFKJonOXqHS+pIeUzA02VIbcaUW1CI4vddGhQo5pR1TTdPYBg6HCbktuBTTxWtexI9Uu+fP9CssumddCe3KQ18rEptvei3B4FCxfF2br6vA5Nl1ewmvsuTg68jiWruVcHssy2sbVtwWm1JqqIYIIr1z6xmS9R9wP2kB6JqUZJU2C2+x+QfkVzR4Io8349YzKncjJWJaj1Mpz09aydxvn3kAnKaeQ42va6k2D9EYxnRbcvLEjBkM+U5qGoLmzZC35KwApaqs1wPAxjGAABoRagKOqjAn//Z';
-
-// @todo stop hardcoding this list - https://www.drupal.org/i/3509080
-let dummyExtensionsList = [
-  {
-    name: 'Extension proof of concept',
-    description:
-      'Enable the xb_test_extension module to make this extension appear',
-    imgSrc: kittenBase64,
-    id: 'experience-builder-test-extension',
-  },
-  {
-    name: 'Extension with longer name 2',
-    description: "A dummy extension. It doesn't do anything.",
-    imgSrc: kittenBase64,
-    id: 'extension2',
-  },
-  {
-    name: 'Extension 3',
-    description: 'Another dummy extension. It does nothing.',
-    imgSrc: kittenBase64,
-    id: 'extension3',
-  },
-  {
-    name: 'Extension 4',
-    description: 'This is a dummy extension that does nothing.',
-    imgSrc: kittenBase64,
-    id: 'extension4',
-  },
-];
-
-if (drupalSettings && drupalSettings.xbExtension) {
-  Object.entries(drupalSettings.xbExtension).forEach(([key, value]) => {
-    dummyExtensionsList = dummyExtensionsList.map((item) => {
-      if (item.id === value.id) {
-        return { ...item, ...value };
-      }
-      return item;
-    });
-  });
-}
-
-// Custom baseQuery function to return mock data during development
-// @ts-ignore
-const customBaseQuery = async (args, api, extraOptions) => {
-  if (args === 'xb-extensions') {
-    return { data: dummyExtensionsList };
-  }
-  return baseQuery(args, api, extraOptions);
-};
-
-// Define a service using a base URL and expected endpoints
-export const extensionsApi = createApi({
-  reducerPath: 'extensionsApi',
-  baseQuery: customBaseQuery,
-  endpoints: (builder) => ({
-    getExtensions: builder.query<ExtensionsList, void>({
-      query: () => `xb-extensions`,
-    }),
-  }),
-});
-
-// Export hooks for usage in functional extensions, which are
-// auto-generated based on the defined endpoints
-export const { useGetExtensionsQuery } = extensionsApi;