Skip to main content
Version: Next

Upgrade Notes

Pimcore 2026.3.0

[General]

  • [DataObject] Class definition files are now cached in-process by Pimcore\Model\DataObject\ClassDefinition\DefinitionFileCache (@internal), so clearing the runtime cache in long-running scripts no longer re-includes the definition file on every ClassDefinition::getById() call (previously very slow and eventually failing with "Too many open files"). The cache is validated against the definition file's modification time and is invalidated whenever Pimcore writes or deletes a definition file. Behavioral note: after RuntimeCache::clear() (or Pimcore::collectGarbage()), getById()/getByName() may now return the same ClassDefinition instance as before the clear (instead of a freshly included copy) as long as the definition file is unchanged — unsaved in-memory modifications of a class definition are therefore no longer discarded by a runtime cache clear. Use ClassDefinition::getById($id, force: true) to force a fresh include from disk.
  • [Composer] Bumped minimum requirements of scheb/2fa-bundle and scheb/2fa-google-authenticator to 8.6.1 and of phpdocumentor/reflection-docblock to 5.6.7 (5.x line) / 6.0.3 (6.x line). These are floor raises within the majors already required since 2026.1.0 and carry no BC impact of their own (see the 2026.1.0 notes below for the major-version upgrade guidance).

[Console]

  • [Commands] pimcore:bundle:install and pimcore:bundle:uninstall now provide shell completion for the bundle argument, suggesting the names of the active Pimcore bundles (bundles loaded by the kernel; registered-but-disabled bundles are not included). Completion requires a one-time setup for your shell — see Console Completion.
  • [Commands] The pimcore:bundle:* commands were refactored to the #[AsCommand] attribute. The @internal AbstractBundleCommand changed accordingly: buildName() was removed and configureDescriptionAndHelp() was replaced by configureBundleHelp(), which derives the help text from the attribute description; completeBundleArgument() was added. The @internal helper Pimcore\Bundle\CoreBundle\Command\Bundle\Helper\PostStateChange is now a readonly class; a subclass must itself be declared readonly.
  • [Commands] The table output of pimcore:bundle:list uses the narrow I? / UI? column headers (with an explaining legend) again instead of Installable / Uninstallable. The --json output is unchanged and keeps the full key names.

[Maintenance]

  • [DataObjects] The cleanupBrickTables and cleanupFieldcollectionTables maintenance tasks now drop orphaned object_brick_* / object_collection_* tables (including their data) when no existing brick/fieldcollection definition owns them - previously such tables were only reported as an error on every maintenance run. This affects installations where definitions were removed on the filesystem (e.g. by deleting var/classes/*.php files during a deployment) instead of through the regular delete path. A table is only dropped when no candidate parse of its name resolves to a live definition, and the tasks do nothing when the class definition directory itself is unavailable; still, make sure the definitions in var/classes/ are in sync with your database before running maintenance. See Cleanup Data Storage.
  • The @internal Pimcore\Maintenance\Tasks\DataObject\DataObjectTaskHelperInterface changed: getCollectionNames(string $dir) was replaced by getObjectBrickCollectionNames() / getFieldcollectionCollectionNames(), and matchCollectionKeys(), dropOrphanedTable() were added; cleanupTable() now returns bool.

[GenericExecutionEngine]

  • [JobRun] Log entries stored in the generic_execution_engine_job_run.log column are now delimited by a short versioned frame (a version token wrapped in ASCII record separators, 0x1E) instead of a newline, so a newline that belongs to a single (multi-line) log message is no longer mistaken for an entry boundary. The version token is framed rather than using a bare 0x1E so that legacy payloads, which were stored verbatim and may already contain a stray 0x1E, are never split on such a byte. Logs written in the previous newline-delimited format are still read on a best-effort basis, so no migration is required. The parsing of the column has moved from JobRun::getLogs() into the new @internal Pimcore\Bundle\GenericExecutionEngineBundle\Utils\LogParser, and the @internal value object LogLine now takes the timestamp and message as separate constructor arguments and no longer exposes appendLogLine().

[Assets]

  • [Thumbnails] The cache lifetime used for the Cache-Control and Expires HTTP headers when a thumbnail is delivered on-the-fly through the thumbnail service is now configurable via pimcore.assets.thumbnails.cache_lifetime (in seconds). It defaults to 604800 (one week), which preserves the previous hard-coded behavior.
  • Added a new optional $parameters argument to Asset::saveVersion() to allow passing custom arguments to the PRE_UPDATE / POST_UPDATE / POST_UPDATE_FAILURE versioning events, analogous to Concrete::saveVersion(). To stay backwards-compatible for classes overriding saveVersion(), the argument is documented in the docblock but not yet part of the method signature (it is read via func_get_arg()); it will become a regular signature parameter in the next major version.

[Documents]

  • [Renderlets] Custom renderlet configuration parameters are now passed to renderlet controllers as query parameters. Accessing these custom parameters via request attributes is deprecated and will be removed in Pimcore 2027. Update custom renderlet controllers from $request->attributes->get('myParam') to $request->query->get('myParam').

[DataObject]

  • [Relations] The ownername column has been widened from VARCHAR(70) to VARCHAR(190) in the per-class relation tables (object_relations_*), the advanced-relation metadata tables (object_metadata_*) and object_url_slugs. The generated ownername for a localized field nested inside an object brick or field collection (e.g. /objectbrick~<field>/<brickKey>/localizedfield~localizedfield) can exceed 70 characters, which caused "Data too long for column 'ownername'" on save under strict SQL mode. Existing installations are updated automatically by the migration Version20260721000000; no code or configuration changes are required.

[Database]

  • Several columns using the deprecated, ambiguous utf8/utf8_bin/utf8_general_ci charset/collation names have been modernized in install.sql: lock_keys, assets_image_thumbnail_cache.filename, search_backend_data.key, tags.name, properties.cpath and users_workspaces_asset/document/object.cpath now use real utf8mb4. assets.filename/path and documents.key/path move to the explicit utf8mb3 name instead (their composite fullpath index already uses the full 3072-byte InnoDB index-prefix budget at 3 bytes/char and would overflow it at 4 bytes/char) - note MySQL has deprecated utf8mb3 itself too, so this remains a known limitation pending a future index/schema redesign, not a fully modernized state. Existing installations are updated automatically by the migration Version20260729120000; no code or configuration changes are required.
    • This migration only touches a column when its current collation and length still match the stock legacy definition; a column a project has already widened or otherwise customized is left untouched (a notice is logged) instead of being silently reset.
    • The migration is irreversible (down() throws) — reverting utf8mb4 columns back to utf8/utf8mb3 could silently replace stored 4-byte characters (e.g. emoji) with ? given this application's intentionally permissive sql_mode=''. Restore from a backup if you need to roll back.
    • The ALTER TABLE/CONVERT TO CHARACTER SET statements rewrite the affected columns' storage and typically run as full table rebuilds, which can take time and hold locks on assets, documents, objects and properties on large installations — plan to run this migration during a maintenance window on such installs.

Pimcore 2026.2.12

[Documents]

  • [Areabricks] In editmode, areabrick names and descriptions are now translated via the studio translation domain whenever that domain is registered (i.e. Pimcore Studio is installed), so these UI labels show up in Studio's translations instead of the website's messages domain. Labels that were already translated in the messages domain keep working as a read-only fallback, but missing keys are no longer auto-created there - they are created in the studio domain instead. Installations without the studio domain keep translating them via messages as before.

[Routing]

  • [Pimcore Context] The route matcher is no longer accepted under pimcore.context.<name>.routes (e.g. { route: my_api }); config using it now fails with Unrecognized option "route" under "pimcore.context.<name>.routes.0". Available options are "host", "methods", "path". Matching a Pimcore context by route name never actually worked: the context is resolved (and cached on the request) by several kernel.request listeners - CustomAdminEntryPointCheckListener, RoutingListener, FullPageCacheListener - that all run before Symfony's own RouterListener populates the request's _route attribute, so a route: entry could never match and the request silently fell back to the default context. If you have such a config, switch to a path: (optionally combined with host:/methods:) matcher instead:
    pimcore:
    context:
    api:
    routes:
    - { path: ^/my/api/ }
    This does not affect pimcore.web_profiler.toolbar.excluded_routes, which still supports route:.

Pimcore 2026.2.5

[Custom Reports]

  • Added a pimcore_custom_reports.enabled_adapters config option to enable/disable individual Custom Report data-source adapters (e.g. the built-in sql adapter) per project. Adapters not listed default to enabled; a disabled adapter is removed from the shared pimcore.custom_report.adapter.factories service locator, so it becomes unavailable to every consumer (the classic admin controller and the Studio backend bundle alike).
    pimcore_custom_reports:
    enabled_adapters:
    sql: false
    We recommend disabling the built-in sql adapter unless specifically needed: any user with the reports_config permission can otherwise define arbitrary SELECT statements against the application's database, including tables never intended to be exposed. See Custom Reports for details.

[Assets]

  • [Assets] New opt-in asset storage operation queue (pimcore.assets.storage_operation_queue.enabled, default false): when enabled, moving or deleting asset folders no longer performs the physical per-object storage operations synchronously. Instead the operation is recorded and the storage adapter transparently resolves reads until the new pimcore:assets:storage-queue:process command (which must be scheduled, e.g. as a nightly cron) applies it. Backends with native directory rename (local filesystem, SFTP) keep renaming natively — including their thumbnails and asset_cache renditions, for free; object storages (S3, Azure, GCS) get O(1) folder moves for originals, while their derived content (thumbnails, asset_cache) is instead cleaned up and regenerated on demand rather than moved. pimcore:assets:storage-queue:status reports pending operations and warns about stale rows. See Asset Storage Operation Queue for semantics and caveats. Disabled by default — no behavior change unless enabled.
  • [Thumbnails] Automatic clipping (pimcore.assets.image.thumbnails.clip_auto_support) is only applied again when the image actually carries a clipping path, i.e. the Photoshop image resource 8BIM 2999 ('Name of clipping path'). Images that only contain saved Photoshop paths (8BIM 2000 - 2998) are no longer clipped, since clipping them by an arbitrary path removed their whole content and resulted in empty thumbnails. Existing thumbnails of such images have to be cleared to be re-generated.

Pimcore 2026.2.0

[General]

  • [Assets][Thumbnails][CDN] New core events for thumbnail-config lifecycle were introduced to support CDN purge integration:

    • Pimcore\Event\ImageThumbnailConfigEvents::POST_UPDATE and POST_DELETE
    • Pimcore\Event\VideoThumbnailConfigEvents::POST_UPDATE and POST_DELETE
    • Payload classes: Pimcore\Event\Model\Asset\Image\Thumbnail\ConfigEvent and Pimcore\Event\Model\Asset\Video\Thumbnail\ConfigEvent (both expose getConfig(): Config).
    • The events are dispatched from Asset\Image\Thumbnail\Config\Dao::save()/delete() and Asset\Video\Thumbnail\Config\Dao::save()/delete() after the underlying settings store and cache writes succeed. They fire on every save/delete path, including admin UI, API and programmatic changes (the magic __call delegation also routes through the Dao). Subscribers can use them to react to thumbnail-pipeline changes — Pimcore's bundled CdnPurgeListener uses them to dispatch thumb-{configName} CDN purges.
  • [Composer] Bumped minimum requirement of friendsofsymfony/jsrouting-bundle to 3.6.0.

[Maintenance Mode]

  • The legacy file-based maintenance mode backward-compatibility layer has been fully removed. The following deprecated static methods have been deleted from Pimcore\Tool\Admin:

    • getMaintenanceModeFile()
    • getMaintenanceModeScheduleLoginFile()
    • activateMaintenanceMode()
    • deactivateMaintenanceMode()
    • isInMaintenanceMode()
    • isMaintenanceModeScheduledForLogin()
    • scheduleMaintenanceModeOnLogin()
    • unscheduleMaintenanceModeOnLogin()

    If you are still using any of these methods, replace them with the Pimcore\Maintenance\Mode\MaintenanceModeHelperInterface service. Inject it via the service container and use activate(), deactivate(), and isActive() instead.

  • The BC fallback that read the legacy maintenance.php configuration file has also been removed from MaintenanceModeCommand, MaintenancePageListener, Bootstrap, and Console\Application. Any existing var/config/maintenance.php file will be ignored. Ensure maintenance mode is managed exclusively through MaintenanceModeHelperInterface.

  • Admin::getMinimizedScriptPath() has been removed from Pimcore\Tool\Admin.

[Translations]

  • Translation::DOMAIN_ADMIN constant is deprecated since 2026.2 and will be removed in a future release. Avoid referencing the admin translation domain directly.
  • Translator::$adminPath and Translator::$adminTranslationMapping properties are deprecated since 2026.2.

Pimcore 2026.1.0

Tasks to Do Prior the Update

Symfony 7.3+ Required

Pimcore 13 requires Symfony 7.3 or higher. All Symfony 6.x components are no longer supported.

  1. Update all Symfony components to version 7.3 or higher
  2. Test your application thoroughly with Symfony 7.x
  3. Remove any explicit Symfony 6.x version constraints from your composer.json

Migrate Folder structure for email logs

The folder structure for email logs has changed to YYYY/MM/DD/<log filename>. Please execute the command pimcore:migrate:mail-logs-folder-structure to move the files into the new folder structure or move the files manually.

Database Collation: utf8mb4_unicode_520_ci

Pimcore 2026.1 now explicitly uses utf8mb4_unicode_520_ci as the collation for all utf8mb4 tables and columns. Previous versions specified DEFAULT CHARSET=utf8mb4 without an explicit COLLATE clause in install.sql and Dao CREATE TABLE statements. Due to MySQL/MariaDB behavior, this caused the charset's built-in default collation (for example utf8mb4_general_ci on MySQL 5.7 / MariaDB or utf8mb4_0900_ai_ci on MySQL 8) to be used instead of the database-level default (utf8mb4_unicode_520_ci).

This mismatch can cause issues with foreign key constraints between tables that have different collations and may lead to unexpected sorting behavior.

Important: Some columns intentionally use a different collation (e.g. utf8mb4_bin for case-sensitive keys and JSON data). These columns must not be changed. The queries below only target columns that use the typical default, non-binary utf8mb4 collations (utf8mb4_general_ci and utf8mb4_0900_ai_ci); adjust this list if your server uses a different default.

Use the following SQL to identify all affected tables and columns in your database:

-- List all tables with default non-binary utf8mb4 table collations
SELECT TABLE_NAME, TABLE_COLLATION
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_SCHEMA = 'your_database_name'
AND TABLE_TYPE = 'BASE TABLE'
AND TABLE_COLLATION IN ('utf8mb4_general_ci', 'utf8mb4_0900_ai_ci')
ORDER BY TABLE_NAME;

-- List all columns with default non-binary utf8mb4 collations
SELECT TABLE_NAME, COLUMN_NAME, COLUMN_TYPE, COLLATION_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'your_database_name'
AND COLLATION_NAME IN ('utf8mb4_general_ci', 'utf8mb4_0900_ai_ci')
ORDER BY TABLE_NAME, COLUMN_NAME;

[General]

  • Thumbnail generation adapters now implement Video\AdapterInterface and Document\AdapterInterface and improved adapters' initialization.
  • The abstract class Pimcore\Video\Adapter has been removed. Pimcore\Video\Adapter\Ffmpeg now directly implements Pimcore\Video\AdapterInterface. If you extended Pimcore\Video\Adapter, update your class to implement Pimcore\Video\AdapterInterface directly.
  • The hard-coded space between quantity value and unit has been removed for class definition quantity fields (e.g. AbstractQuantityValue, QuantityValueRange). Spacing and formatting between value and unit is now controlled by locale- and translation-based formatting instead of being fixed in the code.
  • The reset_password rate limiter configuration has been moved to the studio-backend bundle and is no longer part of Pimcore's core configuration.
  • Removed legacy Admin UI (Classic UI) EditmodeListener.
  • Added support for PHP 8.5 and bumped minimum requirement of Symfony to 7.4.
  • Dropped support for PHP 8.3 and Symfony 6.
  • [QuantityValue] Introduced foreign key constraints on __unit columns in object store, query, localized, objectbrick and fieldcollection tables for QuantityValue, InputQuantityValue and QuantityValueRange fields. These constraints reference quantityvalue_units(id) with ON DELETE SET NULL and ON UPDATE CASCADE, ensuring referential integrity. The migration automatically cleans up orphaned unit references (setting them to NULL) and changes the __unit column type from varchar(64) to varchar(50) to match the referenced quantityvalue_units.id column. If you have custom unit IDs longer than 50 characters, they will be truncated.

Composer Dependency Majors: scheb/2fa 8.x and phpdocumentor/reflection-docblock 6.x

  • [Composer] The constraint for scheb/2fa-bundle and scheb/2fa-google-authenticator has been raised from ^6.0 || ^7.5 to ^8.4. Projects still on scheb/2fa 6.x/7.x are forced onto 8.x when upgrading. Pimcore's own integration (Pimcore\Security\User\User, the scheb_two_factor.security.google_authenticator service and the admin 2FA flow) is fully compatible — no action is needed for standard setups. Review the following if your project extends 2FA (see also the official upgrade guide):
    • The priority of the two-factor authenticator has changed from 0 to -100. If you register custom security authenticators on a 2FA-protected firewall and rely on their order relative to the two-factor authenticator, re-check and adjust their priority.
    • With Symfony 7.4, passing an associative options array to the UserTotpCode and UserGoogleTotpCode validator constraints is deprecated; Symfony 8 no longer accepts it. Pass named constructor arguments instead (e.g. new UserTotpCode(message: '...')).
    • getGoogleAuthenticatorUsername() and getTotpAuthenticationUsername() on the scheb model interfaces may now return null; code consuming these values should handle it.
    • scheb/2fa 8.x requires PHP >= 8.4, which matches Pimcore's platform requirement, and pulls in spomky-labs/otphp 11.x transitively.
  • [Composer] The constraint for phpdocumentor/reflection-docblock has been widened from ^5.2 to ^5.6 || ^6.0, so a composer update may resolve to 6.x. Pimcore does not use this library directly (it is a transitive dependency of the Symfony PropertyInfo/Serializer components, which support 6.x), so no action is needed unless your own code parses docblocks with it. In that case, note for 6.x (see the upgrade guide):
    • The static ::create() factory methods on tag classes (e.g. Param::create(), Method::create()) have been removed; create tags through StandardTagFactory::createInstance() instead. StandardTagFactory can no longer be instantiated directly with new.
    • Method::getArguments() has been removed.
    • phpdocumentor/type-resolver is bumped to 2.0, which replaces the legacy Collection type handling with real generics support (e.g. Collection<MyClass>).
    • If your project is not ready for 6.x, you can keep the 5.x line by requiring "phpdocumentor/reflection-docblock": "^5.6" in your project's composer.json.

Removed deprecated and discontinued bundles

The following bundles have been removed:

  • GlossaryBundle
  • SimpleBackendSearchBundle
  • SeoBundle: dropped http_error_log feature and DB Table, and removed Document SEO Editor
  • StaticRouteBundle
  • WordExportBundle
  • XliffBundle

[DataObjects]

  • Add a new optional $parameters argument to Concrete::saveVersion() to allow passing of arguments to events.
  • Removed the following methods from Pimcore\Model\DataObject\Service as part of the Admin UI removal:
    • calculateCellValue()
    • mapFieldname()
    • getDataForEditmode() in ManyToManyObjectRelation and AdvancedManyToManyObjectRelation now uses Element\Service::gridElementData() instead of the removed Service::gridObjectData(). As a result, the returned data for each related object no longer includes computed grid columns (e.g. brick fields, localized fields, classification store values, or helper columns). Only the base element data (id, type, path, etc.) is returned. If you rely on additional field data being present in the editmode payload of these relations, you need to fetch it separately.

[Database]

  • All utf8mb4 tables now use utf8mb4_unicode_520_ci as their default collation to match Doctrine's default_table_options configuration. Columns inherit this collation unless a different one is explicitly defined (for example utf8mb4_bin for case-sensitive keys). Previously, install.sql and Dao CREATE TABLE statements specified DEFAULT CHARSET=utf8mb4 without an explicit COLLATE clause, which caused MySQL/MariaDB to assign the charset's built-in default collation (utf8mb4_general_ci) instead of the intended utf8mb4_unicode_520_ci. Existing installations need to update the collation of their tables and columns manually, details see 'Tasks to Do Prior the Update' chapter above.

[Models]

  • Added a new optional $parameters argument to AbstractUser::save() and AbstractUser::delete(), as well as their interface methods, to allow passing of arguments to UserRoleEvent.

[Generic Execution Engine]

  • Added an $offset parameter to support paging in getRunningJobsByUserId() in JobRunRepositoryInterface.
  • Added the possibility to pass an optional $criteria array to getTotalCount(), getJobRunById(), getJobRunsByUserId() and getRunningJobsByUserId() in JobRunRepositoryInterface.
  • Added the possibility to pass optional $ownerId and $executionContext parameters to getTotalCount().

[Installer]

The installer has been completely redesigned with a profile-based architecture. The old individual CLI options for pimcore:install have been removed, and the command now uses a profile-driven setup via --install-profile.

New Command Invocation

The installer is now invoked with:

vendor/bin/pimcore-install --install-profile=App\\Install\\MyProfile

If you have scripts or CI pipelines that invoke pimcore:install with the old options, update them to use --install-profile with a profile class. Create an install profile implementing InstallProfileInterface for your project.

Please see the documentation for further information: https://docs.pimcore.com/platform/

Removed CLI Options

The following CLI options have been removed:

Old OptionReplacement
--mysql-host-socketUse DATABASE_URL env var (Doctrine DSN format)
--mysql-usernameUse DATABASE_URL env var
--mysql-passwordUse DATABASE_URL env var
--mysql-databaseUse DATABASE_URL env var
--mysql-portUse DATABASE_URL env var
--mysql-ssl-cert-pathUse DATABASE_URL env var
--encryption-secretUse PIMCORE_ENCRYPTION_SECRET env var directly
--instance-identifierUse PIMCORE_INSTANCE_IDENTIFIER env var directly
--product-keyUse PIMCORE_PRODUCT_KEY env var directly
--install-bundlesBundles are now defined by the install profile's getBundles() method
--skip-database-structureUse InstallStepFilterInterface in your profile to skip steps
--skip-database-dataUse InstallStepFilterInterface in your profile to skip steps
--skip-database-data-dumpUse InstallStepFilterInterface in your profile to skip steps
--skip-database-configConfiguration is now written to .env.local; to avoid writing installer-generated config, use InstallStepFilterInterface to skip WriteEnv and, if needed, WriteDoctrineConfig
--skip-product-registration-configConfiguration is now written to .env.local; to avoid writing installer-generated config, use InstallStepFilterInterface to skip WriteEnv
--only-stepsUse InstallStepFilterInterface to control which InstallStep enum values are skipped

The options --admin-username and --admin-password are retained but now also accept the env vars PIMCORE_ADMIN_USER and PIMCORE_ADMIN_PASSWORD respectively.

New CLI Options

New OptionDescription
--install-profile(Required) FQCN of the install profile class implementing InstallProfileInterface
--env-definitionFQCN(s) of additional EnvVarDefinitionInterface implementations (repeatable)
--post-install-commandsFQCN(s) of PostInstallCommandsProviderInterface implementations (repeatable)
--skip-validationSkip env var validation (no value = skip all; with value = skip by key/class/FQCN)

Removed Environment Variables (PIMCORE_INSTALL_* Prefix)

All PIMCORE_INSTALL_* environment variables have been removed. The old installer derived env vars by prepending PIMCORE_INSTALL_ to the uppercased option name. These are replaced by standard env var names:

DATABASE_URL uses Doctrine DSN syntax and can describe either a TCP connection (for example mysql://user:pass@host:3306/dbname) or a unix socket connection.

Old Env VarNew Env Var
PIMCORE_INSTALL_ADMIN_USERNAMEPIMCORE_ADMIN_USER
PIMCORE_INSTALL_ADMIN_PASSWORDPIMCORE_ADMIN_PASSWORD
PIMCORE_INSTALL_MYSQL_HOST_SOCKETDATABASE_URL (Doctrine DSN format, e.g. mysql://user:pass@localhost/dbname?unix_socket=/var/run/mysqld/mysqld.sock)
PIMCORE_INSTALL_MYSQL_USERNAMEDATABASE_URL
PIMCORE_INSTALL_MYSQL_PASSWORDDATABASE_URL
PIMCORE_INSTALL_MYSQL_DATABASEDATABASE_URL
PIMCORE_INSTALL_MYSQL_PORTDATABASE_URL
PIMCORE_INSTALL_MYSQL_SSL_CERT_PATHDATABASE_URL
PIMCORE_INSTALL_ENCRYPTION_SECRETPIMCORE_ENCRYPTION_SECRET
PIMCORE_INSTALL_INSTANCE_IDENTIFIERPIMCORE_INSTANCE_IDENTIFIER
PIMCORE_INSTALL_PRODUCT_KEYPIMCORE_PRODUCT_KEY
PIMCORE_INSTALL_INSTALL_BUNDLESRemoved — bundles are defined by the profile

Configuration Output Changes

The installer no longer writes the former local/user configuration YAML files for database and product registration settings. These values are now written to .env.local using Symfony Flex-style section markers (###> section-name ### / ###< section-name ###). The installer still generates config/packages/doctrine_mapping_types.yaml for Doctrine mapping types.

Old Config FileNew Location
config/local/database.yaml.env.local (DATABASE_URL)
config/local/product_registration.yaml.env.local (PIMCORE_ENCRYPTION_SECRET, PIMCORE_INSTANCE_IDENTIFIER, PIMCORE_PRODUCT_KEY)
system.yml / system.template.ymlLegacy system config files are no longer written by the installer

Removed Classes and Events

  • Pimcore\Bundle\InstallBundle\SystemConfig\ConfigWriter — removed (no more YAML config writing)
  • Pimcore\Bundle\InstallBundle\Event\BundleSetupEvent — removed (bundles are now defined by the profile)
  • Pimcore\Bundle\InstallBundle\DependencyInjection\Configuration — removed (no more pimcore_install.parameters.database_credentials config tree)
  • The config/installer.yaml option pimcore_install.parameters.database_credentials is no longer supported. Use DATABASE_URL env var or the interactive installer prompts instead.

Two-Phase Architecture

The installer now runs in two phases:

  1. Phase 1 (lightweight InstallerKernel): Collects and validates all env vars from the profile's EnvVarDefinitionInterface implementations, writes .env.local, writes Doctrine config.
  2. Phase 2 (real App\Kernel): Sets up the database, imports data sources, creates admin user, registers and installs bundles, runs post-install commands.

Profile Extensibility

Install profiles can implement additional interfaces for advanced control:

  • InstallStepFilterInterface — skip specific install steps (useful for PaaS environments)
  • PostInstallHookInterface — run custom PHP code near the end of phase 2, before finalization steps such as cache clearing and install marker cleanup
  • DataSourceInterface — import SQL dumps or other data during installation

Doctrine enum Mapping Type Removed

The enum: string Doctrine mapping type is no longer registered; only the bit: boolean mapping remains. The minimum doctrine/dbal requirement was raised to ^4.4, where the explicit enum mapping is no longer needed.

Action required for existing installations: Please check your Doctrine configuration for any registered mapping types and remove the enum: string mapping if it is present.

[OpenSearch / Elasticsearch DSN Configuration]

Search engine configuration now uses DSN-based env vars instead of separate host/port/authentication parameters:

  • OpenSearch: PIMCORE_OPENSEARCH_DSN=opensearch://admin:admin@localhost:9200?ssl=true
  • Elasticsearch: PIMCORE_ELASTICSEARCH_DSN=elasticsearch://elastic:changeme@localhost:9200

The DSN is parsed at runtime in the client factory. The old configuration approach with separate hosts arrays in YAML is replaced by a single dsn config option.

[Messenger Transport DSN Changes]

This is a breaking change for existing installations.

The PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX env var (previously named PIMCORE_MESSENGER_TRANSPORT_DSN) format has changed. It must now include a trailing separator for queue name concatenation, because Pimcore bundle configs append queue names directly to this value.

Before (old format):

PIMCORE_MESSENGER_TRANSPORT_DSN=doctrine://default

After (new format):

# Doctrine
PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX=doctrine://default?queue_name=

# AMQP
PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX=amqp://guest:guest@rabbit:5672/%2f/

# Redis
PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX=redis://localhost:6379/

All Pimcore bundle transport configs now use the container parameter %pimcore.messenger.transport_dsn_prefix% with direct concatenation:

framework:
messenger:
transports:
pimcore_core: '%pimcore.messenger.transport_dsn_prefix%pimcore_core'
pimcore_maintenance: '%pimcore.messenger.transport_dsn_prefix%pimcore_maintenance'

A container-level default is provided in bundles/CoreBundle/config/pimcore/default.yaml:

parameters:
env(PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX): 'doctrine://default?queue_name='

If you have PIMCORE_MESSENGER_TRANSPORT_DSN_PREFIX (or the old PIMCORE_MESSENGER_TRANSPORT_DSN) explicitly set in your .env or environment, update its value to include the trailing separator and use the new name. For Doctrine, change doctrine://default to doctrine://default?queue_name=. Failure to do this will result in invalid transport DSNs like doctrine://defaultpimcore_core. If you have custom transport definitions in your bundle or project YAML that hardcode doctrine://default?queue_name=, replace them with '%pimcore.messenger.transport_dsn_prefix%' concatenation to support backend-agnostic transport switching.

Symfony Templating Component Removed

The Symfony\Component\Templating\EngineInterface and related services have been completely removed.

What's Removed:

  • pimcore.templating.engine.delegating service
  • Symfony\Component\Templating\EngineInterface support
  • Pimcore\Templating\TwigDefaultDelegatingEngine class

Action Required: Update your code to use Twig\Environment directly instead of EngineInterface.

QuantityValue Formatting Changes

  • The space between QuantityValue value and unit is going to be removed. Please make sure any custom code that relies on the space is updated accordingly.

Deprecated Kernel Extension Hooks

Overriding Pimcore\Kernel::configureContainer() and Pimcore\Kernel::configureRoutes() is now deprecated and will be removed in Pimcore 2027.1.

These methods were exposed as protected on Pimcore\Kernel via trait aliases of Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait's private methods. Symfony does not consider them part of its public API and has changed their signatures between minor versions, which can break Pimcore subclasses on Symfony upgrades.

Migration:

Replace overrides of configureContainer() with one of the following stable, public extension points:

  • Move container configuration to config/packages/*.yaml (or config/packages/<env>/*.yaml).
  • Add a compiler pass for programmatic container manipulation.
  • Override the public Pimcore\Kernel::registerContainerConfiguration(LoaderInterface $loader) method directly. Call parent::registerContainerConfiguration($loader) first, then load additional configuration via $loader->load(...).

Replace overrides of configureRoutes() with one of the following:

  • Move routes to config/routes/*.yaml.
  • Register a custom routing loader as a service tagged routing.loader.