How to manage multiple themes in a Pimcore project using the CoreShop Theme Bundle

15.05.2025

Theme Management in Pimcore with CoreShop

The CoreShop Theme Bundle provides a standardized solution for building and managing themes in Pimcore - even without using CoreShop's eCommerce features. This makes the bundle ideal for a wide range of Pimcore projects, from content-driven websites to complex multi-site installations.

Why Use Different Themes in Pimcore?

In some of our projects, we operate up to 20 or 30 different websites within a single Pimcore instance. This is common when multiple sites or landing pages share the same data model - for example:

  • Microsites or marketing pages with different layouts
  • Separate visual themes for web and print output
  • Distinct portals running off the same backend

The CoreShop Theme Bundle helps ensure a clean and scalable architecture for managing such setups.

How To Install And Configure The Theme Bundle

We aim for maximum flexibility and reusability. Our base theme contains shared functionality and styling, which we extend in project-specific themes. Here's how you can get started.

1. Installing the CoreShop Theme Bundle

Install the bundle using Composer:

composer require coreshop/theme-bundle:^4.0

Then enable it in your Kernel.php:

public function registerBundlesToCollection(BundleCollection $collection)
{
    $collection->addBundles([
        new \CoreShop\Bundle\ThemeBundle\CoreShopThemeBundle(),
    ]);
}

2. Configuring the Theme Resolver

CoreShop provides several built-in theme resolvers. For example, you can assign themes via Pimcore document properties:

core_shop_theme:
   default_resolvers:
     pimcore_document_property: true

Assign the theme using a document property named theme:

Then CoreShop tries to resolve the theme based on the name base.

Our Pimcore Theme Folder Structure

Internally, CoreShop relies on the Sylius Theme Bundle and uses the filesystem configuration source. By default, it looks for composer.json files in the %kernel.project_dir%/themes directory.

Here's what a typical setup looks like:

Each theme contains all necessary assets - such as Twig templates, JavaScript, CSS, images, and fonts - neatly organized in its own folder.

Theme Inheritance with Sylius

The Sylius Theme Bundle also supports theme inheritance, allowing one theme to extend another - for instance, using a composer.json file:

{
  "name": "cors/portal",
  "description": "CORS portal theme",
  "authors": [
    {
      "name": "CORS GmbH",
      "email": "office@cors.gmbh",
      "homepage": "https://www.cors.gmbh/",
      "role": "TEAM"
    }
  ],
  "extra": {
    "sylius-theme": {
      "title": "portal",
      "parents": [
        "cors/base"
      ]
    }
  }
}

This setup allows full reuse of layouts, assets, and logic from the base theme.

Managing Theme-Based Assets in Pimcore

We store all theme-based assets in their respective theme folders and configure Webpack Encore builds accordingly.

Webpack Encore Configuration

Each theme has its own build target for CSS and JS files.

webpack_encore:
   output_path: '%kernel.project_dir%/public/build/base'
   builds:
       admin: '%kernel.project_dir%/public/build/admin'
       base: '%kernel.project_dir%/public/build/base'
       # Example for an additional theme
       #portal: '%kernel.project_dir%/public/build/portal'

Symfony Asset Packages

framework:
   assets:
       json_manifest_path: '%kernel.project_dir%/public/build/base/manifest.json'
       packages:
           admin:
               base_path: '/build/admin/'
               json_manifest_path: '%kernel.project_dir%/public/build/admin/manifest.json'
           base:
               base_path: '/build/base/'
               json_manifest_path: '%kernel.project_dir%/public/build/base/manifest.json'
           # portal:
           #     base_path: '/build/portal/'
           #     json_manifest_path: '%kernel.project_dir%/public/build/portal/manifest.json'

With this configuration, asset paths are resolved dynamically using the appropriate Symfony asset package. For example, using a custom Twig extension:

<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('images/favicons/apple-touch-icon.png','base') }}">

Custom Webpack Configs Per Theme

We define separate webpack config files per theme:

const Encore = require('@symfony/webpack-encore');

/**
 *
 * @param {string} name
 * @param {string} outputPath
 * @param {string} publicPath
 * @param {Array.<{name:string, src:string}>} entries
 * @param {Array.<{name:string, src:string}>} styleEntries
 * @param {Array.<{from:string, to:string}>} copyFiles
 * @param {Array.<Plugin>} plugins
 * @param {boolean} defaults
 * @param {boolean} mangle
 * @returns {webpack.Configuration}
 */
function getConfig({
                       name,
                       outputPath,
                       publicPath,
                       entries = [],
                       styleEntries = [],
                       copyFiles = [],
                       plugins = [],
                       defaults = true,
                       mangle = true
                   }) {
    Encore.reset();

    if (!mangle) {
        Encore.configureTerserPlugin((options) => {
            return {
                terserOptions: {
                    mangle: false,
                }
            };
        })
    }

    Encore
    // directory where all compiled assets will be stored
    .setOutputPath(outputPath)

    // what's the public path to this directory (relative to your project's document root dir)
    .setPublicPath(publicPath)

    .cleanupOutputBeforeBuild()

    // allow sass/scss files to be processed
    .enableSassLoader()

    .enablePostCssLoader((options) => {
        options.postcssOptions = {
            path: 'postcss.config.js'
        };
    })
    .enableSingleRuntimeChunk()
    .enableSourceMaps(!Encore.isProduction())
    .enableVersioning(Encore.isProduction())
    .enableTypeScriptLoader()
    ;
    // ##########################################################################################
    // ### default integrations for all configs
    // ##########################################################################################
    if (defaults) {
        // Put your default js includes here, if needed
        /*Encore
        .addEntry('js/base', './themes/base/assets/js/base.js')
        ;*/
    }

    entries.forEach(entry => {
        if (entry.name && entry.src) {
            Encore.addEntry(entry.name, entry.src);
        }
    })

    styleEntries.forEach(entry => {
        if (entry.name && entry.src) {
            Encore.addStyleEntry(entry.name, entry.src);
        }
    })

    if (defaults) {
        // Put your default copyFiles here, if needed
        /*copyFiles = [
            ...[
                {
                    from: './themes/base/assets/images',
                    to: 'images/[path][name].[ext]',
                }, {
                    from: './themes/base/assets/fonts',
                    to: 'fonts/[path][name].[ext]',
                }
            ],
            ...copyFiles
        ];*/
    }

    Encore
    .copyFiles(copyFiles)

    plugins.forEach(plugin => {
        Encore.addPlugin(plugin);
    })

    const config = Encore.getWebpackConfig();
    config.name = name;
    return config;
}

// export the final configuration
const admin = getConfig({
    name: 'admin',
    outputPath: 'public/build/admin',
    publicPath: '/build/admin',
    defaults: false,
    mangle: false,
    styleEntries: [
        {
            name: 'css/admin/edit',
            src: './themes/base/admin/assets/css/edit.scss'
        }
    ],
    copyFiles: [
        {
            from: './themes/base/admin/assets/js',
            to: 'js/[path][name].[ext]',
        },
    ]
});
const base = getConfig(require('./themes/base/webpack-web.config.js'));
const portal = getConfig(require('./themes/portal/webpack-web.config.js'));

module.exports = [admin, base, portal];

This helps us build each theme independently and manage theme-specific dependencies.

Georg Wurz / Project Lead Development CORS
GEorg Wurz / Lead Development

Ready to Build Theme-Based Pimcore Sites?

Using the CoreShop Theme Bundle, you can build scalable, modular, and reusable themes for your Pimcore projects- without needing any eCommerce functionality. This is ideal for multisite architectures, landing pages, portals, or content-driven applications.

If you need help setting up your Pimcore theme structure, feel free to reach out.

👉 More details can be found in the official documentation

Contact us