一键导入
pimcore-studio-ui-error-handling
Error handling patterns in Pimcore Studio UI - trackError, ApiError, GeneralError, ErrorBoundary, and proper error flow
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Error handling patterns in Pimcore Studio UI - trackError, ApiError, GeneralError, ErrorBoundary, and proper error flow
用 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-error-handling |
| description | Error handling patterns in Pimcore Studio UI - trackError, ApiError, GeneralError, ErrorBoundary, and proper error flow |
| metadata | {"audience":"pimcore-developers","focus":"error-handling"} |
Error handling in Pimcore Studio UI: trackError for centralized reporting, ApiError/GeneralError wrappers, ErrorBoundary for render errors, and the standard reactive pattern for RTK Query.
BEFORE writing any import, read CRITICAL-IMPORT-PATHS.md.
All examples below use bundle imports (@pimcore/studio-ui-bundle/*). For core development (@sdk/*, @Pimcore/*), see the referenced file.
import { trackError, ApiError, GeneralError, isApiErrorData, ErrorBoundary } from '@pimcore/studio-ui-bundle/modules/app'
| Symbol | Purpose |
|---|---|
trackError(provider, handler?) | Centralized error display. Deduplicates errors within the same execution cycle. |
ApiError(error) | Wraps RTK Query / structured API errors. Extracts errorKey and renders localized messages via error.${errorKey} translation keys. |
GeneralError(message) | Wraps simple string errors. Throws after display — stops execution. |
isApiErrorData(unknown) | Type guard for unknown error shapes. Returns true if object matches API error structure. |
ErrorBoundary | React component that catches render errors. Optional fallback prop. |
ApiError or GeneralErrorErrorModalServicetrackError(new GeneralError(...)) does not execute| Scenario | Use |
|---|---|
| RTK Query error from a query or mutation | ApiError |
| Axios/fetch error with structured response data | ApiError({ data: error.response.data }) |
| Network failure / no response | GeneralError |
| Validation failure in client code | GeneralError |
| Missing required configuration / unexpected state | GeneralError |
This is the only correct way to handle RTK Query errors in Pimcore Studio. Use the error state from the hook with useEffect and trackError.
import { useAssetUpdateMutation } from '@pimcore/studio-ui-bundle/api/asset'
import { trackError, ApiError } from '@pimcore/studio-ui-bundle/modules/app'
import { useMessage } from '@pimcore/studio-ui-bundle/components'
import { useTranslation } from 'react-i18next'
import { useEffect } from 'react'
import { isNil } from 'lodash'
const AssetEditor = ({ asset }: { asset: Asset }): React.JSX.Element => {
const { t } = useTranslation()
const messageApi = useMessage()
const [updateAsset, { data, error }] = useAssetUpdateMutation()
// Track errors
useEffect(() => {
if (!isNil(error)) {
trackError(new ApiError(error))
}
}, [error])
// Handle success separately
useEffect(() => {
if (!isNil(data)) {
void messageApi.success(t('save-success'))
}
}, [data])
const handleSave = (values: FormValues): void => {
updateAsset({ id: asset.id, body: values })
}
return <Form onFinish={ handleSave }>{/* fields */}</Form>
}
Why this pattern:
trackError deduplicates and provides consistent UXUse a separate useEffect per mutation error. Combining them into one effect causes stale closures and missed updates.
const [createAsset, { error: createError }] = useAssetCreateMutation()
const [updateAsset, { error: updateError }] = useAssetUpdateMutation()
useEffect(() => {
if (!isNil(createError)) trackError(new ApiError(createError))
}, [createError])
useEffect(() => {
if (!isNil(updateError)) trackError(new ApiError(updateError))
}, [updateError])
When the API returns:
{
"errorKey": "asset.not-found",
"message": "Asset with ID 123 was not found"
}
ApiError looks up the translation key error.asset.not-found. If the translation exists, it displays the localized message. Otherwise, it falls back to the raw message from the response.
Use GeneralError when there is no API response — validation failures, missing config, unexpected state. Remember it throws after display.
import { trackError, GeneralError } from '@pimcore/studio-ui-bundle/modules/app'
import { isNil } from 'lodash'
const processElement = (element: Element | undefined): void => {
if (isNil(element)) {
trackError(new GeneralError('Cannot process: element is undefined'))
// throws — code below is unreachable
}
element.update()
}
Outside RTK Query, convert errors manually. Use ApiError when you have a structured response, GeneralError for network failures.
import { trackError, ApiError, GeneralError } from '@pimcore/studio-ui-bundle/modules/app'
import { isNil } from 'lodash'
const uploadFile = async (url: string, data: FormData): Promise<void> => {
try {
await axios.post(url, data)
} catch (error: any) {
if (!isNil(error.response)) {
trackError(new ApiError({ data: error.response.data }))
} else {
trackError(new GeneralError('Network error'))
}
}
}
Catches React render errors and prevents the entire app from crashing.
import { ErrorBoundary } from '@pimcore/studio-ui-bundle/modules/app'
<ErrorBoundary fallback={ <div>Something went wrong</div> }>
<MyComponent />
</ErrorBoundary>
Important: The widget container wraps every widget in an ErrorBoundary automatically. Do not add an ErrorBoundary around your widget root. Add your own only for isolated component trees that should fail independently (e.g., a third-party chart, a complex feature panel).
Use when you receive an error from a non-RTK-Query source and need to determine its shape.
import { trackError, ApiError, GeneralError, isApiErrorData } from '@pimcore/studio-ui-bundle/modules/app'
const handleExternalError = (error: unknown): void => {
if (isApiErrorData(error)) {
trackError(new ApiError(error))
} else if (error instanceof Error) {
trackError(new GeneralError(error.message))
} else {
trackError(new GeneralError('An unknown error occurred'))
}
}
The .unwrap() pattern from generic Redux Toolkit usage is wrong in Pimcore Studio.
// ❌ WRONG
const handleSave = async (): Promise<void> => {
try {
await updateAsset({ id, body }).unwrap()
message.success('Saved')
} catch (error) {
message.error('Save failed')
}
}
Problems:
trackError — no centralized error handling, no error modalAlways use the useEffect + error state pattern shown above.
trackError already shows the error modal. Do not also show a toast for the same error.
// ❌ WRONG — duplicate display
useEffect(() => {
if (!isNil(error)) {
trackError(new ApiError(error))
messageApi.error('Operation failed') // redundant!
}
}, [error])
// ✅ CORRECT
useEffect(() => {
if (!isNil(error)) {
trackError(new ApiError(error))
}
}, [error])
// ❌ WRONG — error could be undefined on mount
useEffect(() => {
trackError(new ApiError(error))
}, [error])
// ✅ CORRECT
useEffect(() => {
if (!isNil(error)) {
trackError(new ApiError(error))
}
}, [error])
// ❌ WRONG — user never sees this
console.error('API call failed:', error)
// ✅ CORRECT
trackError(new ApiError(error))
// ❌ WRONG — stale closures, missed updates
useEffect(() => {
if (!isNil(createError)) trackError(new ApiError(createError))
if (!isNil(updateError)) trackError(new ApiError(updateError))
}, [createError, updateError])
// ✅ CORRECT — separate useEffect per error
useEffect(() => {
if (!isNil(createError)) trackError(new ApiError(createError))
}, [createError])
useEffect(() => {
if (!isNil(updateError)) trackError(new ApiError(updateError))
}, [updateError])
// ❌ WRONG — widget container already provides ErrorBoundary
<ErrorBoundary fallback={ <div>Error</div> }>
<MyWidget />
</ErrorBoundary>
// ✅ CORRECT — just render the widget
<MyWidget />