Upgrade Notes
The following steps are necessary when updating to newer versions.
Upgrade to 2026.4.0
Save and Delete Hooks Check Only the Affected IDs
The data object save and delete hooks, the "Deliver directly on save" check and the cleanup of outdated rows in the
exported file no longer load the IDs of the whole class. They query the configured listing restricted to the IDs
concerned (id IN (...)), so the cost no longer grows with the number of objects of the class. ExporterInterface and
getRelevantItemIds() are unchanged and still return the full list for bulk exports. Behaviour that differs:
RelevantItemListEventis dispatched for the checks of single items as well, once per 1000 IDs. The listing the listeners get is not restricted to the IDs yet; the restriction is added after the listeners ran. A listener must not rely on the listing holding all items of the class. A listener that sets a limit or an offset keeps the previous behaviour (the full list is loaded and checked).- For save hook types other than
queue, the queue items of the configuration are now deleted only for the saved relevant IDs, not for all relevant IDs of the class on any save. AbstractExporterhas two new public methods,filterRelevantItemIds(array $ids)andfilterLegalItemIds(array $ids), andDataObjectSubscriberfive new protected static methods (getConfigurations(),filterRelevantItemIds(),deleteQueueItems(),saveQueueItem(),queueDeletedElement()). A subclass that declares a method of the same name needs to rename it. Exporters that do not extendAbstractExporterare checked against their fullgetRelevantItemIds()list as before.
Flysystem Storages Are Injected
The bundle's services no longer look up their storages with Pimcore\Tool\Storage::get(); it remains only as a
fallback for subclasses constructed without the new argument (see below). The export storages pimcore.dataHubFileExport.storage
and pimcore.dataHubFileExport.temp.storage are injected into the services that use them:
- The exporter types (
CSV,XML,JSON, based onExporter\Type\AbstractExporter) get the temporary storage as a new optional last constructor argument?FilesystemOperator $tmpStorage = null.Exporter\File::getFileStorage()returns it (types that only implementExporterTypeInterfacestill use theStorage::get()lookup there). The storage is kept in a private property, so custom exporter types are free to declare members of their own. - The
Downloadtransmitter gets the download storage as a new optional last constructor argument?FilesystemOperator $downloadStorage = null.
Both arguments are optional: a subclass that is registered without them keeps working and falls back to the
Pimcore\Tool\Storage lookup as before. If your subclass overrides the constructor, add the argument and pass it to
parent::__construct(); custom service definitions should set $tmpStorage / $downloadStorage as the bundle's
services.yml does. DownloadController and ConfigurationEventSubscriber are @internal and now take the download
storage as constructor argument.
generateAssetData() Deprecated
AbstractExporter::generateAssetData() and File::generateAssetData() are deprecated and will be removed in 2027.1.
Since the output-formatter refactoring in 2026.1 they are no longer called by the bundle: asset relations are formatted
by the services tagged pimcore.datahub.fileExport.output_formatter. A custom exporter that overrides
generateAssetData() still runs, but its override has had no effect on the exported data since 2026.1. Calling the
method now triggers a deprecation.
Migrate such an override to an output formatter. Implement
Pimcore\Bundle\DataHubFileExportBundle\Exporter\OutputFormatter\OutputFormatterInterface for the column type
whose representation you customised (image, hotspotimage, imageGallery or video) and tag the service:
<?php
namespace App\DataHubFileExport\OutputFormatter;
use Pimcore\Bundle\DataHubFileExportBundle\Exporter\OutputFormatter\OutputFormatterInterface;
class MyImageFormatter implements OutputFormatterInterface
{
public function getType(): string
{
return 'image';
}
public function format(string $value, string $configName, array $config): mixed
{
// $value is the raw grid cell value of the column (the frontend path of the asset), $config is the
// export configuration, so the schema's thumbnail settings are available as
// $config['schema']['thumbnailImage']. Return the value that should be written to the file.
}
}
App\DataHubFileExport\OutputFormatter\MyImageFormatter:
tags:
- { name: "pimcore.datahub.fileExport.output_formatter", priority: -1 }
Formatters are indexed by getType() and the last one registered for a type wins. The shipped formatters use the
default priority 0, and services of the same priority are iterated in container definition order, where bundle
services come after application services. The negative priority makes your formatter come last so that it replaces
the shipped one. See Customize and Extend.
Frontend-Aware Asset URLs
All exported asset URLs are now built with the frontend-aware core API, so that
pimcore.assets.frontend_prefixes is applied and the pimcore.frontend.path.asset /
pimcore.frontend.path.asset.image.thumbnail / pimcore.frontend.path.asset.video.thumbnail frontend events are
dispatched (CDN listeners). Previously this was only the case for image originals; hotspot images, video originals and
thumbnails bypassed the prefixes.
The shape of the emitted URL changes for these values:
| Exported value | Before | Now |
|---|---|---|
| Hotspot image (original) | raw storage path, no prefix | frontend_prefixes.source + path, pimcore.frontend.path.asset dispatched |
| Video (original) | raw storage path, no prefix | frontend_prefixes.source + path, pimcore.frontend.path.asset dispatched |
| Image thumbnail | Tool::getHostUrl() + path | prefix-aware path, no host |
| Hotspot image thumbnail | path without any prefix | frontend_prefixes.thumbnail (or thumbnail_deferred) + path |
| Video thumbnail | path relative to the asset's folder, no prefix, not URL-encoded | frontend_prefixes.thumbnail + URL-encoded full path, pimcore.frontend.path.asset.video.thumbnail dispatched |
Video thumbnails are built with Asset\Video::getFrontendThumbnail(), which applies frontend_prefixes.thumbnail also
outside of a frontend request. Asset\Video::getThumbnail() does that only inside a frontend request, so it is not
used for exports.
Image thumbnails no longer get a host prepended. Tool::getHostUrl() depends on the current request, which does not
exist in a cron/CLI/messenger export: it defaulted the scheme to http even for HTTPS-only sites and returned an
empty host when no domain was configured in Settings -> System -> Website, so the very same configuration produced
different URLs depending on how the export was triggered. Asset originals never used the host either, and the core
derives the scheme from the current request only, so these variants now behave the same way.
If the consumer of the exported file needs absolute URLs, configure absolute frontend prefixes (the documented Pimcore way to serve assets from a CDN) instead of relying on the exporter to guess a host:
pimcore:
assets:
frontend_prefixes:
source: 'https://cdn.example.com'
thumbnail: 'https://cdn.example.com'
thumbnail_deferred: 'https://cdn.example.com'
Alternatively, a listener on pimcore.frontend.path.asset / pimcore.frontend.path.asset.image.thumbnail can
rewrite the paths — those events are now dispatched for every exported image and original asset path.
Upgrade to 2026.2.5
Frontend Build Ships as a Packaged Archive
The compiled Studio frontend is no longer committed as an expanded
src/Resources/public/studio/build/ directory. It now ships as a single archive
(build-dist/build-<id>.zip) that is extracted into
src/Resources/public/studio/build/ automatically during cache warmup.
The already-required pimcore/studio-ui-bundle ^2026.2.6 provides the archive
extraction, so no dependency change is needed.
Note: Read-only filesystem deployments must run
bin/console cache:warmup(orcache:clear) during the build/deploy phase while the bundle directory (usually undervendor/) is still writable. Standard Pimcore deployments already do this. Whenassets:installruns in copy mode, runcache:warmupbefore it, otherwise no frontend assets are copied. If the filesystem becomes read-only before the first warmup, the bundle fails withBuildArchiveNotWritableExceptionbecause there is no build to serve.
Upgrade to 2026.1.0
PHP / Dependency Updates
- PHP minimum requirement raised to
8.4. PHP8.5is now supported. - Dropped support for PHP
8.3and Symfonyv6. - Removed the
pimcore/admin-ui-classic-bundledependency. - Removed Doctrine DBAL enum mapping support.
Removed ExtJS / Admin Classic UI
- Removed all ExtJS-based frontend JavaScript files (
src/Resources/public/js/), including the full configuration UI, execution UI, log tab, transmitter configuration, workspace editor, and startup script. The configuration UI is now a Pimcore Studio plugin. - Removed
StudioRouteLoader: Studio routes are now registered directly viarouting.ymlusing attribute-based routing. PimcoreDataHubFileExportBundleno longer implementsPimcoreBundleAdminClassicInterfaceand no longer usesBundleAdminClassicTrait.PimcoreDataHubFileExportBundle::registerDependentBundles()no longer registersPimcoreAdminBundle.PimcoreDataHubFileExportBundleis notfinal, because other bundles extend it (for exampledata-hub-productsup).
New Features
- The CSV delimiter character is now configurable per export configuration. The default value remains
,. - Transmitters now access the exported file and its storage through the
ExporterInterfaceinstead of directly via theExporterTypeInterface. This decoupling enables post-export file manipulation (e.g. bundling assets into a zip archive).
BC Breaks / Configuration Changes
- The Symfony Messenger transport DSN parameter has been renamed from
%pimcore.messenger.transport_dsn%to%pimcore.messenger.transport_dsn_prefix%. Update yourconfig.ymlor environment variables accordingly. TransmitterInterfaceandAbstractTransmitterno longer depend onExporterTypeInterface. The methodssetExporterType()/getExporterType()have been replaced bysetExporter(ExporterInterface)/getExporter(). Custom transmitters must be updated to use the newExporterInterfaceto access the file and storage.ExporterInterfacehas two new methods:getFile(): stringandgetFileStorage(): FilesystemOperator. Custom exporter implementations must implement these methods.
Removed StudioFile Class: Merged into AbstractExporter
Pimcore\Bundle\DataHubFileExportBundle\Exporter\StudioFilehas been removed. All Studio GridService-based data extraction logic has been moved intoAbstractExporter. Custom classes that extendedStudioFilemust now extendFileinstead.- The service ID
pimcore.datahub.fileExport.exporter.studioFilehas been renamed topimcore.datahub.fileExport.exporter.file. TheHelper::STUDIO_EXPORTER_SERVICEconstant has been updated accordingly. Update any service configuration or code that references the old service ID.
Constructor Signature Change in AbstractExporter
AbstractExporter::__construct()now requires two additional parameters:GridServiceInterface $gridServiceandOutputFormatterLoaderInterface $formatterLoader, inserted before the$exporterTypesarray parameter. The new signature is:Custom exporters that extendpublic function __construct(
LocaleServiceInterface $localeService,
EventDispatcherInterface $eventDispatcher,
RouterInterface $router,
AssetRelationFixer $assetRelationFixer,
LoggerInterface $applicationLogger,
GridServiceInterface $gridService, // NEW
OutputFormatterLoaderInterface $formatterLoader, // NEW
array $exporterTypes = [],
array $transmitter = [],
)AbstractExporterorFileand override the constructor must update their signatures to include these parameters and pass them toparent::__construct().
Data Extraction Now Uses Studio GridService Exclusively
AbstractExporterno longer callsDataObject\Service::getCsvDataForObject(). All data extraction is now performed through the StudioGridServiceInterface::getGridDataForElement().- The legacy grid config format (
schema.config.columnswithhelperDefinitions) is no longer supported by the base exporter. Configurations must use the Studio column format (schema.columns). getGridConfigData()now returnsarray{columns: array, language: ?string}instead of the previousarray{fields: array, helperDefinitions: array, language: ?string}. Code that relied on thefieldsorhelperDefinitionskeys must be updated.getFieldMapping()now derives headers fromcolumn['label'] ?? column['key']instead ofDataObject\Service::getCsvDataForObject().getItemData()now has an explicit return type ofarray|bool.- The protected method
fixAssetRelations()has been removed and replaced by the private methodfixStudioAssetRelations(), which delegates to registeredOutputFormatterinstances. Custom exporters that overrodefixAssetRelations()must migrate to theOutputFormattersystem instead. - The protected method
mapLabelToOperatorId()is no longer called fromgetItemData().
Logger Interface Change
AbstractExporter::$applicationLogger,AbstractExporter::__construct()andAbstractTransmitter::setApplicationLogger()now type-hint againstPsr\Log\LoggerInterfaceinstead ofPimcore\Log\ApplicationLogger. Custom exporter and transmitter classes that override these must update their type hints accordingly.Exporter\Type\AbstractExporter::__construct()constructor parameter$applicationLoggerchanged fromApplicationLoggertoLoggerInterface.- The
ApplicationLoggernamespace was updated fromPimcore\Log\ApplicationLoggertoPimcore\Bundle\ApplicationLoggerBundle\ApplicationLogger. Update your imports if you referenced the old namespace directly.
Classes Marked as Final / Internal
The following classes are now marked @internal and/or final. If you have extended or relied on these classes, you
must refactor your implementation:
CronExecutionCommand: nowfinaland@internalExportFileCommand: nowfinaland@internalExportExecutionDispatcher: nowfinaland@internalExportExecutionHandler: nowfinaland@internalExportExecutionMessage: nowfinal readonlyand@internalConfigurationEventSubscriber: nowfinaland@internalCronExecutionService: nowfinaland@internalExporterServiceCollector: nowfinaland@internalInstaller: nowfinaland@internalResetConfigEvent: nowfinal; properties changed fromprotectedtoprivateValidateConfigEvent: nowfinal; property changed fromprotectedtoprivateDataObjectSubscriber: now@internal(notfinal, as it is extended bydata-hub-productsup)
Return Type Changes
Installer::getLastMigrationVersionClassName()return type changed from?stringtostring(non-nullable).
Upgrade to 3.4.0
The Pimcore Studio integration replaces the tree-based schema definition with the pipeline-based column format that Pimcore Studio grids use.
- In 3.4.x, configurations created in the Admin Classic UI kept executing unchanged.
- A configuration has to be migrated to the pipeline-based format before it can be edited in Pimcore Studio. Pimcore Studio guides you through the migration when you open such a configuration.
The read-only execution support for legacy tree-based schemas was removed in 2026.1.0, earlier than the 2027.1.0
originally announced. AbstractExporter::getGridConfigData() now reads schema.columns only, and the shipped
defaultExporter (pimcore.datahub.fileExport.exporter.file) is that same Studio-based exporter. A configuration that
was never migrated fails at export time with
No schema columns defined for config: "<name>". Migrate every configuration in Pimcore Studio while still on 3.4.x,
before upgrading to 2026.1.0.
Upgrade to 3.0.0
- File name configuration has been changed. Extract the date/time format options into the new "date
format for filename" field.
Additionally, the format options have to be adjusted to the Carbon format. For example,
%Y-%m-%d_%H-%i-%shas to be changed toY-m-d_H-i-s. Further documentation on Carbon format options is available here. - Change return type to
boolofexportDatainExporterTypeInterfaceandAbstractExporter
Upgrade to 2.2.0
- The folder where the temporary file and the output file will be generated can now be configured in the flysystem configuration.
Upgrade to 2.1.0
- Make sure all running exports are done prior to upgrading to version 2.1.0.
Upgrade to 2.0.0
- Make sure all running exports are done prior to upgrading to version 2.0.0.
Upgrade to 1.1.0
- Execute all migrations of the bundle.
Upgrade to Pimcore X
- Update to latest (allowed) bundle version in Pimcore 6.9 and execute all migrations.
- Then update to Pimcore X.