# Pimcore 11.5 to 12.3.3 Upgrade Guide

**Status:** Work In Progress (WIP)  
**Last Updated:** March 30, 2026  
**Target Version:** Pimcore 12.3.3 (Platform Version 2025.4)

---

## 📋 Table of Contents

1. [Overview](#overview)
2. [Prerequisites](#prerequisites)
3. [Pre-Upgrade Checklist](#pre-upgrade-checklist)
4. [Step-by-Step Upgrade](#step-by-step-upgrade)
5. [Code Changes Required](#code-changes-required)
6. [Configuration Changes](#configuration-changes)
7. [Database Migrations](#database-migrations)
8. [Troubleshooting](#troubleshooting)
9. [Post-Upgrade Testing](#post-upgrade-testing)
10. [Rollback Plan](#rollback-plan)

---

## 1. Overview

### Important: Two Setup Paths

This guide covers **two different deployment scenarios**:

1. **Docker Setup** (Local development) - Steps 1-8 with `docker compose exec php` commands
2. **Shared Hosting / VPS** (Production/Test servers) - See **Appendix A** for non-Docker instructions

> **Note:** If you're using shared hosting with SSH access, skip the Docker-specific commands and refer to **Appendix A** for the appropriate commands.

### Why Upgrade?

**Critical Security Fix:** CVE-2026-27461  
- SQL injection vulnerability in dependency listing endpoints
- Allows admin users to extract database contents including password hashes
- **No patch available for 11.x LTS** - upgrade to 12.3.3 is required

### Key Changes in Pimcore 12

| Feature              | Version 11   | Version 12 |
|---------             |------------  |------------|
| PHP Version          | 8.1 - 8.2    | 8.3+ |
| Symfony              | 6.4          | 6.4 or 7.x |
| Doctrine DBAL        | 3.x          | 4.x |
| License              | GPLv3        | POCL (Pimcore Open Core License) |
| Product Registration | Not required | Required |
| Encryption Secret    | Optional     | Required |

---

## 2. Prerequisites

### System Requirements

- **PHP 8.3+** (required)
- **MySQL 8.0+** or **MariaDB 10.11+**
- **OpenSearch 2.x** (or Elasticsearch 7.10+)
- **Redis** (recommended)

### Backup (CRITICAL)

```bash
# 1. Backup database
mysqldump -u root -p liberty > liberty_backup_$(date +%Y%m%d).sql

# 2. Backup composer files
cp composer.json composer.json.backup-$(date +%Y%m%d)
cp composer.lock composer.lock.backup-$(date +%Y%m%d)

# 3. Backup config files
cp -r config config.backup-$(date +%Y%m%d)

# 4. Backup src files
cp -r src src.backup-$(date +%Y%m%d)
```

---

## 3. Pre-Upgrade Checklist

### 3.1 Check Current Versions

```bash
# Check current Pimcore version
docker compose exec php php bin/console pimcore:version

# Check current bundle versions
docker compose exec php composer show pimcore/pimcore
docker compose exec php composer show pimcore/data-hub
docker compose exec php composer show pimcore/portal-engine
```

### 3.2 Check Custom Code Compatibility

Review these files for breaking changes:
- Custom DAO classes (method signatures)
- Custom controllers (return types)
- Event listeners (type hints)
- Twig templates (deprecated features)

### 3.3 Enterprise License Verification

Ensure you have:
- ✅ Active Pimcore Enterprise license
- ✅ Access to enterprise.repo.pimcore.com
- ✅ Product registration capability

---

## 4. Step-by-Step Upgrade

### Step 1: Update Docker Image to PHP 8.3

**File:** `docker-compose.yaml`

```yaml
# BEFORE
services:
  php:
    image: pimcore/pimcore:php8.2-debug-latest

# AFTER
services:
  php:
    image: pimcore/pimcore:php8.3-debug-latest
```

**Commands:**
```bash
# Stop and remove old containers
docker compose stop php supervisord
docker compose rm -f php supervisord

# Pull and start new containers
docker compose up -d php supervisord

# Verify PHP version
docker compose exec php php -v
# Expected: PHP 8.3.x
```

---

### Step 2: Update composer.json

**File:** `composer.json`

#### 2.1 Core Package

```json
// BEFORE
"pimcore/pimcore": "^11.5.8"

// AFTER
"pimcore/pimcore": "^12.3.3"
```

#### 2.2 Bundle Versions (Platform Version 2025.4)

```json
// BEFORE
"pimcore/asset-metadata-class-definitions": "^2.0",
"pimcore/data-hub": "^1.9",
"pimcore/data-importer": "^1.10",
"pimcore/openid-connect": "^1.2",
"pimcore/portal-engine": "^4.2",
"pimcore/statistics-explorer": "^2.0",

// AFTER
"pimcore/asset-metadata-class-definitions": "^3.3",
"pimcore/data-hub": "^2.3",
"pimcore/data-importer": "^2.3",
"pimcore/openid-connect": "^1.5",
"pimcore/portal-engine": "^5.2",
"pimcore/statistics-explorer": "^3.0",
```

#### 2.3 New Required Bundles

```json
// ADD THESE
"pimcore/admin-ui-classic-bundle": "^2.3",
"pimcore/tinymce-bundle": "^2.0",
"pimcore/symfony-freeze": "^6",
"phpoffice/phpspreadsheet": "^3.3",
"rybakit/twig-deferred-extension": "^3.0"
```

#### 2.4 composer.json

```json
{
  "require": {
    "pimcore/pimcore": "^12.3.3",
    "pimcore/admin-ui-classic-bundle": "^2.3",
    "pimcore/asset-metadata-class-definitions": "^3.3",
    "pimcore/bundle-generator": "^2.0",
    "pimcore/data-hub": "^2.3",
    "pimcore/data-importer": "^2.3",
    "pimcore/openid-connect": "^1.5",
    "pimcore/portal-engine": "^5.2",
    "pimcore/statistics-explorer": "^3.0",
    "pimcore/symfony-freeze": "^6",
    "pimcore/tinymce-bundle": "^2.0",
    "rybakit/twig-deferred-extension": "^3.0",
    "phpoffice/phpspreadsheet": "^3.3",
    "symfony/dotenv": "^6.0"
  }
}
```

---

### Step 3: Run Composer Update

```bash
# Update composer dependencies
docker compose exec php composer update --no-interaction

# If you encounter memory issues
docker compose exec php php -d memory_limit=-1 /usr/local/bin/composer update --no-interaction
```

**Expected Output:**
- Pimcore core updated to 12.3.3
- All bundles updated to compatible versions
- New bundles installed

---

### Step 4: Generate Encryption Secret

**Why:** Pimcore 12 requires encryption secret for product registration.

```bash
# Generate secret
docker compose exec php vendor/bin/generate-defuse-key

# Output example:
# def000001f8b90a119bedcc96a9da901b18683ec6873cb4330023673f55df89a874ae320376cbdd53b1db15d7f26f6c50ac6d64a6fc7b77f3ada328b878da7ce5f379da7
```

**File:** `config/config.yaml`

```yaml
pimcore:
    encryption:
        secret: 'YOUR_GENERATED_SECRET_HERE'
```

---

### Step 5: Product Registration

**Why:** Pimcore 12 requires product registration to run.

```bash
# Clear cache to get registration URL
docker compose exec php php bin/console cache:clear --no-warmup 2>&1 | grep "license.pimcore.com"
```

**Output:**
```
https://license.pimcore.com/register?instance_identifier=XXX&instance_hash=XXX
```

**Steps:**
1. Visit the registration URL
2. Login with your Pimcore enterprise account
3. Complete registration
4. Copy the product key

**File:** `config/config.yaml`

```yaml
pimcore:
    encryption:
        secret: 'YOUR_SECRET'
    product_registration:
        instance_identifier: 'YOUR_INSTANCE_ID'
        product_key: 'YOUR_PRODUCT_KEY'
```

---

### Step 6: Configuration Changes

#### 6.1 Update Routing (annotation → attribute)

**File:** `config/routes.yaml`

```yaml
# BEFORE (Pimcore 11)
app:
    resource: "../src/Controller/"
    type: annotation

# AFTER (Pimcore 12)
app:
    resource: "../src/Controller/"
    type: attribute
```

**Also update bundle routing files:**
- `bundles/Liberty/AssetMetaImporterBundle/Resources/config/pimcore/routing.yml`
- `bundles/AssetsMetaDataBundle/Resources/config/pimcore/routing.yml`

```yaml
# Change type from 'annotation' to 'attribute'
type: attribute
```

#### 6.2 Update Security Config (if needed)

**File:** `config/packages/security.yaml`

Remove deprecated setting:
```yaml
# REMOVE THIS LINE
enable_authenticator_manager: true
```

---

### Step 7: Install Bundle Assets

```bash
# Install bundle assets
docker compose exec php php bin/console assets:install --relative

# Expected output: All bundles symlinked successfully
```

---

### Step 8: Run Database Migrations

**Why:** Pimcore 12 requires database schema updates.

```bash
# Dry run first (optional)
docker compose exec php php bin/console doctrine:migrations:migrate --prefix=Pimcore\\Bundle\\CoreBundle --dry-run

# Run migrations
docker compose exec php php bin/console doctrine:migrations:migrate --prefix=Pimcore\\Bundle\\CoreBundle

# When prompted, type: yes
```

**Key Migrations:**
- Asset customSettings: serialize → JSON
- New columns for notifications, versions
- Index optimizations

---

## 5. Code Changes Required

### 5.1 Custom DAO Classes

**File:** `src/Model/*/Dao.php`

```php
// BEFORE
public function assignVariablesToModel($data)

// AFTER
public function assignVariablesToModel(array $data): void
```

### 5.2 Configuration Classes

**File:** `bundles/*/DependencyInjection/Configuration.php`

```php
// BEFORE
public function getConfigTreeBuilder()

// AFTER
public function getConfigTreeBuilder(): TreeBuilder
```

### 5.3 Portal Engine Extensions

**File:** `src/PortalEngine/Controller/Rest/Api/DataPool/AssetController.php`

```php
// BEFORE
public function documentPreviewAction($id, $page): JsonResponse

// AFTER
public function documentPreviewAction($id, $page, ?string $publicShareHash = null, ?\Pimcore\Bundle\PortalEngineBundle\Service\PublicShare\PublicShareService $publicShareService = null): JsonResponse
```

**File:** `src/PortalEngine/Service/Rest/Api/SearchHandlerExtended.php`

```php
// BEFORE
use Symfony\Component\Security\Core\Security;

// AFTER
use Symfony\Bundle\SecurityBundle\Security;
```

### 5.4 Twig Templates

**Null checks for image editables:**

```twig
{# BEFORE #}
<img src="{{ pimcore_image('website-logo').getSrc() }}">

{# AFTER #}
{% set websiteLogo = pimcore_image('website-logo') %}
{% if websiteLogo %}
    <img src="{{ websiteLogo.getSrc() }}">
{% endif %}
```

---

## 6. Database Migrations

### Migration Summary

| Table | Change |
|-------|--------|
| `assets` | `customSettings` column: serialize → JSON |
| `notifications` | Added `isStudio` column |
| `classes` | Added `definitionModificationDate` column |
| `versions` | Added `public` column with index |

### Manual Database Fixes (if needed)

```sql
-- Fix collation if needed
ALTER DATABASE liberty COLLATE utf8mb4_unicode_520_ci;

-- Check for failed migrations
SELECT * FROM migration_versions WHERE executed_at IS NULL;
```

---

## 7. Troubleshooting

### 7.1 Common Errors

#### Error: "Class 'PimcoreTinymceBundle' not found"
**Solution:** Install tinymce-bundle
```bash
docker compose exec php composer require pimcore/tinymce-bundle:^2.0
```

#### Error: "pimcore.encryption.secret is not set"
**Solution:** Generate and add encryption secret (see Step 4)

#### Error: "Your product key is empty"
**Solution:** Complete product registration (see Step 5)

#### Error: "Syntax error" in templates with pimcore_image()
**Solution:** Add null checks (see Code Changes section)

#### Error: "Cannot load resource ... annotation"
**Solution:** Change routing type from `annotation` to `attribute`

#### Error: "JSON decode error" with assets
**Solution:** Run database migrations (see Step 8)

### 7.2 Cache Issues

```bash
# Full cache clear
docker compose exec php php bin/console cache:clear

# Remove cache directory manually
docker compose exec php rm -rf var/cache/*

# Warmup cache
docker compose exec php php bin/console cache:warmup
```

---

## 8. Post-Upgrade Testing

### 8.1 Admin Panel Tests

| Test | URL | Expected Result |
|------|-----|-----------------|
| Login | `/admin/login` | 200 OK |
| Dashboard | `/admin` | Loads without errors |
| Data Objects | `/admin/objects` | List and edit objects |
| Assets | `/admin/assets` | View and upload assets |
| Documents | `/admin/documents` | Edit pages |

### 8.2 Frontend Tests

| Test | Check |
|------|-------|
| Homepage | Loads without 500 errors |
| Navigation | All links work |
| Images | Thumbnails generate |
| Editables | All editables render |

### 8.3 API Tests (if applicable)

| Test | Endpoint |
|------|----------|
| Data Hub | `/pimcore-datahub-webservices` |
| Portal Engine | Portal frontend |
| REST API | Custom endpoints |

---

## 9. Rollback Plan

If upgrade fails:

```bash
# 1. Restore composer files
cp composer.json.backup-YYYYMMDD composer.json
cp composer.lock.backup-YYYYMMDD composer.lock

# 2. Restore config
cp -r config.backup-YYYYMMDD/* config/

# 3. Restore src (if needed)
cp -r src.backup-YYYYMMDD/* src/

# 4. Downgrade Docker image
# Edit docker-compose.yaml: php8.3 → php8.2

# 5. Restart containers
docker compose up -d

# 6. Restore database
mysql -u root -p liberty < liberty_backup_YYYYMMDD.sql

# 7. Reinstall vendors
docker compose exec php composer install

# 8. Clear cache
docker compose exec php php bin/console cache:clear
```

---

## Appendix A: Non-Docker / Shared Hosting Upgrade

If you're **not using Docker** (shared hosting with SSH access), follow these modified steps:

### A.1 Check PHP Version

```bash
# Check current PHP version
php -v

# If PHP < 8.3, contact hosting provider or use alternative PHP binary
# Some hosts provide: php83, php8.3, or /usr/bin/php8.3
which php83
```

### A.2 Backup Procedures

```bash
# Navigate to project root
cd /path/to/pimcore

# Backup database (using hosting provider's tools or mysqldump if available)
mysqldump -u DB_USER -p DB_NAME > backup_$(date +%Y%m%d).sql

# If mysqldump not available, use phpMyAdmin or hosting control panel

# Backup files
tar -czf backup_files_$(date +%Y%m%d).tar.gz composer.json composer.lock config/ src/ var/
```

### A.3 Update Composer Dependencies

```bash
# Ensure composer is available
which composer

# If composer not available, download it:
curl -sS https://getcomposer.org/installer | php
# Then use: php composer.phar instead of composer

# Update composer.json (manually edit or use sed)
# Then run update with memory limit
php -d memory_limit=-1 composer.phar update --no-interaction --no-dev

# Or if composer is available globally:
COMPOSER_MEMORY_LIMIT=-1 composer update --no-interaction
```

### A.4 Clear Cache Manually

```bash
# Remove cache directories manually
rm -rf var/cache/*
rm -rf var/tmp/*

# If permissions issues:
# Contact hosting support or use control panel file manager
```

### A.5 Run Commands Without Docker

Replace all `docker compose exec php` commands with direct PHP:

| Docker Command | Shared Hosting Equivalent |
|----------------|---------------------------|
| `docker compose exec php php bin/console ...` | `php bin/console ...` |
| `docker compose exec php composer ...` | `php composer.phar ...` or `composer ...` |
| `docker compose exec php vendor/bin/...` | `php vendor/bin/...` |

### A.6 Web Server Configuration

**For Apache (.htaccess):**

Ensure this is in your `public/.htaccess`:
```apache
# PHP 8.3 handler (if needed)
AddHandler application/x-httpd-php83 .php

# Or use:
SetEnv PHP_VERSION 8.3
```

**For PHP-FPM (if available):**
Create/Edit `.user.ini` in project root:
```ini
memory_limit = 2048M
max_execution_time = 300
```

### A.7 Common Shared Hosting Issues

| Issue | Solution |
|-------|----------|
| PHP version too old | Contact host to enable PHP 8.3, or use alternative PHP binary |
| Memory limit too low | Add to `.user.ini`: `memory_limit = 2048M` |
| Execution timeout | Add to `.user.ini`: `max_execution_time = 300` |
| No SSH access | Use hosting control panel file manager + phpMyAdmin |
| No composer | Upload `composer.phar` and use `php composer.phar` |
| Permission denied | Files should be owned by web server user (www-data, apache, etc.) |

### A.8 Step-by-Step for Shared Hosting

```bash
# 1. SSH into server
ssh user@your-hosting.com

# 2. Navigate to project
cd ~/public_html/pimcore

# 3. Enable maintenance mode (if available)
php bin/console pimcore:maintenance-mode:enable

# 4. Backup
cp composer.json composer.json.backup
tar -czf backup_$(date +%Y%m%d).tar.gz .

# 5. Update PHP version (via control panel or .htaccess)
# Check with: php -v

# 6. Update composer.json (manually or via sed)
# Edit composer.json with new versions

# 7. Run composer update
php -d memory_limit=-1 composer.phar update --no-interaction

# 8. Generate encryption secret
php vendor/bin/generate-defuse-key
# Add to config/config.yaml

# 9. Register product (follow URL, get key, add to config)
php bin/console cache:clear 2>&1 | grep license.pimcore.com

# 10. Update config files
# - config/config.yaml (encryption + registration)
# - config/routes.yaml (annotation → attribute)

# 11. Install assets
php bin/console assets:install

# 12. Run migrations
php bin/console doctrine:migrations:migrate --prefix=Pimcore\\Bundle\\CoreBundle

# 13. Clear cache
rm -rf var/cache/*
php bin/console cache:clear

# 14. Disable maintenance mode
php bin/console pimcore:maintenance-mode:disable
```

### A.9 File Permissions on Shared Hosting

```bash
# Set correct permissions (common for shared hosting)
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
chmod -R 775 var/
chmod -R 775 public/var/

# If you get permission errors, contact hosting support
# They may need to run these commands as the web server user
```

---

## 10. Known Issues (WIP)

### Open Issues

1. **Asset thumbnails missing** - Expected in local dev without asset files
2. **ImageMagick decode errors** - Some formats may need delegates
3. **CookieConsent routes** - May need route definitions added

### Pending Tasks

- [ ] Test on staging environment
- [ ] Verify all custom bundles work
- [ ] Test Data Hub configurations
- [ ] Test Portal Engine search
- [ ] Performance testing
- [ ] Production deployment plan

---

## References

- [Pimcore 12 Upgrade Notes](https://docs.pimcore.com/platform/Pimcore/Installation_and_Upgrade/Updating_Pimcore/V11_to_V12/)
- [CVE-2026-27461 Details](https://nvd.nist.gov/vuln/detail/CVE-2026-27461)
- [Platform Version 2025.4](https://docs.pimcore.com/platform/Platform_Version/Platform_Version_Releases/2025.4)
- [Symfony Freeze Bundle](https://docs.pimcore.com/platform/Platform_Version/Platform_Version_Releases/2025.4#symfony-freeze-bundle)

---

**Document Version:** 1.0  
**Author:** Development Team  
**Status:** Work In Progress
