Visualizing custom data in a plugin
Plugins let you build lightweight UI extensions that surface metadata directly in the Cortex UI. Cortex already renders custom data automatically in the Custom data & metrics tab on every entity page. However, many teams want to go further, like adding richer visualizations, grouping related fields, embedding links, or turning custom data into more opinionated dashboards.
This guide shows how to build a plugin page that reads custom data and renders it using your own UI.
Overview
Cortex supports attaching structured metadata to any catalog entity using Custom Data. This metadata automatically appears in the Custom Data tab of the entity page.
If your team wants to:
organize fields into custom sections
add tables, charts, or diagrams
highlight important metadata
provide context-specific controls
match a specific visualization or compliance requirement
a plugin can fetch the same custom data and render a tailored UI that sits alongside the built-in Cortex experience.
Step 1: Add custom data to an entity
To add custom data to an entity, you can use the Custom Data API. To illustrate how this works, we’ll use the following fictional metadata object:
{
"service_owner": "Payments Team",
"languages": "Node.js, TypeScript",
"dependencies": "Redis, Kafka, BillingAPI",
"service_tier": "Tier 2",
"last_reviewed": "2025-11-01",
"links": "[https://docs.example.com/runbook, https://example.com/architecture]"
}This object is stored under the custom data key sample-metadata.
sample-metadataStep 2: Register the plugin
Once you have custom data to visualize, register your plugin in Cortex. Follow the Creating Plugins documentation, making sure to choose Specific entity types as the context. This ensures your visualization displays when viewing an entity that contains the custom data.

Step 3: Download the Plugin Template
After registering the plugin, Cortex provides a downloadable plugin starter template, including:
React setup
Plugin Context provider
Styling tokens (
--cortex-plugin-*)Local development instructions
A preconfigured route structure
Download the template from the Register plugin page in Cortex. This template is where you’ll build your visualization.

Step 4: Build the Custom Data Visualization
Inside the downloaded template:
Add a new component
Use the Plugin Context to read the current entity tag
Fetch custom data using
CortexApi.proxyFetch()Render a styled table or any UI you choose
Below is a complete example component (SampleMetadataView.tsx) that renders the fictional sample-metadata object.
Example Component
import React, { useEffect, useState } from "react";
import { CardTitle } from "@cortexapps/react-plugin-ui";
import { CortexApi } from "@cortexapps/plugin-core";
import { Heading, Section, Subsection } from "./UtilityComponents";
import { usePluginContextProvider } from "./PluginContextProvider";
type SampleMetadata = {
service_owner?: string;
languages?: string;
dependencies?: string;
service_tier?: string;
last_reviewed?: string;
links?: string;
};
const SampleMetadataView: React.FC = () => {
const context = usePluginContextProvider();
const [data, setData] = useState<SampleMetadata | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchMetadata = async () => {
try {
const tag = encodeURIComponent(context.entity.tag);
const response = await CortexApi.proxyFetch(
`https://api.getcortexapp.com/api/v1/catalog/custom-data?tag=${tag}&key=sample-metadata`
);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const json = await response.json();
const entry = Array.isArray(json) ? json[0] : json;
setData(entry?.value || null);
} catch (e: any) {
setError("Unable to load sample metadata");
} finally {
setLoading(false);
}
};
if (context?.entity?.tag) fetchMetadata();
}, [context]);
const renderRow = (label: string, value: React.ReactNode) => (
<tr key={label}>
<th
style={{
textAlign: "left",
padding: "8px 12px",
fontWeight: 500,
width: "35%",
backgroundColor: "var(--cortex-plugin-input)",
borderBottom: "1px solid var(--cortex-plugin-border)"
}}
>
{label}
</th>
<td
style={{
padding: "8px 12px",
borderBottom: "1px solid var(--cortex-plugin-border)",
color: "var(--cortex-plugin-primary)"
}}
>
{value || "Not set"}
</td>
</tr>
);
return (
<Section>
<Heading>
<CardTitle>Sample Metadata</CardTitle>
</Heading>
<Subsection>
{loading && <div>Loading…</div>}
{error && <div>{error}</div>}
{!loading && !error && data && (
<div
style={{
borderRadius: 8,
border: "1px solid var(--cortex-plugin-border)",
backgroundColor: "var(--cortex-plugin-background)",
overflowX: "auto"
}}
>
<table style={{ width: "100%", borderCollapse: "collapse" }}>
<tbody>
{renderRow("Service Owner", data.service_owner)}
{renderRow("Languages", data.languages)}
{renderRow("Dependencies", data.dependencies)}
{renderRow("Service Tier", data.service_tier)}
{renderRow("Last Reviewed", data.last_reviewed)}
{renderRow("Links", data.links)}
</tbody>
</table>
</div>
)}
</Subsection>
</Section>
);
};
export default SampleMetadataView;Add the component to App.tsx
App.tsxInside the downloaded template, open App.tsx.
In a single-view plugin like this, the recommended pattern is to render your visualization directly from App.tsx. Here’s what the minimal setup looks like:
src/App.tsx
src/App.tsximport React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import PluginProvider from "./RoutingPluginProvider";
import ErrorBoundary from "./ErrorBoundary";
import SampleMetadataView from "./components/SampleMetadataView";
import "../baseStyles.css";
const App: React.FC = () => {
const queryClient = new QueryClient();
return (
<ErrorBoundary>
<QueryClientProvider client={queryClient}>
<PluginProvider enableRouting initialEntries={["/"]}>
<SampleMetadataView />
</PluginProvider>
</QueryClientProvider>
</ErrorBoundary>
);
};
export default App;Step 5: Upload and Preview the Plugin in Cortex
Once your plugin is ready:
Run
npm run build(or equivalent)Upload the generated
UI.htmlbundle in the Cortex UIOpen an entity of a supported type
Select your plugin in the left sidebar
Verify your custom visualization displays correctly

This gives you a full end-to-end workflow for developing, testing, and launching plugin-based custom data visualizations.
Last updated
Was this helpful?