skills/pimcore-studio-ui-using-sdk-in-bundles/SKILL.md
How bundles consume the Pimcore Studio UI SDK - plugins, modules, DI, registries, and imports
npx skillsauth add pimcore/skills pimcore-studio-ui-using-sdk-in-bundlesInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Core principles for consuming the Pimcore Studio UI SDK in bundles:
Use this when:
Pimcore Studio uses Webpack Module Federation to enable dynamic loading of bundles:
Key Benefit: Bundles are independently built and loaded, but share common dependencies.
Bundle Build → Entrypoint Registration → Runtime Loading → Plugin Execution → Modules Register → UI Renders
plugins.ts exports are discoveredonInit, onStartup)Plugins are entry points that hook into the application lifecycle:
import { type IAbstractPlugin } from '@pimcore/studio-ui-bundle'
export const MyPlugin: IAbstractPlugin = {
name: 'MyPlugin',
// Phase 1: Service registration (early)
onInit: ({ container }) => {
// Register or override services in DI container
container.rebind(serviceIds['Some/Service'])
.to(MyCustomService)
.inSingletonScope()
},
// Phase 2: Module registration (after services ready)
onStartup: ({ moduleSystem }) => {
// Register modules that will configure the app
moduleSystem.registerModule(MyModule)
}
}
When to use each hook:
onInit: Register NEW services or OVERRIDE existing onesonStartup: Register modules that CONFIGURE existing servicesModules are configuration snippets that run after services are initialized but before React renders:
import { type AbstractModule, container } from '@pimcore/studio-ui-bundle'
import { serviceIds } from '@pimcore/studio-ui-bundle/app'
export const MyModule: AbstractModule = {
onInit: () => {
// Access services from container
const widgetRegistry = container.get<WidgetRegistry>(serviceIds.widgetManager)
// Configure the service
widgetRegistry.registerWidget({
name: 'my-widget',
component: MyWidgetComponent
})
}
}
Common module tasks:
Plugin = "When and how to integrate with Studio lifecycle"
Module = "What to configure once services are ready"
One plugin can register multiple modules - organize by feature or concern.
All services are managed by an IoC (Inversion of Control) container.
import { container } from '@pimcore/studio-ui-bundle'
import { serviceIds } from '@pimcore/studio-ui-bundle/app'
// Get a service
const widgetRegistry = container.get<WidgetRegistry>(
serviceIds.widgetManager
)
import { useInjection } from '@pimcore/studio-ui-bundle/app'
import { serviceIds } from '@pimcore/studio-ui-bundle/app'
const MyComponent = () => {
const widgetManager = useInjection<WidgetManager>(
serviceIds['Widget/WidgetManager']
)
// Use the service
}
Replace core services with custom implementations:
// In plugin's onInit
onInit: ({ container }) => {
container.rebind(serviceIds['Asset/Editor/FolderTabManager'])
.to(CustomFolderTabManager)
.inSingletonScope()
}
Key Principle: All services are registered with string IDs (serviceIds), enabling type-safe access and easy replacement.
BEFORE writing any import, read CRITICAL-IMPORT-PATHS.md.
All examples below use bundle imports (@pimcore/studio-ui-bundle/*). The @sdk/* alias maps to the same code but is only available inside studio-ui-bundle core.
// Base SDK and container
import {
type IAbstractPlugin,
type AbstractModule,
container
} from '@pimcore/studio-ui-bundle'
// DI container, service IDs, hooks, store, router
import {
serviceIds,
useInjection,
store,
router
} from '@pimcore/studio-ui-bundle/app'
// Reusable UI components (based on Ant Design)
import {
Content,
Header,
Sidebar
} from '@pimcore/studio-ui-bundle/components'
// Auto-generated API hooks from OpenAPI
import {
useAssetGetByIdQuery,
useAssetUpdateMutation
} from '@pimcore/studio-ui-bundle/api/asset'
import {
useDataObjectGetByIdQuery
} from '@pimcore/studio-ui-bundle/api/data-object'
// Application modules (asset editor, data object editor, etc.)
import { type AssetEditorTabManager } from '@pimcore/studio-ui-bundle/modules/asset'
import { type WidgetRegistry } from '@pimcore/studio-ui-bundle/modules/widget-manager'
// Helper utilities
import { formatters } from '@pimcore/studio-ui-bundle/utils'
import { generateUuid } from '@pimcore/studio-ui-bundle/utils'
Import Principle: Folder structure mirrors import paths
studio-ui-bundle/assets/js/src/core/app/sdk/api/asset/index.ts@pimcore/studio-ui-bundle/api/asset@sdk/api/assetThe @sdk/* alias (core only) and @pimcore/studio-ui-bundle/* (bundles) both point to the same SDK folder.
Manages React components with two patterns:
1. Single Components (unique, one per slot):
componentRegistry.override('asset-toolbar-actions', MyToolbarComponent)
2. Slots (multiple components with priority):
componentRegistry.registerToSlot({
slot: 'data-object-context-menu',
component: MyMenuItemComponent,
priority: 10
})
Extension points are defined in component-config.ts - these are the available slots.
Manages widgets in 4 areas: main, left, bottom, right
// Register widget
widgetRegistry.registerWidget({
name: 'my-widget',
component: MyWidgetComponent
})
// Open widget programmatically
const widgetManager = useInjection<WidgetManager>(
serviceIds['Widget/WidgetManager']
)
widgetManager.open({
name: 'my-widget',
config: { /* widget config */ }
})
Centralized context menu management:
contextMenuRegistry.registerToSlot({
slot: 'tree-node-context-menu',
component: MyMenuItem,
priority: 100
})
// Or override entire slot provider
contextMenuRegistry.overrideSlotProvider('grid-row-context-menu', MyMenuProvider)
Priority system controls menu item order (higher = appears first).
Abstract base classes for extensible type systems (grid cells, layouts, metadata, filters, etc.).
// Example: Custom grid cell type
@injectable()
class CustomCellType extends DynamicTypeGridCellAbstract {
id = 'custom-cell'
getGridCellComponent(props) {
return <CustomCellComponent {...props} />
}
}
// Register in module
gridCellRegistry.registerDynamicType(container.get('DynamicTypes/GridCell/CustomCell'))
For details, see the pimcore-studio-dynamic-types skill.
// assets/js/src/plugins.ts
import { type IAbstractPlugin } from '@pimcore/studio-ui-bundle'
import { MyModule } from './modules/my-module'
export const MyPlugin: IAbstractPlugin = {
name: 'MyPlugin',
onStartup({ moduleSystem }) {
moduleSystem.registerModule(MyModule)
}
}
// assets/js/src/modules/my-module.tsx
import { type AbstractModule, container } from '@pimcore/studio-ui-bundle'
import { serviceIds } from '@pimcore/studio-ui-bundle/app'
import { type WidgetRegistry } from '@pimcore/studio-ui-bundle/modules/widget-manager'
import { MyWidget } from '../components/my-widget'
export const MyModule: AbstractModule = {
onInit: () => {
const widgetRegistry = container.get<WidgetRegistry>(
serviceIds.widgetManager
)
widgetRegistry.registerWidget({
name: 'my-widget',
component: MyWidget
})
}
}
// assets/js/src/components/my-widget.tsx
import React from 'react'
import { useInjection } from '@pimcore/studio-ui-bundle/app'
import { serviceIds } from '@pimcore/studio-ui-bundle/app'
export const MyWidget = () => {
const someService = useInjection<SomeService>(serviceIds.someService)
return <div>My Widget</div>
}
export const TabExtension: AbstractModule = {
onInit: () => {
const tabManager = container.get<AssetEditorTabManager>(
serviceIds['Asset/Editor/AssetEditorTabManager']
)
tabManager.register({
key: 'my-tab',
label: 'My Tab',
component: MyTabComponent
})
}
}
export const ToolbarExtension: AbstractModule = {
onInit: () => {
const componentRegistry = container.get<ComponentRegistry>(
serviceIds.componentRegistry
)
componentRegistry.registerToSlot({
slot: 'asset-editor-toolbar',
component: MyButton,
priority: 50
})
}
}
export const NavExtension: AbstractModule = {
onInit: () => {
const mainNavRegistry = container.get<MainNavRegistry>(
serviceIds.mainNavRegistry
)
mainNavRegistry.registerMainNavItem({
path: 'My Tool/Sub Section',
widgetConfig: {
name: 'My Tool',
id: 'my-tool',
component: 'my-tool-widget'
}
})
}
}
import { useAssetGetByIdQuery } from '@pimcore/studio-ui-bundle/api/asset'
const MyComponent = ({ assetId }: { assetId: number }) => {
const { data, isLoading, error } = useAssetGetByIdQuery({ id: assetId })
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error</div>
if (!data) return null
return <div>{data.filename}</div>
}
❌ Don't import from internal paths
// BAD
import { Something } from '@pimcore/studio-ui-bundle/src/internal/path'
✅ Only import from public API paths
// GOOD
import { Something } from '@pimcore/studio-ui-bundle/components'
❌ Don't access services outside DI container
// BAD - direct instantiation
const service = new SomeService()
✅ Always get services from container
// GOOD
const service = container.get<SomeService>(serviceIds.someService)
❌ Don't configure in onInit of plugin
// BAD - too early, services might not be ready
onInit: ({ container }) => {
const registry = container.get<Registry>(...)
registry.register(...) // Service might not be fully initialized
}
✅ Configure in modules (via onStartup)
// GOOD - services are ready
onStartup: ({ moduleSystem }) => {
moduleSystem.registerModule(MyConfigModule)
}
@pimcore/studio-ui-bundle (npm)tools
UX and UI design conventions for Pimcore Studio - layout, spacing, action labels, writing style, and design principles for consistent extensions
tools
Widget system in Pimcore Studio UI - registering widgets, opening them in layout areas, WidgetManagerTabConfig, and connecting widgets to navigation
development
TypeScript coding standards and best practices for Pimcore Studio UI - type safety, null checks, and code quality
tools
Adding and customizing editor tabs in Pimcore Studio UI - tab managers, registration, override, permissions, and detachable tabs