ワンクリックで
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 職業分類に基づく
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
| 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)