+
+
+
+
+
+
+
Marketplace Settings
+
+
+
+ Configure one or more marketplace registries and choose which one is active for plugin and
+ theme browsing.
+
+
+
+
How registry selection works
+
+ Only one marketplace is active at a time. EmDash fetches plugin and theme listings from
+ the selected registry only, and does not merge results across multiple registries.
+
+
+
+
+
Security notice
+
+ Only use the official EmDash marketplace URL or registries you fully trust. Marketplace
+ requests are made by your server, so an untrusted registry can control metadata,
+ downloads, and update responses.
+
+
+ Use HTTPS for production registries. Localhost HTTP URLs are intended only for local
+ development.
+
+
+
+ {saveStatus && (
+
+
+ {saveStatus}
+
+ )}
+
+
+
+ {isLoading ? (
+
+ Loading settings...
+
+ ) : (
+
+ )}
+
+ );
+}
+
+export default MarketplaceSettings;
diff --git a/packages/admin/src/lib/api/client.ts b/packages/admin/src/lib/api/client.ts
index b46a84964..b07282b45 100644
--- a/packages/admin/src/lib/api/client.ts
+++ b/packages/admin/src/lib/api/client.ts
@@ -131,10 +131,9 @@ export interface AdminManifest {
locales: string[];
};
/**
- * Marketplace registry URL. Present when `marketplace` is configured
- * in the EmDash integration. Enables marketplace features in the UI.
+ * Whether marketplace browsing/install features are enabled.
*/
- marketplace?: string;
+ marketplace?: boolean;
}
/**
diff --git a/packages/admin/src/lib/api/settings.ts b/packages/admin/src/lib/api/settings.ts
index 67822ee4c..dbdf2c66d 100644
--- a/packages/admin/src/lib/api/settings.ts
+++ b/packages/admin/src/lib/api/settings.ts
@@ -5,6 +5,16 @@
import { API_BASE, apiFetch, parseApiResponse } from "./client.js";
export interface SiteSettings {
+ // Marketplace
+ marketplace?: {
+ registries: Array<{
+ id: string;
+ label: string;
+ url: string;
+ }>;
+ activeRegistryId?: string;
+ };
+
// Identity
title: string;
tagline?: string;
diff --git a/packages/admin/src/router.tsx b/packages/admin/src/router.tsx
index 63e85f064..e9649cfc7 100644
--- a/packages/admin/src/router.tsx
+++ b/packages/admin/src/router.tsx
@@ -43,6 +43,7 @@ import { AllowedDomainsSettings } from "./components/settings/AllowedDomainsSett
import { ApiTokenSettings } from "./components/settings/ApiTokenSettings";
import { EmailSettings } from "./components/settings/EmailSettings";
import { GeneralSettings } from "./components/settings/GeneralSettings";
+import { MarketplaceSettings } from "./components/settings/MarketplaceSettings";
import { SecuritySettings } from "./components/settings/SecuritySettings";
import { SeoSettings } from "./components/settings/SeoSettings";
import { SocialSettings } from "./components/settings/SocialSettings";
@@ -1118,6 +1119,13 @@ const emailSettingsRoute = createRoute({
component: EmailSettings,
});
+// Marketplace settings route
+const marketplaceSettingsRoute = createRoute({
+ getParentRoute: () => adminLayoutRoute,
+ path: "/settings/marketplace",
+ component: MarketplaceSettings,
+});
+
// General settings route
const generalSettingsRoute = createRoute({
getParentRoute: () => adminLayoutRoute,
@@ -1566,6 +1574,7 @@ const adminRoutes = adminLayoutRoute.addChildren([
allowedDomainsSettingsRoute,
apiTokenSettingsRoute,
emailSettingsRoute,
+ marketplaceSettingsRoute,
wordpressImportRoute,
notFoundRoute,
]);
diff --git a/packages/admin/tests/components/PluginManager.test.tsx b/packages/admin/tests/components/PluginManager.test.tsx
index 7781637b7..30b4edd80 100644
--- a/packages/admin/tests/components/PluginManager.test.tsx
+++ b/packages/admin/tests/components/PluginManager.test.tsx
@@ -203,9 +203,7 @@ describe("PluginManager", () => {
it("shows Marketplace link when manifest has marketplace URL", async () => {
const screen = await render(