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:
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 inexposesin 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 pageid.
See Plugins → Manifest Reference → studio for the full schema.
Step 2 — Create the page directory
mkdir -p src/studio/pagesStep 3 — Write the page component
src/studio/pages/HelloOverview.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:
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:
{
"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.createStudioRemoteConfigvalidates this at build time and throws a clear error. @rspack/cliis a dev dependency — it's the build tool for Studio bundles.
Install:
pnpm installStep 6 — Build the page
pnpm buildOr just the Studio part:
pnpm build:studioOutput lands in dist/widgets/:
dist/widgets/
├── remoteEntry.js
├── __federation_expose_HelloOverview-<hash>.js
├── ... chunksThe remoteEntry.js is the Module Federation entry that Studio loads; the chunks are your page bundle and its code-split pieces.
Step 7 — Link and reload
If the plugin isn't already linked:
pnpm kb marketplace link /path/to/plugins/hello-plugin
pnpm kb marketplace clear-cacheRestart the REST API (so the new studio block in the manifest is picked up) and hard-reload Studio in your browser:
kb-dev restart restModule 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.
- Check
dist/widgets/remoteEntry.jsexists. - Verify the plugin is in
.kb/marketplace.lock(pnpm kb marketplace list). - Clear the registry cache:
pnpm kb marketplace clear-cache. - Hard-reload Studio in the browser.
- 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'— useuseNavigationfrom@kb-labs/sdk/studio. - ❌
import { useQuery } from '@tanstack/react-query'— useuseDatafrom@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.
What to read next
- Plugins → Studio Pages — the full reference including every hook and the complete list of allowed imports.
- Services → Studio — how Studio loads remotes at runtime.
- Plugins → REST Routes — how your pages talk back to the server.