KB LabsDocs

Adding a Studio Page

Last updated April 7, 2026


Build and link a React page that mounts in Studio via Module Federation.

Studio is the platform's web UI, and any plugin can contribute pages that appear in Studio's sidebar. Pages are React components built as Module Federation remotes — the Studio host loads them dynamically at runtime. This guide walks through adding one page to an existing plugin.

For the full reference with every hook and every edge case, see Plugins → Studio Pages. This page is the tutorial flow.

Prerequisites

  • An existing plugin package (if you don't have one, start with Your First Plugin).
  • Studio running (kb-dev start studio).
  • React 18 — not React 19. Studio itself runs React 18, and Module Federation requires shared copies to match.

The golden rule

Studio page code imports only from @kb-labs/sdk/studio. Never from antd, @kb-labs/studio-hooks, @kb-labs/studio-ui-kit, or any other package directly. Breaking this rule means your plugin will break on every internal Studio version bump.

The build helper comes from @kb-labs/sdk/studio-build. Same rule — never import directly from @kb-labs/studio-plugin-tools.

What we're building

A Studio page for the @example/hello-plugin from Your First Plugin. It shows a button that calls the plugin's REST endpoint (or CLI command via the REST bridge) and displays the result.

Step 1 — Add the studio block to the manifest

Open your plugin's src/manifest.ts and add a studio section:

TypeScript
export const manifest = {
  schema: 'kb.plugin/3',
  id: '@example/hello-plugin',
  version: '0.1.0',
  display: { /* ... */ },
  permissions: /* ... */,
  cli: { /* ... */ },
 
  studio: {
    version: 2 as const,
    remoteName: 'helloPlugin',
    pages: [
      {
        id: 'hello.overview',
        title: 'Hello',
        icon: 'SmileOutlined',
        route: '/p/hello',
        entry: './HelloOverview',
        order: 1,
      },
    ],
    menus: [
      {
        id: 'hello',
        label: 'Hello',
        icon: 'SmileOutlined',
        target: 'hello.overview',
        order: 50,
      },
    ],
  },
};

Key fields:

  • version: 2 — literal, required.
  • remoteName — MF remote name. Must match the name in your rspack config.
  • pages[].entry — the exposed module path. Must match the key in exposes in the rspack config.
  • pages[].route — where the page mounts in Studio's URL. Convention: /p/<plugin-name>.
  • pages[].icon — Ant Design icon name (Studio host resolves it).
  • menus[].target — references a page id.

See Plugins → Manifest Reference → studio for the full schema.

Step 2 — Create the page directory

Bash
mkdir -p src/studio/pages

Step 3 — Write the page component

src/studio/pages/HelloOverview.tsx:

TSX
import {
  useData,
  useMutateData,
  useNotification,
  Card,
  Space,
  Button,
  Title,
  Text,
} from '@kb-labs/sdk/studio';
 
interface GreetResponse {
  greeting: string;
  source: 'deterministic' | 'llm';
}
 
export default function HelloOverview() {
  const { success, error: notifyError } = useNotification();
 
  const { mutate: greet, data, isPending } = useMutateData<GreetResponse, { name: string; ai?: boolean }>(
    '/api/v1/plugins/hello/greet',
    { method: 'POST' },
  );
 
  const handleGreet = () => {
    greet(
      { name: 'Studio user', ai: false },
      {
        onSuccess: (response) => success(`Greeted: ${response.greeting}`),
        onError: (err) => notifyError(`Failed: ${err.message}`),
      },
    );
  };
 
  return (
    <Card>
      <Space direction="vertical" style={{ width: '100%' }}>
        <Title level={3}>Hello Plugin</Title>
        <Text type="secondary">Click the button to call the plugin's REST endpoint.</Text>
 
        <Button type="primary" loading={isPending} onClick={handleGreet}>
          Say Hi
        </Button>
 
        {data && (
          <Card size="small">
            <Text strong>Response:</Text>
            <pre>{JSON.stringify(data, null, 2)}</pre>
          </Card>
        )}
      </Space>
    </Card>
  );
}

Every import comes from @kb-labs/sdk/studio. The SDK re-exports Ant Design components via @kb-labs/studio-ui-kit, and the hooks via @kb-labs/studio-hooks. You never import from those directly.

useMutateData handles the POST request to the plugin's REST route through the gateway. The fetch wrapper automatically attaches auth, correlation IDs, and error handling.

Step 4 — Create the rspack config

rspack.studio.config.mjs:

JavaScript
import { createStudioRemoteConfig } from '@kb-labs/sdk/studio-build';
 
export default await createStudioRemoteConfig({
  name: 'helloPlugin',  // must match manifest.studio.remoteName
  exposes: {
    './HelloOverview': './src/studio/pages/HelloOverview.tsx',
  },
});

createStudioRemoteConfig does the whole rspack setup: Module Federation plugin, swc-loader for TypeScript/JSX, CSS loaders, the shared dependency scope, React 18 version validation, and output to dist/widgets/.

Step 5 — Update package.json

Add the build script:

JSON
{
  "scripts": {
    "build": "tsup && pnpm build:studio",
    "build:studio": "rspack build --config rspack.studio.config.mjs"
  },
  "devDependencies": {
    "@rspack/cli": "^1.0.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "@types/react": "^18.3.0",
    "@types/react-dom": "^18.3.0"
  }
}

Critical:

  • Pin React to ^18.3.1. React 19 breaks Module Federation with the Studio host. createStudioRemoteConfig validates this at build time and throws a clear error.
  • @rspack/cli is a dev dependency — it's the build tool for Studio bundles.

Install:

Bash
pnpm install

Step 6 — Build the page

Bash
pnpm build

Or just the Studio part:

Bash
pnpm build:studio

Output lands in dist/widgets/:

dist/widgets/
├── remoteEntry.js
├── __federation_expose_HelloOverview-<hash>.js
├── ... chunks

The remoteEntry.js is the Module Federation entry that Studio loads; the chunks are your page bundle and its code-split pieces.

If the plugin isn't already linked:

Bash
pnpm kb marketplace link /path/to/plugins/hello-plugin
pnpm kb marketplace clear-cache

Restart the REST API (so the new studio block in the manifest is picked up) and hard-reload Studio in your browser:

Bash
kb-dev restart rest

Module Federation remotes are cached aggressively in the browser. A normal reload may not pick up the new bundle; use a hard reload (Cmd+Shift+R on Mac, Ctrl+Shift+R on Windows/Linux) or clear the Studio cache.

Step 8 — See it in Studio

Open http://localhost:3000 and look at the sidebar. You should see a new entry called "Hello" with the smile icon. Click it — the page mounts at /p/hello and renders your component.

Click "Say Hi" and the page calls your plugin's REST endpoint. The response shows up in the card below.

Troubleshooting

Plugin built but the page doesn't appear in Studio.

  1. Check dist/widgets/remoteEntry.js exists.
  2. Verify the plugin is in .kb/marketplace.lock (pnpm kb marketplace list).
  3. Clear the registry cache: pnpm kb marketplace clear-cache.
  4. Hard-reload Studio in the browser.
  5. Check Studio's browser console for Module Federation loading errors.

"Objects are not valid as a React child" crash when the page mounts.

You bundled React 19. Studio host runs React 18. Pin to ^18.3.1 in your devDependencies, reinstall, rebuild.

"Shared module is not available for eager consumption".

A shared dependency version mismatch. Your plugin bundled a version of a shared dep (like Ant Design) that doesn't match the host's. Pin shared deps to the versions in STUDIO_SHARED_DEPS — see Plugins → Studio Pages for the list.

"Cannot find module '@kb-labs/sdk/studio'".

Your tsconfig.json moduleResolution needs to be "bundler" or "node16" to support subpath exports. Older values ("node", "classic") don't understand the modern exports field.

Page renders but REST calls fail with 401.

The gateway isn't routing Studio auth properly. Make sure Studio is configured to use the gateway's address (http://localhost:4000 by default), not the REST API directly.

What you CAN'T do

The golden rule Callout lists everything you must not import. Common mistakes:

  • import { Button } from 'antd' — use @kb-labs/sdk/studio.
  • import { useNavigate } from 'react-router-dom' — use useNavigation from @kb-labs/sdk/studio.
  • import { useQuery } from '@tanstack/react-query' — use useData from @kb-labs/sdk/studio.
  • import { useData } from '@kb-labs/studio-hooks' — go through the SDK.
  • import { createStudioRemoteConfig } from '@kb-labs/studio-plugin-tools' — use @kb-labs/sdk/studio-build.

If you need a component that isn't in the UIKit re-exports, file an issue against UIKit. Don't reach into antd directly.

Adding a Studio Page — KB Labs Docs