Install
$ agentstack add skill-cors-gmbh-pimcore-skills-pimcore-12-upgrade ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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 No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ● Dynamic code execution Used
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.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
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 →About
Pimcore 11 to Pimcore 12 Upgrade
You are helping upgrade a Pimcore 11 codebase to Pimcore 12. First determine whether this is a Project or a Bundle, as the upgrade steps differ.
Step 0: Determine Project vs Bundle
Project = A full Pimcore application (has bin/console, config/, public/index.php, docker-compose.yaml). Bundle = A reusable Symfony/Pimcore bundle (has no bin/console, extends AbstractPimcoreBundle, published as composer package).
Check for indicators:
# Project indicators
ls bin/console config/bundles.php public/index.php docker-compose.yaml 2>/dev/null
# Bundle indicators
grep -r "AbstractPimcoreBundle\|AbstractBundle" src/ --include="*.php" -l
Part A: Upgrade Steps for PROJECTS
A1: Update PHP Requirement
Pimcore 12 requires PHP 8.3+.
composer.json:
"php": ">=8.3"
Docker: Update PHP image to 8.3 or 8.4.
A2: Update composer.json Dependencies
composer require pimcore/pimcore:^12.0
composer require pimcore/admin-ui-classic-bundle:^2.0
If using CORS packages:
composer require cors/dev:12.x-dev --dev
composer require cors/saml:12.x-dev # if SAML is used
composer require cors/cors:^0.3.0 # if cors-bundle is used, check latest version
If using CoreShop packages (individual or full suite):
# CoreShop v4 → v5 (all coreshop/* packages)
# For x-dev packages: 4.1.x-dev → 5.0.x-dev
# For stable constraints: ^4.1 → ^5.0
composer require coreshop/theme-bundle:5.0.x-dev # if used
composer require coreshop/menu-bundle:5.0.x-dev # if used
composer require coreshop/messenger-bundle:5.0.x-dev # if used
composer require coreshop/registry:^5.0 # if used
Symfony version support changes:
"symfony/dotenv": "^6.4 | ^7.4",
"symfony/runtime": "^6.4 | ^7.4"
A3: Pimcore License Configuration
Create config/license.yaml:
pimcore:
encryption:
secret: '%env(PIMCORE_ENCRYPTION_SECRET)%'
product_registration:
instance_identifier: '%env(PIMCORE_INSTANCE_IDENTIFIER)%'
product_key: '%env(PIMCORE_PRODUCT_KEY)%'
Import it in config/config.yaml:
imports:
- { resource: license.yaml }
Add to .env (or .env.local):
PIMCORE_ENCRYPTION_SECRET=your_secret_here
PIMCORE_INSTANCE_IDENTIFIER=your_instance_id_here
PIMCORE_PRODUCT_KEY=your_product_key_here
Important: PIMCORE_ENCRYPTION_SECRET must not be empty, otherwise you get:
`pimcore.encryption.secret` is not set.
Run `vendor/bin/generate-defuse-key` to generate a secret and set it as container parameter `pimcore.encryption.secret`.
Generate the secret with:
docker compose exec php vendor/bin/generate-defuse-key
Then set the output as PIMCORE_ENCRYPTION_SECRET in .env.local.
A4: Docker Compose Updates
Pimcore 12 introduces new required services. Update docker-compose.yaml:
Add these services:
mercure- Real-time notifications (required for Pimcore Studio)opensearch:2.13.0- Search engine (replaces Elasticsearch for Generic Data Index)opensearch-dashboards:2.13.0- OpenSearch UI (optional, for debugging)rabbitmq:3-management- Message queue
Remove if present (enterprise-only):
gotenbergpdfreactor
If using cors/dev, update docker-compose include:
include:
- vendor/cors/dev/docker-compose-dev.yaml
A4b: Update .docker/composer-setup.sh
If the project has a .docker/composer-setup.sh, update the Docker image registry from the old GitLab registry to GitHub Container Registry:
Before (Pimcore 11):
docker run --rm --env COMPOSER_AUTH="$COMPOSER_AUTH" --volume "$(pwd)":/var/www/html:cached git.e-conomix.at:5050/cors/docker/php-alpine-${DOCKER_ALPINE_VERSION}-cli:${DOCKER_PHP_VERSION}-${DOCKER_BASE_IMAGE} composer install --no-scripts --no-interaction
After (Pimcore 12):
docker run --rm --env COMPOSER_AUTH="$COMPOSER_AUTH" --volume "$(pwd)":/var/www/html:cached ghcr.io/cors-gmbh/pimcore-docker/php-fpm:${DOCKER_PHP_VERSION}-alpine${DOCKER_ALPINE_VERSION}-${DOCKER_BASE_IMAGE} composer install --no-scripts --no-interaction
Key changes:
- Registry:
git.e-conomix.at:5050/cors/docker/→ghcr.io/cors-gmbh/pimcore-docker/ - Image name format:
php-alpine-{ALPINE}-cli:{PHP}-{BASE}→php-fpm:{PHP}-alpine{ALPINE}-{BASE}
A5: Update Static Analysis & Code Quality Configs
Replace local configs with shared cors/dev imports:
ecs.php:
import('vendor/cors/dev/ecs.php');
$ecsConfig->parallel();
$ecsConfig->paths(['src']);
};
phpstan.neon:
includes:
- vendor/cors/dev/phpstan.neon
parameters:
paths:
- src
psalm.xml:
A6: Update CI/CD Pipeline
Update .gitlab-ci.yml to reference the correct cors/docker version:
include:
- project: 'cors/docker'
ref: "8.0"
file: '.project-gitlab-ci.yml'
A7: Migrate Bundles from Kernel.php to config/bundles.php
Pimcore 11 projects often register bundles in src/Kernel.php via registerBundlesToCollection(). In Pimcore 12, migrate all bundles to config/bundles.php instead.
Before (Pimcore 11 - src/Kernel.php):
class Kernel extends PimcoreKernel
{
public function registerBundlesToCollection(BundleCollection $collection): void
{
$collection->addBundle(new SentryBundle(), 0, ['staging', 'prod']);
$collection->addBundle(new CORSBundle());
$collection->addBundle(new PimcoreAdminBundle(), 60);
$collection->addBundle(new CoreShopCoreBundle(), 40);
}
}
After (Pimcore 12 - config/bundles.php):
['all' => true],
Sentry\SentryBundle\SentryBundle::class => ['staging' => true, 'prod' => true],
CORS\Bundle\CORSBundle\CORSBundle::class => ['all' => true],
CoreShop\Bundle\CoreBundle\CoreShopCoreBundle::class => ['all' => true],
];
After (Pimcore 12 - src/Kernel.php):
class Kernel extends PimcoreKernel
{
}
Migration rules:
- Bundles with no environment restriction →
['all' => true] - Bundles with
['staging', 'prod']→['staging' => true, 'prod' => true] - Priority parameter from
addBundle()is no longer needed (Symfony handles order via bundles.php) - Remove all
useimports and theregisterBundlesToCollectionmethod from Kernel.php - Remove bundles for packages that were removed during the upgrade
A8: Migrate Annotations to Attributes
Symfony 7 removed support for annotation route loaders. Update config/routes.yaml:
Before:
app:
resource: "../src/Controller/"
type: annotation
After:
app:
resource: "../src/Controller/"
type: attribute
Also update config/services.yaml if it references the old Symfony\Component\Security\Core\Security class (removed in Symfony 7):
Before:
- '@Symfony\Component\Security\Core\Security'
After:
- '@Symfony\Bundle\SecurityBundle\Security'
A8b: Remove Deprecated Security Config Options
Symfony 7 removed enable_authenticator_manager (it's now always enabled). Remove it from config/packages/security.yaml:
Before:
security:
enable_authenticator_manager: true
After:
security:
A8c: Fix Serializer Interface Changes
Symfony 7 enforces strict signatures on NormalizerInterface. Update all custom normalizers:
normalize() method:
// Before (Symfony 6):
public function normalize($object, string $format = null, array $context = [])
// After (Symfony 7):
public function normalize(mixed $data, ?string $format = null, array $context = []): \ArrayObject|array|string|int|float|bool|null
supportsNormalization() method:
// Before (Symfony 6):
public function supportsNormalization($data, $format = null)
// After (Symfony 7):
public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
New required method getSupportedTypes():
public function getSupportedTypes(?string $format): array
{
return [
MyClass::class => true,
];
}
A9: Pimcore Studio API Route Changes
If you have custom admin controllers, note the API prefix change:
- Pimcore 11:
/admin/... - Pimcore 12:
/pimcore-studio/api/...
A10: Deprecation Cleanup
Check for deprecations:
bin/console debug:container --deprecations
grep -rn "StaticRoutesBundle" config/ src/ # deprecated since 12.3
A11: Clean Up and Run Migrations (LAST STEP)
Important: Only run migrations after everything else works (cache:clear, manual browser test).
- Delete all existing app migrations in
src/Migrations/- they reference removed bundles and use deprecatedContainerAwareTrait(removed in Symfony 7).
- Create a cleanup migration that removes old entries from the
migration_versionstable:
final class VersionXXXX extends AbstractMigration
{
public function getDescription(): string
{
return 'Pimcore 12 upgrade: clean up old app migrations from migration_versions table';
}
public function up(Schema $schema): void
{
$this->addSql("DELETE FROM migration_versions WHERE version LIKE 'App\\\\Migrations\\\\%' AND version != 'App\\\\Migrations\\\\VersionXXXX'");
}
public function down(Schema $schema): void
{
}
}
- Run migrations (Pimcore's own migrations handle data conversions like serialized PHP to JSON):
docker compose exec php bin/console doctrine:migrations:migrate --no-interaction
Before running migrations, verify:
docker compose exec php bin/console cache:clearworks without errors- Manual test in browser shows no critical errors
- Ask the user for confirmation before executing
Part B: Upgrade Steps for BUNDLES
Bundles follow a simpler process because they don't manage Docker, database, or application config directly.
B1: Update composer.json
{
"require": {
"php": ">=8.3",
"pimcore/pimcore": "^11.0 | ^12.0"
},
"require-dev": {
"cors/dev": "12.x-dev",
"pimcore/admin-ui-classic-bundle": "^2.0"
}
}
Key points:
- Support both
^11.0 | ^12.0if possible for backwards compatibility cors/devgoes to12.x-dev(notdev-main)pimcore/admin-ui-classic-bundlebumps to^2.0- Add Symfony 7 support:
"symfony/*": "^6.4 | ^7.4"
B2: Update CI/CD Pipeline
Bundle pipelines use a different CI template than projects:
include:
- project: 'cors/docker'
ref: "8.0"
file: '.bundle-gitlab-ci.yml'
Note: .bundle-gitlab-ci.yml (not .project-gitlab-ci.yml).
B3: Update Static Analysis Configs
Same as project (Step A5) - use shared cors/dev imports for ecs.php, phpstan.neon, psalm.xml.
Important for psalm.xml: Make sure ` is defined locally (not just inherited via xi:include), otherwise vendor/bin/psalm` without arguments won't analyze any files. See Step A5 for the correct config.
B4: Add #[\Override] Attributes
PHP 8.3 introduces #[\Override]. Add it to all methods that override a parent class or implement an interface method:
final class MyBundle extends AbstractPimcoreBundle
{
#[\Override]
public function getNiceName(): string { return 'My Bundle'; }
#[\Override]
public function getDescription(): string { return 'Description'; }
}
Common methods that need #[\Override]:
getNiceName(),getDescription()on bundle classesload()on DependencyInjection Extension classesgetConfigTreeBuilder()on Configuration classesgetSubscribedEvents()on EventSubscriber classesgetType()on Document Editable classescompile()on Twig Node classesparse(),getTag()on Twig TokenParser classesgetTokenParsers()on Twig Extension classes
B4b: Fix Symfony 7 Return Type Declarations
Symfony 7 enforces strict return type declarations. Methods that previously had no return type now require one:
Command execute() method:
// Before (Pimcore 11 / Symfony 6):
protected function execute(InputInterface $input, OutputInterface $output)
// After (Pimcore 12 / Symfony 7):
protected function execute(InputInterface $input, OutputInterface $output): int
Search all Command classes and add : int return type to execute():
grep -rl "protected function execute(InputInterface \$input, OutputInterface \$output)$" src/ --include="*.php"
B5: Add final to Classes
Make classes final unless they are explicitly designed for extension:
final class MyListener implements EventSubscriberInterface { ... }
final class MyExtension extends Extension { ... }
final class MyController extends UserAwareController { ... }
B6: Add declare(strict_types=1)
Every PHP file must have declare(strict_types=1); after the opening 0)
UnusedClass/PossiblyUnusedMethod→ suppress in psalm.xml (false positives for DI-based bundles)MissingConstructorfrom vendor code → suppress in psalm.xml
Suppress bundle-typical false positives in psalm.xml:
Quick Reference: Version Matrix
| Dependency | Pimcore 11 | Pimcore 12 | |---|---|---| | PHP | >=8.1 | >=8.3 | | Symfony | ^6.4 | ^6.4 \| ^7.4 | | pimcore/pimcore | ^11.0 | ^12.0 | | pimcore/admin-ui-classic-bundle | ^1.x | ^2.0 | | pimcore optional bundles (file-explorer, google-marketing, newsletter, system-info, web-to-print) | ^1.x | ^2.0 | | pimcore/ecommerce-framework-bundle | ^1.0 | removed (no P12 version) | | pimcore/personalization-bundle | ^1.0 | removed (no P12 version) | | coreshop/* (core-shop, theme-bundle, menu-bundle, messenger-bundle, registry, etc.) | 4.x / 4.1.x-dev | ^5.0 / 5.0.x-dev | | cors/dev | dev-main / ^1.0@dev | 12.x-dev | | cors/cors | ^0.2 | ^0.3.0 | | cors/saml | ^11.0 | 12.x-dev | | cors/web-care | ^11.0 | ^2.0 | | cors/prometheus | ^11.0 | ^2.0 | | cors/docker CI ref | 7.0 | 8.0 | | phpstan/phpstan | ^1.10 | ^1.10 \|\| ^2.0 | | vimeo/psalm | ^5.0 | ^5.0 \|\| ^6.0 | | symfony/webpack-encore-bundle | ^1.17 | ^1.17 \| ^2.0 |
Packages with NO Pimcore 12 Support (as of Feb 2026)
These packages must be removed when upgrading to Pimcore 12:
| Package | Notes | |---|---| | dachcom-digital/dynamic-search | Requires pimcore ^11.0 | | dachcom-digital/dynamic-search-data-provider-trinity | Requires pimcore ^11.0 | | dachcom-digital/dynamic-search-index-provider-elasticsearch | Requires pimcore ^11.0 | | dachcom-digital/emailizr | Requires pimcore ^11.0 | | dachcom-digital/seo | Requires pimcore ^11.0 | | cors/kubernetes-console | Requires admin-ui-classic-bundle ^1.0 |
Project require-dev Cleanup
When using cors/dev:12.x-dev, the following packages are bundled and should be removed from project-level require-dev:
phpstan/phpstanphpstan/phpstan-symfonysymplify/easy-coding-standardvimeo/psalm
Replacing Removed Dachcom Bundles
dachcom-digital/emailizr
The emailizr bundle provides CSS inlining for email templates via custom Twig tags ({% emailizr_inline_style %}, {% end_emailizr_inline_style %}), a emailizr_style_collector global, and an emailizr_inline_style() Twig function.
Steps to replace:
- Install required dependencies:
composer require pelago/emogrifier twig/inky-extra
- Create the following classes in
src/Emailizr/:
src/Emailizr/Collector/CssCollector.php - Collects CSS file paths for inlining:
*/
class CssCollector implements \IteratorAggregate
{
/** @var list */
protected array $cssFiles = [];
public function add(string $file): void
{
$this->cssFiles[] = $file;
}
public function removeAll(): void
{
$this->cssFiles = [];
}
/**
* @return \ArrayIterator, string>
*/
#[\Override]
public function getIterator(): \Arr
…
## Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- **Author:** [cors-gmbh](https://github.com/cors-gmbh)
- **Source:** [cors-gmbh/pimcore-skills](https://github.com/cors-gmbh/pimcore-skills)
- **License:** MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.