diff --git a/apps/studio/src/App.tsx b/apps/studio/src/App.tsx
index f134038ba3..89ebe94321 100644
--- a/apps/studio/src/App.tsx
+++ b/apps/studio/src/App.tsx
@@ -62,6 +62,7 @@ export default function App() {
const handleSelectPackage = useCallback((pkg: InstalledPackage) => {
setSelectedPackage(pkg);
setSelectedObject(null);
+ setSelectedMeta(null);
setSelectedView('overview');
}, []);
@@ -126,9 +127,17 @@ export default function App() {
/>
{selectedView === 'object' && selectedObject ? (
-
+
) : selectedView === 'metadata' && selectedMeta ? (
-
+
) : selectedView === 'packages' ? (
) : selectedView === 'api-console' ? (
diff --git a/apps/studio/src/components/MetadataInspector.tsx b/apps/studio/src/components/MetadataInspector.tsx
index 3944554ecb..dbfea2065e 100644
--- a/apps/studio/src/components/MetadataInspector.tsx
+++ b/apps/studio/src/components/MetadataInspector.tsx
@@ -18,6 +18,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/comp
interface MetadataInspectorProps {
metaType: string;
metaName: string;
+ packageId?: string;
}
const TYPE_ICONS: Record
= {
@@ -173,7 +174,7 @@ function JsonTree({ data, depth = 0 }: { data: any; depth?: number }) {
}
-export function MetadataInspector({ metaType, metaName }: MetadataInspectorProps) {
+export function MetadataInspector({ metaType, metaName, packageId }: MetadataInspectorProps) {
const client = useClient();
const [item, setItem] = useState(null);
const [loading, setLoading] = useState(true);
@@ -189,7 +190,7 @@ export function MetadataInspector({ metaType, metaName }: MetadataInspectorProps
async function load() {
try {
- const result: any = await client.meta.getItem(metaType, metaName);
+ const result: any = await client.meta.getItem(metaType, metaName, packageId ? { packageId } : undefined);
if (mounted) {
setItem(result?.item || result);
}
@@ -201,7 +202,7 @@ export function MetadataInspector({ metaType, metaName }: MetadataInspectorProps
}
load();
return () => { mounted = false; };
- }, [client, metaType, metaName]);
+ }, [client, metaType, metaName, packageId]);
if (loading) {
return (
diff --git a/apps/studio/src/plugins/built-in/default-plugin.tsx b/apps/studio/src/plugins/built-in/default-plugin.tsx
index bc6a5e7972..5eac0d76bd 100644
--- a/apps/studio/src/plugins/built-in/default-plugin.tsx
+++ b/apps/studio/src/plugins/built-in/default-plugin.tsx
@@ -15,8 +15,8 @@ import type { StudioPlugin, MetadataViewerProps } from '../types';
// ─── Viewer Component (adapts MetadataInspector to plugin interface) ─
-function DefaultViewerComponent({ metadataType, metadataName }: MetadataViewerProps) {
- return ;
+function DefaultViewerComponent({ metadataType, metadataName, packageId }: MetadataViewerProps) {
+ return ;
}
// ─── Plugin Definition ───────────────────────────────────────────────
diff --git a/apps/studio/src/plugins/plugin-host.tsx b/apps/studio/src/plugins/plugin-host.tsx
index 2378249693..958c1198e9 100644
--- a/apps/studio/src/plugins/plugin-host.tsx
+++ b/apps/studio/src/plugins/plugin-host.tsx
@@ -48,11 +48,13 @@ interface PluginHostProps {
metadataName: string;
/** Pre-loaded metadata data (optional) */
data?: any;
+ /** Package ID to filter metadata by (optional) */
+ packageId?: string;
}
// ─── Component ───────────────────────────────────────────────────────
-export function PluginHost({ metadataType, metadataName, data }: PluginHostProps) {
+export function PluginHost({ metadataType, metadataName, data, packageId }: PluginHostProps) {
const [activeMode, setActiveMode] = useState('preview');
// Get available modes and viewers for this metadata type
@@ -176,6 +178,7 @@ export function PluginHost({ metadataType, metadataName, data }: PluginHostProps
metadataName={metadataName}
data={data}
mode={effectiveMode}
+ packageId={packageId}
/>
diff --git a/apps/studio/src/plugins/types.ts b/apps/studio/src/plugins/types.ts
index 11f9afcbd1..9c68d36ff6 100644
--- a/apps/studio/src/plugins/types.ts
+++ b/apps/studio/src/plugins/types.ts
@@ -26,6 +26,8 @@ export interface MetadataViewerProps {
data?: any;
/** Current view mode */
mode: ViewMode;
+ /** Package ID to filter metadata by (optional) */
+ packageId?: string;
}
/** Context passed to action handlers */
diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts
index c4694ad651..93fb191f4d 100644
--- a/packages/client/src/index.ts
+++ b/packages/client/src/index.ts
@@ -359,10 +359,15 @@ export class ObjectStackClient {
* Get a specific metadata item by type and name
* @param type - Metadata type (e.g., 'object', 'plugin')
* @param name - Item name (snake_case identifier)
+ * @param options - Optional filters (e.g., packageId to scope by package)
*/
- getItem: async (type: string, name: string) => {
+ getItem: async (type: string, name: string, options?: { packageId?: string }) => {
const route = this.getRoute('metadata');
- const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`);
+ const params = new URLSearchParams();
+ if (options?.packageId) params.set('package', options.packageId);
+ const qs = params.toString();
+ const url = `${this.baseUrl}${route}/${type}/${name}${qs ? `?${qs}` : ''}`;
+ const res = await this.fetch(url);
return this.unwrapResponse(res);
},
diff --git a/packages/runtime/src/http-dispatcher.ts b/packages/runtime/src/http-dispatcher.ts
index f881648790..814389603b 100644
--- a/packages/runtime/src/http-dispatcher.ts
+++ b/packages/runtime/src/http-dispatcher.ts
@@ -384,12 +384,14 @@ export class HttpDispatcher {
// /metadata/:type/:name
if (parts.length === 2) {
const [type, name] = parts;
+ // Extract optional package filter from query string
+ const packageId = query?.package || undefined;
// PUT /metadata/:type/:name (Save)
if (method === 'PUT' && body) {
// Try to get the protocol service directly
const protocol = await this.resolveService('protocol');
-
+
if (protocol && typeof protocol.saveMetaItem === 'function') {
try {
const result = await protocol.saveMetaItem({ type, name, item: body });
@@ -398,7 +400,7 @@ export class HttpDispatcher {
return { handled: true, response: this.error(e.message, 400) };
}
}
-
+
// Fallback to broker if protocol not available (legacy)
if (broker) {
try {
@@ -430,12 +432,12 @@ export class HttpDispatcher {
// If type is singular (e.g. 'app'), use it directly
// If plural (e.g. 'apps'), slice it
const singularType = type.endsWith('s') ? type.slice(0, -1) : type;
-
+
// Try Protocol Service First (Preferred)
const protocol = await this.resolveService('protocol');
if (protocol && typeof protocol.getMetaItem === 'function') {
try {
- const data = await protocol.getMetaItem({ type: singularType, name });
+ const data = await protocol.getMetaItem({ type: singularType, name, packageId });
return { handled: true, response: this.success(data) };
} catch (e: any) {
// Protocol might throw if not found or not supported