# Migrate plugins from Grafana version 11.6.x to 12.0.x

This guide helps you migrate plugins from Grafana version 11.6.x to 12.0.x.

## Prerequisites[​](#prerequisites "Direct link to Prerequisites")

Before starting the migration:

* Back up your plugin code
* Ensure your development environment is up to date
* Familiarize yourself with [the reactive APIs](https://grafana.com/developers/plugin-tools/how-to-guides/ui-extensions.md) introduced in Grafana 11.4

## Deprecated UI extension APIs removal[​](#deprecated-ui-extension-apis-removal "Direct link to Deprecated UI extension APIs removal")

The deprecated UI extension APIs have been removed in Grafana 12 in favor of the new reactive APIs introduced in Grafana 11.4. The following APIs have been removed:

* `usePluginExtensions()`
* `usePluginLinkExtensions()`
* `usePluginComponentExtensions()`
* `getPluginExtensions()`
* `getPluginLinkExtensions()`
* `getPluginComponentExtensions()`
* `AppPlugin.configureExtensionLink()`
* `AppPlugin.configureExtensionComponent()`

Using any of these APIs in Grafana 12 will result in an error. Additionally, the TypeScript types `PluginExtensionLinkConfig` and `PluginExtensionComponentConfig` have been removed.

info

If you need your plugin to work with both Grafana 12.0.x and older versions, you can implement runtime checks to conditionally use the appropriate APIs. For more information, refer to [Manage backwards compatibility with runtime checks](https://grafana.com/developers/plugin-tools/how-to-guides/runtime-checks.md#example-conditionally-use-react-hooks).

### AppPlugin.configureExtensionLink()[​](#apppluginconfigureextensionlink "Direct link to AppPlugin.configureExtensionLink()")

Replace the `configureExtensionLink` method with the `addLink` method. Update the `extensionPointId` parameter to `targets`, which accepts either a `string` or `string[]`.

```diff
- new AppPlugin().configureExtensionLink({
+ new AppPlugin().addLink({
- extensionPointId: PluginExtensionPoints.DashboardPanelMenu,
+ targets: PluginExtensionPoints.DashboardPanelMenu,
    title: 'Component title 0',
    description: 'Component description 0',
    component: () => <div />,
});

```

### AppPlugin.configureExtensionComponent()[​](#apppluginconfigureextensioncomponent "Direct link to AppPlugin.configureExtensionComponent()")

Replace the `configureExtensionComponent` method with the `addComponent` method. Update the `extensionPointId` parameter to `targets`, which accepts either a `string` or `string[]`.

```diff
- new AppPlugin().configureExtensionComponent({
+ new AppPlugin().addComponent({
- extensionPointId: PluginExtensionPoints.CommandPalette,
+ targets: PluginExtensionPoints.CommandPalette,
    title: 'Component title 0',
    description: 'Component description 0',
    component: () => <div />,
});

```

### getPluginLinkExtensions() and usePluginLinkExtensions()[​](#getpluginlinkextensions-and-usepluginlinkextensions "Direct link to getPluginLinkExtensions() and usePluginLinkExtensions()")

Both the `getPluginLinkExtensions()` function and the `usePluginLinkExtensions()` React hook can be replaced with the `usePluginLinks()` React hook.

getPluginLinkExtensions

```diff
- const { extensions } = getPluginLinkExtensions({
+ const { links, isLoading } = usePluginLinks({
    extensionPointId: 'grafana/dashboard/panel/menu/v1',
    limitPerPlugin: 2,
    context: {
      panelId: '...',
    },
});

```

usePluginLinkExtensions

```diff
- const { extensions, isLoading } = usePluginLinkExtensions({
+ const { links, isLoading } = usePluginLinks({
    extensionPointId: 'grafana/dashboard/panel/menu/v1',
    limitPerPlugin: 2,
    context: {
      panelId: '...',
    },
});

```

### getPluginComponentExtensions() and usePluginComponentExtensions()[​](#getplugincomponentextensions-and-useplugincomponentextensions "Direct link to getPluginComponentExtensions() and usePluginComponentExtensions()")

You can replace both the `getPluginComponentExtensions()` function and the `usePluginComponentExtensions()` React hook with the `usePluginComponents()` React hook.

getPluginComponentExtensions

```diff
- const { extensions } = getPluginComponentExtensions({
+ const { components, isLoading } = usePluginComponents({
    extensionPointId: 'grafana/user/profile/tab/v1',
    limitPerPlugin: 2,
});

```

usePluginComponentExtensions

```diff
- const { extensions, isLoading } = usePluginComponentExtensions({
+ const { components, isLoading } = usePluginComponents({
    extensionPointId: 'grafana/user/profile/tab/v1',
    limitPerPlugin: 2,
});

```

### getPluginExtensions() and usePluginExtensions()[​](#getpluginextensions-and-usepluginextensions "Direct link to getPluginExtensions() and usePluginExtensions()")

Replace the `getPluginExtensions()` function and the `usePluginExtensions()` React hook based on their usage:

* **For links:** Follow the [link extensions](https://github.com/grafana/plugin-tools/pull/1639/files#getpluginlinkextensions-and-usepluginlinkextensions) instructions.

* **For components:** Follow the [component extensions](https://github.com/grafana/plugin-tools/pull/1639/files#getplugincomponentextensions-and-useplugincomponentextensions) instructions.

* **For both links and components:** Use both `usePluginLinks` and `usePluginComponents`.

### PluginExtensionLinkConfig and PluginExtensionComponentConfig[​](#pluginextensionlinkconfig-and-pluginextensioncomponentconfig "Direct link to PluginExtensionLinkConfig and PluginExtensionComponentConfig")

The types `PluginExtensionLinkConfig` and `PluginExtensionComponentConfig` have been removed from `@grafana/data`. Replace them with `PluginExtensionAddedLinkConfig` and `PluginExtensionAddedComponentConfig`, respectively.

### GetPluginExtensionsOptions[​](#getpluginextensionsoptions "Direct link to GetPluginExtensionsOptions")

The `GetPluginExtensionsOptions` type has been removed from `@grafana/runtime` in favor of specific types that match their corresponding hook parameters.

## Quick reference[​](#quick-reference "Direct link to Quick reference")

The following table summarizes the API changes with notes explaining the key differences:

| Deprecated API                            | Equivalent API                                                                         | Notes                                                                             |
| ----------------------------------------- | -------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `AppPlugin.configureExtensionLink()`      | `AppPlugin.addLink()`                                                                  | `extensionPointId` parameter renamed to `targets`, accepts `string` or `string[]` |
| `AppPlugin.configureExtensionComponent()` | `AppPlugin.addComponent()`                                                             | `extensionPointId` parameter renamed to `targets`, accepts `string` or `string[]` |
| `getPluginLinkExtensions()`               | `usePluginLinks()`                                                                     | Returns `{ links, isLoading }` instead of `{ extensions }`                        |
| `usePluginLinkExtensions()`               | `usePluginLinks()`                                                                     | Returns `{ links, isLoading }` instead of `{ extensions }`                        |
| `getPluginComponentExtensions()`          | `usePluginComponents()`                                                                | Returns `{ components, isLoading }` instead of `{ extensions }`                   |
| `usePluginComponentExtensions()`          | `usePluginComponents()`                                                                | Returns `{ components, isLoading }` instead of `{ extensions, isLoading }`        |
| `getPluginExtensions()`                   | `usePluginLinks()` or `usePluginComponents()`                                          | Split into two separate hooks based on extension type (links or components)       |
| `usePluginExtensions()`                   | `usePluginLinks()` or `usePluginComponents()`                                          | Split into two separate hooks based on extension type (links or components)       |
| `PluginExtensionComponentConfig`          | `PluginExtensionAddedComponentConfig`                                                  | Updated type definition for component configuration                               |
| `PluginExtensionLinkConfig`               | `PluginExtensionAddedLinkConfig`                                                       | Updated type definition for link configuration                                    |
| `GetPluginExtensionsOptions`              | `UsePluginLinksOptions` or `UsePluginComponentsOptions` or `UsePluginFunctionsOptions` | Updated type definition for hook parameters                                       |

## Migrate from `Select` to `Combobox`[​](#migrate-from-select-to-combobox "Direct link to migrate-from-select-to-combobox")

Grafana 11.5.0 introduced the new `Combobox` component as a replacement for the `Select` component. The `Combobox` component has been designed with performance in mind, particularly for handling extremely large data sets. See the [component documentation](https://developers.grafana.com/ui/latest/index.html?path=/docs/forms-combobox--docs) for detailed usage guidelines.

The `Select` and `MultiSelect` components are now deprecated in Grafana 12 and will not receive substantial updates or fixes. We recommend migrating to `Combobox` and `MultiCombobox`.

### Basic usage[​](#basic-usage "Direct link to Basic usage")

For simple use cases, the `Combobox` component has a similar API to `Select`.

```tsx
/**
 * Previous Select implementation
 */
import { SelectableValue } from '@grafana/data';
import { Select } from '@grafana/ui';

const options: SelectableValue[] = [
  { label: 'Loki', value: 'datasource-loki' },
  { label: 'Prometheus', value: 'datasource-prom' },
];
const [value, setValue] = useState<SelectableValue>();

<Select value={value} options={options} onChange={(newOption) => setValue(newOption)} />;

/**
 * New Combobox implementation
 */
import { Combobox, ComboboxOption } from '@grafana/ui';

const options: ComboboxOption[] = [
  { label: 'Loki', value: 'datasource-loki' },
  { label: 'Prometheus', value: 'datasource-prom' },
];

// It is preferred to pass in only the 'value' property from options to the value prop
const [value, setValue] = useState<string>();

<Combobox value={value} options={options} onChange={(newOption) => setValue(newOption.value)} />;

```

### Async data loading[​](#async-data-loading "Direct link to Async data loading")

The `Combobox` component handles asynchronous data loading through a function passed to the `options` prop. This function:

* Receives the current input value as a parameter
* Returns a Promise resolving to `ComboboxOption[]`
* Automatically debounces calls to prevent excessive API requests

```tsx
import { Combobox, ComboboxOption } from '@grafana/ui';

const [value, setValue] = useState<string>();

const loadOptions = useCallback(async (input: string): Promise<ComboboxOption[]> => {
  const response = await fetch(`/api/options?query=${input}`);
  return response.json();
}, []);

<Combobox value={value} options={loadOptions} onChange={(newOption) => setValue(newOption.value)} />;

```

warning

Do not request *all* options on the initial open. It's recommended to use this async behaviour only when calling an API that is able to filter results on the server side.

### Quick reference[​](#quick-reference-1 "Direct link to Quick reference")

The following table summarizes the component deprecations changes with notes explaining the key differences:

| Deprecated               | Equivalent      | Notes                                        |
| ------------------------ | --------------- | -------------------------------------------- |
| `Select`                 | `Combobox`      | See props table below                        |
| `MultiSelect`            | `MultiCombobox` | See props table below                        |
| `AsyncSelect`            | `Combobox`      | Use an async function for the `options` prop |
| `VirtualizedSelect`      | `Combobox`      | Combobox is virtualised by default           |
| `AsyncVirtualizedSelect` | `Combobox`      | Combobox is virtualised by default           |
| `AsyncMultiSelect`       | `MultiSelect`   | Use an async function for the `options` prop |

The following table summarizes the prop changes with notes explaining the key differences:

| Deprecated                       | Equivalent                      | Notes                                                                                |
| -------------------------------- | ------------------------------- | ------------------------------------------------------------------------------------ |
| `SelectableValue`                | `ComboboxOption`                | Is exported from `@grafana/ui` instead                                               |
| `SelectableValue["value"]`       | `ComboboxOption["value"]`       | Value is required and must be primitive `string \| number`                           |
| `SelectableValue["description"]` | `ComboboxOption["description"]` | Description is no longer wrapped and may be truncated                                |
| `onChange`                       | `onChange`                      | Recommended to only store the `value` in state                                       |
| `value`                          | `value`                         | Recommended to only pass in the primitive `string \| number` value                   |
| `options`                        | `options`                       | Can be a `ComboboxOption[]` or a function that returns a `Promise<ComboboxOption[]>` |
| `isLoading`                      | `loading`                       | Not required if using async options                                                  |
| `allowCustomValue`               | `createCustomValue`             | Will emit an object like `{ label: "Foo", value: "Foo" }` from onChange              |
| `width="auto"`                   | `width="auto"`                  | `minWidth` is required when using the auto-sizing input                              |

Many props from the old Select component that controlled subtle behavior or customized rendering - such as `createOptionPosition` or `openMenuOnFocus` - are no longer supported, in order to simplify the API and provide a more consistent experience. If you have a specific use case that requires this, please [open an issue](https://github.com/grafana/grafana/issues/new?template=1-feature_requests.md) in the Grafana repo.
