diff --git a/.github/workflows/dev-build.yaml b/.github/workflows/dev-build.yaml
index 6d938af8ee9d0e1c55e973a1de6685206ce5c3ac..86e10bbcd98c1952ccd2f8ef15ac0956a1fb8d0d 100644
--- a/.github/workflows/dev-build.yaml
+++ b/.github/workflows/dev-build.yaml
@@ -6,7 +6,7 @@ concurrency:
 
 on:
   push:
-    branches: ['chrome-extension'] # put your current branch to create a build. Core team only.
+    branches: ['agent-skill-plugins'] # put your current branch to create a build. Core team only.
     paths-ignore:
       - '**.md'
       - 'cloud-deployments/*'
diff --git a/frontend/src/models/admin.js b/frontend/src/models/admin.js
index c8fe2cc9786eaa22354f257505b8bcde6001cc38..336e98789b43defb67f0b5451f8525959b9d6b66 100644
--- a/frontend/src/models/admin.js
+++ b/frontend/src/models/admin.js
@@ -156,6 +156,8 @@ const Admin = {
   },
 
   // System Preferences
+  // TODO: remove this in favor of systemPreferencesByFields
+  // DEPRECATED: use systemPreferencesByFields instead
   systemPreferences: async () => {
     return await fetch(`${API_BASE}/admin/system-preferences`, {
       method: "GET",
@@ -167,6 +169,26 @@ const Admin = {
         return null;
       });
   },
+
+  /**
+   * Fetches system preferences by fields
+   * @param {string[]} labels - Array of labels for settings
+   * @returns {Promise<{settings: Object, error: string}>} - System preferences object
+   */
+  systemPreferencesByFields: async (labels = []) => {
+    return await fetch(
+      `${API_BASE}/admin/system-preferences-for?labels=${labels.join(",")}`,
+      {
+        method: "GET",
+        headers: baseHeaders(),
+      }
+    )
+      .then((res) => res.json())
+      .catch((e) => {
+        console.error(e);
+        return null;
+      });
+  },
   updateSystemPreferences: async (updates = {}) => {
     return await fetch(`${API_BASE}/admin/system-preferences`, {
       method: "POST",
diff --git a/frontend/src/models/experimental/agentPlugins.js b/frontend/src/models/experimental/agentPlugins.js
new file mode 100644
index 0000000000000000000000000000000000000000..9a544d5f12f4d06cacdda594335fbd328f7eab8b
--- /dev/null
+++ b/frontend/src/models/experimental/agentPlugins.js
@@ -0,0 +1,43 @@
+import { API_BASE } from "@/utils/constants";
+import { baseHeaders } from "@/utils/request";
+
+const AgentPlugins = {
+  toggleFeature: async function (hubId, active = false) {
+    return await fetch(
+      `${API_BASE}/experimental/agent-plugins/${hubId}/toggle`,
+      {
+        method: "POST",
+        headers: baseHeaders(),
+        body: JSON.stringify({ active }),
+      }
+    )
+      .then((res) => {
+        if (!res.ok) throw new Error("Could not update agent plugin status.");
+        return true;
+      })
+      .catch((e) => {
+        console.error(e);
+        return false;
+      });
+  },
+  updatePluginConfig: async function (hubId, updates = {}) {
+    return await fetch(
+      `${API_BASE}/experimental/agent-plugins/${hubId}/config`,
+      {
+        method: "POST",
+        headers: baseHeaders(),
+        body: JSON.stringify({ updates }),
+      }
+    )
+      .then((res) => {
+        if (!res.ok) throw new Error("Could not update agent plugin config.");
+        return true;
+      })
+      .catch((e) => {
+        console.error(e);
+        return false;
+      });
+  },
+};
+
+export default AgentPlugins;
diff --git a/frontend/src/models/system.js b/frontend/src/models/system.js
index 095244a4858d5aefd39fee9c12e9478d9cba0264..cb2f34e023c24725dc4b7dec8de63f291300d5a5 100644
--- a/frontend/src/models/system.js
+++ b/frontend/src/models/system.js
@@ -2,6 +2,7 @@ import { API_BASE, AUTH_TIMESTAMP, fullApiUrl } from "@/utils/constants";
 import { baseHeaders, safeJsonParse } from "@/utils/request";
 import DataConnector from "./dataConnector";
 import LiveDocumentSync from "./experimental/liveSync";
+import AgentPlugins from "./experimental/agentPlugins";
 
 const System = {
   cacheKeys: {
@@ -675,6 +676,7 @@ const System = {
   },
   experimentalFeatures: {
     liveSync: LiveDocumentSync,
+    agentPlugins: AgentPlugins,
   },
 };
 
diff --git a/frontend/src/pages/Admin/Agents/Imported/ImportedSkillConfig/index.jsx b/frontend/src/pages/Admin/Agents/Imported/ImportedSkillConfig/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..644d931d26bfb3759083b004ec4590d0bd5ba92c
--- /dev/null
+++ b/frontend/src/pages/Admin/Agents/Imported/ImportedSkillConfig/index.jsx
@@ -0,0 +1,180 @@
+import System from "@/models/system";
+import showToast from "@/utils/toast";
+import { Plug } from "@phosphor-icons/react";
+import { useEffect, useState } from "react";
+import { sentenceCase } from "text-case";
+
+/**
+ * Converts setup_args to inputs for the form builder
+ * @param {object} setupArgs - The setup arguments object
+ * @returns {object} - The inputs object
+ */
+function inputsFromArgs(setupArgs) {
+  if (
+    !setupArgs ||
+    setupArgs.constructor?.call?.().toString() !== "[object Object]"
+  ) {
+    return {};
+  }
+  return Object.entries(setupArgs).reduce(
+    (acc, [key, props]) => ({
+      ...acc,
+      [key]: props.hasOwnProperty("value")
+        ? props.value
+        : props?.input?.default || "",
+    }),
+    {}
+  );
+}
+
+/**
+ * Imported skill config component for imported skills only.
+ * @returns {JSX.Element}
+ */
+export default function ImportedSkillConfig({
+  selectedSkill, // imported skill config object
+  setImportedSkills, // function to set imported skills since config is file-write
+}) {
+  const [config, setConfig] = useState(selectedSkill);
+  const [hasChanges, setHasChanges] = useState(false);
+  const [inputs, setInputs] = useState(
+    inputsFromArgs(selectedSkill?.setup_args)
+  );
+
+  const hasSetupArgs =
+    selectedSkill?.setup_args &&
+    Object.keys(selectedSkill.setup_args).length > 0;
+
+  async function toggleSkill() {
+    const updatedConfig = { ...selectedSkill, active: !config.active };
+    await System.experimentalFeatures.agentPlugins.updatePluginConfig(
+      config.hubId,
+      { active: !config.active }
+    );
+    setImportedSkills((prev) =>
+      prev.map((s) => (s.hubId === config.hubId ? updatedConfig : s))
+    );
+    setConfig(updatedConfig);
+  }
+
+  async function handleSubmit(e) {
+    e.preventDefault();
+    const errors = [];
+    const updatedConfig = { ...config };
+
+    for (const [key, value] of Object.entries(inputs)) {
+      const settings = config.setup_args[key];
+      if (settings.required && !value) {
+        errors.push(`${key} is required to have a value.`);
+        continue;
+      }
+      if (typeof value !== settings.type) {
+        errors.push(`${key} must be of type ${settings.type}.`);
+        continue;
+      }
+      updatedConfig.setup_args[key].value = value;
+    }
+
+    if (errors.length > 0) {
+      errors.forEach((error) => showToast(error, "error"));
+      return;
+    }
+
+    await System.experimentalFeatures.agentPlugins.updatePluginConfig(
+      config.hubId,
+      updatedConfig
+    );
+    setConfig(updatedConfig);
+    setImportedSkills((prev) =>
+      prev.map((skill) =>
+        skill.hubId === config.hubId ? updatedConfig : skill
+      )
+    );
+    showToast("Skill config updated successfully.", "success");
+  }
+
+  useEffect(() => {
+    setHasChanges(
+      JSON.stringify(inputs) !==
+        JSON.stringify(inputsFromArgs(selectedSkill.setup_args))
+    );
+  }, [inputs]);
+
+  return (
+    <>
+      <div className="p-2">
+        <div className="flex flex-col gap-y-[18px] max-w-[500px]">
+          <div className="flex items-center gap-x-2">
+            <Plug size={24} color="white" weight="bold" />
+            <label htmlFor="name" className="text-white text-md font-bold">
+              {sentenceCase(config.name)}
+            </label>
+            <label className="border-none relative inline-flex cursor-pointer items-center ml-auto">
+              <input
+                type="checkbox"
+                className="peer sr-only"
+                checked={config.active}
+                onChange={() => toggleSkill()}
+              />
+              <div className="pointer-events-none peer h-6 w-11 rounded-full bg-stone-400 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:shadow-xl after:border after:border-gray-600 after:bg-white after:box-shadow-md after:transition-all after:content-[''] peer-checked:bg-lime-300 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-800"></div>
+              <span className="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300"></span>
+            </label>
+          </div>
+          <p className="text-white text-opacity-60 text-xs font-medium py-1.5">
+            {config.description} by{" "}
+            <a
+              href={config.author_url}
+              target="_blank"
+              rel="noopener noreferrer"
+              className="text-white hover:underline"
+            >
+              {config.author}
+            </a>
+          </p>
+
+          {hasSetupArgs ? (
+            <div className="flex flex-col gap-y-2">
+              {Object.entries(config.setup_args).map(([key, props]) => (
+                <div key={key} className="flex flex-col gap-y-1">
+                  <label htmlFor={key} className="text-white text-sm font-bold">
+                    {key}
+                  </label>
+                  <input
+                    type={props?.input?.type || "text"}
+                    required={props?.input?.required}
+                    defaultValue={
+                      props.hasOwnProperty("value")
+                        ? props.value
+                        : props?.input?.default || ""
+                    }
+                    onChange={(e) =>
+                      setInputs({ ...inputs, [key]: e.target.value })
+                    }
+                    placeholder={props?.input?.placeholder || ""}
+                    className="bg-transparent border border-white border-opacity-20 rounded-md p-2 text-white text-sm"
+                  />
+                  <p className="text-white text-opacity-60 text-xs font-medium py-1.5">
+                    {props?.input?.hint}
+                  </p>
+                </div>
+              ))}
+              {hasChanges && (
+                <button
+                  onClick={handleSubmit}
+                  type="button"
+                  className="bg-blue-500 text-white rounded-md p-2"
+                >
+                  Save
+                </button>
+              )}
+            </div>
+          ) : (
+            <p className="text-white text-opacity-60 text-sm font-medium py-1.5">
+              There are no options to modify for this skill.
+            </p>
+          )}
+        </div>
+      </div>
+    </>
+  );
+}
diff --git a/frontend/src/pages/Admin/Agents/Imported/SkillList/index.jsx b/frontend/src/pages/Admin/Agents/Imported/SkillList/index.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..b6358077ac9e55f3e08d893819d977737eb70b2a
--- /dev/null
+++ b/frontend/src/pages/Admin/Agents/Imported/SkillList/index.jsx
@@ -0,0 +1,59 @@
+import { CaretRight } from "@phosphor-icons/react";
+import { isMobile } from "react-device-detect";
+import { sentenceCase } from "text-case";
+
+export default function ImportedSkillList({
+  skills = [],
+  selectedSkill = null,
+  handleClick = null,
+}) {
+  if (skills.length === 0)
+    return (
+      <div className="text-white/60 text-center text-xs flex flex-col gap-y-2">
+        <p>No imported skills found</p>
+        <p>
+          Learn about agent skills in the{" "}
+          <a
+            href="https://docs.anythingllm.com/agent/custom/developer-guide"
+            target="_blank"
+            className="text-white/80 hover:underline"
+          >
+            AnythingLLM Agent Docs
+          </a>
+          .
+        </p>
+      </div>
+    );
+
+  return (
+    <div
+      className={`bg-white/5 text-white rounded-xl ${
+        isMobile ? "w-full" : "min-w-[360px] w-fit"
+      }`}
+    >
+      {skills.map((config, index) => (
+        <div
+          key={config.hubId}
+          className={`py-3 px-4 flex items-center justify-between ${
+            index === 0 ? "rounded-t-xl" : ""
+          } ${
+            index === Object.keys(skills).length - 1
+              ? "rounded-b-xl"
+              : "border-b border-white/10"
+          } cursor-pointer transition-all duration-300  hover:bg-white/5 ${
+            selectedSkill === config.hubId ? "bg-white/10" : ""
+          }`}
+          onClick={() => handleClick?.({ ...config, imported: true })}
+        >
+          <div className="text-sm font-light">{sentenceCase(config.name)}</div>
+          <div className="flex items-center gap-x-2">
+            <div className="text-sm text-white/60 font-medium">
+              {config.active ? "On" : "Off"}
+            </div>
+            <CaretRight size={14} weight="bold" className="text-white/80" />
+          </div>
+        </div>
+      ))}
+    </div>
+  );
+}
diff --git a/frontend/src/pages/Admin/Agents/index.jsx b/frontend/src/pages/Admin/Agents/index.jsx
index 9cb1a93ae7161aedbda907daa4d29620dc513640..99c093d5cb27bce6d97dba6f2ac401e389d3e09c 100644
--- a/frontend/src/pages/Admin/Agents/index.jsx
+++ b/frontend/src/pages/Admin/Agents/index.jsx
@@ -4,18 +4,21 @@ import { isMobile } from "react-device-detect";
 import Admin from "@/models/admin";
 import System from "@/models/system";
 import showToast from "@/utils/toast";
-import { CaretLeft, CaretRight, Robot } from "@phosphor-icons/react";
+import { CaretLeft, CaretRight, Plug, Robot } from "@phosphor-icons/react";
 import ContextualSaveBar from "@/components/ContextualSaveBar";
 import { castToType } from "@/utils/types";
 import { FullScreenLoader } from "@/components/Preloader";
 import { defaultSkills, configurableSkills } from "./skills";
 import { DefaultBadge } from "./Badges/default";
+import ImportedSkillList from "./Imported/SkillList";
+import ImportedSkillConfig from "./Imported/ImportedSkillConfig";
 
 export default function AdminAgents() {
   const [hasChanges, setHasChanges] = useState(false);
   const [settings, setSettings] = useState({});
   const [selectedSkill, setSelectedSkill] = useState("");
   const [agentSkills, setAgentSkills] = useState([]);
+  const [importedSkills, setImportedSkills] = useState([]);
   const [loading, setLoading] = useState(true);
   const [showSkillModal, setShowSkillModal] = useState(false);
   const formEl = useRef(null);
@@ -37,9 +40,13 @@ export default function AdminAgents() {
   useEffect(() => {
     async function fetchSettings() {
       const _settings = await System.keys();
-      const _preferences = await Admin.systemPreferences();
+      const _preferences = await Admin.systemPreferencesByFields([
+        "default_agent_skills",
+        "imported_agent_skills",
+      ]);
       setSettings({ ..._settings, preferences: _preferences.settings } ?? {});
       setAgentSkills(_preferences.settings?.default_agent_skills ?? []);
+      setImportedSkills(_preferences.settings?.imported_agent_skills ?? []);
       setLoading(false);
     }
     fetchSettings();
@@ -84,9 +91,13 @@ export default function AdminAgents() {
 
     if (success) {
       const _settings = await System.keys();
-      const _preferences = await Admin.systemPreferences();
+      const _preferences = await Admin.systemPreferencesByFields([
+        "default_agent_skills",
+        "imported_agent_skills",
+      ]);
       setSettings({ ..._settings, preferences: _preferences.settings } ?? {});
       setAgentSkills(_preferences.settings?.default_agent_skills ?? []);
+      setImportedSkills(_preferences.settings?.imported_agent_skills ?? []);
       showToast(`Agent preferences saved successfully.`, "success", {
         clear: true,
       });
@@ -97,9 +108,10 @@ export default function AdminAgents() {
     setHasChanges(false);
   };
 
-  const SelectedSkillComponent =
-    configurableSkills[selectedSkill]?.component ||
-    defaultSkills[selectedSkill]?.component;
+  const SelectedSkillComponent = selectedSkill.imported
+    ? ImportedSkillConfig
+    : configurableSkills[selectedSkill]?.component ||
+      defaultSkills[selectedSkill]?.component;
 
   if (loading) {
     return (
@@ -157,6 +169,16 @@ export default function AdminAgents() {
               }}
               activeSkills={agentSkills}
             />
+
+            <div className="text-white flex items-center gap-x-2">
+              <Plug size={24} />
+              <p className="text-lg font-medium">Custom Skills</p>
+            </div>
+            <ImportedSkillList
+              skills={importedSkills}
+              selectedSkill={selectedSkill}
+              handleClick={setSelectedSkill}
+            />
           </div>
 
           {/* Selected agent skill modal */}
@@ -181,17 +203,27 @@ export default function AdminAgents() {
                 <div className="flex-1 overflow-y-auto p-4">
                   <div className="bg-[#303237] text-white rounded-xl p-4">
                     {SelectedSkillComponent ? (
-                      <SelectedSkillComponent
-                        skill={configurableSkills[selectedSkill]?.skill}
-                        settings={settings}
-                        toggleSkill={toggleAgentSkill}
-                        enabled={agentSkills.includes(
-                          configurableSkills[selectedSkill]?.skill
+                      <>
+                        {selectedSkill.imported ? (
+                          <ImportedSkillConfig
+                            key={selectedSkill.hubId}
+                            selectedSkill={selectedSkill}
+                            setImportedSkills={setImportedSkills}
+                          />
+                        ) : (
+                          <SelectedSkillComponent
+                            skill={configurableSkills[selectedSkill]?.skill}
+                            settings={settings}
+                            toggleSkill={toggleAgentSkill}
+                            enabled={agentSkills.includes(
+                              configurableSkills[selectedSkill]?.skill
+                            )}
+                            setHasChanges={setHasChanges}
+                            {...(configurableSkills[selectedSkill] ||
+                              defaultSkills[selectedSkill])}
+                          />
                         )}
-                        setHasChanges={setHasChanges}
-                        {...(configurableSkills[selectedSkill] ||
-                          defaultSkills[selectedSkill])}
-                      />
+                      </>
                     ) : (
                       <div className="flex flex-col items-center justify-center h-full text-white/60">
                         <Robot size={40} />
@@ -216,7 +248,7 @@ export default function AdminAgents() {
     >
       <form
         onSubmit={handleSubmit}
-        onChange={() => setHasChanges(true)}
+        onChange={() => !selectedSkill.imported && setHasChanges(true)}
         ref={formEl}
         className="flex-1 flex gap-x-6 p-4 mt-10"
       >
@@ -247,23 +279,43 @@ export default function AdminAgents() {
             handleClick={setSelectedSkill}
             activeSkills={agentSkills}
           />
+
+          <div className="text-white flex items-center gap-x-2">
+            <Plug size={24} />
+            <p className="text-lg font-medium">Custom Skills</p>
+          </div>
+          <ImportedSkillList
+            skills={importedSkills}
+            selectedSkill={selectedSkill}
+            handleClick={setSelectedSkill}
+          />
         </div>
 
         {/* Selected agent skill setting panel */}
         <div className="flex-[2] flex flex-col gap-y-[18px] mt-10">
           <div className="bg-[#303237] text-white rounded-xl flex-1 p-4">
             {SelectedSkillComponent ? (
-              <SelectedSkillComponent
-                skill={configurableSkills[selectedSkill]?.skill}
-                settings={settings}
-                toggleSkill={toggleAgentSkill}
-                enabled={agentSkills.includes(
-                  configurableSkills[selectedSkill]?.skill
+              <>
+                {selectedSkill.imported ? (
+                  <ImportedSkillConfig
+                    key={selectedSkill.hubId}
+                    selectedSkill={selectedSkill}
+                    setImportedSkills={setImportedSkills}
+                  />
+                ) : (
+                  <SelectedSkillComponent
+                    skill={configurableSkills[selectedSkill]?.skill}
+                    settings={settings}
+                    toggleSkill={toggleAgentSkill}
+                    enabled={agentSkills.includes(
+                      configurableSkills[selectedSkill]?.skill
+                    )}
+                    setHasChanges={setHasChanges}
+                    {...(configurableSkills[selectedSkill] ||
+                      defaultSkills[selectedSkill])}
+                  />
                 )}
-                setHasChanges={setHasChanges}
-                {...(configurableSkills[selectedSkill] ||
-                  defaultSkills[selectedSkill])}
-              />
+              </>
             ) : (
               <div className="flex flex-col items-center justify-center h-full text-white/60">
                 <Robot size={40} />
diff --git a/server/.gitignore b/server/.gitignore
index adcf7aa4b5be49a167eddc502e650c9134c69bdd..e78e20b97ea2281b4d79a0dab5086bf946358bd0 100644
--- a/server/.gitignore
+++ b/server/.gitignore
@@ -8,6 +8,7 @@ storage/tmp/*
 storage/vector-cache/*.json
 storage/exports
 storage/imports
+storage/plugins/agent-skills/*
 !storage/documents/DOCUMENTS.md
 logs/server.log
 *.db
diff --git a/server/endpoints/admin.js b/server/endpoints/admin.js
index 457d7567b95da0fe1536bd7e09deec7bbac5a9af..994c8e41654bd7594dacae08bd1d15cc1030d1e7 100644
--- a/server/endpoints/admin.js
+++ b/server/endpoints/admin.js
@@ -24,6 +24,7 @@ const {
   ROLES,
 } = require("../utils/middleware/multiUserProtected");
 const { validatedRequest } = require("../utils/middleware/validatedRequest");
+const ImportedPlugin = require("../utils/agents/imported");
 
 function adminEndpoints(app) {
   if (!app) return;
@@ -311,7 +312,109 @@ function adminEndpoints(app) {
     }
   );
 
-  // TODO: Allow specification of which props to get instead of returning all of them all the time.
+  // System preferences but only by array of labels
+  app.get(
+    "/admin/system-preferences-for",
+    [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
+    async (request, response) => {
+      try {
+        const requestedSettings = {};
+        const labels = request.query.labels?.split(",") || [];
+        const needEmbedder = [
+          "text_splitter_chunk_size",
+          "max_embed_chunk_size",
+        ];
+        const noRecord = [
+          "max_embed_chunk_size",
+          "agent_sql_connections",
+          "imported_agent_skills",
+          "feature_flags",
+          "meta_page_title",
+          "meta_page_favicon",
+        ];
+
+        for (const label of labels) {
+          // Skip any settings that are not explicitly defined as public
+          if (!SystemSettings.publicFields.includes(label)) continue;
+
+          // Only get the embedder if the setting actually needs it
+          let embedder = needEmbedder.includes(label)
+            ? getEmbeddingEngineSelection()
+            : null;
+          // Only get the record from db if the setting actually needs it
+          let setting = noRecord.includes(label)
+            ? null
+            : await SystemSettings.get({ label });
+
+          switch (label) {
+            case "limit_user_messages":
+              requestedSettings[label] = setting?.value === "true";
+              break;
+            case "message_limit":
+              requestedSettings[label] = setting?.value
+                ? Number(setting.value)
+                : 10;
+              break;
+            case "footer_data":
+              requestedSettings[label] = setting?.value ?? JSON.stringify([]);
+              break;
+            case "support_email":
+              requestedSettings[label] = setting?.value || null;
+              break;
+            case "text_splitter_chunk_size":
+              requestedSettings[label] =
+                setting?.value || embedder?.embeddingMaxChunkLength || null;
+              break;
+            case "text_splitter_chunk_overlap":
+              requestedSettings[label] = setting?.value || null;
+              break;
+            case "max_embed_chunk_size":
+              requestedSettings[label] =
+                embedder?.embeddingMaxChunkLength || 1000;
+              break;
+            case "agent_search_provider":
+              requestedSettings[label] = setting?.value || null;
+              break;
+            case "agent_sql_connections":
+              requestedSettings[label] =
+                await SystemSettings.brief.agent_sql_connections();
+              break;
+            case "default_agent_skills":
+              requestedSettings[label] = safeJsonParse(setting?.value, []);
+              break;
+            case "imported_agent_skills":
+              requestedSettings[label] = ImportedPlugin.listImportedPlugins();
+              break;
+            case "custom_app_name":
+              requestedSettings[label] = setting?.value || null;
+              break;
+            case "feature_flags":
+              requestedSettings[label] =
+                (await SystemSettings.getFeatureFlags()) || {};
+              break;
+            case "meta_page_title":
+              requestedSettings[label] =
+                await SystemSettings.getValueOrFallback({ label }, null);
+              break;
+            case "meta_page_favicon":
+              requestedSettings[label] =
+                await SystemSettings.getValueOrFallback({ label }, null);
+              break;
+            default:
+              break;
+          }
+        }
+
+        response.status(200).json({ settings: requestedSettings });
+      } catch (e) {
+        console.error(e);
+        response.sendStatus(500).end();
+      }
+    }
+  );
+
+  // TODO: Delete this endpoint
+  // DEPRECATED - use /admin/system-preferences-for instead with ?labels=... comma separated string of labels
   app.get(
     "/admin/system-preferences",
     [validatedRequest, flexUserRoleValid([ROLES.admin, ROLES.manager])],
@@ -352,6 +455,7 @@ function adminEndpoints(app) {
                 ?.value,
               []
             ) || [],
+          imported_agent_skills: ImportedPlugin.listImportedPlugins(),
           custom_app_name:
             (await SystemSettings.get({ label: "custom_app_name" }))?.value ||
             null,
diff --git a/server/endpoints/experimental/imported-agent-plugins.js b/server/endpoints/experimental/imported-agent-plugins.js
new file mode 100644
index 0000000000000000000000000000000000000000..cdc0148cb2f16eae0fe86636b3ebb01e97a3ca16
--- /dev/null
+++ b/server/endpoints/experimental/imported-agent-plugins.js
@@ -0,0 +1,50 @@
+const ImportedPlugin = require("../../utils/agents/imported");
+const { reqBody } = require("../../utils/http");
+const {
+  flexUserRoleValid,
+  ROLES,
+} = require("../../utils/middleware/multiUserProtected");
+const { validatedRequest } = require("../../utils/middleware/validatedRequest");
+
+function importedAgentPluginEndpoints(app) {
+  if (!app) return;
+
+  app.post(
+    "/experimental/agent-plugins/:hubId/toggle",
+    [validatedRequest, flexUserRoleValid([ROLES.admin])],
+    (request, response) => {
+      try {
+        const { hubId } = request.params;
+        const { active } = reqBody(request);
+        const updatedConfig = ImportedPlugin.updateImportedPlugin(hubId, {
+          active: Boolean(active),
+        });
+        response.status(200).json(updatedConfig);
+      } catch (e) {
+        console.error(e);
+        response.status(500).end();
+      }
+    }
+  );
+
+  app.post(
+    "/experimental/agent-plugins/:hubId/config",
+    [validatedRequest, flexUserRoleValid([ROLES.admin])],
+    (request, response) => {
+      try {
+        const { hubId } = request.params;
+        const { updates } = reqBody(request);
+        const updatedConfig = ImportedPlugin.updateImportedPlugin(
+          hubId,
+          updates
+        );
+        response.status(200).json(updatedConfig);
+      } catch (e) {
+        console.error(e);
+        response.status(500).end();
+      }
+    }
+  );
+}
+
+module.exports = { importedAgentPluginEndpoints };
diff --git a/server/endpoints/experimental/index.js b/server/endpoints/experimental/index.js
index e7dd144c5b0636ef868c62513fbce0cfdde84629..cc390811a9b4a340aaaa77fa5b17aa2d5ee89b0d 100644
--- a/server/endpoints/experimental/index.js
+++ b/server/endpoints/experimental/index.js
@@ -1,5 +1,6 @@
 const { fineTuningEndpoints } = require("./fineTuning");
 const { liveSyncEndpoints } = require("./liveSync");
+const { importedAgentPluginEndpoints } = require("./imported-agent-plugins");
 
 // All endpoints here are not stable and can move around - have breaking changes
 // or are opt-in features that are not fully released.
@@ -7,6 +8,7 @@ const { liveSyncEndpoints } = require("./liveSync");
 function experimentalEndpoints(router) {
   liveSyncEndpoints(router);
   fineTuningEndpoints(router);
+  importedAgentPluginEndpoints(router);
 }
 
 module.exports = { experimentalEndpoints };
diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js
index e9ae3f3e9613fae591bd87dcda8071b8ceda0857..c2c03ffa0996e1e659c5b9b3163eadcba75bac7c 100644
--- a/server/models/systemSettings.js
+++ b/server/models/systemSettings.js
@@ -15,6 +15,23 @@ function isNullOrNaN(value) {
 
 const SystemSettings = {
   protectedFields: ["multi_user_mode"],
+  publicFields: [
+    "limit_user_messages",
+    "message_limit",
+    "footer_data",
+    "support_email",
+    "text_splitter_chunk_size",
+    "text_splitter_chunk_overlap",
+    "max_embed_chunk_size",
+    "agent_search_provider",
+    "agent_sql_connections",
+    "default_agent_skills",
+    "imported_agent_skills",
+    "custom_app_name",
+    "feature_flags",
+    "meta_page_title",
+    "meta_page_favicon",
+  ],
   supportedFields: [
     "limit_user_messages",
     "message_limit",
diff --git a/server/utils/agents/aibitat/index.js b/server/utils/agents/aibitat/index.js
index 0d3aab1add4940fa1df43c3bfa68a44f77048b86..56da25eb1dd63cd8f0abae6753595c31fab90c08 100644
--- a/server/utils/agents/aibitat/index.js
+++ b/server/utils/agents/aibitat/index.js
@@ -504,9 +504,13 @@ Only return the role.
    * @param {string} pluginName this name of the plugin being called
    * @returns string of the plugin to be called compensating for children denoted by # in the string.
    * eg: sql-agent:list-database-connections
+   * or is a custom plugin
+   * eg: @@custom-plugin-name
    */
   #parseFunctionName(pluginName = "") {
-    if (!pluginName.includes("#")) return pluginName;
+    if (!pluginName.includes("#") && !pluginName.startsWith("@@"))
+      return pluginName;
+    if (pluginName.startsWith("@@")) return pluginName.replace("@@", "");
     return pluginName.split("#")[1];
   }
 
diff --git a/server/utils/agents/defaults.js b/server/utils/agents/defaults.js
index a6d30ca15b17c7fcbf3ce41d24c1bb7f43693906..6154fab66728de590551b0f208b4fdcb826e92df 100644
--- a/server/utils/agents/defaults.js
+++ b/server/utils/agents/defaults.js
@@ -2,6 +2,7 @@ const AgentPlugins = require("./aibitat/plugins");
 const { SystemSettings } = require("../../models/systemSettings");
 const { safeJsonParse } = require("../http");
 const Provider = require("./aibitat/providers/ai-provider");
+const ImportedPlugin = require("./imported");
 
 const USER_AGENT = {
   name: "USER",
@@ -27,6 +28,7 @@ const WORKSPACE_AGENT = {
       functions: [
         ...defaultFunctions,
         ...(await agentSkillsFromSystemSettings()),
+        ...(await ImportedPlugin.activeImportedPlugins()),
       ],
     };
   },
diff --git a/server/utils/agents/imported.js b/server/utils/agents/imported.js
new file mode 100644
index 0000000000000000000000000000000000000000..136f4a3ad1d619c0988d63aaec2cc91dd9044aa7
--- /dev/null
+++ b/server/utils/agents/imported.js
@@ -0,0 +1,176 @@
+const fs = require("fs");
+const path = require("path");
+const { safeJsonParse } = require("../http");
+const { isWithin, normalizePath } = require("../files");
+const pluginsPath =
+  process.env.NODE_ENV === "development"
+    ? path.resolve(__dirname, "../../storage/plugins/agent-skills")
+    : path.resolve(process.env.STORAGE_DIR, "plugins", "agent-skills");
+
+class ImportedPlugin {
+  constructor(config) {
+    this.config = config;
+    this.handlerLocation = path.resolve(
+      pluginsPath,
+      this.config.hubId,
+      "handler.js"
+    );
+    delete require.cache[require.resolve(this.handlerLocation)];
+    this.handler = require(this.handlerLocation);
+    this.name = config.hubId;
+    this.startupConfig = {
+      params: {},
+    };
+  }
+
+  /**
+   * Gets the imported plugin handler.
+   * @param {string} hubId - The hub ID of the plugin.
+   * @returns {ImportedPlugin} - The plugin handler.
+   */
+  static loadPluginByHubId(hubId) {
+    const configLocation = path.resolve(
+      pluginsPath,
+      normalizePath(hubId),
+      "plugin.json"
+    );
+    if (!this.isValidLocation(configLocation)) return;
+    const config = safeJsonParse(fs.readFileSync(configLocation, "utf8"));
+    return new ImportedPlugin(config);
+  }
+
+  static isValidLocation(pathToValidate) {
+    if (!isWithin(pluginsPath, pathToValidate)) return false;
+    if (!fs.existsSync(pathToValidate)) return false;
+    return true;
+  }
+
+  /**
+   * Loads plugins from `plugins` folder in storage that are custom loaded and defined.
+   * only loads plugins that are active: true.
+   * @returns {Promise<string[]>} - array of plugin names to be loaded later.
+   */
+  static async activeImportedPlugins() {
+    const plugins = [];
+    const folders = fs.readdirSync(path.resolve(pluginsPath));
+    for (const folder of folders) {
+      const configLocation = path.resolve(
+        pluginsPath,
+        normalizePath(folder),
+        "plugin.json"
+      );
+      if (!this.isValidLocation(configLocation)) continue;
+      const config = safeJsonParse(fs.readFileSync(configLocation, "utf8"));
+      if (config.active) plugins.push(`@@${config.hubId}`);
+    }
+    return plugins;
+  }
+
+  /**
+   * Lists all imported plugins.
+   * @returns {Array} - array of plugin configurations (JSON).
+   */
+  static listImportedPlugins() {
+    const plugins = [];
+    if (!fs.existsSync(pluginsPath)) return plugins;
+
+    const folders = fs.readdirSync(path.resolve(pluginsPath));
+    for (const folder of folders) {
+      const configLocation = path.resolve(
+        pluginsPath,
+        normalizePath(folder),
+        "plugin.json"
+      );
+      if (!this.isValidLocation(configLocation)) continue;
+      const config = safeJsonParse(fs.readFileSync(configLocation, "utf8"));
+      plugins.push(config);
+    }
+    return plugins;
+  }
+
+  /**
+   * Updates a plugin configuration.
+   * @param {string} hubId - The hub ID of the plugin.
+   * @param {object} config - The configuration to update.
+   * @returns {object} - The updated configuration.
+   */
+  static updateImportedPlugin(hubId, config) {
+    const configLocation = path.resolve(
+      pluginsPath,
+      normalizePath(hubId),
+      "plugin.json"
+    );
+    if (!this.isValidLocation(configLocation)) return;
+
+    const currentConfig = safeJsonParse(
+      fs.readFileSync(configLocation, "utf8"),
+      null
+    );
+    if (!currentConfig) return;
+
+    const updatedConfig = { ...currentConfig, ...config };
+    fs.writeFileSync(configLocation, JSON.stringify(updatedConfig, null, 2));
+    return updatedConfig;
+  }
+
+  /**
+   * Validates if the handler.js file exists for the given plugin.
+   * @param {string} hubId - The hub ID of the plugin.
+   * @returns {boolean} - True if the handler.js file exists, false otherwise.
+   */
+  static validateImportedPluginHandler(hubId) {
+    const handlerLocation = path.resolve(
+      pluginsPath,
+      normalizePath(hubId),
+      "handler.js"
+    );
+    return this.isValidLocation(handlerLocation);
+  }
+
+  parseCallOptions() {
+    const callOpts = {};
+    if (!this.config.setup_args || typeof this.config.setup_args !== "object") {
+      return callOpts;
+    }
+    for (const [param, definition] of Object.entries(this.config.setup_args)) {
+      if (definition.required && !definition?.value) {
+        console.log(
+          `'${param}' required value for '${this.name}' plugin is missing. Plugin may not function or crash agent.`
+        );
+        continue;
+      }
+      callOpts[param] = definition.value || definition.default || null;
+    }
+    return callOpts;
+  }
+
+  plugin(runtimeArgs = {}) {
+    const customFunctions = this.handler.runtime;
+    return {
+      runtimeArgs,
+      name: this.name,
+      config: this.config,
+      setup(aibitat) {
+        aibitat.function({
+          super: aibitat,
+          name: this.name,
+          config: this.config,
+          runtimeArgs: this.runtimeArgs,
+          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.
+          examples: this.config.examples ?? [],
+          parameters: {
+            $schema: "http://json-schema.org/draft-07/schema#",
+            type: "object",
+            properties: this.config.entrypoint.params ?? {},
+            additionalProperties: false,
+          },
+          ...customFunctions,
+        });
+      },
+    };
+  }
+}
+
+module.exports = ImportedPlugin;
diff --git a/server/utils/agents/index.js b/server/utils/agents/index.js
index 86563d1850f7a3bc6b043088e550dfede119e974..521b9e9cad1ee7e9baaa4b7afe9d5ca577bc6ee0 100644
--- a/server/utils/agents/index.js
+++ b/server/utils/agents/index.js
@@ -6,6 +6,7 @@ const {
 const { WorkspaceChats } = require("../../models/workspaceChats");
 const { safeJsonParse } = require("../http");
 const { USER_AGENT, WORKSPACE_AGENT } = require("./defaults");
+const ImportedPlugin = require("./imported");
 
 class AgentHandler {
   #invocationUUID;
@@ -292,6 +293,27 @@ class AgentHandler {
         continue;
       }
 
+      // Load imported plugin. This is marked by `@@` in the array of functions to load.
+      // and is the @@hubID of the plugin.
+      if (name.startsWith("@@")) {
+        const hubId = name.replace("@@", "");
+        const valid = ImportedPlugin.validateImportedPluginHandler(hubId);
+        if (!valid) {
+          this.log(
+            `Imported plugin by hubId ${hubId} not found in plugin directory. Skipping inclusion to agent cluster.`
+          );
+          continue;
+        }
+
+        const plugin = ImportedPlugin.loadPluginByHubId(hubId);
+        const callOpts = plugin.parseCallOptions();
+        this.aibitat.use(plugin.plugin(callOpts));
+        this.log(
+          `Attached ${plugin.name} (${hubId}) imported plugin to Agent cluster`
+        );
+        continue;
+      }
+
       // Load single-stage plugin.
       if (!AgentPlugins.hasOwnProperty(name)) {
         this.log(
diff --git a/server/utils/http/index.js b/server/utils/http/index.js
index e812b8abd77a5805dd71eb8763d3473d3297535a..7e76f327d9a22226f88c1b5cee32aef8d53b9367 100644
--- a/server/utils/http/index.js
+++ b/server/utils/http/index.js
@@ -64,6 +64,8 @@ function parseAuthHeader(headerValue = null, apiKey = null) {
 }
 
 function safeJsonParse(jsonString, fallback = null) {
+  if (jsonString === null) return fallback;
+
   try {
     return JSON.parse(jsonString);
   } catch {}