Open a Tab Using Web API

Last modified: October 16, 2025

Introduction

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

Prerequisites

Before starting this how-to, make sure you have completed the following prerequisites:

Opening a Tab

Create a menu item to open the tab. This is done inside the loaded method in Main class, as described below. 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 – the name of the extension prefixed with "extension/"; for example, "extension/myextension" in the following example
    • uiEntryPoint – the name mapped from the manifest.json file

To open a tab called My Extension Tab, add the following code to the main entry point (src/main/index.ts):

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

class Main implements IComponent {
  tabs: { [menuId: string]: TabHandle } = {};
  async loaded(componentContext: ComponentContext) {
    const studioPro = getStudioProApi(componentContext);

    // 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 = await 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(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 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 React, { StrictMode } from "react";
    import { createRoot } from "react-dom/client";
    import { IComponent } from "@mendix/extensions-api";
    
    export const component: IComponent = {
        async loaded(componentContext) {
            createRoot(document.getElementById("root")!).render(
                <StrictMode>
                    <h1>tab3</h1>
                </StrictMode>
            );
        },
    };

    In this example, you will add three 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, getStudioProApi, TabHandle, ComponentContext, TabInfo, UISpec } from "@mendix/extensions-api";
    
    class Main implements IComponent {
        tabs: { [menuId: string]: TabHandle } = {};
    
        async loaded(componentContext: ComponentContext) {
            const studioPro = getStudioProApi(componentContext);
    
            // 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");
                        this.tabs["myextension.ShowTab1"] = await studioPro.ui.tabs.open(tab1Spec.info, tab1Spec.ui);
                    }
                    if (args.menuId === "myextension.ShowTab2") {
                        const tab2Spec = this.createTabSpec("tab2", "Tab 2 Title");
                        this.tabs["myextension.ShowTab2"] = await studioPro.ui.tabs.open(tab2Spec.info, tab2Spec.ui);
                    }
                    if (args.menuId === "myextension.ShowTab3") {
                        const tab3Spec = this.createTabSpec("tab3", "Tab 3 Title");
                        this.tabs["myextension.ShowTab3"] = await 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. Below 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 build-extension.mjs to match the manifest with an entry for each tab. Add entry points for each tab to the entryPoints array and make sure the variable appDir stays unaltered, as follows:

      import * as esbuild from 'esbuild'
      import {copyToAppPlugin, copyManifestPlugin, commonConfig} from "./build.helpers.mjs"
      import parseArgs from "minimist"
    
      const outDir = `dist/myextension`
      const appDir ="<path to your application>"
      const extensionDirectoryName = "extensions"
    
      const entryPoints = [
          {
              in: 'src/main/index.ts',
              out: 'main'
          }   
      ]
    
      const allTabs = ["tab1", "tab2", "tab3"]
      entryPoints.push(...allTabs.map(tabName => ({
          in: `src/ui/${tabName}/index.tsx`,
          out: tabName
      })))
    
      const args = parseArgs(process.argv.slice(2))
      const buildContext = await esbuild.context({
        ...commonConfig,
        outdir: outDir,
        plugins: [copyManifestPlugin(outDir), copyToAppPlugin(appDir, outDir, extensionDirectoryName)],
        entryPoints
      })
    
      if('watch' in args) {
          await buildContext.watch();
      } 
      else {
          await buildContext.rebuild();
          await buildContext.dispose();
      }

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

Extensibility Feedback

If you would like to provide additional feedback, you can complete a small survey.

Any feedback is appreciated.