원클릭으로
pimcore-studio-ui-using-sdk-in-bundles
How bundles consume the Pimcore Studio UI SDK - plugins, modules, DI, registries, and imports
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
How bundles consume the Pimcore Studio UI SDK - plugins, modules, DI, registries, and imports
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
| name | pimcore-studio-ui-using-sdk-in-bundles |
| description | How bundles consume the Pimcore Studio UI SDK - plugins, modules, DI, registries, and imports |
| metadata | {"audience":"pimcore-developers","focus":"sdk-architecture"} |
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)Use when creating a new color theme or token set for Pimcore Studio UI (a DynamicTypeThemeAbstract subclass registered in the ThemeRegistry), including choosing an extends chain, filling AntD color tokens, seeding the boot palette, and picking colors that meet WCAG AAA contrast
Building filters in Pimcore Studio UI - the declarative defineFilter framework for filter panels and sidebars (createFiltersStore, FiltersRenderer, useFilterQuery) and custom field-filter dynamic types for per-column listing filters (DynamicTypeFieldFilterAbstract, DynamicTypeFieldFilterRegistry). Use this whenever building, customizing, or extending any kind of filtering, filter panel, filter sidebar, or search/filter UI, or adding a new filter type to a listing or data view - even when the user only says "add filtering", "let users filter by X", "filter this list", or "build a filter sidebar".
Building listings in Pimcore Studio UI using the ListingBuilder decorator pattern - sorting, paging, filtering, inline editing, and custom decorators
UX and UI design conventions for Pimcore Studio - layout, spacing, action labels, writing style, and design principles for consistent extensions
Pimcore Studio migration and review checklist — 34-point verification checklist, anti-patterns to avoid, and reference base classes for validating Studio code completeness
Pimcore Studio PHP code style rules — strict types, class modifiers, final/readonly patterns, formatting (120 chars), named arguments, imports, PHPDoc, @throws documentation, and constructor promotion