{"id":551736,"date":"2026-07-31T06:47:08","date_gmt":"2026-07-31T06:47:08","guid":{"rendered":"https:\/\/webkul.com\/blog\/?p=551736"},"modified":"2026-07-31T06:52:41","modified_gmt":"2026-07-31T06:52:41","slug":"pimcore-studio-plugin","status":"publish","type":"post","link":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/","title":{"rendered":"Build a Pimcore Studio Plugin: React Nav + Screen"},"content":{"rendered":"<p>Pimcore 12 (Platform 2026.1) ships Studio, a React single-page app. A <strong>Pimcore Studio Plugin<\/strong> is now a React micro-frontend, not an ExtJS panel.<\/p>\n<p>This guide walks you through building a Pimcore Studio Plugin end to end.<\/p>\n<p>Our goal is simple. Add a nav item and a screen to Studio by shipping a federated React remote the host loads at runtime.<\/p>\n<p>The old admin panel is retired. So if you searched <em>pimcore extjs replacement<\/em> or <em>pimcore studio ui extend<\/em>, this post is your map.<\/p>\n<p>We will cover the PHP entry-point provider, the rsbuild toolchain, and the React registration. Every command, class, and config key here is real.<\/p>\n<p>If you are new to Pimcore setup, start with our guide to the <a href=\"https:\/\/webkul.com\/blog\/setup-for-pimcore-development-environment\/\">Pimcore development environment<\/a>. It covers the base install this plugin sits on.<\/p>\n<figure><img decoding=\"async\" class=\"alignnone size-full wp-image-551754\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp\" alt=\"pimcore-studio-plugin-studio-navitem-1\" width=\"1529\" height=\"757\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1-300x149.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1-1200x594.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1-250x124.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1-768x380.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/figure>\n<h2>Why Studio Plugins Replaced ExtJS, and the Three-Part Architecture<\/h2>\n<p>Pimcore&#8217;s classic admin ran on ExtJS. In Platform 2026.1, that admin is removed. Studio, a React SPA, takes its place.<\/p>\n<p>The whole shell is built on <strong>Module Federation<\/strong>. Studio is the host. Your plugin is a federated remote container it loads and bootstraps.<\/p>\n<p>You no longer register ExtJS classes. You ship a React remote instead \u2014 that remote is your Pimcore Studio Plugin.<\/p>\n<p>A plugin has three moving parts. Learn these once and the rest is mechanical.<\/p>\n<p>First, a PHP seam. A <code>WebpackEntryPointProvider<\/code> service, tagged for Studio, tells the host where your built JS manifest lives on disk.<\/p>\n<p>Second, the build output. rsbuild writes a federated bundle to <code>public\/studio\/build\/&lt;PLUGIN_NAME&gt;\/<\/code> inside your bundle root.<\/p>\n<p>Third, the runtime. The host injects an <code>exposeRemote.js<\/code> bootstrap, registers your remote, then calls your plugin object&#8217;s <code>onInit<\/code> hook.<\/p>\n<p>That hook registers your nav items and widgets into the host&#8217;s dependency-injection container. Nothing lands in a database.<\/p>\n<figure><img decoding=\"async\" class=\"alignnone size-full wp-image-551755\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-architecture-1.webp\" alt=\"pimcore-studio-plugin-architecture-1\" width=\"1529\" height=\"757\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-architecture-1.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-architecture-1-300x149.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-architecture-1-1200x594.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-architecture-1-250x124.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-architecture-1-768x380.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/figure>\n<h2>Scaffold the Pimcore Studio Plugin Directory<\/h2>\n<p>Your frontend lives in an <code>assets\/<\/code> directory inside the bundle. This is the source root for your Pimcore Studio Plugin.<\/p>\n<p>The layout is small and predictable:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">assets\/\n\u251c\u2500\u2500 js\/src\/\n\u2502   \u251c\u2500\u2500 main.ts        # rsbuild entry (intentionally empty)\n\u2502   \u251c\u2500\u2500 plugins.ts     # federation root export\n\u2502   \u251c\u2500\u2500 index.ts       # plugin object definition\n\u2502   \u251c\u2500\u2500 screens\/       # React screen components\n\u2502   \u2514\u2500\u2500 api\/           # fetch clients to your \/api backend\n\u251c\u2500\u2500 rsbuild.config.ts\n\u251c\u2500\u2500 tsconfig.json\n\u2514\u2500\u2500 package.json<\/pre>\n<p>Note that <code>main.ts<\/code> stays empty on purpose. The real surface is the federation <code>exposes<\/code>, not the entry file.<\/p>\n<figure><img decoding=\"async\" class=\"alignnone size-full wp-image-551756\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-bundle-tree-1.webp\" alt=\"pimcore-studio-plugin-bundle-tree-1\" width=\"1529\" height=\"757\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-bundle-tree-1.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-bundle-tree-1-300x149.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-bundle-tree-1-1200x594.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-bundle-tree-1-250x124.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-bundle-tree-1-768x380.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/figure>\n<h2>Pin the Studio SDK in package.json<\/h2>\n<p>The SDK version is the single most important line in your <code>package.json<\/code>. Pin it to the exact host version.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">{\n  &quot;dependencies&quot;: {\n    &quot;@pimcore\/studio-ui-bundle&quot;: &quot;2026.1.3&quot;,\n    &quot;antd&quot;: &quot;^5.22.0&quot;,\n    &quot;i18next&quot;: &quot;^23.16.8&quot;,\n    &quot;react&quot;: &quot;18.3.1&quot;,\n    &quot;react-dom&quot;: &quot;18.3.1&quot;,\n    &quot;react-i18next&quot;: &quot;^14.1.3&quot;,\n    &quot;react-redux&quot;: &quot;^9.1.2&quot;\n  },\n  &quot;scripts&quot;: {\n    &quot;build&quot;: &quot;rsbuild build&quot;,\n    &quot;dev&quot;: &quot;NODE_ENV=development rsbuild build --watch&quot;,\n    &quot;type-check&quot;: &quot;tsc --noEmit&quot;,\n    &quot;test&quot;: &quot;vitest run&quot;\n  }\n}<\/pre>\n<p>Never use <code>^<\/code>, <code>canary<\/code>, or <code>latest<\/code> on the SDK. Version drift causes &#8220;remotes not resolved&#8221; errors or type-check failures.<\/p>\n<p>Verify the host version with a quick composer check. Run <code>composer show pimcore\/studio-ui-bundle<\/code> and match the npm pin to it.<\/p>\n<p>The npm package is types-only. It ships <code>.d.ts<\/code> files for TypeScript, but no runtime <code>.js<\/code>. The real code comes from the running host.<\/p>\n<h2>Configure rsbuild and Module Federation<\/h2>\n<p>The <code>rsbuild.config.ts<\/code> file wires up Module Federation and the output path. Two constants act as the single source of truth.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">const PLUGIN_NAME = &#039;product_enrichment&#039;\nconst BUNDLE_ASSET_NAME = &#039;productenrichmentagent&#039;\n\nexport default defineConfig({\n    plugins: [\n        pluginReact(),\n        pluginModuleFederation({\n            name: PLUGIN_NAME,\n            filename: &#039;remoteEntry.js&#039;,\n            exposes: { &#039;.&#039;: &#039;.\/js\/src\/plugins.ts&#039; },\n            remotes: {\n                &#039;@pimcore\/studio-ui-bundle&#039;: createDynamicRemote(&#039;pimcore_studio_ui_bundle&#039;),\n            },\n            shared: {\n                react: { singleton: true, requiredVersion: false },\n                &#039;react-dom&#039;: { singleton: true, requiredVersion: false },\n                antd: { singleton: true, requiredVersion: false },\n                i18next: { singleton: true, requiredVersion: false },\n                &#039;react-i18next&#039;: { singleton: true, requiredVersion: false },\n                &#039;react-redux&#039;: { singleton: true, requiredVersion: false },\n            },\n        }),\n        pluginGenerateEntrypoints(),\n    ],\n    source: { entry: { main: &#039;.\/js\/src\/main.ts&#039; } },\n    output: {\n        distPath: { root: &#039;..\/public\/studio\/build\/&#039; + PLUGIN_NAME },\n        assetPrefix: &#039;\/bundles\/&#039; + BUNDLE_ASSET_NAME + &#039;\/studio\/build\/&#039; + PLUGIN_NAME + &#039;\/&#039;,\n        cleanDistPath: true,\n        manifest: true,\n        filename: { js: &#039;[name].js&#039; },\n    },\n})<\/pre>\n<p>The <code>remotes<\/code> mapping externalizes the SDK. Without it, the build fails with <code>Package subpath '.\/app' is not defined by \"exports\"<\/code>.<\/p>\n<p>That <code>distPath<\/code> escapes <code>assets\/<\/code> into the bundle&#8217;s <code>public\/<\/code> folder. This placement is what lets <code>assets:install<\/code> publish it later.<\/p>\n<p>Your <code>tsconfig.json<\/code> needs <code>skipLibCheck: true<\/code>. The SDK&#8217;s type tree references many packages, and checking all of them drowns you in false positives.<\/p>\n<h2>Register the Bundle Before You Wire Studio<\/h2>\n<p>None of the PHP seam matters until Symfony knows the bundle exists. Register and install it first.<\/p>\n<p>Add the bundle to <code>config\/bundles.php<\/code> so the kernel loads it:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">Webkul\\ProductEnrichmentAgentBundle\\ProductEnrichmentAgentBundle::class =&gt; [&#039;all&#039; =&gt; true],<\/pre>\n<p>Then install it and warm the cache:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">bin\/console cache:clear\nbin\/console pimcore:bundle:install ProductEnrichmentAgentBundle<\/pre>\n<p>Skip this and the provider, <code>services.yaml<\/code>, and controllers never load. Your nav will never appear, no matter how clean the frontend build is.<\/p>\n<h2>Register the Entry Point in PHP<\/h2>\n<p>Now the PHP seam. This class tells Studio where your Pimcore Studio Plugin manifest sits on disk.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">namespace Webkul\\ProductEnrichmentAgentBundle\\Webpack;\n\nuse Pimcore\\Bundle\\StudioUiBundle\\Webpack\\WebpackEntryPointProviderInterface;\n\nclass WebpackEntryPointProvider implements WebpackEntryPointProviderInterface\n{\n    public function getEntryPointsJsonLocations(): array\n    {\n        return glob(__DIR__ . &#039;\/..\/..\/public\/studio\/build\/*\/entrypoints.json&#039;) ?: [];\n    }\n\n    public function getEntryPoints(): array\n    {\n        return $this-&gt;getEntryPointsJsonLocations() === [] ? [] : [&#039;exposeRemote&#039;];\n    }\n\n    public function getOptionalEntryPoints(): array\n    {\n        return [];\n    }\n}<\/pre>\n<p>The <code>glob()<\/code> uses a wildcard directory, so renaming the plugin never breaks the lookup. That <code>?: []<\/code> guard returns an empty array before your first build.<\/p>\n<p>Note <code>getEntryPoints()<\/code> returns <code>['exposeRemote']<\/code>, not <code>main<\/code>. That named entry is the Module Federation bootstrap the host injects.<\/p>\n<p>Wrap the whole class in <code>if (interface_exists(WebpackEntryPointProviderInterface::class)) { \u2026 }<\/code>. This lets the bundle boot headless for CLI and jobs when Studio is absent.<\/p>\n<p>Then tag the service so the host discovers it. Add these three lines to your <code>services.yaml<\/code>:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">Webkul\\ProductEnrichmentAgentBundle\\Webpack\\WebpackEntryPointProvider:\n    tags:\n        - { name: &#039;pimcore_studio_ui.webpack_entry_point_provider&#039; }<\/pre>\n<p>The tag string is exactly <code>pimcore_studio_ui.webpack_entry_point_provider<\/code>. No alias, no id.<\/p>\n<h2>Write the JS Entry and the Plugin Object<\/h2>\n<p>The federation root is <code>plugins.ts<\/code>. It re-exports the plugin object the host imports from <code>'.'<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">import { productEnrichmentAgentPlugin } from &#039;.\/index&#039;\nexport { productEnrichmentAgentPlugin }<\/pre>\n<p>The plugin object itself lives in <code>index.ts<\/code>. Its <code>onInit<\/code> hook receives the host&#8217;s container and registers everything. Start with the imports and the local types, so the snippet compiles as shown:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">import type { ComponentType } from &#039;react&#039;\nimport type { Container } from &#039;inversify&#039;\nimport { serviceIds } from &#039;@pimcore\/studio-ui-bundle\/app&#039;\nimport { CredentialConfig } from &#039;.\/screens\/CredentialConfig&#039;\nimport { ProductEnrichmentIcon, PRODUCT_ENRICHMENT_ICON } from &#039;.\/components\/ProductEnrichmentIcon&#039;\nimport { PE_NAV_PERMISSION } from &#039;.\/nav\/permissions&#039;\nimport { WIDGET_CREDENTIALS } from &#039;.\/components\/widgetIds&#039;\n\ninterface MainNavRegistryLike {\n    registerMainNavItem: (item: Record&lt;string, unknown&gt;) =&gt; void\n}\ninterface WidgetRegistryLike {\n    registerWidget: (widget: { name: string; component: ComponentType }) =&gt; void\n}<\/pre>\n<p>With the types in place, the plugin object is a small, declarative shape:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">export const productEnrichmentAgentPlugin = {\n    name: &#039;product-enrichment-agent&#039;,\n    priority: 70,\n\n    onInit: ({ container }: { container: Container }): void =&gt; {\n        try {\n            const mainNav = container.get&lt;MainNavRegistryLike&gt;(serviceIds.mainNavRegistry)\n            const widgets = container.get&lt;WidgetRegistryLike&gt;(serviceIds.widgetManager)\n\n            widgets.registerWidget({ name: WIDGET_CREDENTIALS, component: CredentialConfig })\n\n            mainNav.registerMainNavItem({\n                path: &#039;Product Enrichment&#039;,\n                label: &#039;Product Enrichment&#039;,\n                icon: PRODUCT_ENRICHMENT_ICON,\n                order: 9999,\n            })\n\n            mainNav.registerMainNavItem({\n                path: &#039;Product Enrichment\/Provider &amp; Credential Config&#039;,\n                label: &#039;Provider &amp; Credential Config&#039;,\n                icon: &#039;key&#039;,\n                order: 10,\n                permission: PE_NAV_PERMISSION.credentialsView,\n                id: WIDGET_CREDENTIALS,\n                widgetConfig: {\n                    type: &#039;tab&#039;,\n                    name: &#039;Provider &amp; Credential Config&#039;,\n                    component: WIDGET_CREDENTIALS,\n                    id: WIDGET_CREDENTIALS,\n                    config: { translationKey: &#039;Provider &amp; Credential Config&#039; },\n                },\n            })\n        } catch (err) {\n            console.error(&#039;[ProductEnrichment] failed to register plugin:&#039;, err)\n        }\n    },\n\n    onStartup: (): void =&gt; {},\n}<\/pre>\n<p>The host hands you the <code>container<\/code>. You resolve services with <code>container.get(serviceIds.mainNavRegistry)<\/code> and friends.<\/p>\n<p>Always import IDs from <code>serviceIds<\/code>. Never invent a string like <code>'MainNavRegistry'<\/code>.<\/p>\n<p>Register parent folders first, then leaves. A folder has no <code>widgetConfig<\/code>; a leaf does. The leaf&#8217;s <code>component<\/code> is a string widget name, bound separately by <code>registerWidget<\/code>.<\/p>\n<p>A leaf <code>icon:<\/code> must be an existing host icon name, or it renders blank. Built-in names like <code>'key'<\/code> resolve; an unknown string silently produces an empty glyph.<\/p>\n<p>Keep the whole body in a <code>try\/catch<\/code> with a prefixed log. If <code>onInit<\/code> throws, the host silently drops your entire nav, and this log is your only breadcrumb.<\/p>\n<figure><img decoding=\"async\" class=\"alignnone size-full wp-image-551758\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-2.webp\" alt=\"pimcore-studio-plugin-studio-navitem-2\" width=\"1529\" height=\"757\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-2.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-2-300x149.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-2-1200x594.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-2-250x124.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-2-768x380.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/figure>\n<h2>Build a React Screen and Mount It<\/h2>\n<p>A screen is a plain React component. It uses SDK components and talks to your bundle&#8217;s own <code>\/api<\/code> backend.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">import React, { useCallback, useEffect, useState } from &#039;react&#039;\nimport { useTranslation } from &#039;@pimcore\/studio-ui-bundle\/app&#039;\nimport { Content, Flex, Text, Title } from &#039;@pimcore\/studio-ui-bundle\/components&#039;\nimport { listProfiles, type ProfileRow } from &#039;..\/api\/profiles&#039;\n\nexport const Profiles = (): React.ReactElement =&gt; {\n    const { t } = useTranslation()\n    const [rows, setRows] = useState&lt;ProfileRow[]&gt;([])\n    const [loading, setLoading] = useState&lt;boolean&gt;(true)\n    const [error, setError] = useState&lt;string | null&gt;(null)\n\n    const reload = useCallback(async (): Promise&lt;void&gt; =&gt; {\n        setLoading(true); setError(null)\n        try {\n            setRows(await listProfiles())\n        } catch (e) {\n            setError(e instanceof Error ? e.message : String(e))\n        } finally {\n            setLoading(false)\n        }\n    }, [])\n\n    useEffect(() =&gt; { void reload() }, [reload])\n\n    return (\n        &lt;Content padded loading={loading}&gt;\n            &lt;Flex align=&quot;center&quot; justify=&quot;space-between&quot;&gt;\n                &lt;Title level={4}&gt;{t(&#039;Enrichment Profiles&#039;)}&lt;\/Title&gt;\n            &lt;\/Flex&gt;\n            {error !== null &amp;&amp; &lt;Text type=&quot;danger&quot;&gt;{error}&lt;\/Text&gt;}\n            {!loading &amp;&amp; rows.length === 0 &amp;&amp; &lt;Text type=&quot;secondary&quot;&gt;{t(&#039;No profiles yet.&#039;)}&lt;\/Text&gt;}\n        &lt;\/Content&gt;\n    )\n}<\/pre>\n<p>Import UI parts from <code>@pimcore\/studio-ui-bundle\/components<\/code>. Never import raw <code>antd<\/code>. The SDK wraps antd with the host&#8217;s theme and i18n.<\/p>\n<p>Your fetch client lives in <code>api\/<\/code>. It carries the Studio session cookie and throws on failure.<\/p>\n<p>The bundle ships the matching <code>ProfileController<\/code>, mounted at <code>\/api\/product-enrichment-agent\/profiles<\/code>, so this endpoint is real, not assumed:<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">export const PROFILE_BASE = &#039;\/api\/product-enrichment-agent&#039;\n\nasync function getJson&lt;T&gt;(path: string, fallback: string): Promise&lt;T&gt; {\n    const res = await fetch(PROFILE_BASE + path, {\n        method: &#039;GET&#039;,\n        credentials: &#039;include&#039;,\n        headers: { Accept: &#039;application\/json&#039; },\n    })\n    const result = await res.json()\n    if (!result.success || result.data === undefined) {\n        throw new Error(result.error ?? fallback)\n    }\n    return result.data\n}\n\nexport async function listProfiles(): Promise&lt;ProfileRow[]&gt; {\n    const data = await getJson&lt;{ profiles?: ProfileRow[] }&gt;(&#039;\/profiles&#039;, &#039;Failed to load profiles.&#039;)\n    return data.profiles ?? []\n}<\/pre>\n<p>That <code>credentials: 'include'<\/code> line is critical. It carries the session cookie so backend auth works. Let the screen&#8217;s <code>try\/catch<\/code> render the error state.<\/p>\n<figure><img decoding=\"async\" class=\"alignnone size-full wp-image-551761\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-plugin-screen-1.webp\" alt=\"pimcore-studio-plugin-plugin-screen-1\" width=\"1529\" height=\"757\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-plugin-screen-1.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-plugin-screen-1-300x149.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-plugin-screen-1-1200x594.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-plugin-screen-1-250x124.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-plugin-screen-1-768x380.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/figure>\n<h2>The Rules-of-Hooks Trap That Hides Your Menu<\/h2>\n<p>This gotcha deserves its own section. It is the single most common Studio plugin killer.<\/p>\n<p>A hook placed after a conditional <code>return<\/code> compiles fine under SWC. On the second render it throws <code>Rendered more hooks than during the previous render<\/code>.<\/p>\n<p>The host catches that error and swallows it. Your entire menu then vanishes with no error on first paint.<\/p>\n<p>The fix is a rule, not a workaround. Every <code>useState<\/code>, <code>useEffect<\/code>, <code>useRef<\/code>, and <code>useMemo<\/code> must sit above any conditional return.<\/p>\n<p>So never write <code>if (loading) return &lt;Spinner\/&gt;<\/code> before your hooks. Instead, render async states as JSX and gate them with <code>&lt;Content loading={loading}&gt;<\/code>.<\/p>\n<p>Watch for identifier shadowing too. A <code>useState<\/code> setter shadowed by a later <code>const<\/code> binding fails silently, and state updates stop working.<\/p>\n<h2>Build and Load Your Pimcore Studio Plugin<\/h2>\n<p>The build loop is a short, ordered sequence. Do the steps in order.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">cd assets\nnpm install\nnpm run type-check\nnpm run build\nbin\/console cache:clear\nbin\/console assets:install public --symlink --relative<\/pre>\n<p>The <code>type-check<\/code> step matters because rsbuild&#8217;s SWC is more permissive than <code>tsc<\/code>. Real type errors slip through the build otherwise.<\/p>\n<p><code>npm run build<\/code> writes to <code>..\/public\/studio\/build\/product_enrichment\/<\/code>. It produces <code>entrypoints.json<\/code>, <code>remoteEntry.js<\/code>, and the hashed JS chunks.<\/p>\n<p>Then <code>assets:install<\/code> symlinks that output under <code>public\/bundles\/productenrichmentagent\/<\/code>. Do not run it without building first; it only symlinks, it does not compile.<\/p>\n<p>For live work, run the watch loop with <code>npm run dev<\/code>. It rebuilds on every source change into the same output directory.<\/p>\n<p>Studio must actually be served and running for the remote to load. Open the Studio URL for your install, then do a hard browser refresh.<\/p>\n<p><code>cache:clear<\/code> never touches static JS, and the browser caches <code>remoteEntry.js<\/code> aggressively.<\/p>\n<figure><img decoding=\"async\" class=\"alignnone size-full wp-image-551763\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-build-terminal-1.webp\" alt=\"pimcore-studio-plugin-build-terminal-1\" width=\"1529\" height=\"757\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-build-terminal-1.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-build-terminal-1-300x149.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-build-terminal-1-1200x594.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-build-terminal-1-250x124.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-build-terminal-1-768x380.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/figure>\n<h2>Gotchas Every Pimcore Studio Plugin Developer Hits<\/h2>\n<p>A few traps recur across every <strong>Pimcore Studio Plugin<\/strong>. Learn them before they cost you an afternoon.<\/p>\n<p><strong>Version pin.<\/strong> The npm SDK must equal the composer host version. Drift breaks federation at runtime or types at build.<\/p>\n<p><strong>Shared singletons.<\/strong> react, react-dom, antd, i18next, react-i18next, and react-redux are all <code>singleton: true<\/code>. That unifies hooks, theme, and the Redux store with the host.<\/p>\n<p><strong>Container ownership.<\/strong> When building in Docker, pass <code>-u \"$(id -u):$(id -g)\"<\/code> and <code>-e HOME=\/tmp<\/code>. Otherwise root owns your <code>node_modules<\/code> and build output.<\/p>\n<p><strong>Mount the bundle root.<\/strong> The output path escapes <code>assets\/<\/code>, so mount the whole bundle, not just <code>assets\/<\/code>. Otherwise the host never sees the build.<\/p>\n<p><strong>ACL is backend-owned.<\/strong> A frontend <code>can(permission)<\/code> check is cosmetic. It disables a button but never blocks the API. The controller&#8217;s <code>#[IsGranted(...)]<\/code> is the real authority.<\/p>\n<p><strong>Translations are automatic.<\/strong> Use <code>useTranslation()<\/code> and English string keys. The shared i18n singleton auto-registers new keys in the host translation admin.<\/p>\n<p><strong>Icons need currentColor.<\/strong> Register a custom SVG via <code>iconLibrary.register<\/code>. Use <code>stroke=\"currentColor\"<\/code> so it inherits the nav&#8217;s active and inactive theme.<\/p>\n<p><strong>The classic bundle is gone.<\/strong> Do not look for ExtJS hooks. In 2026.1 there is only the Studio remote path described here.<\/p>\n<h2>Verify the Pimcore Studio Plugin Registration<\/h2>\n<p>Two console commands confirm your Pimcore Studio Plugin wiring before you touch the browser. Run them after <code>cache:clear<\/code>.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"\">bin\/console debug:container --tag pimcore_studio_ui.webpack_entry_point_provider\nbin\/console lint:container<\/pre>\n<p>The first lists your provider under the Studio tag. If it is missing, recheck the <code>services.yaml<\/code> tag string.<\/p>\n<p>The second validates the container compiles cleanly. A green result here means the PHP seam is sound.<\/p>\n<figure><img decoding=\"async\" class=\"alignnone size-full wp-image-551766\" src=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-verify-container-1.webp\" alt=\"pimcore-studio-plugin-verify-container-1\" width=\"1529\" height=\"757\" srcset=\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-verify-container-1.webp 1529w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-verify-container-1-300x149.webp 300w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-verify-container-1-1200x594.webp 1200w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-verify-container-1-250x124.webp 250w, https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-verify-container-1-768x380.webp 768w\" sizes=\"(max-width: 1529px) 100vw, 1529px\" loading=\"lazy\" \/><\/figure>\n<h2>Wrap-Up and Next Steps<\/h2>\n<p>You now have a full <strong>Pimcore Studio Plugin<\/strong> path: PHP points at the manifest, rsbuild builds the remote, and React registers a nav item and screen.<\/p>\n<p>The pattern scales \u2014 add more widgets, screens, and <code>\/api<\/code> routes on the same foundation.<\/p>\n<p>Studio plugins also pair well with connectors. See how we ship UI for the <a href=\"https:\/\/webkul.com\/blog\/pimcore-magento-2-connector\/\">Pimcore Magento 2 connector<\/a>, the <a href=\"https:\/\/webkul.com\/blog\/pimcore-shopify-connector\/\">Pimcore Shopify connector<\/a><\/p>\n<p>Need a custom Studio plugin or a full PIM integration built for you? <a href=\"https:\/\/webkul.com\/pimcore-development\/\">Talk to the Webkul Pimcore development team<\/a> and we will help you ship it.<\/p>\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Pimcore 12 (Platform 2026.1) ships Studio, a React single-page app. A Pimcore Studio Plugin is now a React micro-frontend, not an ExtJS panel. This guide walks you through building a Pimcore Studio Plugin end to end. Our goal is simple. Add a nav item and a screen to Studio by shipping a federated React remote <a href=\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/\">[&#8230;]<\/a><\/p>\n","protected":false},"author":399,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8189,1],"tags":[8201,8486,8202,7703],"class_list":["post-551736","post","type-post","status-publish","format-standard","hentry","category-pimcore","category-uncategorized","tag-pimcore","tag-pimcore-connector","tag-pimcore-pim","tag-product-information-management"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Build a Pimcore Studio Plugin: React Nav + Screen<\/title>\n<meta name=\"description\" content=\"Build a Pimcore Studio Plugin for Pimcore 12 (2026.1): a React micro-frontend adding a nav item and screen, with the PHP provider and rsbuild toolchain\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Build a Pimcore Studio Plugin: React Nav + Screen\" \/>\n<meta property=\"og:description\" content=\"Build a Pimcore Studio Plugin for Pimcore 12 (2026.1): a React micro-frontend adding a nav item and screen, with the PHP provider and rsbuild toolchain\" \/>\n<meta property=\"og:url\" content=\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/\" \/>\n<meta property=\"og:site_name\" content=\"Webkul Blog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/webkul\/\" \/>\n<meta property=\"article:published_time\" content=\"2026-07-31T06:47:08+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-07-31T06:52:41+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp\" \/>\n<meta name=\"author\" content=\"Nitesh Arora\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@webkul\" \/>\n<meta name=\"twitter:site\" content=\"@webkul\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Nitesh Arora\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/\"},\"author\":{\"name\":\"Nitesh Arora\",\"@id\":\"https:\/\/webkul.com\/blog\/#\/schema\/person\/3b0c0c713bd646e411ad5b4563364bb0\"},\"headline\":\"Build a Pimcore Studio Plugin: React Nav + Screen\",\"datePublished\":\"2026-07-31T06:47:08+00:00\",\"dateModified\":\"2026-07-31T06:52:41+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/\"},\"wordCount\":1461,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/webkul.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp\",\"keywords\":[\"Pimcore\",\"Pimcore connector\",\"Pimcore PIM\",\"Product Information Management\"],\"articleSection\":[\"PimCore\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/\",\"url\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/\",\"name\":\"Build a Pimcore Studio Plugin: React Nav + Screen\",\"isPartOf\":{\"@id\":\"https:\/\/webkul.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp\",\"datePublished\":\"2026-07-31T06:47:08+00:00\",\"dateModified\":\"2026-07-31T06:52:41+00:00\",\"description\":\"Build a Pimcore Studio Plugin for Pimcore 12 (2026.1): a React micro-frontend adding a nav item and screen, with the PHP provider and rsbuild toolchain\",\"breadcrumb\":{\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#primaryimage\",\"url\":\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp\",\"contentUrl\":\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp\",\"width\":1529,\"height\":757,\"caption\":\"pimcore-studio-plugin-studio-navitem-1\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/webkul.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Build a Pimcore Studio Plugin: React Nav + Screen\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/webkul.com\/blog\/#website\",\"url\":\"https:\/\/webkul.com\/blog\/\",\"name\":\"Webkul Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/webkul.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/webkul.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/webkul.com\/blog\/#organization\",\"name\":\"WebKul Software Private Limited\",\"url\":\"https:\/\/webkul.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/webkul.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2021\/08\/webkul-logo-accent-sq.png\",\"contentUrl\":\"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2021\/08\/webkul-logo-accent-sq.png\",\"width\":380,\"height\":380,\"caption\":\"WebKul Software Private Limited\"},\"image\":{\"@id\":\"https:\/\/webkul.com\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/webkul\/\",\"https:\/\/x.com\/webkul\",\"https:\/\/www.instagram.com\/webkul\/\",\"https:\/\/www.linkedin.com\/company\/webkul\",\"https:\/\/www.youtube.com\/user\/webkul\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/webkul.com\/blog\/#\/schema\/person\/3b0c0c713bd646e411ad5b4563364bb0\",\"name\":\"Nitesh Arora\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/webkul.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/2628caeef979fc167146e3d99675895a39d9ca9c9105fef4b733893fd28a25f5?s=96&d=https%3A%2F%2Fcdnblog.webkul.com%2Fblog%2Fwp-content%2Fuploads%2F2019%2F10%2Fmike.png&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/2628caeef979fc167146e3d99675895a39d9ca9c9105fef4b733893fd28a25f5?s=96&d=https%3A%2F%2Fcdnblog.webkul.com%2Fblog%2Fwp-content%2Fuploads%2F2019%2F10%2Fmike.png&r=g\",\"caption\":\"Nitesh Arora\"},\"url\":\"https:\/\/webkul.com\/blog\/author\/nitesh-arora945\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Build a Pimcore Studio Plugin: React Nav + Screen","description":"Build a Pimcore Studio Plugin for Pimcore 12 (2026.1): a React micro-frontend adding a nav item and screen, with the PHP provider and rsbuild toolchain","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/","og_locale":"en_US","og_type":"article","og_title":"Build a Pimcore Studio Plugin: React Nav + Screen","og_description":"Build a Pimcore Studio Plugin for Pimcore 12 (2026.1): a React micro-frontend adding a nav item and screen, with the PHP provider and rsbuild toolchain","og_url":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/","og_site_name":"Webkul Blog","article_publisher":"https:\/\/www.facebook.com\/webkul\/","article_published_time":"2026-07-31T06:47:08+00:00","article_modified_time":"2026-07-31T06:52:41+00:00","og_image":[{"url":"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp","type":"","width":"","height":""}],"author":"Nitesh Arora","twitter_card":"summary_large_image","twitter_creator":"@webkul","twitter_site":"@webkul","twitter_misc":{"Written by":"Nitesh Arora","Est. reading time":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#article","isPartOf":{"@id":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/"},"author":{"name":"Nitesh Arora","@id":"https:\/\/webkul.com\/blog\/#\/schema\/person\/3b0c0c713bd646e411ad5b4563364bb0"},"headline":"Build a Pimcore Studio Plugin: React Nav + Screen","datePublished":"2026-07-31T06:47:08+00:00","dateModified":"2026-07-31T06:52:41+00:00","mainEntityOfPage":{"@id":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/"},"wordCount":1461,"commentCount":0,"publisher":{"@id":"https:\/\/webkul.com\/blog\/#organization"},"image":{"@id":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#primaryimage"},"thumbnailUrl":"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp","keywords":["Pimcore","Pimcore connector","Pimcore PIM","Product Information Management"],"articleSection":["PimCore"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/","url":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/","name":"Build a Pimcore Studio Plugin: React Nav + Screen","isPartOf":{"@id":"https:\/\/webkul.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#primaryimage"},"image":{"@id":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#primaryimage"},"thumbnailUrl":"https:\/\/webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp","datePublished":"2026-07-31T06:47:08+00:00","dateModified":"2026-07-31T06:52:41+00:00","description":"Build a Pimcore Studio Plugin for Pimcore 12 (2026.1): a React micro-frontend adding a nav item and screen, with the PHP provider and rsbuild toolchain","breadcrumb":{"@id":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#primaryimage","url":"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp","contentUrl":"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2026\/07\/pimcore-studio-plugin-studio-navitem-1.webp","width":1529,"height":757,"caption":"pimcore-studio-plugin-studio-navitem-1"},{"@type":"BreadcrumbList","@id":"https:\/\/webkul.com\/blog\/pimcore-studio-plugin\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/webkul.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Build a Pimcore Studio Plugin: React Nav + Screen"}]},{"@type":"WebSite","@id":"https:\/\/webkul.com\/blog\/#website","url":"https:\/\/webkul.com\/blog\/","name":"Webkul Blog","description":"","publisher":{"@id":"https:\/\/webkul.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/webkul.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/webkul.com\/blog\/#organization","name":"WebKul Software Private Limited","url":"https:\/\/webkul.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/webkul.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2021\/08\/webkul-logo-accent-sq.png","contentUrl":"https:\/\/cdnblog.webkul.com\/blog\/wp-content\/uploads\/2021\/08\/webkul-logo-accent-sq.png","width":380,"height":380,"caption":"WebKul Software Private Limited"},"image":{"@id":"https:\/\/webkul.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/webkul\/","https:\/\/x.com\/webkul","https:\/\/www.instagram.com\/webkul\/","https:\/\/www.linkedin.com\/company\/webkul","https:\/\/www.youtube.com\/user\/webkul\/"]},{"@type":"Person","@id":"https:\/\/webkul.com\/blog\/#\/schema\/person\/3b0c0c713bd646e411ad5b4563364bb0","name":"Nitesh Arora","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/webkul.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/2628caeef979fc167146e3d99675895a39d9ca9c9105fef4b733893fd28a25f5?s=96&d=https%3A%2F%2Fcdnblog.webkul.com%2Fblog%2Fwp-content%2Fuploads%2F2019%2F10%2Fmike.png&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/2628caeef979fc167146e3d99675895a39d9ca9c9105fef4b733893fd28a25f5?s=96&d=https%3A%2F%2Fcdnblog.webkul.com%2Fblog%2Fwp-content%2Fuploads%2F2019%2F10%2Fmike.png&r=g","caption":"Nitesh Arora"},"url":"https:\/\/webkul.com\/blog\/author\/nitesh-arora945\/"}]}},"amp_enabled":true,"_links":{"self":[{"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/posts\/551736","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/users\/399"}],"replies":[{"embeddable":true,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/comments?post=551736"}],"version-history":[{"count":14,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/posts\/551736\/revisions"}],"predecessor-version":[{"id":551769,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/posts\/551736\/revisions\/551769"}],"wp:attachment":[{"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/media?parent=551736"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/categories?post=551736"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/webkul.com\/blog\/wp-json\/wp\/v2\/tags?post=551736"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}