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:
The CoreShop Theme Bundle helps ensure a clean and scalable architecture for managing such setups.
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.
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(),
]);
}
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
.
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.
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.
We store all theme-based assets in their respective theme folders and configure Webpack Encore builds accordingly.
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') }}">
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.
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