Side menu
Defining a page as a side-menu entry (basicType)
With the virtualType and configureType we have learned how to configure a page for your Extension product, but that won't make it appear on the side-menu. For that you need to use the function basicType coming from $plugin.DSL. As an example usage of that method, one could do the following:
import { IPlugin } from '@shell/core/types';
// this is the definition of a "blank cluster" for Rancher Dashboard
// definition of a "blank cluster" in Rancher Dashboard
const BLANK_CLUSTER = '_';
export function init($plugin: IPlugin, store: any) {
  const YOUR_PRODUCT_NAME = 'myProductName';
  const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster';
  const CUSTOM_PAGE_NAME = 'page1';
  
  const { 
    product,
    configureType,
    virtualType,
    basicType
  } = $plugin.DSL(store, YOUR_PRODUCT_NAME);
  // registering a top-level product
  product({
    icon: 'gear',
    inStore: 'management',
    weight: 100,
    to: {
      name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER
      }
    }
  });
  
  // defining a k8s resource as page
  configureType(YOUR_K8S_RESOURCE_NAME, {
    displayName: 'some-custom-name-you-wish-to-assign-to-this-resource',
    isCreatable: true,
    isEditable:  true,
    isRemovable: true,
    showAge:     true,
    showState:   true,
    canYaml:     true,
    customRoute: {
      name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER,
        resource: YOUR_K8S_RESOURCE_NAME
      }
    }
  });
  
  // creating a custom page
  virtualType({
    labelKey: 'some.translation.key',
    name:     CUSTOM_PAGE_NAME,
    route:    {
      name:   `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME }`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER
      }
    }
  });
  // => => => registering the defined pages as side-menu entries
  basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME]);
}
On the above example we are creating two side menu entries on a "root" level for your YOUR_K8S_RESOURCE_NAME and CUSTOM_PAGE_NAME pages.
Menu entries can also be grouped under a common "folder/group" in the side menu. For that the basicType takes an additional parameter which will be the name for the folder/group" in the side-menu. An example of the grouping as a follow-up on the example above would be:
// update of the function usage based on the example above
// => => => registering the defined pages as side-menu entries as a group 
  basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME], 'my-custom-group-name');
NOTE: On the example above the label of the group on the side-menu will be
my-custom-group-name.
Side menu ordering (weightType and weightGroup)
How about if you wanted to change the side-menu ordering for your Extension product? That can be achieved by using the functions weightType and weightGroup coming from $plugin.DSL. Let's then look at the following example:
import { IPlugin } from '@shell/core/types';
// this is the definition of a "blank cluster" for Rancher Dashboard
// definition of a "blank cluster" in Rancher Dashboard
const BLANK_CLUSTER = '_';
export function init($plugin: IPlugin, store: any) {
  const YOUR_PRODUCT_NAME = 'myProductName';
  const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster';
  const CUSTOM_PAGE_NAME_1 = 'page1';
  const CUSTOM_PAGE_NAME_2 = 'page2';
  const CUSTOM_PAGE_NAME_3 = 'page3';
  
  const { 
    product,
    configureType,
    virtualType,
    basicType
  } = $plugin.DSL(store, YOUR_PRODUCT_NAME);
  // registering a top-level product
  product({
    icon: 'gear',
    inStore: 'management',
    weight: 100,
    to: {
      name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_2 }`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER
      }
    }
  });
  // defining a k8s resource as page
  configureType(YOUR_K8S_RESOURCE_NAME, {
    displayName: 'some-custom-name-you-wish-to-assign-to-this-resource',
    isCreatable: true,
    isEditable:  true,
    isRemovable: true,
    showAge:     true,
    showState:   true,
    canYaml:     true,
    customRoute: {
      name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER,
        resource: YOUR_K8S_RESOURCE_NAME
      }
    }
  });
  // creating a custom page
  virtualType({
    labelKey: 'some.translation.key',
    name:     CUSTOM_PAGE_NAME_1,
    route:    {
      name:   `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_1 }`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER
      }
    }
  });
  // creating yet another custom page
  virtualType({
    labelKey: 'some.translation.key',
    name:     CUSTOM_PAGE_NAME_2,
    route:    {
      name:   `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_2 }`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER
      }
    }
  });
  virtualType({
    labelKey: 'some.translation.key',
    name:     CUSTOM_PAGE_NAME_3,
    route:    {
      name:   `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_3 }`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER
      }
    }
  });
  // registering some of the defined pages as side-menu entries in the root level
  basicType([CUSTOM_PAGE_NAME_2, CUSTOM_PAGE_NAME_3]);
  // registering some of the defined pages as side-menu entries in a group
  basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME_1], 'myAdvancedGroup');
}
Note: All individual root elements (in the example would be
CUSTOM_PAGE_NAME_2andCUSTOM_PAGE_NAME_3) are placed under a pseudo-group calledroot, which in turn has always a default weight of1000. In the example provided above we are registering 4 pages: 1 is a "resource" page (YOUR_K8S_RESOURCE_NAME) and 3 are "custom" pages (CUSTOM_PAGE_NAME_1,CUSTOM_PAGE_NAME_2andCUSTOM_PAGE_NAME_3).
These pages are set as side-menu entries being YOUR_K8S_RESOURCE_NAME and CUSTOM_PAGE_NAME_1 in a group called myAdvancedGroup and 2 other pages(CUSTOM_PAGE_NAME_2 and CUSTOM_PAGE_NAME_3) as a root level side-menu entry.
The default ordering of these side-menu entries is the order on which you register them using basicType, taking also into consideration pseudo-group root, which in turn will always be above any other custom groups, provided the fact that the developer hasn't defined any custom group weight yet.
In the above example the side-menu output would be something like:
- CUSTOM_PAGE_NAME_2
- CUSTOM_PAGE_NAME_3
- myAdvancedGroup
- YOUR_K8S_RESOURCE_NAME
- CUSTOM_PAGE_NAME_1
 
If we wanted to define some custom ordering for these menu entries, we would need to use the functions weightType and weightGroup, like:
import { IPlugin } from '@shell/core/types';
// this is the definition of a "blank cluster" for Rancher Dashboard
// definition of a "blank cluster" in Rancher Dashboard
const BLANK_CLUSTER = '_';
export function init($plugin: IPlugin, store: any) {
  const YOUR_PRODUCT_NAME = 'myProductName';
  const YOUR_K8S_RESOURCE_NAME = 'provisioning.cattle.io.cluster';
  const CUSTOM_PAGE_NAME_1 = 'page1';
  const CUSTOM_PAGE_NAME_2 = 'page2';
  const CUSTOM_PAGE_NAME_3 = 'page3';
  
  const { 
    product,
    configureType,
    virtualType,
    basicType
  } = $plugin.DSL(store, YOUR_PRODUCT_NAME);
  // registering a top-level product
  product({
    icon: 'gear',
    inStore: 'management',
    weight: 100,
    to: {
      name: `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_2 }`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER
      }
    }
  });
  // defining a k8s resource as page
  configureType(YOUR_K8S_RESOURCE_NAME, {
    displayName: 'some-custom-name-you-wish-to-assign-to-this-resource',
    isCreatable: true,
    isEditable:  true,
    isRemovable: true,
    showAge:     true,
    showState:   true,
    canYaml:     true,
    customRoute: {
      name: `${ YOUR_PRODUCT_NAME }-c-cluster-resource`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER,
        resource: YOUR_K8S_RESOURCE_NAME
      }
    }
  });
  // creating a custom page
  virtualType({
    labelKey: 'some.translation.key',
    name:     CUSTOM_PAGE_NAME_1,
    route:    {
      name:   `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_1 }`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER
      }
    }
  });
  // creating yet another custom page
  virtualType({
    labelKey: 'some.translation.key',
    name:     CUSTOM_PAGE_NAME_2,
    route:    {
      name:   `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_2 }`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER
      }
    }
  });
  virtualType({
    labelKey: 'some.translation.key',
    name:     CUSTOM_PAGE_NAME_3,
    route:    {
      name:   `${ YOUR_PRODUCT_NAME }-c-cluster-${ CUSTOM_PAGE_NAME_3 }`,
      params: {
        product: YOUR_PRODUCT_NAME,
        cluster: BLANK_CLUSTER
      }
    }
  });
  // registering some of the defined pages as side-menu entries in the root level
  basicType([CUSTOM_PAGE_NAME_2, CUSTOM_PAGE_NAME_3]);
  // registering some of the defined pages as side-menu entries in a group
  basicType([YOUR_K8S_RESOURCE_NAME, CUSTOM_PAGE_NAME_1], 'myAdvancedGroup');
  // => => => individual ordering of each menu entry
  weightType(CUSTOM_PAGE_NAME_1, 2, true);
  weightType(YOUR_K8S_RESOURCE_NAME, 1, true);
  weightType(CUSTOM_PAGE_NAME_3, 2, true);
  weightType(CUSTOM_PAGE_NAME_2, 1, true);
  // => => => ordering of the grouped entry
  weightGroup('myAdvancedGroup', 1001, true);
}
Given the example provided above, what would be the output in terms of ordering of this side-menu?
- myAdvancedGroup
- CUSTOM_PAGE_NAME_1
- YOUR_K8S_RESOURCE_NAME
 
- CUSTOM_PAGE_NAME_3
- CUSTOM_PAGE_NAME_2
Interpreting the code on the example, it's easy to follow the ordering defined:
- We are setting 3 root level side-menu items: CUSTOM_PAGE_NAME_2,CUSTOM_PAGE_NAME_3andmyAdvancedGroup
- Technically, as mentioned on the note above, CUSTOM_PAGE_NAME_2andCUSTOM_PAGE_NAME_3are placed under a group calledrootwhich has no label associated, hence why it's not perceived as "group" likemyAdvancedGroup
- Since we are giving a weight of 1001tomyAdvancedGroup(the bigger, the higher it will sit on the menu ordering - higher than the default1000ofroot), themyAdvancedGroupmenu will be above theCUSTOM_PAGE_NAME_2andCUSTOM_PAGE_NAME_3side-menu entries
- Inside the myAdvancedGroupgroup we are setting a specific order as well: weight of2toCUSTOM_PAGE_NAME_1and a weight of1toYOUR_K8S_RESOURCE_NAME.This will make the side-menu entry forCUSTOM_PAGE_NAME_1appear higher thanYOUR_K8S_RESOURCE_NAMEinside the groupmyAdvancedGroup
- As for the CUSTOM_PAGE_NAME_2andCUSTOM_PAGE_NAME_3they are done inside that virtual group calledroot. SinceCUSTOM_PAGE_NAME_3is set a weight of2andCUSTOM_PAGE_NAME_3is set a weight of1,CUSTOM_PAGE_NAME_3will appear aboveCUSTOM_PAGE_NAME_2
NOTE: The last parameter for the
weightTypeandweightGroupfunctions is a boolean that should be set totrueat all times so that it works properly.
Customizing the Product Side-Menu Entry
When you register your Extension product, a top-level side-menu entry is automatically created for it.
How It Works:
- Label Computation:
The label is generated by searching for the translation key product.<productId>. If not found, it defaults to the product ID with its first letter capitalized.
- Customization Example:
To change the side‑menu entry for a product with the ID myProduct, add the following to your translation file at the root level:This translation file should reside in your package’s localization directory (e.g.,product:
 myProduct: My Product./pkg/<extension-name>/l10n). For more details, see the Localization documentation.