diff --git a/ui/src/components/ComponentPreview.tsx b/ui/src/components/ComponentPreview.tsx
index fb33b82f4cb3561fd2dd06247024543b0da57291..fc0141c1f77c7f73f2b8331a683e0c7e4c3b7279 100644
--- a/ui/src/components/ComponentPreview.tsx
+++ b/ui/src/components/ComponentPreview.tsx
@@ -3,13 +3,11 @@ import { useEffect } from 'react';
 import styles from './ComponentPreview.module.css';
 import clsx from 'clsx';
 import Panel from '@/components/Panel';
-import type {
-  ComponentListItem,
-  SectionListItem,
-} from '@/components/list/List';
+import type { XBComponent } from '@/types/Component';
+import type { Section } from '@/types/Section';
 
 interface ComponentPreviewProps {
-  componentListItem: ComponentListItem | SectionListItem;
+  componentListItem: XBComponent | Section;
 }
 
 const { drupalSettings } = window;
diff --git a/ui/src/components/DummyPropsEditForm.tsx b/ui/src/components/DummyPropsEditForm.tsx
index d95db000f69811e6a9188bf9b8f68b0b51164f24..33e8c04f9e05c37c2b57c717cc1370b0c7e5cf4f 100644
--- a/ui/src/components/DummyPropsEditForm.tsx
+++ b/ui/src/components/DummyPropsEditForm.tsx
@@ -21,7 +21,7 @@ import { useDrupalBehaviors } from '@/hooks/useDrupalBehaviors';
 import useXbParams from '@/hooks/useXbParams';
 import { clearFieldValues } from '@/features/form/formStateSlice';
 import type { FieldData } from '@/types/Component';
-import type { Component } from '@/types/Component';
+import type { XBComponent } from '@/types/Component';
 import { componentHasFieldData } from '@/types/Component';
 import type { AjaxUpdateFormStateEvent } from '@/types/Ajax';
 import { AJAX_UPDATE_FORM_STATE_EVENT } from '@/types/Ajax';
@@ -229,7 +229,7 @@ const DummyPropsEditForm: React.FC<DummyPropsEditFormProps> = () => {
 
   const buildPreparedModel = (
     model: ComponentModel,
-    component: Component,
+    component: XBComponent,
   ): ComponentModel => {
     if (!componentHasFieldData(component)) {
       return model;
diff --git a/ui/src/components/dynamicComponents/DynamicComponents.tsx b/ui/src/components/dynamicComponents/DynamicComponents.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..09c985d5c5c12d5036695053e95d5b51fb76aa95
--- /dev/null
+++ b/ui/src/components/dynamicComponents/DynamicComponents.tsx
@@ -0,0 +1,56 @@
+import { Flex, Heading, Box, ScrollArea, Spinner } from '@radix-ui/themes';
+import type React from 'react';
+
+import DynamicComponentsLibrary from '@/components/dynamicComponents/DynamicComponentsLibrary';
+import { useGetComponentsQuery } from '@/services/componentAndLayout';
+import ErrorCard from '@/components/error/ErrorCard';
+
+interface DynamicComponentsGroupsPopoverProps {}
+
+const DynamicComponents: React.FC<DynamicComponentsGroupsPopoverProps> = () => {
+  const {
+    data: dynamicComponents,
+    isLoading,
+    isError,
+    isFetching,
+    refetch,
+  } = useGetComponentsQuery({
+    mode: 'include',
+    libraries: ['dynamic_components'],
+  });
+
+  if (isLoading || isFetching) {
+    return (
+      <Flex justify="center" align="center">
+        <Spinner />
+      </Flex>
+    );
+  }
+
+  return (
+    <>
+      <Flex justify="between">
+        <Heading as="h3" size="3" mb="4">
+          Dynamic Components
+        </Heading>
+      </Flex>
+      <Box mr="-4">
+        <ScrollArea style={{ maxHeight: '380px', width: '100%' }} type="scroll">
+          <Box pr="4" mt="3">
+            {isError && (
+              <ErrorCard
+                title="Error fetching Dynamic component list."
+                resetErrorBoundary={refetch}
+              />
+            )}
+            {!isError && dynamicComponents && (
+              <DynamicComponentsLibrary dynamicComponents={dynamicComponents} />
+            )}
+          </Box>
+        </ScrollArea>
+      </Box>
+    </>
+  );
+};
+
+export default DynamicComponents;
diff --git a/ui/src/components/dynamicComponents/DynamicComponentsLibrary.tsx b/ui/src/components/dynamicComponents/DynamicComponentsLibrary.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..0f7241e2e6a262e45d201cec28501501156ae2da
--- /dev/null
+++ b/ui/src/components/dynamicComponents/DynamicComponentsLibrary.tsx
@@ -0,0 +1,80 @@
+import type React from 'react';
+import {
+  AccordionRoot,
+  AccordionDetails,
+} from '@/components/form/components/Accordion';
+import ErrorBoundary from '@/components/error/ErrorBoundary';
+import { setOpenLayoutItem } from '@/features/ui/primaryPanelSlice';
+import styles from '@/components/sidebar/Library.module.css';
+import { useState } from 'react';
+import List from '@/components/list/List';
+import type { ComponentsList } from '@/types/Component';
+
+interface LibraryProps {
+  dynamicComponents: ComponentsList;
+}
+
+const Library: React.FC<LibraryProps> = ({ dynamicComponents }) => {
+  const [openCategories, setOpenCategories] = useState<string[]>([]);
+
+  const categoriesSet = new Set<string>();
+  Object.values(dynamicComponents).forEach((component) => {
+    categoriesSet.add(component.category);
+  });
+
+  const categories = Array.from(categoriesSet)
+    .map((categoryName) => {
+      return {
+        name: categoryName.replace(/\w/, (c) => c.toUpperCase()),
+        id: categoryName,
+      };
+    })
+    .sort((a, b) => a.name.localeCompare(b.name));
+
+  const onClickHandler = (categoryName: string) => {
+    setOpenCategories((state) => {
+      if (!state.includes(categoryName)) {
+        return [...state, categoryName];
+      }
+      return state.filter((stateName) => stateName !== categoryName);
+    });
+  };
+
+  return (
+    <>
+      <AccordionRoot
+        value={openCategories}
+        onValueChange={() => setOpenLayoutItem}
+      >
+        {categories.map((category) => (
+          <AccordionDetails
+            key={category.id}
+            value={category.id}
+            title={category.name}
+            onTriggerClick={() => onClickHandler(category.id)}
+            className={styles.accordionDetails}
+            triggerClassName={styles.accordionDetailsTrigger}
+          >
+            <ErrorBoundary
+              title={`An unexpected error has occurred while fetching ${category.name}.`}
+            >
+              <List
+                // filtered dynamicComponents that match the current category
+                items={Object.fromEntries(
+                  Object.entries(dynamicComponents).filter(
+                    ([key, component]) => component.category === category.id,
+                  ),
+                )}
+                isLoading={false}
+                type="component"
+                label="Components"
+              />
+            </ErrorBoundary>
+          </AccordionDetails>
+        ))}
+      </AccordionRoot>
+    </>
+  );
+};
+
+export default Library;
diff --git a/ui/src/components/form/components/Accordion.tsx b/ui/src/components/form/components/Accordion.tsx
index 7852daa52d631d432a96938700fc275180c75af0..f5448f68ea00460cc46f6027b75d58b37aafbd33 100644
--- a/ui/src/components/form/components/Accordion.tsx
+++ b/ui/src/components/form/components/Accordion.tsx
@@ -58,9 +58,14 @@ const AccordionDetails = ({
         className={clsx(styles.trigger, triggerClassName)}
         onClick={onTriggerClick}
       >
-        <Text size="2" weight="medium" {...summaryAttributes}>
-          {title}
-        </Text>
+        {typeof title === 'string' ? (
+          <Text size="2" weight="medium" {...summaryAttributes}>
+            {title}
+          </Text>
+        ) : (
+          title
+        )}
+
         <ChevronRightIcon className={styles.chevron} aria-hidden />
       </Accordion.Trigger>
     </Flex>
diff --git a/ui/src/components/form/inputBehaviors.tsx b/ui/src/components/form/inputBehaviors.tsx
index c890cd7e4c55e40271c948abb18be3c3bd6825a7..68461d5f18073d47ccc6db235b792b86f0f1efbb 100644
--- a/ui/src/components/form/inputBehaviors.tsx
+++ b/ui/src/components/form/inputBehaviors.tsx
@@ -42,7 +42,7 @@ import {
   clearFieldError,
 } from '@/features/form/formStateSlice';
 import type { ErrorObject } from 'ajv/dist/types';
-import type { Component } from '@/types/Component';
+import type { XBComponent } from '@/types/Component';
 import { componentHasFieldData } from '@/types/Component';
 import { FORM_TYPES } from '@/features/form/constants';
 import { useUpdateComponentMutation } from '@/services/preview';
@@ -462,7 +462,7 @@ export default InputBehaviors;
 
 export const syncPropSourcesToResolvedValues = (
   sources: Sources,
-  component: Component,
+  component: XBComponent,
   resolvedValues: ResolvedValues,
 ): Sources => {
   if (!componentHasFieldData(component)) {
diff --git a/ui/src/components/list/ComponentList.tsx b/ui/src/components/list/ComponentList.tsx
index 03c571c7389c8643a8cac5c738002eab6adb68a4..56d47f092e63e7668e5f4c17c5f2a12b0632fcfa 100644
--- a/ui/src/components/list/ComponentList.tsx
+++ b/ui/src/components/list/ComponentList.tsx
@@ -2,10 +2,16 @@ import { useEffect } from 'react';
 import { useErrorBoundary } from 'react-error-boundary';
 import { useGetComponentsQuery } from '@/services/componentAndLayout';
 import List from '@/components/list/List';
-import { toArray } from 'lodash';
 
 const ComponentList = () => {
-  const { data: components, error, isLoading } = useGetComponentsQuery();
+  const {
+    data: components,
+    error,
+    isLoading,
+  } = useGetComponentsQuery({
+    mode: 'exclude',
+    libraries: ['dynamic_components'],
+  });
   const { showBoundary } = useErrorBoundary();
 
   useEffect(() => {
@@ -14,13 +20,9 @@ const ComponentList = () => {
     }
   }, [error, showBoundary]);
 
-  const sortedComponents = components
-    ? toArray(components).sort((a, b) => a.name.localeCompare(b.name))
-    : {};
-
   return (
     <List
-      items={sortedComponents}
+      items={components}
       isLoading={isLoading}
       type="component"
       label="Components"
diff --git a/ui/src/components/list/ExposedJsComponent.tsx b/ui/src/components/list/ExposedJsComponent.tsx
index c84b646fa6a7fdd629b8a911090b5b53a290160a..71cc4f4fcc74619775be853a1d58abce5907981e 100644
--- a/ui/src/components/list/ExposedJsComponent.tsx
+++ b/ui/src/components/list/ExposedJsComponent.tsx
@@ -1,4 +1,3 @@
-import type { ComponentListItem } from '@/components/list/List';
 import SidebarNode from '@/components/sidebar/SidebarNode';
 import type React from 'react';
 import { useEffect } from 'react';
@@ -17,6 +16,7 @@ import type { CodeComponentSerialized } from '@/types/CodeComponent';
 import { selectLayout } from '@/features/layout/layoutModelSlice';
 import { componentExistsInLayout } from '@/features/layout/layoutUtils';
 import { useErrorBoundary } from 'react-error-boundary';
+import type { JSComponent } from '@/types/Component';
 import { useNavigate } from 'react-router-dom';
 import useXbParams from '@/hooks/useXbParams';
 
@@ -27,9 +27,7 @@ function removeJsPrefix(input: string): string {
   return input;
 }
 
-const ExposedJsComponent: React.FC<{ component: ComponentListItem }> = (
-  props,
-) => {
+const ExposedJsComponent: React.FC<{ component: JSComponent }> = (props) => {
   const dispatch = useAppDispatch();
   const { component } = props;
   const machineName = removeJsPrefix(component.id);
diff --git a/ui/src/components/list/List.tsx b/ui/src/components/list/List.tsx
index 8f00d905708e59b5e659269ec70d1983ce2251a4..3e5835cb4e4ef12801e8989bac0d3beb8924d101 100644
--- a/ui/src/components/list/List.tsx
+++ b/ui/src/components/list/List.tsx
@@ -1,4 +1,5 @@
 import type React from 'react';
+import { useMemo } from 'react';
 import { useEffect, useRef, useCallback } from 'react';
 import styles from './List.module.css';
 import {
@@ -11,42 +12,20 @@ import { useAppDispatch, useAppSelector } from '@/app/hooks';
 import { Box, Flex, Spinner } from '@radix-ui/themes';
 import clsx from 'clsx';
 import ListItem from '@/components/list/ListItem';
-import type { LayoutModelPiece } from '@/features/layout/layoutModelSlice';
 import {
   isDropTargetBetweenTwoElementsOfSameComponent,
   isDropTargetInSlotAllowedByEdgeDistance,
 } from '@/features/sortable/sortableUtils';
-import type { TransformConfig } from '@/utils/transforms';
+import type { ComponentsList } from '@/types/Component';
+import type { SectionsList } from '@/types/Section';
 
 export interface ListProps {
-  items: ListData | undefined;
+  items: ComponentsList | SectionsList | undefined;
   isLoading: boolean;
   type: 'component' | 'section';
   label: string;
 }
 
-export interface ListItemBase {
-  id: string;
-  name: string;
-  metadata: Record<string, any>;
-  default_markup: string;
-  css: string;
-  js_header: string;
-  js_footer: string;
-}
-export interface ComponentListItem extends ListItemBase {
-  field_data: Record<string, any>;
-  source: string;
-  transforms: TransformConfig;
-}
-export interface SectionListItem extends ListItemBase {
-  layoutModel: LayoutModelPiece;
-}
-
-interface ListData {
-  [key: string]: ComponentListItem | SectionListItem;
-}
-
 const List: React.FC<ListProps> = (props) => {
   const { items, isLoading, type } = props;
   const dispatch = useAppDispatch();
@@ -54,6 +33,15 @@ const List: React.FC<ListProps> = (props) => {
   const listElRef = useRef<HTMLDivElement>(null);
   const { isDragging } = useAppSelector(selectDragging);
 
+  // Sort items and convert to array.
+  const sortedItems = useMemo(() => {
+    return items
+      ? Object.entries(items).sort(([, a], [, b]) =>
+          a.name.localeCompare(b.name),
+        )
+      : [];
+  }, [items]);
+
   const handleDragStart = useCallback(() => {
     dispatch(setListDragging(true));
   }, [dispatch]);
@@ -124,8 +112,8 @@ const List: React.FC<ListProps> = (props) => {
       <Box className={isDragging ? 'list-dragging' : ''}>
         <Spinner loading={isLoading}>
           <Flex direction="column" width="100%" ref={listElRef}>
-            {items &&
-              Object.entries(items).map(([id, item]) => (
+            {sortedItems &&
+              sortedItems.map(([id, item]) => (
                 <ListItem item={item} key={id} type={type} />
               ))}
           </Flex>
diff --git a/ui/src/components/list/ListItem.tsx b/ui/src/components/list/ListItem.tsx
index 9e7f5d9d962b0ff3f69ee93073572950b75d1acb..a4d998a0d250d94391a17d8796ef5577925f34fe 100644
--- a/ui/src/components/list/ListItem.tsx
+++ b/ui/src/components/list/ListItem.tsx
@@ -1,8 +1,6 @@
 import type React from 'react';
-import type {
-  ComponentListItem,
-  SectionListItem,
-} from '@/components/list/List';
+import type { XBComponent, JSComponent } from '@/types/Component';
+import type { Section } from '@/types/Section';
 import { useState } from 'react';
 import clsx from 'clsx';
 import styles from '@/components/list/List.module.css';
@@ -24,14 +22,14 @@ import useXbParams from '@/hooks/useXbParams';
 import { DEFAULT_REGION } from '@/features/ui/uiSlice';
 
 const ListItem: React.FC<{
-  item: ComponentListItem | SectionListItem;
+  item: XBComponent | Section;
   type: 'component' | 'section';
 }> = (props) => {
   const { item, type } = props;
   const dispatch = useAppDispatch();
   const layout = useAppSelector(selectLayout);
   const [previewingComponent, setPreviewingComponent] = useState<
-    ComponentListItem | SectionListItem
+    XBComponent | Section
   >();
   const {
     componentId: selectedComponent,
@@ -55,7 +53,7 @@ const ListItem: React.FC<{
           addNewComponentToLayout(
             {
               to: newPath,
-              component: item as ComponentListItem,
+              component: item as XBComponent,
             },
             setSelectedComponent,
           ),
@@ -65,7 +63,7 @@ const ListItem: React.FC<{
           addNewSectionToLayout(
             {
               to: newPath,
-              layoutModel: (item as SectionListItem).layoutModel,
+              layoutModel: (item as Section).layoutModel,
             },
             setSelectedComponent,
           ),
@@ -74,23 +72,22 @@ const ListItem: React.FC<{
     }
   };
 
-  const handleMouseEnter = (component: ComponentListItem | SectionListItem) => {
+  const handleMouseEnter = (component: XBComponent | Section) => {
     setPreviewingComponent(component);
   };
 
   const renderItem = () => {
     if (
       type === 'component' &&
-      (item as ComponentListItem).source === 'Code component'
+      (item as JSComponent).source === 'Code component'
     ) {
-      return <ExposedJsComponent component={item as ComponentListItem} />;
+      return <ExposedJsComponent component={item as JSComponent} />;
     }
     return (
       <SidebarNode
         title={item.name}
         variant={
-          type === 'component' &&
-          (item as ComponentListItem).source === 'Blocks'
+          type === 'component' && (item as XBComponent).source === 'Blocks'
             ? 'blockComponent'
             : type
         }
diff --git a/ui/src/components/topbar/Topbar.tsx b/ui/src/components/topbar/Topbar.tsx
index f80ad0a0ed39686f96bcfbcfdc6b64b4925c0df8..85064b74e64fe43ba10ad673f64b2a6d065cbf50 100644
--- a/ui/src/components/topbar/Topbar.tsx
+++ b/ui/src/components/topbar/Topbar.tsx
@@ -5,7 +5,6 @@ import {
   Flex,
   Grid,
   SegmentedControl,
-  Text,
   Tooltip,
 } from '@radix-ui/themes';
 import Panel from '@/components/Panel';
@@ -19,10 +18,9 @@ import clsx from 'clsx';
 import UnpublishedChanges from '@/components/review/UnpublishedChanges';
 import PageInfo from '../pageInfo/PageInfo';
 import ExtensionsList from '@/components/extensions/ExtensionsList';
-import type React from 'react';
-import { handleNonWorkingBtn } from '@/utils/function-utils';
 import TopbarPopover from '@/components/topbar/menu/TopbarPopover';
 import topBarStyles from '@/components/topbar/Topbar.module.css';
+import DynamicComponents from '@/components/dynamicComponents/DynamicComponents';
 
 const PREVIOUS_URL_STORAGE_KEY = 'XBPreviousURL';
 
@@ -94,23 +92,19 @@ const Topbar = () => {
               <ExtensionsList />
             </TopbarPopover>
             <TopbarPopover
-              tooltip="CMS"
+              tooltip="Dynamic components"
               trigger={
                 <Button
                   variant="ghost"
                   color="gray"
                   size="2"
                   className={clsx(styles.topBarButton)}
-                  onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
-                    e.preventDefault();
-                    handleNonWorkingBtn();
-                  }}
                 >
                   <CMSIcon height="24" width="auto" />
                 </Button>
               }
             >
-              <Text>Not yet supported</Text>
+              <DynamicComponents />
             </TopbarPopover>
           </Flex>
           <Flex align="center" justify="center" gap="2">
diff --git a/ui/src/components/topbar/menu/TopbarPopover.tsx b/ui/src/components/topbar/menu/TopbarPopover.tsx
index 952ea770c7400dd1e935146b2f58d93632e6bfb4..10e337461ba345a16a168edaf2602a1187e477ce 100644
--- a/ui/src/components/topbar/menu/TopbarPopover.tsx
+++ b/ui/src/components/topbar/menu/TopbarPopover.tsx
@@ -22,7 +22,9 @@ const TopbarPopover: React.FC<TopbarPopoverProps> = ({
         <Popover.Trigger>{trigger}</Popover.Trigger>
       </Tooltip>
       <Popover.Content asChild width="100vw" maxWidth="400px">
-        <Panel className={clsx(styles.content, 'xb-app')}>{children}</Panel>
+        <Panel className={clsx(styles.content, 'xb-app')} maxHeight="50vh">
+          {children}
+        </Panel>
       </Popover.Content>
     </Popover.Root>
   );
diff --git a/ui/src/features/layout/layoutModelSlice.ts b/ui/src/features/layout/layoutModelSlice.ts
index 9a211faa227497ff967ef83c767d8f5b7a836388..f810216053b10ee50ce7fcb1ea6da1fc8b14c99c 100644
--- a/ui/src/features/layout/layoutModelSlice.ts
+++ b/ui/src/features/layout/layoutModelSlice.ts
@@ -1,6 +1,6 @@
 // cspell:ignore uuidv
 import type { AppDispatch, RootState } from '@/app/store';
-import type { Component } from '@/types/Component';
+import type { XBComponent } from '@/types/Component';
 import { componentHasFieldData } from '@/types/Component';
 import type { UUID } from '@/types/UUID';
 import type { PayloadAction } from '@reduxjs/toolkit';
@@ -112,7 +112,7 @@ type InsertMultipleNodesPayload = {
 
 type AddNewNodePayload = {
   to: number[];
-  component: Component;
+  component: XBComponent;
 };
 
 type AddNewSectionPayload = {
@@ -403,7 +403,7 @@ export const addNewComponentToLayout =
   (dispatch: AppDispatch) => {
     const { to, component } = payload;
     // Populate the model data with the default values
-    const buildInitialData = (component: Component): ComponentModel => {
+    const buildInitialData = (component: XBComponent): ComponentModel => {
       if (componentHasFieldData(component)) {
         const initialData: EvaluatedComponentModel = {
           name: component.name,
diff --git a/ui/src/hooks/useGetComponentName.ts b/ui/src/hooks/useGetComponentName.ts
index c369e28a8cf86f4458b62fde2aeafddff469b395..df82a887f3ad0f235da417db3fa6d4d99f990308 100644
--- a/ui/src/hooks/useGetComponentName.ts
+++ b/ui/src/hooks/useGetComponentName.ts
@@ -4,7 +4,7 @@ import type {
   LayoutChildNode,
   LayoutNode,
 } from '@/features/layout/layoutModelSlice';
-import type { Component } from '@/types/Component';
+import type { XBComponent } from '@/types/Component';
 import { componentHasFieldData } from '@/types/Component';
 
 const useGetComponentName = (
@@ -15,7 +15,7 @@ const useGetComponentName = (
 
   const findPresentationSlotName = (
     slotName: string | undefined,
-    parentComponent: Component,
+    parentComponent: XBComponent,
   ) => {
     if (
       slotName &&
diff --git a/ui/src/hooks/usePreviewSortable.ts b/ui/src/hooks/usePreviewSortable.ts
index d458f34fb9febb56e7a57a9487f231e26091f567..fea63b94143fc9b829dedbc3fa70ae34bd0a6620 100644
--- a/ui/src/hooks/usePreviewSortable.ts
+++ b/ui/src/hooks/usePreviewSortable.ts
@@ -115,19 +115,23 @@ function usePreviewSortable(
               );
             }
           } else if (type === 'section') {
-            // Adding a section template.
-            ev.item.innerHTML = '<p>Loading section...</p>';
-            dispatch(
-              addNewSectionToLayout(
-                {
-                  to: newPath,
-                  layoutModel:
-                    sectionsRef?.current?.[ev.clone.dataset.xbComponentId]
-                      .layoutModel,
-                },
-                setSelectedComponent,
-              ),
-            );
+            if (
+              sectionsRef?.current?.[ev.clone.dataset.xbComponentId].layoutModel
+            ) {
+              // Adding a section template.
+              ev.item.innerHTML = '<p>Loading section...</p>';
+              dispatch(
+                addNewSectionToLayout(
+                  {
+                    to: newPath,
+                    layoutModel:
+                      sectionsRef.current[ev.clone.dataset.xbComponentId]
+                        .layoutModel,
+                  },
+                  setSelectedComponent,
+                ),
+              );
+            }
           }
         }
       }
diff --git a/ui/src/services/componentAndLayout.ts b/ui/src/services/componentAndLayout.ts
index 247d4f092b31d142a52e25da0fc75d4ef7db6fe1..f0a5abb302fe574c0b107dc1b81f764a532f7327 100644
--- a/ui/src/services/componentAndLayout.ts
+++ b/ui/src/services/componentAndLayout.ts
@@ -1,18 +1,40 @@
 import { createApi } from '@reduxjs/toolkit/query/react';
 import { baseQuery } from '@/services/baseQuery';
 import type { CodeComponentSerialized } from '@/types/CodeComponent';
-import type { ComponentsList } from '@/types/Component';
+import type { ComponentsList, libraryTypes } from '@/types/Component';
 import type { RootLayoutModel } from '@/features/layout/layoutModelSlice';
 import { setPageData } from '@/features/pageData/pageDataSlice';
 
+type getComponentsQueryOptions = {
+  libraries: libraryTypes[];
+  mode: 'include' | 'exclude';
+};
+
 export const componentAndLayoutApi = createApi({
   reducerPath: 'componentAndLayoutApi',
   baseQuery,
   tagTypes: ['Components', 'CodeComponents', 'CodeComponentAutoSave', 'Layout'],
   endpoints: (builder) => ({
-    getComponents: builder.query<ComponentsList, void>({
+    getComponents: builder.query<
+      ComponentsList,
+      getComponentsQueryOptions | void
+    >({
       query: () => `xb/api/config/component`,
       providesTags: () => [{ type: 'Components', id: 'LIST' }],
+      transformResponse: (response: ComponentsList, meta, arg) => {
+        if (!arg || !Array.isArray(arg.libraries)) {
+          // If no filter is provided, return all components.
+          return response;
+        }
+
+        // Filter the response based on the include/exclude and the list of library types passed.
+        return Object.fromEntries(
+          Object.entries(response).filter(([, value]) => {
+            const isIncluded = arg.libraries.includes(value.library);
+            return arg.mode === 'include' ? isIncluded : !isIncluded;
+          }),
+        );
+      },
     }),
     getLayoutById: builder.query<
       RootLayoutModel & {
diff --git a/ui/src/services/sections.ts b/ui/src/services/sections.ts
index cbd07afa3ec8005fa973f8c38af7c9996afc704b..707f271c80840f608c0c6cacb576e251a807af3d 100644
--- a/ui/src/services/sections.ts
+++ b/ui/src/services/sections.ts
@@ -2,6 +2,7 @@
 import { createApi } from '@reduxjs/toolkit/query/react';
 import { baseQuery } from '@/services/baseQuery';
 import type { LayoutModelPiece } from '@/features/layout/layoutModelSlice';
+import type { SectionsList } from '@/types/Section';
 
 interface SaveSectionData extends LayoutModelPiece {
   name: string;
@@ -13,7 +14,7 @@ export const sectionApi = createApi({
   baseQuery,
   tagTypes: ['Sections'],
   endpoints: (builder) => ({
-    getSections: builder.query<any, void>({
+    getSections: builder.query<SectionsList, void>({
       query: () => `/xb/api/config/pattern`,
       providesTags: () => [{ type: 'Sections', id: 'LIST' }],
     }),
diff --git a/ui/src/types/Component.ts b/ui/src/types/Component.ts
index 5693cff6d04da342cab33b063818f5278a1fa314..9680d4e386b763d3b8139864e6e46aa2dc7a06a7 100644
--- a/ui/src/types/Component.ts
+++ b/ui/src/types/Component.ts
@@ -23,18 +23,40 @@ export interface FieldDataItem {
   [x: string | number | symbol]: unknown;
 }
 
-export interface SimpleComponent {
-  name: string;
+interface BaseComponent {
   id: string;
+  name: string;
+  library: string;
+  category: string;
+  source: string;
   default_markup: string;
   css: string;
   js_header: string;
   js_footer: string;
-  // The source plugin label.
-  source: string;
 }
 
-export interface PropSourceComponent extends SimpleComponent {
+export type libraryTypes =
+  | 'dynamic_components'
+  | 'primary_components'
+  | 'extension_components'
+  | 'elements';
+
+// For now, these are only Blocks. Later, it will be more.
+export interface DynamicComponent extends BaseComponent {
+  library: 'dynamic_components';
+}
+
+// JSComponent Interface
+export interface JSComponent extends BaseComponent {
+  library: 'primary_components';
+  source: 'Code component';
+  dynamic_prop_source_candidates: any[];
+  transforms: any[];
+}
+
+// PropSourceComponent Interface
+export interface PropSourceComponent extends BaseComponent {
+  library: 'elements' | 'extension_components';
   // @todo rename this to propSources - https://www.drupal.org/project/experience_builder/issues/3504421
   field_data: FieldData;
   metadata: {
@@ -46,20 +68,21 @@ export interface PropSourceComponent extends SimpleComponent {
     };
     [key: string]: any;
   };
-  source: string;
+  dynamic_prop_source_candidates: Record<string, any>;
   transforms: TransformConfig;
 }
+// Union type for any component
+export type XBComponent = DynamicComponent | JSComponent | PropSourceComponent;
 
-export type Component = PropSourceComponent | SimpleComponent;
-
+// ComponentsList representing the API response
 export interface ComponentsList {
-  [key: string]: Component;
+  [key: string]: XBComponent;
 }
 
 /**
  * Type predicate.
  *
- * @param {Component | undefined} component
+ * @param {XBComponent | undefined} component
  *   Component to test.
  *
  * @return boolean
@@ -68,7 +91,7 @@ export interface ComponentsList {
  * @todo rename this to componentHasPropSources in https://www.drupal.org/project/experience_builder/issues/3504421
  */
 export const componentHasFieldData = (
-  component: Component | undefined,
+  component: XBComponent | undefined,
 ): component is PropSourceComponent => {
   return component !== undefined && 'field_data' in component;
 };
diff --git a/ui/src/types/Section.ts b/ui/src/types/Section.ts
index 250d57d392217a36a6682f1e424c2036ad88c10d..f26fb258d66a198493adc5b6d0a9d9411ef6e437 100644
--- a/ui/src/types/Section.ts
+++ b/ui/src/types/Section.ts
@@ -1,14 +1,16 @@
-import type { LayoutModelSliceState } from '@/features/layout/layoutModelSlice';
-import type { Component } from '@/types/Component';
+import type { LayoutModelPiece } from '@/features/layout/layoutModelSlice';
 
 export interface Section {
+  layoutModel: LayoutModelPiece;
   name: string;
   id: string;
   default_markup: string;
-  components: Component[];
-  layoutModel: LayoutModelSliceState;
+  css: string;
+  js_header: string;
+  js_footer: string;
 }
 
+// Type for the API response, an object keyed by section ID
 export interface SectionsList {
   [key: string]: Section;
 }
diff --git a/ui/tests/e2e/extension.cy.js b/ui/tests/e2e/extension.cy.js
index 5a26d4cccb84486a960fdfff6f5931131e71e58d..1959ca20f9e4e5fd25edb740328bfc9d39195f7e 100644
--- a/ui/tests/e2e/extension.cy.js
+++ b/ui/tests/e2e/extension.cy.js
@@ -15,37 +15,42 @@ describe('extending experience builder', () => {
   it('Insert, focus, delete a component', () => {
     cy.loadURLandWaitForXBLoaded();
     cy.openLibraryPanel();
-    const availableComponents = [];
+
     cy.get('.primaryPanelContent [data-state="open"]').contains('Components');
 
     // Get the components list from the sidebar so it can be compared to the
     // component select dropdown provided by the extension.
     cy.get('.primaryPanelContent [data-state="open"] [data-xb-name]').then(
       ($components) => {
+        const availableComponents = [];
+
         $components.each((index, item) => {
-          availableComponents.push(item.textContent);
+          availableComponents.push(item.textContent.trim());
+        });
+
+        cy.findByTestId('ex-select-component').then(($select) => {
+          const extensionComponents = [];
+          // Get all the items with values in the extension component list, which
+          // will be compared to the component list from the XB UI.
+          $select.find('option').each((index, item) => {
+            if (item.value) {
+              extensionComponents.push(item.textContent.trim());
+            }
+          });
+
+          // Check if every available component is included in the extension components
+          const allAvailableComponentsExist = availableComponents.every(
+            (component) => extensionComponents.includes(component),
+          );
+
+          expect(
+            allAvailableComponentsExist,
+            'All library components exist in the extension component dropdown',
+          ).to.be.true;
         });
       },
     );
 
-    cy.findByTestId('ex-select-component').then(($select) => {
-      const extensionComponents = [];
-      // Get all the items with values in the extension component list, which
-      // will be compared to the component list from the XB UI.
-      $select.find('option').each((index, item) => {
-        if (item.value) {
-          extensionComponents.push(item.textContent);
-        }
-      });
-
-      // Comparing these two arrays as strings works reliably was opposed to
-      // deep equal.
-      expect(
-        extensionComponents.sort().join(),
-        'The extension provides a components dropdown with every available component in the XB UI',
-      ).to.equal(availableComponents.sort().join());
-    });
-
     cy.log(
       'Confirm that an extension can select an item in the layout, focus it, then delete it',
     );
diff --git a/ui/tests/e2e/primary-panel.cy.js b/ui/tests/e2e/primary-panel.cy.js
index 87d88c589c1372531b74acd3a3f4710cdeca5436..430ff63ae1994d9a1c6ae48a480af7128ec2b786 100644
--- a/ui/tests/e2e/primary-panel.cy.js
+++ b/ui/tests/e2e/primary-panel.cy.js
@@ -20,7 +20,11 @@ describe('Primary panel', () => {
         .reduce((acc, _, index) => {
           const paddedIndex = String(index + 1).padStart(2, '0');
           const id = `experience_builder:component_${paddedIndex}`;
-          acc[id] = { id, name: `Component ${paddedIndex}` };
+          acc[id] = {
+            id,
+            name: `Component ${paddedIndex}`,
+            library: 'elements',
+          };
           return acc;
         }, {}),
     }).as('getComponents');