Open a Tab Using Web API

Last modified: April 4, 2025

Introduction

This how-to shows you how to open a tab in Studio Pro from an extension. This tab will contain your web content.

Prerequisites

This how-to uses the results of Get Started with the Web Extensibility API. Please complete that how-to before starting this one. You should also be familiar with creating menus as described in Create a Menu Using Web API.

Opening a Tab

Firstly, create a menu item to open the tab. This is done inside the loaded event in Main. For more information see Create a Menu Using Web API.

In a listener event called menuItemActivated the studioPro.ui.tabs.open(<tabinfo>, <uispec>) call opens a new tab where:

  • <TabInfo> is an object containing the title of the tab, which will be shown in the title bar of your tab in Studio Pro.

  • <uispec> is an object containing two required properties:

    • componentName which is the name of the extension prefixed with “extension/”. For example “extension/myextension” in the following example.
    • uiEntryPoint which is the name mapped from the manifest.json file. See below for examples with multiple tabs.

An example of the class Main to open a tab called My Extension Tab looks similar to the following:

import { IComponent, studioPro, TabHandle } from "@mendix/extensions-api";

class Main implements IComponent {
  tabs: { [menuId: string]: Promise<TabHandle> } = {};
  async loaded() {
    // Add menu items to the Extensions menu to open and close our tab
    await studioPro.ui.extensionsMenu.add({
      menuId: "myextension.MainMenu",
      caption: "MyExtension Menu",
      subMenus: [
        { menuId: "myextension.ShowTabMenuItem", caption: "Show tab" },
        {
          menuId: "myextension.CloseTabMenuItem",
          caption: "Close tab",
        },
      ],
    });

    studioPro.ui.extensionsMenu.addEventListener(
      "menuItemActivated",
      async (args) => {
        // Open a tab when the menu item is clicked
        if (args.menuId === "myextension.ShowTabMenuItem") {
          const handle = studioPro.ui.tabs.open(
            {
              title: "My Extension Tab",
            },
            {
              componentName: "extension/myextension",
              uiEntrypoint: "tab",
            }
          );

          // Track the open tab
          this.tabs["myextension.MainMenu"] = handle;
        }

        // Close the tab opened previously
        if (args.menuId === "myextension.CloseTabMenuItem") {
          studioPro.ui.tabs.close(await this.tabs["myextension.MainMenu"]);
        }
      }
    );
  }
}

export const component: IComponent = new Main();

Filling the Tabs With Content

In the previous example, the uiEntryPoint property of the <uispec> object had the value “tab”. This value must match the one from the manifest.

If you want to have multiple tabs in your extension, you need to structure the folders and set up the manifest file correctly.

To do this, follow these steps:

  1. Add a new method createTabSpec in your Main class.

    createTabSpec(tab: string, title: string): { info: TabInfo, ui: UISpec} {
            const info: TabInfo = { title };
            const ui: UISpec = {
                componentName: "extension/myextension",
                uiEntrypoint: tab,
            };
    
            return {info, ui};
        }
    
  2. Add three folders inside the ui folder, one for each tab you want to display contents for.

  3. Create an index.tsx file in each folder.

  4. Put the following code in each index.tsx file (this example is for tab3):

    import { StrictMode } from "react";
    import { createRoot } from "react-dom/client";
    
    createRoot(document.getElementById("root")!).render(
      <StrictMode>
        <h1>tab3</h1>
      </StrictMode>
    );
    

    In this example, we’ll add 3 tabs: tab1, tab2, and tab3.

  5. Create listener events in the Main class to open each of the three tabs. The Main class will then look like this:

    import { IComponent, studioPro, TabInfo, UISpec } from "@mendix/extensions-api";
    
    class Main implements IComponent {
      async loaded() {
        // Add a menu item to the Extensions menu
        await studioPro.ui.extensionsMenu.add({
          menuId: "myextension.MainMenu",
          caption: "Show Tabs",
          subMenus: [
            { menuId: "myextension.ShowTab1", caption: "Show tab 1" },
            { menuId: "myextension.ShowTab2", caption: "Show tab 2" },
            { menuId: "myextension.ShowTab3", caption: "Show tab 3" },
          ],
        });
    
        // Open a tab when the menu item is clicked
        studioPro.ui.extensionsMenu.addEventListener(
          "menuItemActivated",
          async (args) => {
            if (args.menuId === "myextension.ShowTab1") {
              const tab1Spec = this.createTabSpec("tab1", "Tab 1 Title");
              studioPro.ui.tabs.open(tab1Spec.info, tab1Spec.ui);
            }
            if (args.menuId === "myextension.ShowTab2") {
              const tab2Spec = this.createTabSpec("tab2", "Tab 2 Title");
              studioPro.ui.tabs.open(tab2Spec.info, tab2Spec.ui);
            }
            if (args.menuId === "myextension.ShowTab3") {
              const tab3Spec = this.createTabSpec("tab3", "Tab 3 Title");
              studioPro.ui.tabs.open(tab3Spec.info, tab3Spec.ui);
            }
          }
        );
      }
    
      createTabSpec(tab: string, title: string): { info: TabInfo; ui: UISpec } {
        const info: TabInfo = { title };
        const ui: UISpec = {
          componentName: "extension/myextension",
          uiEntrypoint: tab,
        };
    
        return { info, ui };
      }
    }
    
    export const component: IComponent = new Main();
    
  6. Ensure the tabs are added to the manifest.json file. Here is an example of three tabs under the ui property.

    {
      "mendixComponent": {
        "entryPoints": {
          "main": "main.js",
          "ui": {
            "tab1": "tab1.js",
            "tab2": "tab2.js",
            "tab3": "tab3.js"
          }
        }
      }
    }
    
  7. Update vite.config to match the manifest with an entry for each tab. For example:

    import { defineConfig, ResolvedConfig, UserConfig } from "vite";
    
    export default defineConfig({
      build: {
        lib: {
          formats: ["es"],
          entry: {
            main: "src/main/index.ts",
            tab1: "src/ui/tab1/index.tsx",
            tab2: "src/ui/tab2/index.tsx",
            tab3: "src/ui/tab3/index.tsx",
          },
        },
        rollupOptions: {
          external: ["@mendix/component-framework", "@mendix/model-access-sdk"],
        },
        outDir: "./dist/myextension",
      },
    } satisfies UserConfig);
    

After building and installing the extension in our Studio Pro app, each tab will display the content specified in the related index.tsx file.

Conclusion

You now know how to create tabs and populate them with content.

Extensibility Feedback

If you would like to provide us with some additional feedback you can complete a small Survey

Any feedback is much appreciated.