AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Pimcore Studio

skill-cors-gmbh-pimcore-skills-pimcore-studio · by cors-gmbh

Build Pimcore Studio v2 features - React/TypeScript, Ant Design, plugins, modules, dynamic types, DI container, widgets

No reviews yet
0 installs
0 views
view→install

Install

$ agentstack add skill-cors-gmbh-pimcore-skills-pimcore-studio

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-cors-gmbh-pimcore-skills-pimcore-studio)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
5mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Pimcore Studio? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Pimcore Studio v2 Development

You are helping build features for Pimcore Studio v2 (React/TypeScript). Before writing code, load context about the architecture and patterns.

Tech Stack

Pimcore Studio v2 is built with:

  • React 18 — UI components
  • TypeScript — Type safety
  • Ant Design (antd) — UI component library (Form, Table, Modal, Select, Input, Button, Tabs, Tree, etc.)
  • InversifyJS — Dependency injection container
  • react-i18next — Internationalization (useTranslation() hook)
  • Rsbuild — Build tool with Module Federation for plugin loading

Discovering Pimcore Studio Source

When unsure how to do something, read the Studio source code:

vendor/pimcore/studio-ui-bundle/assets/js/src/
├── core/
│   ├── app/
│   │   ├── depency-injection/     # DI container setup (InversifyJS)
│   │   ├── module-system/         # Module registration system
│   │   ├── plugin-system/         # Plugin lifecycle (IAbstractPlugin)
│   │   ├── config/services/       # Service IDs (serviceIds constant)
│   │   ├── i18n/                  # Translation setup (react-i18next)
│   │   ├── api/                   # API utilities
│   │   └── public-api/            # Public API gateway, element API
│   ├── components/                # Pimcore UI components (built on antd)
│   │   ├── form/                  # Form controls
│   │   ├── sidebar/               # Sidebar components
│   │   ├── split/                 # Split layouts
│   │   └── ...
│   └── utils/                     # Utility hooks and helpers
│       ├── hooks/                 # useDebounce, useClickOutside, etc.
│       └── ...
└── modules/                       # Pimcore feature modules
    ├── element/                   # Element handling, DynamicType registries
    ├── widget-manager/            # Widget registry
    └── ...

Search for examples:

grep -r "registerWidget\|registerDynamicType\|IAbstractPlugin" \
  vendor/pimcore/studio-ui-bundle/assets/js/src/

Step 1: Plugin Architecture

Plugin Lifecycle

import { type IAbstractPlugin, container } from '@pimcore/studio-ui-bundle'

const plugin: IAbstractPlugin = {
  name: 'my-plugin',

  onInit() {
    // Phase 1: Runs first, before any modules
    // - Container bindings (InversifyJS)
    // - Dynamic type registration
    // - Registry creation
  },

  onStartup({ moduleSystem }) {
    // Phase 2: Runs after ALL plugins' onInit()
    // - Module registration
    // - Widget registration
    // - Can safely use container bindings from other plugins
  }
}

export default plugin

Order matters: All onInit() calls complete before any onStartup(). Bindings must happen in onInit().

Module System

Modules encapsulate feature registrations:

import { type AbstractModule, container } from '@pimcore/studio-ui-bundle'

export const MyModule: AbstractModule = {
  onInit(): void {
    // Register services, form builders, extensions
  }
}

// In plugin's onStartup():
moduleSystem.registerModule(MyModule)

DI Container (InversifyJS)

import { container } from '@pimcore/studio-ui-bundle'

// Bind
container.bind('MyServiceId').to(MyClass).inSingletonScope()
container.bind('MyServiceId').toConstantValue(myInstance)

// Retrieve
const service = container.get('MyServiceId')

Step 2: Core Studio v2 Services

Widget Registry

import { serviceIds } from '@pimcore/studio-ui-bundle/app'
import { type WidgetRegistry } from '@pimcore/studio-ui-bundle/modules/widget-manager'

const widgetRegistry = container.get(serviceIds['WidgetRegistry'])
widgetRegistry.registerWidget({ name: 'my.widget', component: MyComponent })

Dynamic Type Registry

import { DynamicTypeObjectDataRegistry } from '@pimcore/studio-ui-bundle/modules/element'

const registry = container.get(
  serviceIds['DynamicTypes/ObjectDataRegistry']
)
registry.registerDynamicType(new MyDynamicType())

Step 3: Dynamic Types (Pimcore Data Object Fields)

For custom field types in the class definition editor:

import {
  DynamicTypeObjectDataAbstractSelect,
  DynamicTypeObjectDataAbstractMultiSelect,
  DynamicTypeObjectDataAbstract,
  DynamicTypeFieldFilterMultiselect,
} from '@pimcore/studio-ui-bundle/modules/element'

// Single select
export class DynamicTypeMyField extends DynamicTypeObjectDataAbstractSelect {
  readonly id = 'myFieldType'  // MUST match PHP CoreExtension $fieldtype
  readonly dynamicTypeFieldFilterType = new DynamicTypeFieldFilterMultiselect()
}

// Multi-select
export class DynamicTypeMyFieldMulti extends DynamicTypeObjectDataAbstractMultiSelect {
  readonly id = 'myFieldTypeMultiselect'
  readonly dynamicTypeFieldFilterType = new DynamicTypeFieldFilterMultiselect()
}

// Custom rendering
export class DynamicTypeMyCustom extends DynamicTypeObjectDataAbstract {
  readonly id = 'myCustomFieldType'
  getObjectDataComponent(props: any): React.ReactElement {
    return 
  }
}

Register in onInit() — never in onStartup().

Step 4: Building Components with Ant Design

Form Component

import React from 'react'
import { Form, Input, Switch, InputNumber, Select } from 'antd'
import { useTranslation } from 'react-i18next'

interface MyFormProps {
  data: MyEntity
  onChange: (data: MyEntity) => void
}

export const MyForm: React.FC = ({ data, onChange }) => {
  const { t } = useTranslation()

  return (
    
      
         onChange({ ...data, name: e.target.value })} />
      
      
         onChange({ ...data, active: checked })} />
      
    
  )
}

Table/List Component

import React, { useEffect, useState } from 'react'
import { Table, Button, Space, Popconfirm } from 'antd'

export const MyList: React.FC = () => {
  const [data, setData] = useState([])
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    void (async () => {
      try { setData(await myApi.list()) }
      finally { setLoading(false) }
    })()
  }, [])

  return (
     v ? 'Yes' : 'No' },
      { title: 'Actions', render: (_, record) => (
         handleDelete(record.id)}>
          Delete
        
      )},
    ]} />
  )
}

Modal / Dialog

import { Modal, Form, Input } from 'antd'

const [open, setOpen] = useState(false)

 setOpen(false)}>
  
    
  

Step 5: Select Components with Module-Level Caching

ALWAYS use this pattern for any Select that loads options from an API:

import React from 'react'
import { Select, type SelectProps } from 'antd'

let cachedOptions: Array | null = null
let loadPromise: Promise | null = null

const loadOptions = async () => {
  if (cachedOptions) return cachedOptions
  if (loadPromise) return loadPromise
  loadPromise = (async () => {
    try {
      const items = await myApi.list()
      cachedOptions = items.map(i => ({ value: i.id, label: i.name }))
      return cachedOptions
    } finally { loadPromise = null }
  })()
  return loadPromise
}

export const clearCache = () => { cachedOptions = null; loadPromise = null }

export const MySelect: React.FC = (props) => {
  const [options, setOptions] = React.useState(cachedOptions || [])
  const [loading, setLoading] = React.useState(!cachedOptions)

  React.useEffect(() => {
    void (async () => {
      if (!cachedOptions) setLoading(true)
      try { setOptions(await loadOptions()) }
      finally { setLoading(false) }
    })()
  }, [])

  return 
      (opt?.label ?? '').toLowerCase().includes(input.toLowerCase())
    } />
}

Step 6: API Layer

export interface MyEntity {
  id: number
  name: string
  active: boolean
}

export const myApi = {
  async list(): Promise {
    const res = await fetch('/pimcore-studio/api/my-bundle/entities')
    return (await res.json()).data ?? (await res.json())
  },
  async get(id: number): Promise {
    return (await fetch(`/pimcore-studio/api/my-bundle/entities/${id}`)).json()
  },
  async save(entity: Partial): Promise {
    const method = entity.id ? 'PUT' : 'POST'
    const url = entity.id
      ? `/pimcore-studio/api/my-bundle/entities/${entity.id}`
      : '/pimcore-studio/api/my-bundle/entities'
    return (await fetch(url, {
      method, headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(entity)
    })).json()
  },
  async delete(id: number): Promise {
    await fetch(`/pimcore-studio/api/my-bundle/entities/${id}`, { method: 'DELETE' })
  },
}

Step 7: Translations

File: Resources/translations/studio.en.yaml

my_entity: 'My Entity'
my_entity_name: 'Name'

Usage:

import { useTranslation } from 'react-i18next'
const { t } = useTranslation()
t('my_entity_name')

Step 8: Build System

  • Rsbuild with Module Federation for plugin loading
  • npm run build — Production build
  • npm run dev — Development with hot reload

package.json Template

{
  "name": "@my-scope/my-plugin",
  "version": "1.0.0",
  "main": "src/index.ts",
  "dependencies": {
    "@pimcore/studio-ui-bundle": "^1.0.0",
    "antd": "^5.12.0",
    "react": "18.3.x",
    "react-i18next": "^14.0.0"
  }
}

Step 9: Directory Structure

Resources/assets/pimcore-studio/
├── package.json
└── src/
    ├── main.ts                    # Plugin entry (IAbstractPlugin)
    ├── modules/
    │   ├── icon-library/          # Icon registrations
    │   └── {feature}/
    │       ├── api.ts             # API layer
    │       ├── MyComponent.tsx    # React components (using antd)
    │       └── index.ts
    ├── components/                # Shared components
    │   └── MySelect.tsx           # Selects with caching
    └── dynamic-types/             # Pimcore field types
        └── DynamicTypeMyField.tsx

Reference

See .claude/skills/pimcore-studio/patterns.md for complete code templates.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.