diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml
index 85886fdb517467547fd0594bc52ca65d74cdd664..09d65ea1c7a4c15d4d57e67c9799ab73afddb5bb 100644
--- a/.github/workflows/dev-build.yaml
+++ b/.github/workflows/dev-build.yaml
@@ -6,7 +6,7 @@ concurrency:
 
 on:
   push:
-    branches: ['2670-feat-can-the-font-size-of-the-chat-input-box-be-increased'] # put your current branch to create a build. Core team only.
+    branches: ['2545-feat-community-hub-integration'] # put your current branch to create a build. Core team only.
     paths-ignore:
       - '**.md'
       - 'cloud-deployments/*'
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index e692cd30366f422eb61bec65f2e97b4bb35384de..9ee1c5d8702ada79f0a210bfc443117727676a94 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -69,6 +69,16 @@ const LiveDocumentSyncManage = lazy(
 );
 const FineTuningWalkthrough = lazy(() => import("@/pages/FineTuning"));
 
+const CommunityHubTrending = lazy(
+  () => import("@/pages/GeneralSettings/CommunityHub/Trending")
+);
+const CommunityHubAuthentication = lazy(
+  () => import("@/pages/GeneralSettings/CommunityHub/Authentication")
+);
+const CommunityHubImportItem = lazy(
+  () => import("@/pages/GeneralSettings/CommunityHub/ImportItem")
+);
+
 export default function App() {
   return (
     <ThemeProvider>
@@ -207,6 +217,21 @@ export default function App() {
                     path="/fine-tuning"
                     element={<AdminRoute Component={FineTuningWalkthrough} />}
                   />
+
+                  <Route
+                    path="/settings/community-hub/trending"
+                    element={<AdminRoute Component={CommunityHubTrending} />}
+                  />
+                  <Route
+                    path="/settings/community-hub/authentication"
+                    element={
+                      <AdminRoute Component={CommunityHubAuthentication} />
+                    }
+                  />
+                  <Route
+                    path="/settings/community-hub/import-item"
+                    element={<AdminRoute Component={CommunityHubImportItem} />}
+                  />
                 </Routes>
                 <ToastContainer />
               </I18nextProvider>
diff --git a/frontend/src/components/SettingsSidebar/index.jsx b/frontend/src/components/SettingsSidebar/index.jsx
index bb2d32b2d481e8048a606aa35cc4cc177eb081c8..a20c2feeb1400a1a1ba4bd0a921c7c084de89a74 100644
--- a/frontend/src/components/SettingsSidebar/index.jsx
+++ b/frontend/src/components/SettingsSidebar/index.jsx
@@ -11,6 +11,7 @@ import {
   PencilSimpleLine,
   Nut,
   Toolbox,
+  Globe,
 } from "@phosphor-icons/react";
 import useUser from "@/hooks/useUser";
 import { isMobile } from "react-device-detect";
@@ -291,6 +292,30 @@ const SidebarOptions = ({ user = null, t }) => (
           flex={true}
           roles={["admin"]}
         />
+        <Option
+          btnText="Community Hub"
+          icon={<Globe className="h-5 w-5 flex-shrink-0" />}
+          childOptions={[
+            {
+              btnText: "Explore Trending",
+              href: paths.communityHub.trending(),
+              flex: true,
+              roles: ["admin"],
+            },
+            {
+              btnText: "Your Account",
+              href: paths.communityHub.authentication(),
+              flex: true,
+              roles: ["admin"],
+            },
+            {
+              btnText: "Import Item",
+              href: paths.communityHub.importItem(),
+              flex: true,
+              roles: ["admin"],
+            },
+          ]}
+        />
         <Option
           btnText={t("settings.customization")}
           icon={<PencilSimpleLine className="h-5 w-5 flex-shrink-0" />}
diff --git a/frontend/src/components/WorkspaceChat/index.jsx b/frontend/src/components/WorkspaceChat/index.jsx
index bff792573038a566665c3747dd51ed1a753f6ae8..90fafe5d039ea6cc4b572653f41ac87004642bf2 100644
--- a/frontend/src/components/WorkspaceChat/index.jsx
+++ b/frontend/src/components/WorkspaceChat/index.jsx
@@ -96,7 +96,7 @@ function copyCodeSnippet(uuid) {
 }
 
 // Listens and hunts for all data-code-snippet clicks.
-function setEventDelegatorForCodeSnippets() {
+export function setEventDelegatorForCodeSnippets() {
   document?.addEventListener("click", function (e) {
     const target = e.target.closest("[data-code-snippet]");
     const uuidCode = target?.dataset?.code;
diff --git a/frontend/src/models/communityHub.js b/frontend/src/models/communityHub.js
new file mode 100644
index 0000000000000000000000000000000000000000..517adbc80dbf92eaf065e34399222d39338c0f49
--- /dev/null
+++ b/frontend/src/models/communityHub.js
@@ -0,0 +1,158 @@
+import { API_BASE } from "@/utils/constants";
+import { baseHeaders } from "@/utils/request";
+
+const CommunityHub = {
+  /**
+   * Get an item from the community hub by its import ID.
+   * @param {string} importId - The import ID of the item.
+   * @returns {Promise<{error: string | null, item: object | null}>}
+   */
+  getItemFromImportId: async (importId) => {
+    return await fetch(`${API_BASE}/community-hub/item`, {
+      method: "POST",
+      headers: baseHeaders(),
+      body: JSON.stringify({ importId }),
+    })
+      .then((res) => res.json())
+      .catch((e) => {
+        console.error(e);
+        return {
+          error: e.message,
+          item: null,
+        };
+      });
+  },
+
+  /**
+   * Apply an item to the AnythingLLM instance. Used for simple items like slash commands and system prompts.
+   * @param {string} importId - The import ID of the item.
+   * @param {object} options - Additional options for applying the item for whatever the item type requires.
+   * @returns {Promise<{success: boolean, error: string | null}>}
+   */
+  applyItem: async (importId, options = {}) => {
+    return await fetch(`${API_BASE}/community-hub/apply`, {
+      method: "POST",
+      headers: baseHeaders(),
+      body: JSON.stringify({ importId, options }),
+    })
+      .then((res) => res.json())
+      .catch((e) => {
+        console.error(e);
+        return {
+          success: false,
+          error: e.message,
+        };
+      });
+  },
+
+  /**
+   * Import a bundle item from the community hub.
+   * @param {string} importId - The import ID of the item.
+   * @returns {Promise<{error: string | null, item: object | null}>}
+   */
+  importBundleItem: async (importId) => {
+    return await fetch(`${API_BASE}/community-hub/import`, {
+      method: "POST",
+      headers: baseHeaders(),
+      body: JSON.stringify({ importId }),
+    })
+      .then(async (res) => {
+        const response = await res.json();
+        if (!res.ok) throw new Error(response?.error ?? res.statusText);
+        return response;
+      })
+      .catch((e) => {
+        return {
+          error: e.message,
+          item: null,
+        };
+      });
+  },
+
+  /**
+   * Update the hub settings (API key, etc.)
+   * @param {Object} data - The data to update.
+   * @returns {Promise<{success: boolean, error: string | null}>}
+   */
+  updateSettings: async (data) => {
+    return await fetch(`${API_BASE}/community-hub/settings`, {
+      method: "POST",
+      headers: baseHeaders(),
+      body: JSON.stringify(data),
+    })
+      .then(async (res) => {
+        const response = await res.json();
+        if (!res.ok)
+          throw new Error(response.error || "Failed to update settings");
+        return { success: true, error: null };
+      })
+      .catch((e) => ({
+        success: false,
+        error: e.message,
+      }));
+  },
+
+  /**
+   * Get the hub settings (API key, etc.)
+   * @returns {Promise<{connectionKey: string | null, error: string | null}>}
+   */
+  getSettings: async () => {
+    return await fetch(`${API_BASE}/community-hub/settings`, {
+      method: "GET",
+      headers: baseHeaders(),
+    })
+      .then(async (res) => {
+        const response = await res.json();
+        if (!res.ok)
+          throw new Error(response.error || "Failed to fetch settings");
+        return { connectionKey: response.connectionKey, error: null };
+      })
+      .catch((e) => ({
+        connectionKey: null,
+        error: e.message,
+      }));
+  },
+
+  /**
+   * Fetch the explore items from the community hub that are publicly available.
+   * @returns {Promise<{agentSkills: {items: [], hasMore: boolean, totalCount: number}, systemPrompts: {items: [], hasMore: boolean, totalCount: number}, slashCommands: {items: [], hasMore: boolean, totalCount: number}}>}
+   */
+  fetchExploreItems: async () => {
+    return await fetch(`${API_BASE}/community-hub/explore`, {
+      method: "GET",
+      headers: baseHeaders(),
+    })
+      .then((res) => res.json())
+      .catch((e) => {
+        console.error(e);
+        return {
+          success: false,
+          error: e.message,
+          result: null,
+        };
+      });
+  },
+
+  /**
+   * Fetch the user items from the community hub.
+   * @returns {Promise<{success: boolean, error: string | null, createdByMe: object, teamItems: object[]}>}
+   */
+  fetchUserItems: async () => {
+    return await fetch(`${API_BASE}/community-hub/items`, {
+      method: "GET",
+      headers: baseHeaders(),
+    })
+      .then((res) => res.json())
+      .catch((e) => {
+        console.error(e);
+        return {
+          success: false,
+          error: e.message,
+          createdByMe: {},
+          teamItems: [],
+        };
+      });
+  },
+};
+
+export default CommunityHub;
diff --git a/frontend/src/models/experimental/agentPlugins.js b/frontend/src/models/experimental/agentPlugins.js
index 9a544d5f12f4d06cacdda594335fbd328f7eab8b..092a9a3203d423bb816a43a1bae41f2cf4cab94e 100644
--- a/frontend/src/models/experimental/agentPlugins.js
+++ b/frontend/src/models/experimental/agentPlugins.js
@@ -38,6 +38,20 @@ const AgentPlugins = {
         return false;
       });
   },
+  deletePlugin: async function (hubId) {
+    return await fetch(`${API_BASE}/experimental/agent-plugins/${hubId}`, {
+      method: "DELETE",
+      headers: baseHeaders(),
+    })
+      .then((res) => {
+        if (!res.ok) throw new Error("Could not delete agent plugin config.");
+        return true;
+      })
+      .catch((e) => {
+        console.error(e);
+        return false;
+      });
+  },
 };
 
 export default AgentPlugins;
diff --git a/frontend/src/pages/Admin/Agents/Imported/ImportedSkillConfig/index.jsx b/frontend/src/pages/Admin/Agents/Imported/ImportedSkillConfig/index.jsx
index 4c99da725aeec5a137737a210ae9f27be7bc5f28..a68ff5b16c1c3723a5bc77d251039a798e30656f 100644
--- a/frontend/src/pages/Admin/Agents/Imported/ImportedSkillConfig/index.jsx
+++ b/frontend/src/pages/Admin/Agents/Imported/ImportedSkillConfig/index.jsx
@@ -1,7 +1,7 @@
 import System from "@/models/system";
 import showToast from "@/utils/toast";
-import { Plug } from "@phosphor-icons/react";
-import { useEffect, useState } from "react";
+import { Gear, Plug } from "@phosphor-icons/react";
+import { useEffect, useState, useRef } from "react";
 import { sentenceCase } from "text-case";
 
 /**
@@ -55,6 +55,11 @@ export default function ImportedSkillConfig({
       prev.map((s) => (s.hubId === config.hubId ? updatedConfig : s))
     );
     setConfig(updatedConfig);
+    showToast(
+      `Skill ${updatedConfig.active ? "activated" : "deactivated"}.`,
+      "success",
+      { clear: true }
+    );
   }
 
   async function handleSubmit(e) {
@@ -91,6 +96,7 @@ export default function ImportedSkillConfig({
       )
     );
     showToast("Skill config updated successfully.", "success");
+    setHasChanges(false);
   }
 
   useEffect(() => {
@@ -119,6 +125,10 @@ export default function ImportedSkillConfig({
               <div className="peer-disabled:opacity-50 pointer-events-none peer h-6 w-11 rounded-full bg-[#CFCFD0] after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border-none after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-[#32D583] peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-transparent"></div>
               <span className="ml-3 text-sm font-medium"></span>
             </label>
+            <ManageSkillMenu
+              config={config}
+              setImportedSkills={setImportedSkills}
+            />
           </div>
           <p className="text-white text-opacity-60 text-xs font-medium py-1.5">
             {config.description} by{" "}
@@ -178,3 +188,64 @@ export default function ImportedSkillConfig({
     </>
   );
 }
+
+function ManageSkillMenu({ config, setImportedSkills }) {
+  const [open, setOpen] = useState(false);
+  const menuRef = useRef(null);
+
+  async function deleteSkill() {
+    if (
+      !window.confirm(
+        "Are you sure you want to delete this skill? This action cannot be undone."
+      )
+    )
+      return;
+    const success = await System.experimentalFeatures.agentPlugins.deletePlugin(
+      config.hubId
+    );
+    if (success) {
+      setImportedSkills((prev) => prev.filter((s) => s.hubId !== config.hubId));
+      showToast("Skill deleted successfully.", "success");
+      setOpen(false);
+    } else {
+      showToast("Failed to delete skill.", "error");
+    }
+  }
+
+  useEffect(() => {
+    const handleClickOutside = (event) => {
+      if (menuRef.current && !menuRef.current.contains(event.target)) {
+        setOpen(false);
+      }
+    };
+
+    document.addEventListener("mousedown", handleClickOutside);
+    return () => {
+      document.removeEventListener("mousedown", handleClickOutside);
+    };
+  }, []);
+
+  if (!config.hubId) return null;
+  return (
+    <div className="relative" ref={menuRef}>
+      <button
+        type="button"
+        onClick={() => setOpen(!open)}
+        className={`border-none transition duration-200 hover:rotate-90 outline-none ring-none ${open ? "rotate-90" : ""}`}
+      >
+        <Gear size={24} weight="bold" />
+      </button>
+      {open && (
+        <div className="absolute w-[100px] -top-1 left-7 mt-1 border-[1.5px] border-white/40 rounded-lg bg-theme-action-menu-bg flex flex-col shadow-[0_4px_14px_rgba(0,0,0,0.25)] text-white z-99 md:z-10">
+          <button
+            type="button"
+            onClick={deleteSkill}
+            className="border-none flex items-center rounded-lg gap-x-2 hover:bg-theme-action-menu-item-hover py-1.5 px-2 transition-colors duration-200 w-full text-left"
+          >
+            <span className="text-sm">Delete Skill</span>
+          </button>
+        </div>
+      )}
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/Authentication/UserItems/index.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/Authentication/UserItems/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..03787b02a20cb56934f7e283c5b4760370f9ef08
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/Authentication/UserItems/index.jsx
@@ -0,0 +1,89 @@
+import paths from "@/utils/paths";
+import HubItemCard from "../../Trending/HubItems/HubItemCard";
+import { useUserItems } from "../useUserItems";
+import { HubItemCardSkeleton } from "../../Trending/HubItems";
+import { readableType } from "../../utils";
+
+export default function UserItems({ connectionKey }) {
+  const { loading, userItems } = useUserItems({ connectionKey });
+  const { createdByMe = {}, teamItems = [] } = userItems || {};
+
+  if (loading) return <HubItemCardSkeleton />;
+  return (
+    <div className="flex flex-col gap-y-8">
+      {/* Created By Me Section */}
+      <div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
+        <div className="flex items-center justify-between">
+          <p className="text-lg leading-6 font-bold text-white">
+            Created by me
+          </p>
+          <a
+            href={paths.communityHub.noPrivateItems()}
+            target="_blank"
+            rel="noreferrer"
+            className="text-primary-button hover:text-primary-button/80 text-sm"
+          >
+            Why can't I see my private items?
+          </a>
+        </div>
+        <p className="text-xs leading-[18px] font-base text-white text-opacity-60">
+          Items you have created and shared publicly on the AnythingLLM
+          Community Hub.
+        </p>
+        <div className="flex flex-col gap-4 mt-4">
+          {Object.keys(createdByMe).map((type) => {
+            if (!createdByMe[type]?.items?.length) return null;
+            return (
+              <div key={type} className="rounded-lg w-full">
+                <h3 className="text-white capitalize font-medium mb-3">
+                  {readableType(type)}
+                </h3>
+                <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2">
+                  {createdByMe[type].items.map((item) => (
+                    <HubItemCard key={item.id} type={type} item={item} />
+                  ))}
+                </div>
+              </div>
+            );
+          })}
+        </div>
+      </div>
+
+      {/* Team Items Section */}
+      <div className="w-full flex flex-col gap-y-1 pb-6 border-white border-b-2 border-opacity-10">
+        <div className="items-center">
+          <p className="text-lg leading-6 font-bold text-white">
+            Items by team
+          </p>
+        </div>
+        <p className="text-xs leading-[18px] font-base text-white text-opacity-60">
+          Public and private items shared with teams you belong to.
+        </p>
+        <div className="flex flex-col gap-4 mt-4">
+          {teamItems.map((team) => (
+            <div key={team.teamId} className="flex flex-col gap-y-4">
+              <h3 className="text-white text-sm font-medium">
+                {team.teamName}
+              </h3>
+              {Object.keys(team.items).map((type) => {
+                if (team.items[type].items.length === 0) return null;
+                return (
+                  <div key={type} className="rounded-lg w-full">
+                    <h3 className="text-white capitalize font-medium mb-3">
+                      {readableType(type)}
+                    </h3>
+                    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2">
+                      {team.items[type].items.map((item) => (
+                        <HubItemCard key={item.id} type={type} item={item} />
+                      ))}
+                    </div>
+                  </div>
+                );
+              })}
+            </div>
+          ))}
+        </div>
+      </div>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/Authentication/index.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/Authentication/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..1f54c76b45d57d3efca348e6633bd8e8da20757c
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/Authentication/index.jsx
@@ -0,0 +1,174 @@
+import Sidebar from "@/components/SettingsSidebar";
+import { isMobile } from "react-device-detect";
+import { useEffect, useState } from "react";
+import CommunityHub from "@/models/communityHub";
+import ContextualSaveBar from "@/components/ContextualSaveBar";
+import showToast from "@/utils/toast";
+import { FullScreenLoader } from "@/components/Preloader";
+import paths from "@/utils/paths";
+import { Info } from "@phosphor-icons/react";
+import UserItems from "./UserItems";
+
+function useCommunityHubAuthentication() {
+  const [originalConnectionKey, setOriginalConnectionKey] = useState("");
+  const [hasChanges, setHasChanges] = useState(false);
+  const [connectionKey, setConnectionKey] = useState("");
+  const [loading, setLoading] = useState(true);
+
+  async function resetChanges() {
+    setConnectionKey(originalConnectionKey);
+    setHasChanges(false);
+  }
+
+  async function onConnectionKeyChange(e) {
+    const newConnectionKey = e.target.value;
+    setConnectionKey(newConnectionKey);
+    setHasChanges(true);
+  }
+
+  async function updateConnectionKey() {
+    if (connectionKey === originalConnectionKey) return;
+    setLoading(true);
+    try {
+      const response = await CommunityHub.updateSettings({
+        hub_api_key: connectionKey,
+      });
+      if (!response.success)
+        return showToast("Failed to save API key", "error");
+      setHasChanges(false);
+      showToast("API key saved successfully", "success");
+      setOriginalConnectionKey(connectionKey);
+    } catch (error) {
+      console.error(error);
+      showToast("Failed to save API key", "error");
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  useEffect(() => {
+    const fetchData = async () => {
+      setLoading(true);
+      try {
+        const { connectionKey } = await CommunityHub.getSettings();
+        setOriginalConnectionKey(connectionKey || "");
+        setConnectionKey(connectionKey || "");
+      } catch (error) {
+        console.error("Error fetching data:", error);
+      } finally {
+        setLoading(false);
+      }
+    };
+    fetchData();
+  }, []);
+
+  return {
+    connectionKey,
+    originalConnectionKey,
+    loading,
+    onConnectionKeyChange,
+    updateConnectionKey,
+    hasChanges,
+    resetChanges,
+  };
+}
+
+export default function CommunityHubAuthentication() {
+  const {
+    connectionKey,
+    originalConnectionKey,
+    loading,
+    onConnectionKeyChange,
+    updateConnectionKey,
+    hasChanges,
+    resetChanges,
+  } = useCommunityHubAuthentication();
+  if (loading) return <FullScreenLoader />;
+  return (
+    <div className="w-screen h-screen overflow-hidden bg-theme-bg-container flex">
+      <Sidebar />
+      <ContextualSaveBar
+        showing={hasChanges}
+        onSave={updateConnectionKey}
+        onCancel={resetChanges}
+      />
+      <div
+        style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
+        className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary w-full h-full overflow-y-scroll p-4 md:p-0"
+      >
+        <div className="flex flex-col w-full px-1 md:pl-6 md:pr-[86px] md:py-6 py-16">
+          <div className="w-full flex flex-col gap-y-1 pb-6 border-white light:border-theme-sidebar-border border-b-2 border-opacity-10">
+            <div className="items-center">
+              <p className="text-lg leading-6 font-bold text-theme-text-primary">
+                Your AnythingLLM Community Hub Account
+              </p>
+            </div>
+            <p className="text-xs leading-[18px] font-base text-theme-text-secondary">
+              Connecting your AnythingLLM Community Hub account allows you to
+              access your <b>private</b> AnythingLLM Community Hub items as well
+              as upload your own items to the AnythingLLM Community Hub.
+            </p>
+          </div>
+
+          {!connectionKey && (
+            <div className="border border-theme-border my-2 flex flex-col md:flex-row md:items-center gap-x-2 text-theme-text-primary mb-4 bg-theme-settings-input-bg w-1/2 rounded-lg px-4 py-2">
+              <div className="flex flex-col gap-y-2">
+                <div className="gap-x-2 flex items-center">
+                  <Info size={25} />
+                  <h1 className="text-lg font-semibold">
+                    Why connect my AnythingLLM Community Hub account?
+                  </h1>
+                </div>
+                <p className="text-sm text-theme-text-secondary">
+                  Connecting your AnythingLLM Community Hub account allows you
+                  to pull in your <b>private</b> items from the AnythingLLM
+                  Community Hub as well as upload your own items to the
+                  AnythingLLM Community Hub.
+                  <br />
+                  <br />
+                  <i>
+                    You do not need to connect your AnythingLLM Community Hub
+                    account to pull in public items from the AnythingLLM
+                    Community Hub.
+                  </i>
+                </p>
+              </div>
+            </div>
+          )}
+
+          {/* API Key Section */}
+          <div className="mt-6 mb-12">
+            <div className="flex flex-col w-full max-w-[400px]">
+              <label className="text-theme-text-primary text-sm font-semibold block mb-2">
+                AnythingLLM Hub API Key
+              </label>
+              <input
+                type="password"
+                value={connectionKey || ""}
+                onChange={onConnectionKeyChange}
+                className="bg-theme-settings-input-bg text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
+                placeholder="Enter your AnythingLLM Hub API key"
+              />
+              <p className="text-theme-text-secondary text-xs mt-2">
+                You can get your API key from your{" "}
+                <a
+                  href={paths.communityHub.profile()}
+                  className="underline text-primary-button"
+                >
+                  AnythingLLM Community Hub profile page
+                </a>
+                .
+              </p>
+            </div>
+          </div>
+
+          {!!originalConnectionKey && (
+            <div className="mt-6">
+              <UserItems connectionKey={originalConnectionKey} />
+            </div>
+          )}
+        </div>
+      </div>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/Authentication/useUserItems.js b/frontend/src/pages/GeneralSettings/CommunityHub/Authentication/useUserItems.js
new file mode 100644
index 0000000000000000000000000000000000000000..3222f84740cc04b9018a6c76ce76611e3123cccd
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/Authentication/useUserItems.js
@@ -0,0 +1,39 @@
+import { useState, useEffect } from "react";
+import CommunityHub from "@/models/communityHub";
+
+const DEFAULT_USER_ITEMS = {
+  createdByMe: {
+    agentSkills: { items: [] },
+    systemPrompts: { items: [] },
+    slashCommands: { items: [] },
+  },
+  teamItems: [],
+};
+
+export function useUserItems({ connectionKey }) {
+  const [loading, setLoading] = useState(true);
+  const [userItems, setUserItems] = useState(DEFAULT_USER_ITEMS);
+
+  useEffect(() => {
+    const fetchData = async () => {
+      console.log("fetching user items", connectionKey);
+      if (!connectionKey) return;
+      setLoading(true);
+      try {
+        const { success, createdByMe, teamItems } =
+          await CommunityHub.fetchUserItems();
+        if (success) {
+          setUserItems({ createdByMe, teamItems });
+        }
+      } catch (error) {
+        console.error("Error fetching user items:", error);
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchData();
+  }, [connectionKey]);
+
+  return { loading, userItems };
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/Completed/index.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/Completed/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..38c1aba8d971577891b60c1b567cc770ee489766
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/Completed/index.jsx
@@ -0,0 +1,36 @@
+import CommunityHubImportItemSteps from "..";
+import CTAButton from "@/components/lib/CTAButton";
+
+export default function Completed({ settings, setSettings, setStep }) {
+  return (
+    <div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
+      <div className="bg-theme-bg-primary light:bg-slate-100 shadow-lg text-theme-text-primary rounded-xl flex-1 p-6">
+        <div className="w-full flex flex-col gap-y-2 max-w-[700px]">
+          <h2 className="text-base font-semibold">
+            Community Hub Item Imported
+          </h2>
+          <div className="flex flex-col gap-y-[25px] text-theme-text-secondary text-sm">
+            <p>
+              The "{settings.item.name}" {settings.item.itemType} has been
+              imported successfully! It is now available in your AnythingLLM
+              instance.
+            </p>
+            <p>
+              Any changes you make to this {settings.item.itemType} will not be
+              reflected in the community hub. You can now modify as needed.
+            </p>
+          </div>
+          <CTAButton
+            className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
+            onClick={() => {
+              setSettings({ item: null, itemId: null });
+              setStep(CommunityHubImportItemSteps.itemId.key);
+            }}
+          >
+            Import another item
+          </CTAButton>
+        </div>
+      </div>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/Introduction/index.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/Introduction/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..5456d542bcfa70735357325b903019a362e96326
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/Introduction/index.jsx
@@ -0,0 +1,76 @@
+import CommunityHubImportItemSteps from "..";
+import CTAButton from "@/components/lib/CTAButton";
+import paths from "@/utils/paths";
+import showToast from "@/utils/toast";
+import { useState } from "react";
+
+export default function Introduction({ settings, setSettings, setStep }) {
+  const [itemId, setItemId] = useState(settings.itemId);
+  const handleContinue = () => {
+    if (!itemId) return showToast("Please enter an item ID", "error");
+    setSettings((prev) => ({ ...prev, itemId }));
+    setStep(CommunityHubImportItemSteps.itemId.next());
+  };
+
+  return (
+    <div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
+      <div className="bg-theme-bg-primary light:bg-slate-100 shadow-lg text-theme-text-primary rounded-xl flex-1 p-6">
+        <div className="w-full flex flex-col gap-y-2 max-w-[700px]">
+          <h2 className="text-base font-semibold">
+            Import an item from the community hub
+          </h2>
+          <div className="flex flex-col gap-y-[25px] text-theme-text-secondary">
+            <p>
+              The community hub is a place where you can find, share, and import
+              agent-skills, system prompts, slash commands, and more!
+            </p>
+            <p>
+              These items are created by the AnythingLLM team and community, and
+              are a great way to get started with AnythingLLM as well as extend
+              AnythingLLM in a way that is customized to your needs.
+            </p>
+            <p>
+              There are both <b>private</b> and <b>public</b> items in the
+              community hub. Private items are only visible to you, while public
+              items are visible to everyone.
+            </p>
+
+            <p className="p-4 bg-yellow-800/30 light:bg-yellow-100 rounded-lg border border-yellow-500 text-yellow-500">
+              If you are pulling in a private item, make sure it is{" "}
+              <b>shared with a team</b> you belong to, and you have added a{" "}
+              <a
+                href={paths.communityHub.authentication()}
+                className="underline text-yellow-100 light:text-yellow-500 font-semibold"
+              >
+                Connection Key.
+              </a>
+            </p>
+          </div>
+
+          <div className="flex flex-col gap-y-2 mt-4">
+            <div className="w-full flex flex-col gap-y-4">
+              <div className="flex flex-col w-full">
+                <label className="text-sm font-semibold block mb-3">
+                  Community Hub Item Import ID
+                </label>
+                <input
+                  type="text"
+                  value={itemId}
+                  onChange={(e) => setItemId(e.target.value)}
+                  placeholder="allm-community-id:agent-skill:1234567890"
+                  className="bg-zinc-900 light:bg-white text-theme-text-primary placeholder:text-theme-settings-input-placeholder text-sm rounded-lg focus:outline-primary-button active:outline-primary-button outline-none block w-full p-2.5"
+                />
+              </div>
+            </div>
+          </div>
+          <CTAButton
+            className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
+            onClick={handleContinue}
+          >
+            Continue with import &rarr;
+          </CTAButton>
+        </div>
+      </div>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/AgentSkill.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/AgentSkill.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b3ca6dd96aeaba5bf449c59ea417b1818b2a4a46
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/AgentSkill.jsx
@@ -0,0 +1,186 @@
+import CTAButton from "@/components/lib/CTAButton";
+import CommunityHubImportItemSteps from "../..";
+import showToast from "@/utils/toast";
+import paths from "@/utils/paths";
+import {
+  CaretLeft,
+  CaretRight,
+  CircleNotch,
+  Warning,
+} from "@phosphor-icons/react";
+import { useEffect, useState } from "react";
+import renderMarkdown from "@/utils/chat/markdown";
+import DOMPurify from "dompurify";
+import CommunityHub from "@/models/communityHub";
+import { setEventDelegatorForCodeSnippets } from "@/components/WorkspaceChat";
+
+export default function AgentSkill({ item, settings, setStep }) {
+  const [loading, setLoading] = useState(false);
+  async function importAgentSkill() {
+    try {
+      setLoading(true);
+      const { error } = await CommunityHub.importBundleItem(settings.itemId);
+      if (error) throw new Error(error);
+      showToast(`Agent skill imported successfully!`, "success");
+      setStep(CommunityHubImportItemSteps.completed.key);
+    } catch (e) {
+      console.error(e);
+      showToast(`Failed to import agent skill. ${e.message}`, "error");
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  useEffect(() => {
+    setEventDelegatorForCodeSnippets();
+  }, []);
+
+  return (
+    <div className="flex flex-col mt-4 gap-y-4">
+      <div className="border border-white/10 light:border-orange-500/20 my-2 flex flex-col md:flex-row md:items-center gap-x-2 text-theme-text-primary mb-4 bg-orange-800/30 light:bg-orange-500/10 rounded-lg px-4 py-2">
+        <div className="flex flex-col gap-y-2">
+          <div className="gap-x-2 flex items-center">
+            <Warning size={25} />
+            <h1 className="text-lg font-semibold">
+              {" "}
+              Only import agent skills you trust{" "}
+            </h1>
+          </div>
+          <p className="text-sm">
+            Agent skills can execute code on your AnythingLLM instance, so only
+            import agent skills from sources you trust. You should also review
+            the code before importing. If you are unsure about what a skill does
+            - don't import it!
+          </p>
+        </div>
+      </div>
+
+      <div className="flex flex-col gap-y-1">
+        <h2 className="text-base text-theme-text-primary font-semibold">
+          Review Agent Skill "{item.name}"
+        </h2>
+        {item.creatorUsername && (
+          <p className="text-white/60 light:text-theme-text-secondary text-xs font-mono">
+            Created by{" "}
+            <a
+              href={paths.communityHub.profile(item.creatorUsername)}
+              target="_blank"
+              className="hover:text-blue-500 hover:underline"
+              rel="noreferrer"
+            >
+              @{item.creatorUsername}
+            </a>
+          </p>
+        )}
+        <div className="flex gap-x-1">
+          {item.verified ? (
+            <p className="text-green-500 text-xs font-mono">Verified code</p>
+          ) : (
+            <p className="text-red-500 text-xs font-mono">
+              This skill is not verified.
+            </p>
+          )}
+          <a
+            href="https://docs.anythingllm.com/community-hub/faq#verification"
+            target="_blank"
+            className="text-xs font-mono text-blue-500 hover:underline"
+            rel="noreferrer"
+          >
+            Learn more &rarr;
+          </a>
+        </div>
+      </div>
+      <div className="flex flex-col gap-y-[25px] text-white/80 light:text-theme-text-secondary text-sm">
+        <p>
+          Agent skills unlock new capabilities for your AnythingLLM workspace
+          via{" "}
+          <code className="font-mono bg-zinc-900 light:bg-slate-200 px-1 py-0.5 rounded-md text-sm">
+            @agent
+          </code>{" "}
+          skills that can do specific tasks when invoked.
+        </p>
+      </div>
+      <FileReview item={item} />
+      <CTAButton
+        disabled={loading}
+        className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
+        onClick={importAgentSkill}
+      >
+        {loading ? <CircleNotch size={16} className="animate-spin" /> : null}
+        {loading ? "Importing..." : "Import agent skill"}
+      </CTAButton>
+    </div>
+  );
+}
+
+function FileReview({ item }) {
+  const files = item.manifest.files || [];
+  const [index, setIndex] = useState(0);
+  const [file, setFile] = useState(files[index]);
+  function handlePrevious() {
+    if (index > 0) setIndex(index - 1);
+  }
+
+  function handleNext() {
+    if (index < files.length - 1) setIndex(index + 1);
+  }
+
+  function fileMarkup(file) {
+    const extension = file.name.split(".").pop();
+    switch (extension) {
+      case "js":
+        return "javascript";
+      case "json":
+        return "json";
+      case "md":
+        return "markdown";
+      default:
+        return "text";
+    }
+  }
+
+  useEffect(() => {
+    if (files.length > 0) setFile(files?.[index] || files[0]);
+  }, [index]);
+
+  if (!file) return null;
+  return (
+    <div className="flex flex-col gap-y-2">
+      <div className="flex flex-col gap-y-2">
+        <div className="flex justify-between items-center">
+          <button
+            type="button"
+            className={`bg-black/70 light:bg-slate-200 rounded-md p-1 text-white/60 light:text-theme-text-secondary text-xs font-mono ${
+              index === 0 ? "opacity-50 cursor-not-allowed" : ""
+            }`}
+            onClick={handlePrevious}
+          >
+            <CaretLeft size={16} />
+          </button>
+          <p className="text-white/60 light:text-theme-text-secondary text-xs font-mono">
+            {file.name} ({index + 1} of {files.length} files)
+          </p>
+          <button
+            type="button"
+            className={`bg-black/70 light:bg-slate-200 rounded-md p-1 text-white/60 light:text-theme-text-secondary text-xs font-mono ${
+              index === files.length - 1 ? "opacity-50 cursor-not-allowed" : ""
+            }`}
+            onClick={handleNext}
+          >
+            <CaretRight size={16} />
+          </button>
+        </div>
+        <span
+          className="whitespace-pre-line flex flex-col gap-y-1 text-sm leading-[20px] max-h-[500px] overflow-y-auto hljs"
+          dangerouslySetInnerHTML={{
+            __html: DOMPurify.sanitize(
+              renderMarkdown(
+                `\`\`\`${fileMarkup(file)}\n${file.content}\n\`\`\``
+              )
+            ),
+          }}
+        />
+      </div>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/SlashCommand.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/SlashCommand.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..aa33a777bd1208aba66555d11126e285463320be
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/SlashCommand.jsx
@@ -0,0 +1,81 @@
+import CTAButton from "@/components/lib/CTAButton";
+import CommunityHubImportItemSteps from "../..";
+import showToast from "@/utils/toast";
+import paths from "@/utils/paths";
+import CommunityHub from "@/models/communityHub";
+
+export default function SlashCommand({ item, setStep }) {
+  async function handleSubmit() {
+    try {
+      const { error } = await CommunityHub.applyItem(item.importId);
+      if (error) throw new Error(error);
+      showToast(
+        `Slash command ${item.command} imported successfully!`,
+        "success"
+      );
+      setStep(CommunityHubImportItemSteps.completed.key);
+    } catch (e) {
+      console.error(e);
+      showToast(`Failed to import slash command. ${e.message}`, "error");
+    } finally {
+      setLoading(false);
+    }
+  }
+
+  return (
+    <div className="flex flex-col mt-4 gap-y-4">
+      <div className="flex flex-col gap-y-1">
+        <h2 className="text-base text-theme-text-primary font-semibold">
+          Review Slash Command "{item.name}"
+        </h2>
+        {item.creatorUsername && (
+          <p className="text-white/60 text-xs font-mono">
+            Created by{" "}
+            <a
+              href={paths.communityHub.profile(item.creatorUsername)}
+              target="_blank"
+              className="hover:text-blue-500 hover:underline"
+              rel="noreferrer"
+            >
+              @{item.creatorUsername}
+            </a>
+          </p>
+        )}
+      </div>
+      <div className="flex flex-col gap-y-[25px] text-white/80 light:text-theme-text-secondary text-sm">
+        <p>
+          Slash commands are used to prefill information into a prompt while
+          chatting with a AnythingLLM workspace.
+          <br />
+          <br />
+          The slash command will be available during chatting by simply invoking
+          it with{" "}
+          <code className="font-mono bg-zinc-900 light:bg-slate-200 px-1 py-0.5 rounded-md text-sm">
+            {item.command}
+          </code>{" "}
+          like you would any other command.
+        </p>
+
+        <div className="flex flex-col gap-y-2 mt-2">
+          <div className="w-full text-theme-text-primary text-md gap-x-2 flex items-center">
+            <p className="text-white/60 light:text-theme-text-secondary w-fit font-mono bg-zinc-900 light:bg-slate-200 px-2 py-1 rounded-md text-sm whitespace-pre-line">
+              {item.command}
+            </p>
+          </div>
+
+          <div className="w-full text-theme-text-primary text-md flex flex-col gap-y-2">
+            <p className="text-white/60 light:text-theme-text-secondary font-mono bg-zinc-900 light:bg-slate-200 p-4 rounded-md text-sm whitespace-pre-line max-h-[calc(200px)] overflow-y-auto">
+              {item.prompt}
+            </p>
+          </div>
+        </div>
+      </div>
+      <CTAButton
+        className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
+        onClick={handleSubmit}
+      >
+        Import slash command
+      </CTAButton>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/SystemPrompt.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/SystemPrompt.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..fa12298bc2fe821f39defc8dcf3ebba63c48f25e
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/SystemPrompt.jsx
@@ -0,0 +1,106 @@
+import CTAButton from "@/components/lib/CTAButton";
+import CommunityHubImportItemSteps from "../..";
+import { useEffect, useState } from "react";
+import Workspace from "@/models/workspace";
+import showToast from "@/utils/toast";
+import paths from "@/utils/paths";
+import CommunityHub from "@/models/communityHub";
+
+export default function SystemPrompt({ item, setStep }) {
+  const [destinationWorkspaceSlug, setDestinationWorkspaceSlug] =
+    useState(null);
+  const [workspaces, setWorkspaces] = useState([]);
+  useEffect(() => {
+    async function getWorkspaces() {
+      const workspaces = await Workspace.all();
+      setWorkspaces(workspaces);
+      setDestinationWorkspaceSlug(workspaces[0].slug);
+    }
+    getWorkspaces();
+  }, []);
+
+  async function handleSubmit() {
+    showToast("Applying system prompt to workspace...", "info");
+    const { error } = await CommunityHub.applyItem(item.importId, {
+      workspaceSlug: destinationWorkspaceSlug,
+    });
+    if (error) {
+      return showToast(`Failed to apply system prompt. ${error}`, "error", {
+        clear: true,
+      });
+    }
+
+    showToast("System prompt applied to workspace.", "success", {
+      clear: true,
+    });
+    setStep(CommunityHubImportItemSteps.completed.key);
+  }
+
+  return (
+    <div className="flex flex-col mt-4 gap-y-4">
+      <div className="flex flex-col gap-y-1">
+        <h2 className="text-base text-theme-text-primary font-semibold">
+          Review System Prompt "{item.name}"
+        </h2>
+        {item.creatorUsername && (
+          <p className="text-white/60 light:text-theme-text-secondary text-xs font-mono">
+            Created by{" "}
+            <a
+              href={paths.communityHub.profile(item.creatorUsername)}
+              target="_blank"
+              className="hover:text-blue-500 hover:underline"
+              rel="noreferrer"
+            >
+              @{item.creatorUsername}
+            </a>
+          </p>
+        )}
+      </div>
+      <div className="flex flex-col gap-y-[25px] text-white/80 light:text-theme-text-secondary text-sm">
+        <p>
+          System prompts are used to guide the behavior of the AI agents and can
+          be applied to any existing workspace.
+        </p>
+
+        <div className="flex flex-col gap-y-2">
+          <p className="text-white/60 light:text-theme-text-secondary font-semibold">
+            Provided system prompt:
+          </p>
+          <div className="w-full text-theme-text-primary text-md flex flex-col max-h-[calc(300px)] overflow-y-auto">
+            <p className="text-white/60 light:text-theme-text-secondary font-mono bg-zinc-900 light:bg-slate-200 px-2 py-1 rounded-md text-sm whitespace-pre-line">
+              {item.prompt}
+            </p>
+          </div>
+        </div>
+
+        <div className="flex flex-col w-60">
+          <label className="text-theme-text-primary text-sm font-semibold block mb-3">
+            Apply to Workspace
+          </label>
+          <select
+            name="destinationWorkspaceSlug"
+            required={true}
+            onChange={(e) => setDestinationWorkspaceSlug(e.target.value)}
+            className="bg-zinc-900 light:bg-white border-gray-500 text-theme-text-primary text-sm rounded-lg block w-full p-2.5"
+          >
+            <optgroup label="Available workspaces">
+              {workspaces.map((workspace) => (
+                <option key={workspace.id} value={workspace.slug}>
+                  {workspace.name}
+                </option>
+              ))}
+            </optgroup>
+          </select>
+        </div>
+      </div>
+      {destinationWorkspaceSlug && (
+        <CTAButton
+          className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
+          onClick={handleSubmit}
+        >
+          Apply system prompt to workspace
+        </CTAButton>
+      )}
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/Unknown.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/Unknown.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f96d57619a4cad7319434ded785a298a6fd5648d
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/Unknown.jsx
@@ -0,0 +1,39 @@
+import CTAButton from "@/components/lib/CTAButton";
+import CommunityHubImportItemSteps from "../..";
+import { Warning } from "@phosphor-icons/react";
+
+export default function UnknownItem({ item, setSettings, setStep }) {
+  return (
+    <div className="flex flex-col mt-4 gap-y-4">
+      <div className="w-full flex items-center gap-x-2">
+        <Warning size={24} className="text-red-500" />
+        <h2 className="text-base text-red-500 font-semibold">
+          Unsupported item
+        </h2>
+      </div>
+      <div className="flex flex-col gap-y-[25px] text-white/80 text-sm">
+        <p>
+          We found an item in the community hub, but we don't know what it is or
+          it is not yet supported for import into AnythingLLM.
+        </p>
+        <p>
+          The item ID is: <b>{item.id}</b>
+          <br />
+          The item type is: <b>{item.itemType}</b>
+        </p>
+        <p>
+          Please contact support via email if you need help importing this item.
+        </p>
+      </div>
+      <CTAButton
+        className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
+        onClick={() => {
+          setSettings({ itemId: null, item: null });
+          setStep(CommunityHubImportItemSteps.itemId.key);
+        }}
+      >
+        Try another item
+      </CTAButton>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/index.js b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..725ae45f52dc76a6f0be30c3f08dede148cb7de5
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/HubItem/index.js
@@ -0,0 +1,13 @@
+import SystemPrompt from "./SystemPrompt";
+import SlashCommand from "./SlashCommand";
+import UnknownItem from "./Unknown";
+import AgentSkill from "./AgentSkill";
+
+const HubItemComponent = {
+  "agent-skill": AgentSkill,
+  "system-prompt": SystemPrompt,
+  "slash-command": SlashCommand,
+  unknown: UnknownItem,
+};
+
+export default HubItemComponent;
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/index.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..42aa47d0874995050efcd8fcb9187c7f6427f233
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/PullAndReview/index.jsx
@@ -0,0 +1,85 @@
+import CommunityHub from "@/models/communityHub";
+import CommunityHubImportItemSteps from "..";
+import CTAButton from "@/components/lib/CTAButton";
+import { useEffect, useState } from "react";
+import HubItemComponent from "./HubItem";
+import PreLoader from "@/components/Preloader";
+
+function useGetCommunityHubItem({ importId, updateSettings }) {
+  const [item, setItem] = useState(null);
+  const [loading, setLoading] = useState(false);
+  const [error, setError] = useState(null);
+
+  useEffect(() => {
+    async function fetchItem() {
+      if (!importId) return;
+      setLoading(true);
+      await new Promise((resolve) => setTimeout(resolve, 2000));
+      const { error, item } = await CommunityHub.getItemFromImportId(importId);
+      if (error) setError(error);
+      setItem(item);
+      updateSettings((prev) => ({ ...prev, item }));
+      setLoading(false);
+    }
+    fetchItem();
+  }, [importId]);
+
+  return { item, loading, error };
+}
+
+export default function PullAndReview({ settings, setSettings, setStep }) {
+  const { item, loading, error } = useGetCommunityHubItem({
+    importId: settings.itemId,
+    updateSettings: setSettings,
+  });
+  const ItemComponent =
+    HubItemComponent[item?.itemType] || HubItemComponent["unknown"];
+
+  return (
+    <div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
+      <div className="bg-theme-bg-primary light:bg-slate-100 shadow-lg text-theme-text-primary rounded-xl flex-1 p-6">
+        <div className="w-full flex flex-col gap-y-2 max-w-[700px]">
+          <h2 className="text-base font-semibold">Review item</h2>
+
+          {loading && (
+            <div className="flex h-[200px] min-w-[746px] bg-theme-bg-container light:bg-slate-200 rounded-lg animate-pulse">
+              <div className="w-full h-full flex items-center justify-center">
+                <p className="text-sm text-theme-text-secondary">
+                  Pulling item details from community hub...
+                </p>
+              </div>
+            </div>
+          )}
+          {!loading && error && (
+            <>
+              <div className="flex flex-col gap-y-2 mt-8">
+                <p className="text-red-500">
+                  An error occurred while fetching the item. Please try again
+                  later.
+                </p>
+                <p className="text-red-500/80 text-sm font-mono">{error}</p>
+              </div>
+              <CTAButton
+                className="text-dark-text w-full mt-[18px] h-[34px] hover:bg-accent"
+                onClick={() => {
+                  setSettings({ itemId: null, item: null });
+                  setStep(CommunityHubImportItemSteps.itemId.key);
+                }}
+              >
+                Try another item
+              </CTAButton>
+            </>
+          )}
+          {!loading && !error && item && (
+            <ItemComponent
+              item={item}
+              settings={settings}
+              setSettings={setSettings}
+              setStep={setStep}
+            />
+          )}
+        </div>
+      </div>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/index.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..9a70e7674f7334e13aabd624c20abfd19608924f
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/Steps/index.jsx
@@ -0,0 +1,77 @@
+import { isMobile } from "react-device-detect";
+import { useEffect, useState } from "react";
+import Sidebar from "@/components/SettingsSidebar";
+import Introduction from "./Introduction";
+import PullAndReview from "./PullAndReview";
+import Completed from "./Completed";
+import useQuery from "@/hooks/useQuery";
+
+const CommunityHubImportItemSteps = {
+  itemId: {
+    key: "itemId",
+    name: "1. Paste in Item ID",
+    next: () => "validation",
+    component: ({ settings, setSettings, setStep }) => (
+      <Introduction
+        settings={settings}
+        setSettings={setSettings}
+        setStep={setStep}
+      />
+    ),
+  },
+  validation: {
+    key: "validation",
+    name: "2. Review item",
+    next: () => "completed",
+    component: ({ settings, setSettings, setStep }) => (
+      <PullAndReview
+        settings={settings}
+        setSettings={setSettings}
+        setStep={setStep}
+      />
+    ),
+  },
+  completed: {
+    key: "completed",
+    name: "3. Completed",
+    component: ({ settings, setSettings, setStep }) => (
+      <Completed
+        settings={settings}
+        setSettings={setSettings}
+        setStep={setStep}
+      />
+    ),
+  },
+};
+
+export function CommunityHubImportItemLayout({ setStep, children }) {
+  const query = useQuery();
+  const [settings, setSettings] = useState({
+    itemId: null,
+    item: null,
+  });
+
+  useEffect(() => {
+    function autoForward() {
+      if (query.get("id")) {
+        setSettings({ itemId: query.get("id") });
+        setStep(CommunityHubImportItemSteps.itemId.next());
+      }
+    }
+    autoForward();
+  }, []);
+
+  return (
+    <div className="w-screen h-screen overflow-hidden bg-theme-bg-container flex">
+      <Sidebar />
+      <div
+        style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
+        className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary w-full h-full flex p-4 md:p-0"
+      >
+        {children(settings, setSettings, setStep)}
+      </div>
+    </div>
+  );
+}
+
+export default CommunityHubImportItemSteps;
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/index.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..ee5e9e3d529a053ac6cd2bd94c86062dd233c144
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/ImportItem/index.jsx
@@ -0,0 +1,106 @@
+import React, { useState } from "react";
+import { isMobile } from "react-device-detect";
+import CommunityHubImportItemSteps, {
+  CommunityHubImportItemLayout,
+} from "./Steps";
+
+function SideBarSelection({ setStep, currentStep }) {
+  const currentIndex = Object.keys(CommunityHubImportItemSteps).indexOf(
+    currentStep
+  );
+  return (
+    <div
+      className={`bg-white/5 light:bg-white text-theme-text-primary light:border rounded-xl py-1 px-4 shadow-lg ${
+        isMobile ? "w-full" : "min-w-[360px] w-fit"
+      }`}
+    >
+      {Object.entries(CommunityHubImportItemSteps).map(
+        ([stepKey, props], index) => {
+          const isSelected = currentStep === stepKey;
+          const isLast =
+            index === Object.keys(CommunityHubImportItemSteps).length - 1;
+          const isDone =
+            currentIndex ===
+              Object.keys(CommunityHubImportItemSteps).length - 1 ||
+            index < currentIndex;
+          return (
+            <div
+              key={stepKey}
+              className={[
+                "py-3 flex items-center justify-between transition-all duration-300",
+                isSelected ? "rounded-t-xl" : "",
+                isLast
+                  ? ""
+                  : "border-b border-white/10 light:border-[#026AA2]/10",
+              ].join(" ")}
+            >
+              {isDone || isSelected ? (
+                <button
+                  onClick={() => setStep(stepKey)}
+                  className="border-none hover:underline text-sm font-medium text-theme-text-primary"
+                >
+                  {props.name}
+                </button>
+              ) : (
+                <div className="text-sm text-theme-text-secondary font-medium">
+                  {props.name}
+                </div>
+              )}
+              <div className="flex items-center gap-x-2">
+                {isDone ? (
+                  <div className="w-[14px] h-[14px] rounded-full border border-[#32D583] flex items-center justify-center">
+                    <div className="w-[5.6px] h-[5.6px] rounded-full bg-[#6CE9A6]"></div>
+                  </div>
+                ) : (
+                  <div
+                    className={`w-[14px] h-[14px] rounded-full border border-theme-text-primary ${
+                      isSelected ? "animate-pulse" : "opacity-50"
+                    }`}
+                  />
+                )}
+              </div>
+            </div>
+          );
+        }
+      )}
+    </div>
+  );
+}
+
+export default function CommunityHubImportItemFlow() {
+  const [step, setStep] = useState("itemId");
+
+  const StepPage = CommunityHubImportItemSteps.hasOwnProperty(step)
+    ? CommunityHubImportItemSteps[step]
+    : CommunityHubImportItemSteps.itemId;
+
+  return (
+    <CommunityHubImportItemLayout setStep={setStep}>
+      {(settings, setSettings, setStep) => (
+        <div className="flex flex-col w-full px-1 md:pl-6 md:pr-[86px] md:py-6 py-16">
+          <div className="w-full flex flex-col gap-y-1 pb-6 border-white light:border-theme-sidebar-border border-b-2 border-opacity-10">
+            <div className="items-center">
+              <p className="text-lg leading-6 font-bold text-theme-text-primary">
+                Import a Community Item
+              </p>
+            </div>
+            <p className="text-xs leading-[18px] font-base text-theme-text-secondary">
+              Import items from the AnythingLLM Community Hub to enhance your
+              instance with community-created prompts, skills, and commands.
+            </p>
+          </div>
+          <div className="flex-1 flex h-full">
+            <div className="flex flex-col gap-y-[18px] mt-10 w-[360px] flex-shrink-0">
+              <SideBarSelection setStep={setStep} currentStep={step} />
+            </div>
+            <div className="overflow-y-auto pb-[200px] h-screen no-scroll">
+              <div className="ml-8">
+                {StepPage.component({ settings, setSettings, setStep })}
+              </div>
+            </div>
+          </div>
+        </div>
+      )}
+    </CommunityHubImportItemLayout>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/agentSkill.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/agentSkill.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..cac561be607a4b83dfa96c330a7ec5fc47b3f0f9
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/agentSkill.jsx
@@ -0,0 +1,45 @@
+import { Link } from "react-router-dom";
+import paths from "@/utils/paths";
+import pluralize from "pluralize";
+import { VisibilityIcon } from "./generic";
+
+export default function AgentSkillHubCard({ item }) {
+  return (
+    <>
+      <Link
+        key={item.id}
+        to={paths.communityHub.importItem(item.importId)}
+        className="bg-black/70 light:bg-slate-100 rounded-lg p-3 hover:bg-black/60 light:hover:bg-slate-200 transition-all duration-200 cursor-pointer group border border-transparent hover:border-slate-400"
+      >
+        <div className="flex gap-x-2 items-center">
+          <p className="text-white text-sm font-medium">{item.name}</p>
+          <VisibilityIcon visibility={item.visibility} />
+        </div>
+        <div className="flex flex-col gap-2">
+          <p className="text-white/60 text-xs mt-1">{item.description}</p>
+
+          <p className="font-mono text-xs mt-1 text-white/60">
+            {item.verified ? (
+              <span className="text-green-500">Verified</span>
+            ) : (
+              <span className="text-red-500">Unverified</span>
+            )}{" "}
+            Skill
+          </p>
+          <p className="font-mono text-xs mt-1 text-white/60">
+            {item.manifest.files?.length || 0}{" "}
+            {pluralize("file", item.manifest.files?.length || 0)} found
+          </p>
+        </div>
+        <div className="flex justify-end mt-2">
+          <Link
+            to={paths.communityHub.importItem(item.importId)}
+            className="text-primary-button hover:text-primary-button/80 text-sm font-medium px-3 py-1.5 rounded-md bg-black/30 light:bg-slate-200 group-hover:bg-black/50 light:group-hover:bg-slate-300 transition-all"
+          >
+            Import →
+          </Link>
+        </div>
+      </Link>
+    </>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/generic.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/generic.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3b62dd077e541367870a84a09d08d4894348a5dc
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/generic.jsx
@@ -0,0 +1,45 @@
+import paths from "@/utils/paths";
+import { Eye, LockSimple } from "@phosphor-icons/react";
+import { Link } from "react-router-dom";
+import { Tooltip } from "react-tooltip";
+
+export default function GenericHubCard({ item }) {
+  return (
+    <div
+      key={item.id}
+      className="bg-zinc-800 light:bg-slate-100 rounded-lg p-3 hover:bg-zinc-700 light:hover:bg-slate-200 transition-all duration-200"
+    >
+      <p className="text-white text-sm font-medium">{item.name}</p>
+      <p className="text-white/60 text-xs mt-1">{item.description}</p>
+      <div className="flex justify-end mt-2">
+        <Link
+          className="text-primary-button hover:text-primary-button/80 text-xs"
+          to={paths.communityHub.importItem(item.importId)}
+        >
+          Import →
+        </Link>
+      </div>
+    </div>
+  );
+}
+
+export function VisibilityIcon({ visibility = "public" }) {
+  const Icon = visibility === "private" ? LockSimple : Eye;
+
+  return (
+    <>
+      <div
+        data-tooltip-id="visibility-icon"
+        data-tooltip-content={`This item is ${visibility === "private" ? "private" : "public"}`}
+      >
+        <Icon className="w-4 h-4 text-white/60" />
+      </div>
+      <Tooltip
+        id="visibility-icon"
+        place="top"
+        delayShow={300}
+        className="allm-tooltip !allm-text-xs"
+      />
+    </>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/index.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..f2d53c0f77146bad1d365f4fa881a24a028ad724
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/index.jsx
@@ -0,0 +1,17 @@
+import GenericHubCard from "./generic";
+import SystemPromptHubCard from "./systemPrompt";
+import SlashCommandHubCard from "./slashCommand";
+import AgentSkillHubCard from "./agentSkill";
+
+export default function HubItemCard({ type, item }) {
+  switch (type) {
+    case "systemPrompts":
+      return <SystemPromptHubCard item={item} />;
+    case "slashCommands":
+      return <SlashCommandHubCard item={item} />;
+    case "agentSkills":
+      return <AgentSkillHubCard item={item} />;
+    default:
+      return <GenericHubCard item={item} />;
+  }
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/slashCommand.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/slashCommand.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..7ed6781e74f943ef5b189d7c6ab63320eeed2714
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/slashCommand.jsx
@@ -0,0 +1,45 @@
+import truncate from "truncate";
+import { Link } from "react-router-dom";
+import paths from "@/utils/paths";
+import { VisibilityIcon } from "./generic";
+
+export default function SlashCommandHubCard({ item }) {
+  return (
+    <>
+      <Link
+        key={item.id}
+        to={paths.communityHub.importItem(item.importId)}
+        className="bg-black/70 light:bg-slate-100 rounded-lg p-3 hover:bg-black/60 light:hover:bg-slate-200 transition-all duration-200 cursor-pointer group border border-transparent hover:border-slate-400"
+      >
+        <div className="flex gap-x-2 items-center">
+          <p className="text-white text-sm font-medium">{item.name}</p>
+          <VisibilityIcon visibility={item.visibility} />
+        </div>
+        <div className="flex flex-col gap-2">
+          <p className="text-white/60 text-xs mt-1">{item.description}</p>
+          <label className="text-white/60 text-xs font-semibold mt-4">
+            Command
+          </label>
+          <p className="text-white/60 text-xs bg-zinc-900 light:bg-slate-200 px-2 py-1 rounded-md font-mono border border-slate-800 light:border-slate-300">
+            {item.command}
+          </p>
+
+          <label className="text-white/60 text-xs font-semibold mt-4">
+            Prompt
+          </label>
+          <p className="text-white/60 text-xs bg-zinc-900 light:bg-slate-200 px-2 py-1 rounded-md font-mono border border-slate-800 light:border-slate-300">
+            {truncate(item.prompt, 90)}
+          </p>
+        </div>
+        <div className="flex justify-end mt-2">
+          <Link
+            to={paths.communityHub.importItem(item.importId)}
+            className="text-primary-button hover:text-primary-button/80 text-sm font-medium px-3 py-1.5 rounded-md bg-black/30 light:bg-slate-200 group-hover:bg-black/50 light:group-hover:bg-slate-300 transition-all"
+          >
+            Import →
+          </Link>
+        </div>
+      </Link>
+    </>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/systemPrompt.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/systemPrompt.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..3c929a309c2577dec651cedcd135075f998c2c6a
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/HubItemCard/systemPrompt.jsx
@@ -0,0 +1,38 @@
+import truncate from "truncate";
+import { Link } from "react-router-dom";
+import paths from "@/utils/paths";
+import { VisibilityIcon } from "./generic";
+
+export default function SystemPromptHubCard({ item }) {
+  return (
+    <>
+      <Link
+        key={item.id}
+        to={paths.communityHub.importItem(item.importId)}
+        className="bg-black/70 light:bg-slate-100 rounded-lg p-3 hover:bg-black/60 light:hover:bg-slate-200 transition-all duration-200 cursor-pointer group border border-transparent hover:border-slate-400"
+      >
+        <div className="flex gap-x-2 items-center">
+          <p className="text-white text-sm font-medium">{item.name}</p>
+          <VisibilityIcon visibility={item.visibility} />
+        </div>
+        <div className="flex flex-col gap-2">
+          <p className="text-white/60 text-xs mt-1">{item.description}</p>
+          <label className="text-white/60 text-xs font-semibold mt-4">
+            Prompt
+          </label>
+          <p className="text-white/60 text-xs bg-zinc-900 light:bg-slate-200 px-2 py-1 rounded-md font-mono border border-slate-800 light:border-slate-300">
+            {truncate(item.prompt, 90)}
+          </p>
+        </div>
+        <div className="flex justify-end mt-2">
+          <Link
+            to={paths.communityHub.importItem(item.importId)}
+            className="text-primary-button hover:text-primary-button/80 text-sm font-medium px-3 py-1.5 rounded-md bg-black/30 light:bg-slate-200 group-hover:bg-black/50 light:group-hover:bg-slate-300 transition-all"
+          >
+            Import →
+          </Link>
+        </div>
+      </Link>
+    </>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/index.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..2c1ff58931812593f7e0676e94498848f3397f7e
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/HubItems/index.jsx
@@ -0,0 +1,135 @@
+import { useEffect, useState } from "react";
+import CommunityHub from "@/models/communityHub";
+import paths from "@/utils/paths";
+import HubItemCard from "./HubItemCard";
+import * as Skeleton from "react-loading-skeleton";
+import "react-loading-skeleton/dist/skeleton.css";
+import { readableType, typeToPath } from "../../utils";
+
+const DEFAULT_EXPLORE_ITEMS = {
+  agentSkills: { items: [], hasMore: false, totalCount: 0 },
+  systemPrompts: { items: [], hasMore: false, totalCount: 0 },
+  slashCommands: { items: [], hasMore: false, totalCount: 0 },
+};
+
+function useCommunityHubExploreItems() {
+  const [loading, setLoading] = useState(true);
+  const [exploreItems, setExploreItems] = useState(DEFAULT_EXPLORE_ITEMS);
+  useEffect(() => {
+    const fetchData = async () => {
+      setLoading(true);
+      try {
+        const { success, result } = await CommunityHub.fetchExploreItems();
+        if (success) setExploreItems(result || DEFAULT_EXPLORE_ITEMS);
+      } catch (error) {
+        console.error("Error fetching data:", error);
+      } finally {
+        setLoading(false);
+      }
+    };
+
+    fetchData();
+  }, []);
+
+  return { loading, exploreItems };
+}
+
+export default function HubItems() {
+  const { loading, exploreItems } = useCommunityHubExploreItems();
+  return (
+    <div className="w-full flex flex-col gap-y-1 pb-6 pt-6">
+      <div className="flex flex-col gap-y-2 mb-4">
+        <p className="text-base font-semibold text-theme-text-primary">
+          Recently Added on AnythingLLM Community Hub
+        </p>
+        <p className="text-xs text-theme-text-secondary">
+          Explore the latest additions to the AnythingLLM Community Hub
+        </p>
+      </div>
+      <HubCategory loading={loading} exploreItems={exploreItems} />
+    </div>
+  );
+}
+
+function HubCategory({ loading, exploreItems }) {
+  if (loading) return <HubItemCardSkeleton />;
+  return (
+    <div className="flex flex-col gap-4">
+      {Object.keys(exploreItems).map((type) => {
+        const path = typeToPath(type);
+        if (exploreItems[type].items.length === 0) return null;
+        return (
+          <div key={type} className="rounded-lg w-full">
+            <div className="flex justify-between items-center">
+              <h3 className="text-theme-text-primary capitalize font-medium mb-3">
+                {readableType(type)}
+              </h3>
+              {exploreItems[type].hasMore && (
+                <a
+                  href={paths.communityHub.viewMoreOfType(path)}
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  className="text-primary-button hover:text-primary-button/80 text-sm"
+                >
+                  Explore More →
+                </a>
+              )}
+            </div>
+            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2">
+              {exploreItems[type].items.map((item) => (
+                <HubItemCard key={item.id} type={type} item={item} />
+              ))}
+            </div>
+          </div>
+        );
+      })}
+    </div>
+  );
+}
+
+export function HubItemCardSkeleton() {
+  return (
+    <div className="flex flex-col gap-4">
+      <div className="rounded-lg w-full">
+        <div className="flex justify-between items-center">
+          <Skeleton.default
+            height="40px"
+            width="300px"
+            highlightColor="var(--theme-settings-input-active)"
+            baseColor="var(--theme-settings-input-bg)"
+            count={1}
+          />
+        </div>
+        <Skeleton.default
+          height="200px"
+          width="300px"
+          highlightColor="var(--theme-settings-input-active)"
+          baseColor="var(--theme-settings-input-bg)"
+          count={4}
+          className="rounded-lg"
+          containerClassName="flex flex-wrap gap-2 mt-1"
+        />
+      </div>
+      <div className="rounded-lg w-full">
+        <div className="flex justify-between items-center">
+          <Skeleton.default
+            height="40px"
+            width="300px"
+            highlightColor="var(--theme-settings-input-active)"
+            baseColor="var(--theme-settings-input-bg)"
+            count={1}
+          />
+        </div>
+        <Skeleton.default
+          height="200px"
+          width="300px"
+          highlightColor="var(--theme-settings-input-active)"
+          baseColor="var(--theme-settings-input-bg)"
+          count={4}
+          className="rounded-lg"
+          containerClassName="flex flex-wrap gap-2 mt-1"
+        />
+      </div>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/Trending/index.jsx b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..356029595ab46697720cff22c9d1e2c2cd9b7c63
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/Trending/index.jsx
@@ -0,0 +1,29 @@
+import Sidebar from "@/components/SettingsSidebar";
+import { isMobile } from "react-device-detect";
+import HubItems from "./HubItems";
+
+export default function CommunityHub() {
+  return (
+    <div className="w-screen h-screen overflow-hidden bg-theme-bg-container flex">
+      <Sidebar />
+      <div
+        style={{ height: isMobile ? "100%" : "calc(100% - 32px)" }}
+        className="relative md:ml-[2px] md:mr-[16px] md:my-[16px] md:rounded-[16px] bg-theme-bg-secondary w-full h-full overflow-y-scroll p-4 md:p-0"
+      >
+        <div className="flex flex-col w-full px-1 md:pl-6 md:pr-[86px] md:py-6 py-16">
+          <div className="w-full flex flex-col gap-y-1 pb-6 border-white light:border-theme-sidebar-border border-b-2 border-opacity-10">
+            <div className="items-center">
+              <p className="text-lg leading-6 font-bold text-theme-text-primary">
+                Community Hub
+              </p>
+            </div>
+            <p className="text-xs leading-[18px] font-base text-theme-text-secondary">
+              Share and collaborate with the AnythingLLM community.
+            </p>
+          </div>
+          <HubItems />
+        </div>
+      </div>
+    </div>
+  );
+}
diff --git a/frontend/src/pages/GeneralSettings/CommunityHub/utils.js b/frontend/src/pages/GeneralSettings/CommunityHub/utils.js
new file mode 100644
index 0000000000000000000000000000000000000000..379da33d48da22fc404f21650a0a96a0277ca756
--- /dev/null
+++ b/frontend/src/pages/GeneralSettings/CommunityHub/utils.js
@@ -0,0 +1,37 @@
+/**
+ * Convert a type to a readable string for the community hub.
+ * @param {("agentSkills" | "agentSkill" | "systemPrompts" | "systemPrompt" | "slashCommands" | "slashCommand")} type
+ * @returns {string}
+ */
+export function readableType(type) {
+  switch (type) {
+    case "agentSkills":
+    case "agentSkill":
+      return "Agent Skills";
+    case "systemPrompt":
+    case "systemPrompts":
+      return "System Prompts";
+    case "slashCommand":
+    case "slashCommands":
+      return "Slash Commands";
+  }
+}
+
+/**
+ * Convert a type to a path for the community hub.
+ * @param {("agentSkill" | "agentSkills" | "systemPrompt" | "systemPrompts" | "slashCommand" | "slashCommands")} type
+ * @returns {string}
+ */
+export function typeToPath(type) {
+  switch (type) {
+    case "agentSkill":
+    case "agentSkills":
+      return "agent-skills";
+    case "systemPrompt":
+    case "systemPrompts":
+      return "system-prompts";
+    case "slashCommand":
+    case "slashCommands":
+      return "slash-commands";
+  }
+}
diff --git a/frontend/src/utils/paths.js b/frontend/src/utils/paths.js
index 554cc4599a38935d43826155909fee38d8af4c71..b4ab10573a2c13cda6b3a3245c22208d723da874 100644
--- a/frontend/src/utils/paths.js
+++ b/frontend/src/utils/paths.js
@@ -142,6 +142,38 @@ export default {
       return `/settings/beta-features`;
     },
   },
+  communityHub: {
+    website: () => {
+      return import.meta.env.DEV
+        ? `http://localhost:5173`
+        : `https://hub.anythingllm.com`;
+    },
+    /**
+     * View more items of a given type on the community hub.
+     * @param {string} type - The type of items to view more of. Should be kebab-case.
+     * @returns {string} The path to view more items of the given type.
+     */
+    viewMoreOfType: function (type) {
+      return `${this.website()}/list/${type}`;
+    },
+    trending: () => {
+      return `/settings/community-hub/trending`;
+    },
+    authentication: () => {
+      return `/settings/community-hub/authentication`;
+    },
+    importItem: (importItemId) => {
+      return `/settings/community-hub/import-item${importItemId ? `?id=${importItemId}` : ""}`;
+    },
+    profile: function (username) {
+      if (username) return `${this.website()}/u/${username}`;
+      return `${this.website()}/me`;
+    },
+    noPrivateItems: () => {
+      return "https://docs.anythingllm.com/community-hub/faq#no-private-items";
+    },
+  },
+
   experimental: {
     liveDocumentSync: {
       manage: () => `/settings/beta-features/live-document-sync/manage`,
diff --git a/server/endpoints/communityHub.js b/server/endpoints/communityHub.js
new file mode 100644
index 0000000000000000000000000000000000000000..b8f0981ab528d1e72236de8b3b0c97767361b87c
--- /dev/null
+++ b/server/endpoints/communityHub.js
@@ -0,0 +1,186 @@
+const { SystemSettings } = require("../models/systemSettings");
+const { validatedRequest } = require("../utils/middleware/validatedRequest");
+const { reqBody } = require("../utils/http");
+const { CommunityHub } = require("../models/communityHub");
+const {
+  communityHubDownloadsEnabled,
+  communityHubItem,
+} = require("../utils/middleware/communityHubDownloadsEnabled");
+const { EventLogs } = require("../models/eventLogs");
+const { Telemetry } = require("../models/telemetry");
+const {
+  flexUserRoleValid,
+  ROLES,
+} = require("../utils/middleware/multiUserProtected");
+
+function communityHubEndpoints(app) {
+  if (!app) return;
+
+  app.get(
+    "/community-hub/settings",
+    [validatedRequest, flexUserRoleValid([ROLES.admin])],
+    async (_, response) => {
+      try {
+        const { connectionKey } = await SystemSettings.hubSettings();
+        response.status(200).json({ success: true, connectionKey });
+      } catch (error) {
+        console.error(error);
+        response.status(500).json({ success: false, error: error.message });
+      }
+    }
+  );
+
+  app.post(
+    "/community-hub/settings",
+    [validatedRequest, flexUserRoleValid([ROLES.admin])],
+    async (request, response) => {
+      try {
+        const data = reqBody(request);
+        const result = await SystemSettings.updateSettings(data);
+        if (result.error) throw new Error(result.error);
+        response.status(200).json({ success: true, error: null });
+      } catch (error) {
+        console.error(error);
+        response.status(500).json({ success: false, error: error.message });
+      }
+    }
+  );
+
+  app.get(
+    "/community-hub/explore",
+    [validatedRequest, flexUserRoleValid([ROLES.admin])],
+    async (_, response) => {
+      try {
+        const exploreItems = await CommunityHub.fetchExploreItems();
+        response.status(200).json({ success: true, result: exploreItems });
+      } catch (error) {
+        console.error(error);
+        response.status(500).json({
+          success: false,
+          result: null,
+          error: error.message,
+        });
+      }
+    }
+  );
+
+  app.post(
+    "/community-hub/item",
+    [validatedRequest, flexUserRoleValid([ROLES.admin]), communityHubItem],
+    async (_request, response) => {
+      try {
+        response.status(200).json({
+          success: true,
+          item: response.locals.bundleItem,
+          error: null,
+        });
+      } catch (error) {
+        console.error(error);
+        response.status(500).json({
+          success: false,
+          item: null,
+          error: error.message,
+        });
+      }
+    }
+  );
+
+  /**
+   * Apply an item to the AnythingLLM instance. Used for simple items like slash commands and system prompts.
+   */
+  app.post(
+    "/community-hub/apply",
+    [validatedRequest, flexUserRoleValid([ROLES.admin]), communityHubItem],
+    async (request, response) => {
+      try {
+        const { options = {} } = reqBody(request);
+        const item = response.locals.bundleItem;
+        const { error: applyError } = await CommunityHub.applyItem(item, {
+          ...options,
+          currentUser: response.locals?.user,
+        });
+        if (applyError) throw new Error(applyError);
+
+        await Telemetry.sendTelemetry("community_hub_import", {
+          itemType: response.locals.bundleItem.itemType,
+          visibility: response.locals.bundleItem.visibility,
+        });
+        await EventLogs.logEvent(
+          "community_hub_import",
+          {
+            itemId: response.locals.bundleItem.id,
+            itemType: response.locals.bundleItem.itemType,
+          },
+          response.locals?.user?.id
+        );
+
+        response.status(200).json({ success: true, error: null });
+      } catch (error) {
+        console.error(error);
+        response.status(500).json({ success: false, error: error.message });
+      }
+    }
+  );
+
+  /**
+   * Import a bundle item to the AnythingLLM instance by downloading the zip file and importing it.
+   * or whatever the item type requires. This is not used if the item is a simple text responses like
+   * slash commands or system prompts.
+   */
+  app.post(
+    "/community-hub/import",
+    [
+      validatedRequest,
+      flexUserRoleValid([ROLES.admin]),
+      communityHubItem,
+      communityHubDownloadsEnabled,
+    ],
+    async (_, response) => {
+      try {
+        const { error: importError } = await CommunityHub.importBundleItem({
+          url: response.locals.bundleUrl,
+          item: response.locals.bundleItem,
+        });
+        if (importError) throw new Error(importError);
+
+        await Telemetry.sendTelemetry("community_hub_import", {
+          itemType: response.locals.bundleItem.itemType,
+          visibility: response.locals.bundleItem.visibility,
+        });
+        await EventLogs.logEvent(
+          "community_hub_import",
+          {
+            itemId: response.locals.bundleItem.id,
+            itemType: response.locals.bundleItem.itemType,
+          },
+          response.locals?.user?.id
+        );
+
+        response.status(200).json({ success: true, error: null });
+      } catch (error) {
+        console.error(error);
+        response.status(500).json({
+          success: false,
+          error: error.message,
+        });
+      }
+    }
+  );
+
+  app.get(
+    "/community-hub/items",
+    [validatedRequest, flexUserRoleValid([ROLES.admin])],
+    async (_, response) => {
+      try {
+        const { connectionKey } = await SystemSettings.hubSettings();
+        const items = await CommunityHub.fetchUserItems(connectionKey);
+        response.status(200).json({ success: true, ...items });
+      } catch (error) {
+        console.error(error);
+        response.status(500).json({ success: false, error: error.message });
+      }
+    }
+  );
+}
+
+module.exports = { communityHubEndpoints };
diff --git a/server/endpoints/experimental/imported-agent-plugins.js b/server/endpoints/experimental/imported-agent-plugins.js
index cdc0148cb2f16eae0fe86636b3ebb01e97a3ca16..cabe23d89e785aed02de04a2b1c96cf512dadb31 100644
--- a/server/endpoints/experimental/imported-agent-plugins.js
+++ b/server/endpoints/experimental/imported-agent-plugins.js
@@ -45,6 +45,21 @@ function importedAgentPluginEndpoints(app) {
       }
     }
   );
+
+  app.delete(
+    "/experimental/agent-plugins/:hubId",
+    [validatedRequest, flexUserRoleValid([ROLES.admin])],
+    async (request, response) => {
+      try {
+        const { hubId } = request.params;
+        const result = ImportedPlugin.deletePlugin(hubId);
+        response.status(200).json(result);
+      } catch (e) {
+        console.error(e);
+        response.status(500).end();
+      }
+    }
+  );
 }
 
 module.exports = { importedAgentPluginEndpoints };
diff --git a/server/index.js b/server/index.js
index 041d34c956279fee45c72add4eeecf538ccef01a..caff33e111ce4fafad70253b5566336e56732e77 100644
--- a/server/index.js
+++ b/server/index.js
@@ -25,6 +25,7 @@ const { documentEndpoints } = require("./endpoints/document");
 const { agentWebsocket } = require("./endpoints/agentWebsocket");
 const { experimentalEndpoints } = require("./endpoints/experimental");
 const { browserExtensionEndpoints } = require("./endpoints/browserExtension");
+const { communityHubEndpoints } = require("./endpoints/communityHub");
 const app = express();
 const apiRouter = express.Router();
 const FILE_LIMIT = "3GB";
@@ -59,6 +60,7 @@ documentEndpoints(apiRouter);
 agentWebsocket(apiRouter);
 experimentalEndpoints(apiRouter);
 developerEndpoints(app, apiRouter);
+communityHubEndpoints(apiRouter);
 
 // Externally facing embedder endpoints
 embeddedEndpoints(apiRouter);
diff --git a/server/models/communityHub.js b/server/models/communityHub.js
new file mode 100644
index 0000000000000000000000000000000000000000..e68cdffaebd712daa64e66d39c1e39c470ae8496
--- /dev/null
+++ b/server/models/communityHub.js
@@ -0,0 +1,177 @@
+const ImportedPlugin = require("../utils/agents/imported");
+
+/**
+ * An interface to the AnythingLLM Community Hub external API.
+ */
+const CommunityHub = {
+  importPrefix: "allm-community-id",
+  apiBase:
+    process.env.NODE_ENV === "development"
+      ? "http://127.0.0.1:5001/anythingllm-hub/us-central1/external/v1"
+      : "https://hub.external.anythingllm.com/v1",
+
+  /**
+   * Validate an import ID and return the entity type and ID.
+   * @param {string} importId - The import ID to validate.
+   * @returns {{entityType: string | null, entityId: string | null}}
+   */
+  validateImportId: function (importId) {
+    if (
+      !importId ||
+      !importId.startsWith(this.importPrefix) ||
+      importId.split(":").length !== 3
+    )
+      return { entityType: null, entityId: null };
+    const [_, entityType, entityId] = importId.split(":");
+    if (!entityType || !entityId) return { entityType: null, entityId: null };
+    return {
+      entityType: String(entityType).trim(),
+      entityId: String(entityId).trim(),
+    };
+  },
+
+  /**
+   * Fetch the explore items from the community hub that are publicly available.
+   * @returns {Promise<{agentSkills: {items: [], hasMore: boolean, totalCount: number}, systemPrompts: {items: [], hasMore: boolean, totalCount: number}, slashCommands: {items: [], hasMore: boolean, totalCount: number}}>}
+   */
+  fetchExploreItems: async function () {
+    return await fetch(`${this.apiBase}/explore`, {
+      method: "GET",
+    })
+      .then((response) => response.json())
+      .catch((error) => {
+        console.error("Error fetching explore items:", error);
+        return {
+          agentSkills: {
+            items: [],
+            hasMore: false,
+            totalCount: 0,
+          },
+          systemPrompts: {
+            items: [],
+            hasMore: false,
+            totalCount: 0,
+          },
+          slashCommands: {
+            items: [],
+            hasMore: false,
+            totalCount: 0,
+          },
+        };
+      });
+  },
+
+  /**
+   * Fetch a bundle item from the community hub.
+   * Bundle items are entities that require a downloadURL to be fetched from the community hub.
+   * so we can unzip and import them to the AnythingLLM instance.
+   * @param {string} importId - The import ID of the item.
+   * @returns {Promise<{url: string | null, item: object | null, error: string | null}>}
+   */
+  getBundleItem: async function (importId) {
+    const { entityType, entityId } = this.validateImportId(importId);
+    if (!entityType || !entityId)
+      return { item: null, error: "Invalid import ID" };
+
+    const { SystemSettings } = require("./systemSettings");
+    const { connectionKey } = await SystemSettings.hubSettings();
+    const { url, item, error } = await fetch(
+      `${this.apiBase}/${entityType}/${entityId}/pull`,
+      {
+        method: "GET",
+        headers: {
+          "Content-Type": "application/json",
+          ...(connectionKey
+            ? { Authorization: `Bearer ${connectionKey}` }
+            : {}),
+        },
+      }
+    )
+      .then((response) => response.json())
+      .catch((error) => {
+        console.error(
+          `Error fetching bundle item for import ID ${importId}:`,
+          error
+        );
+        return { url: null, item: null, error: error.message };
+      });
+    return { url, item, error };
+  },
+
+  /**
+   * Apply an item to the AnythingLLM instance. Used for simple items like slash commands and system prompts.
+   * @param {object} item - The item to apply.
+   * @param {object} options - Additional options for applying the item.
+   * @param {object|null} options.currentUser - The current user object.
+   * @returns {Promise<{success: boolean, error: string | null}>}
+   */
+  applyItem: async function (item, options = {}) {
+    if (!item) return { success: false, error: "Item is required" };
+
+    if (item.itemType === "system-prompt") {
+      if (!options?.workspaceSlug)
+        return { success: false, error: "Workspace slug is required" };
+
+      const { Workspace } = require("./workspace");
+      const workspace = await Workspace.get({
+        slug: String(options.workspaceSlug),
+      });
+      if (!workspace) return { success: false, error: "Workspace not found" };
+      await Workspace.update(workspace.id, { openAiPrompt: item.prompt });
+      return { success: true, error: null };
+    }
+
+    if (item.itemType === "slash-command") {
+      const { SlashCommandPresets } = require("./slashCommandsPresets");
+      await SlashCommandPresets.create(options?.currentUser?.id, {
+        command: SlashCommandPresets.formatCommand(String(item.command)),
+        prompt: String(item.prompt),
+        description: String(item.description),
+      });
+      return { success: true, error: null };
+    }
+
+    return {
+      success: false,
+      error: "Unsupported item type. Nothing to apply.",
+    };
+  },
+
+  /**
+   * Import a bundle item to the AnythingLLM instance by downloading the zip file and importing it.
+   * or whatever the item type requires.
+   * @param {{url: string, item: object}} params
+   * @returns {Promise<{success: boolean, error: string | null}>}
+   */
+  importBundleItem: async function ({ url, item }) {
+    if (item.itemType === "agent-skill") {
+      const { success, error } =
+        await ImportedPlugin.importCommunityItemFromUrl(url, item);
+      return { success, error };
+    }
+
+    return {
+      success: false,
+      error: "Unsupported item type. Nothing to import.",
+    };
+  },
+
+  fetchUserItems: async function (connectionKey) {
+    if (!connectionKey) return { createdByMe: {}, teamItems: [] };
+
+    return await fetch(`${this.apiBase}/items`, {
+      method: "GET",
+      headers: {
+        "Content-Type": "application/json",
+        Authorization: `Bearer ${connectionKey}`,
+      },
+    })
+      .then((response) => response.json())
+      .catch((error) => {
+        console.error("Error fetching user items:", error);
+        return { createdByMe: {}, teamItems: [] };
+      });
+  },
+};
+
+module.exports = { CommunityHub };
diff --git a/server/models/slashCommandsPresets.js b/server/models/slashCommandsPresets.js
index 4828c77d59d080a88d2c3d8b94291d4576e69f68..6aab3651667a4cb6738e5a8fdcb60202fcc9146f 100644
--- a/server/models/slashCommandsPresets.js
+++ b/server/models/slashCommandsPresets.js
@@ -39,6 +39,18 @@ const SlashCommandPresets = {
   // Command + userId must be unique combination.
   create: async function (userId = null, presetData = {}) {
     try {
+      const existingPreset = await this.get({
+        userId: userId ? Number(userId) : null,
+        command: String(presetData.command),
+      });
+
+      if (existingPreset) {
+        console.log(
+          "SlashCommandPresets.create - preset already exists - will not create"
+        );
+        return existingPreset;
+      }
+
       const preset = await prisma.slash_command_presets.create({
         data: {
           ...presetData,
diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js
index dd54b8e36217e4f919d6ee258e8d12bd0901f8b5..58011d868ae6206cfd01be440e57cbe6cd59aedc 100644
--- a/server/models/systemSettings.js
+++ b/server/models/systemSettings.js
@@ -14,7 +14,7 @@ function isNullOrNaN(value) {
 }
 
 const SystemSettings = {
-  protectedFields: ["multi_user_mode"],
+  protectedFields: ["multi_user_mode", "hub_api_key"],
   publicFields: [
     "footer_data",
     "support_email",
@@ -49,6 +49,9 @@ const SystemSettings = {
 
     // beta feature flags
     "experimental_live_file_sync",
+
+    // Hub settings
+    "hub_api_key",
   ],
   validations: {
     footer_data: (updates) => {
@@ -165,6 +168,10 @@ const SystemSettings = {
         new MetaGenerator().clearConfig();
       }
     },
+    hub_api_key: (apiKey) => {
+      if (!apiKey) return null;
+      return String(apiKey);
+    },
   },
   currentSettings: async function () {
     const { hasVectorCachedFiles } = require("../utils/files");
@@ -563,6 +570,22 @@ const SystemSettings = {
           ?.value === "enabled",
     };
   },
+
+  /**
+   * Get user configured Community Hub Settings
+   * Connection key is used to authenticate with the Community Hub API
+   * for your account.
+   * @returns {Promise<{connectionKey: string}>}
+   */
+  hubSettings: async function () {
+    try {
+      const hubKey = await this.get({ label: "hub_api_key" });
+      return { connectionKey: hubKey?.value || null };
+    } catch (error) {
+      console.error(error.message);
+      return { connectionKey: null };
+    }
+  },
 };
 
 function mergeConnections(existingConnections = [], updates = []) {
diff --git a/server/package.json b/server/package.json
index e090b431af83c1eac460ae6f4c65751ccaa38d93..a3716dfc8f967c654ba9aa38315cbf68f4801a5e 100644
--- a/server/package.json
+++ b/server/package.json
@@ -38,6 +38,7 @@
     "@qdrant/js-client-rest": "^1.9.0",
     "@xenova/transformers": "^2.14.0",
     "@zilliz/milvus2-sdk-node": "^2.3.5",
+    "adm-zip": "^0.5.16",
     "bcrypt": "^5.1.0",
     "body-parser": "^1.20.2",
     "chalk": "^4",
@@ -92,8 +93,8 @@
     "flow-remove-types": "^2.217.1",
     "globals": "^13.21.0",
     "hermes-eslint": "^0.15.0",
-    "nodemon": "^2.0.22",
     "node-html-markdown": "^1.3.0",
+    "nodemon": "^2.0.22",
     "prettier": "^3.0.3"
   }
-}
\ No newline at end of file
+}
diff --git a/server/utils/agents/imported.js b/server/utils/agents/imported.js
index 8a7da48025139818f7a84f2c9a07625b487f559b..f3ddf3c6ac40d3d8b5d641223ade087f55b77f8a 100644
--- a/server/utils/agents/imported.js
+++ b/server/utils/agents/imported.js
@@ -2,10 +2,12 @@ const fs = require("fs");
 const path = require("path");
 const { safeJsonParse } = require("../http");
 const { isWithin, normalizePath } = require("../files");
+const { CollectorApi } = require("../collectorApi");
 const pluginsPath =
   process.env.NODE_ENV === "development"
     ? path.resolve(__dirname, "../../storage/plugins/agent-skills")
     : path.resolve(process.env.STORAGE_DIR, "plugins", "agent-skills");
+const sharedWebScraper = new CollectorApi();
 
 class ImportedPlugin {
   constructor(config) {
@@ -124,6 +126,20 @@ class ImportedPlugin {
     return updatedConfig;
   }
 
+  /**
+   * Deletes a plugin. Removes the entire folder of the object.
+   * @param {string} hubId - The hub ID of the plugin.
+   * @returns {boolean} - True if the plugin was deleted, false otherwise.
+   */
+  static deletePlugin(hubId) {
+    if (!hubId) throw new Error("No plugin hubID passed.");
+    const pluginFolder = path.resolve(pluginsPath, normalizePath(hubId));
+    if (!this.isValidLocation(pluginFolder)) return;
+    fs.rmSync(pluginFolder, { recursive: true });
+    return true;
+  }
+
+  /**
   /**
    * Validates if the handler.js file exists for the given plugin.
    * @param {string} hubId - The hub ID of the plugin.
@@ -170,6 +186,8 @@ class ImportedPlugin {
           description: this.config.description,
           logger: aibitat?.handlerProps?.log || console.log, // Allows plugin to log to the console.
           introspect: aibitat?.introspect || console.log, // Allows plugin to display a "thought" the chat window UI.
+          runtime: "docker",
+          webScraper: sharedWebScraper,
           examples: this.config.examples ?? [],
           parameters: {
             $schema: "http://json-schema.org/draft-07/schema#",
@@ -182,6 +200,107 @@ class ImportedPlugin {
       },
     };
   }
+
+  /**
+   * Imports a community item from a URL.
+   * The community item is a zip file that contains a plugin.json file and handler.js file.
+   * This function will unzip the file and import the plugin into the agent-skills folder
+   * based on the hubId found in the plugin.json file.
+   * The zip file will be downloaded to the pluginsPath folder and then unzipped and finally deleted.
+   * @param {string} url - The signed URL of the community item zip file.
+   * @param {object} item - The community item.
+   * @returns {Promise<object>} - The result of the import.
+   */
+  static async importCommunityItemFromUrl(url, item) {
+    this.checkPluginFolderExists();
+    const hubId = item.id;
+    if (!hubId) return { success: false, error: "No hubId passed to import." };
+
+    const zipFilePath = path.resolve(pluginsPath, `${item.id}.zip`);
+    const pluginFile = item.manifest.files.find(
+      (file) => file.name === "plugin.json"
+    );
+    if (!pluginFile)
+      return {
+        success: false,
+        error: "No plugin.json file found in manifest.",
+      };
+
+    const pluginFolder = path.resolve(pluginsPath, normalizePath(hubId));
+    if (fs.existsSync(pluginFolder))
+      console.log(
+        "ImportedPlugin.importCommunityItemFromUrl - plugin folder already exists - will overwrite"
+      );
+
+    try {
+      const protocol = new URL(url).protocol.replace(":", "");
+      const httpLib = protocol === "https" ? require("https") : require("http");
+
+      const downloadZipFile = new Promise(async (resolve) => {
+        try {
+          console.log(
+            "ImportedPlugin.importCommunityItemFromUrl - downloading asset from ",
+            new URL(url).origin
+          );
+          const zipFile = fs.createWriteStream(zipFilePath);
+          const request = httpLib.get(url, function (response) {
+            response.pipe(zipFile);
+            zipFile.on("finish", () => {
+              console.log(
+                "ImportedPlugin.importCommunityItemFromUrl - downloaded zip file"
+              );
+              resolve(true);
+            });
+          });
+
+          request.on("error", (error) => {
+            console.error(
+              "ImportedPlugin.importCommunityItemFromUrl - error downloading zip file: ",
+              error
+            );
+            resolve(false);
+          });
+        } catch (error) {
+          console.error(
+            "ImportedPlugin.importCommunityItemFromUrl - error downloading zip file: ",
+            error
+          );
+          resolve(false);
+        }
+      });
+
+      const success = await downloadZipFile;
+      if (!success)
+        return { success: false, error: "Failed to download zip file." };
+
+      // Unzip the file to the plugin folder
+      // Note: https://github.com/cthackers/adm-zip?tab=readme-ov-file#electron-original-fs
+      const AdmZip = require("adm-zip");
+      const zip = new AdmZip(zipFilePath);
+      zip.extractAllTo(pluginFolder);
+
+      // We want to make sure specific keys are set to the proper values for
+      // plugin.json so we read and overwrite the file with the proper values.
+      const pluginJsonPath = path.resolve(pluginFolder, "plugin.json");
+      const pluginJson = safeJsonParse(fs.readFileSync(pluginJsonPath, "utf8"));
+      pluginJson.active = false;
+      pluginJson.hubId = hubId;
+      fs.writeFileSync(pluginJsonPath, JSON.stringify(pluginJson, null, 2));
+
+      console.log(
+        `ImportedPlugin.importCommunityItemFromUrl - successfully imported plugin to agent-skills/${hubId}`
+      );
+      return { success: true, error: null };
+    } catch (error) {
+      console.error(
+        "ImportedPlugin.importCommunityItemFromUrl - error: ",
+        error
+      );
+      return { success: false, error: error.message };
+    } finally {
+      if (fs.existsSync(zipFilePath)) fs.unlinkSync(zipFilePath);
+    }
+  }
 }
 
 module.exports = ImportedPlugin;
diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js
index 2c438462109dc8ab44e740de51b2093a1210afcd..0af4b839b30cdbb709e18a4b0fbd9fbbf532e9c3 100644
--- a/server/utils/helpers/updateENV.js
+++ b/server/utils/helpers/updateENV.js
@@ -939,6 +939,8 @@ function dumpENV() {
     "DISABLE_VIEW_CHAT_HISTORY",
     // Simple SSO
     "SIMPLE_SSO_ENABLED",
+    // Community Hub
+    "COMMUNITY_HUB_BUNDLE_DOWNLOADS_ENABLED",
   ];
 
   // Simple sanitization of each value to prevent ENV injection via newline or quote escaping.
diff --git a/server/utils/middleware/communityHubDownloadsEnabled.js b/server/utils/middleware/communityHubDownloadsEnabled.js
new file mode 100644
index 0000000000000000000000000000000000000000..832176de4643e6e2ff4635e5fe36fe5ece665004
--- /dev/null
+++ b/server/utils/middleware/communityHubDownloadsEnabled.js
@@ -0,0 +1,77 @@
+const { CommunityHub } = require("../../models/communityHub");
+const { reqBody } = require("../http");
+
+/**
+ * ### Must be called after `communityHubItem`
+ * Checks if community hub bundle downloads are enabled. The reason this functionality is disabled
+ * by default is that since AgentSkills, Workspaces, and DataConnectors are all imported from the
+ * community hub via unzipping a bundle - it would be possible for a malicious user to craft and
+ * download a malicious bundle and import it into their own hosted instance. To avoid this, this
+ * functionality is disabled by default and must be enabled manually by the system administrator.
+ *
+ * On hosted systems, this would not be an issue since the user cannot modify this setting, but those
+ * who self-host can still unlock this feature manually by setting the environment variable
+ * which would require someone who likely has the capacity to understand the risks and the
+ * implications of importing unverified items that can run code on their system, container, or instance.
+ * @see {@link https://docs.anythingllm.com/docs/community-hub/import}
+ * @param {import("express").Request} request
+ * @param {import("express").Response} response
+ * @param {import("express").NextFunction} next
+ * @returns {void}
+ */
+function communityHubDownloadsEnabled(request, response, next) {
+  if (!("COMMUNITY_HUB_BUNDLE_DOWNLOADS_ENABLED" in process.env)) {
+    return response.status(422).json({
+      error:
+        "Community Hub bundle downloads are not enabled. The system administrator must enable this feature manually to allow this instance to download these types of items. See https://docs.anythingllm.com/configuration#anythingllm-hub-agent-skills",
+    });
+  }
+
+  // If the admin specifically did not set the system to `allow_all` then downloads are limited to verified items or private items only.
+  // This is to prevent users from downloading unverified items and importing them into their own instance without understanding the risks.
+  const item = response.locals.bundleItem;
+  if (
+    !item.verified &&
+    item.visibility !== "private" &&
+    process.env.COMMUNITY_HUB_BUNDLE_DOWNLOADS_ENABLED !== "allow_all"
+  ) {
+    return response.status(422).json({
+      error:
+        "Community hub bundle downloads are limited to verified public items or private team items only. Please contact the system administrator to review or modify this setting. See https://docs.anythingllm.com/configuration#anythingllm-hub-agent-skills",
+    });
+  }
+  next();
+}
+
+/**
+ * Fetch the bundle item from the community hub.
+ * Sets `response.locals.bundleItem` and `response.locals.bundleUrl`.
+ */
+async function communityHubItem(request, response, next) {
+  const { importId } = reqBody(request);
+  if (!importId)
+    return response.status(500).json({
+      success: false,
+      error: "Import ID is required",
+    });
+
+  const {
+    url,
+    item,
+    error: fetchError,
+  } = await CommunityHub.getBundleItem(importId);
+  if (fetchError)
+    return response.status(500).json({
+      success: false,
+      error: fetchError,
+    });
+
+  response.locals.bundleItem = item;
+  response.locals.bundleUrl = url;
+  next();
+}
+
+module.exports = {
+  communityHubItem,
+  communityHubDownloadsEnabled,
+};
diff --git a/server/yarn.lock b/server/yarn.lock
index 44bab1f03ce577df819527c6c5eacb094bb3660d..7df1a54aa502fc261a80b3df9e74ff517e9acb22 100644
--- a/server/yarn.lock
+++ b/server/yarn.lock
@@ -2273,6 +2273,11 @@ acorn@^8.9.0:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
   integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
 
+adm-zip@^0.5.16:
+  version "0.5.16"
+  resolved "https://registry.yarnpkg.com/adm-zip/-/adm-zip-0.5.16.tgz#0b5e4c779f07dedea5805cdccb1147071d94a909"
+  integrity sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==
+
 agent-base@6:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"