Render links in an extension point
An extension point is a part of your plugin or Grafana UI where you can render content (links, functions or React components) from other plugins. Use them to extend your users' experience based on a context exposed by the extension point.
note
Read more about extensions under key concepts.
For reference documentation, including the APIs, see UI extensions reference guide.
Best practices for rendering links​
- Make sure your UI handles multiple links
Multiple plugins may add links to your extension point. Make sure your extension point can handle this and still provide good user experience. See how you can limit the number of extensions in your extension point. - Share contextual information
Think about what contextual information could be useful for other plugins and add this to thecontextobject. For example, the panel menu extension point shares thepanelIdand thetimeRange. Note that thecontext{}object always gets frozen before being passed to the links, so it can't be mutated. - Avoid unnecessary re-renders
-
Static context
// Define the `context` object outside of the component if it only has static values
const context = { foo: 'bar' };
export const InstanceToolbar = () => {
const { links, isLoading } = usePluginLinks({ extensionPointId, context }); -
Dynamic context
export const InstanceToolbar = ({ instanceId }) => {
// Always use `useMemo()` when the `context` object has "dynamic" values
const context = useMemo(() => ({ instanceId }), [instanceId]);
const { links, isLoading } = usePluginLinks({ extensionPointId, context });
-
Create an extension point to render links​
- Create the extension point component:
src/components/InstanceToolbar.tsx
import { usePluginLinks } from '@grafana/runtime';
export const InstanceToolbar = () => {
// The `extensionPointId` must be prefixed.
// - Core Grafana -> prefix with "grafana/"
// - Plugin -> prefix with "{your-plugin-id}/"
//
// This is also what plugins use when they call `addLink()`
const extensionPointId = 'myorg-foo-app/toolbar/v1';
const { links, isLoading } = usePluginLinks({ extensionPointId });
if (isLoading) {
return <div>Loading...</div>;
}
return (
<div>
{/* Loop through the links added by plugins */}
{links.map(({ id, title, path, onClick }) => (
<a href={path} title={title} key={id} onClick={onClick}>
{title}
</a>
))}
</div>
);
};
- Declare the extension point in your
plugin.json:
src/plugin.json
{
...
"extensions": {
"extensionPoints": [
{
"id": "myorg-foo-app/toolbar/v1",
}
]
}
}
Passing data to links​
- Update the component to pass context data:
src/components/InstanceToolbar.tsx
import { usePluginLinks } from '@grafana/runtime';
export const InstanceToolbar = ({ instanceId }) => {
const extensionPointId = 'myorg-foo-app/toolbar/v1';
// Heads up! Always use `useMemo()` in case the `context` object has any "dynamic" properties
// to prevent unnecessary re-renders (Otherwise a new object would be created on every render, that could
// result in a new links{} object, that could trigger a new re-render, and so on.)
const context = useMemo(() => ({ instanceId }), [instanceId]);
const { links, isLoading } = usePluginLinks({ extensionPointId, context });
// ...
};
- Make sure your
plugin.jsonis up to date:
src/plugin.json
src/plugin.json
{
...
"extensions": {
"extensionPoints": [
{
"id": "myorg-foo-app/toolbar/v1",
}
]
}
}
Limit the number of extensions in your extension point​
If you have limited space on the UI, you can limit the number of extensions in your extension point. By default there is no limit.
- Update the component to limit extensions per plugin:
src/components/InstanceToolbar.tsx
import { usePluginLinks } from '@grafana/runtime';
export const InstanceToolbar = () => {
// Only one link per plugin is allowed.
// (If a plugin registers more than one links, then the rest will be ignored
// and won't be returned by the hook.)
const { links, isLoading } = usePluginLinks({ extensionPointId, limitPerPlugin: 1 });
// ...
};
- Make sure your
plugin.jsonis up to date:
src/plugin.json
src/plugin.json
{
...
"extensions": {
"extensionPoints": [
{
"id": "myorg-foo-app/toolbar/v1",
}
]
}
}
Limit which plugins can register links in your extension point​
- Update the component to filter plugins:
src/components/InstanceToolbar.tsx
import { usePluginLinks } from '@grafana/runtime';
export const InstanceToolbar = () => {
const { links, isLoading } = usePluginLinks({ extensionPointId, limitPerPlugin: 1 });
// You can rely on the `link.pluginId` prop to filter based on the plugin
// that has registered the extension.
const allowedLinks = useMemo(() => {
const allowedPluginIds = ['myorg-a-app', 'myorg-b-app'];
return links.filter(({ pluginId }) => allowedPluginIds.includes(pluginId));
}, [links]);
// ...
};
- Make sure your
plugin.jsonis up to date:
src/plugin.json
src/plugin.json
{
...
"extensions": {
"extensionPoints": [
{
"id": "myorg-foo-app/toolbar/v1",
}
]
}
}