Skip to content

Client UI

Suggest Edits

Client entries use registerPlugin() from @makinbakin/sdk. Keep UI contributions predictable and built from SDK components where practical. Plugin UI should feel like part of Bakin: dense enough for repeated work, accessible, and clear about loading, empty, error, and permission states.

The tested minimal client entry lives at docs/snippets/plugin-basic/client.tsx.

Source: docs/snippets/plugin-basic/client.tsx

import { registerPlugin } from '@makinbakin/sdk'
function DocsBasicPage() {
return <div>Hello from a Bakin plugin.</div>
}
registerPlugin({
id: 'docs-basic',
navItems: [
{
id: 'docs-basic',
label: 'Docs Basic',
icon: 'Puzzle',
href: '/docs-basic',
order: 100,
},
],
routes: {
'/docs-basic': DocsBasicPage,
},
})

Navigation items should be stable and specific to the plugin. Use lucide icon names. Include order only when the plugin has a strong placement requirement.

FieldMeaning
idStable item ID. Prefix with the plugin ID.
labelSidebar label.
iconLucide icon name.
hrefRoute path.
orderOptional sort order. Defaults to 100.
childrenNested nav items.

Use routes for plugin-owned pages. The host catch-all route renders registered plugin routes and passes route params into the component.

registerPlugin({
id: 'docs-basic',
routes: {
'/docs-basic': DocsBasicPage,
'/docs-basic/[id]': DocsBasicDetailPage,
},
})

Patterns support exact paths and dynamic segments in :id, [id], or $id form. If a route is visible in navigation, also declare it in bakin-plugin.json contributes.clientRoutes.

Use page:/... slots when the host already owns the route and the plugin fills that route. Core plugins use this for built-in pages such as Tasks, Assets, Schedule, Team, Models, Health, Workflows, and Memory.

registerPlugin({
id: 'tasks',
slots: {
'page:/tasks': KanbanBoard,
},
})

For a new route owned by your plugin, prefer routes.

Slots let plugins add focused UI to existing Bakin workflows.

SlotUse it for
asset-previewCustom asset card preview content.
asset-detail-modalAsset detail panels.
task-assetsTask drawer asset attachments.
task-sidebarTask-specific side panels.
home-widgetDashboard widgets.
page:/<route>Host-owned page mount.

Register with registerSlot() directly when you need a custom order. Lower order renders first.

Import common UI from @makinbakin/sdk/ui and shared app components from @makinbakin/sdk/components.

import { Button } from '@makinbakin/sdk/ui'
import { PluginHeader } from '@makinbakin/sdk/components'

Custom UI is fine when the domain needs it, but keep Bakin conventions: small radii, clear tables and filters, keyboard-friendly controls, visible empty states, and no layout shift when data loads.

During development, Bakin can unregister and reload client contributions. If a plugin maintains a client-side registry outside registerPlugin(), enroll cleanup with registerPluginCleanup(id, fn).

import { registerPluginCleanup } from '@makinbakin/sdk'
registerPluginCleanup('docs-basic', () => {
// Clear plugin-owned client registries here.
})

Import supported surfaces only:

import { registerPlugin } from '@makinbakin/sdk'
import { Button } from '@makinbakin/sdk/ui'
import type { NavItem } from '@makinbakin/sdk'

Host internals can change without warning. SDK exports are the contract.