Skip to main content
Version: 2026.2

Custom Reports

caution

To use this feature, enable the PimcoreCustomReportsBundle in your bundles.php file:

Pimcore\Bundle\CustomReportsBundle\PimcoreCustomReportsBundle::class => ['all' => true]

Then install it with:

bin/console pimcore:bundle:install PimcoreCustomReportsBundle

This page covers report configuration, built-in data source adapters, permissions, and how to implement custom adapters with both a PHP backend and a Pimcore Studio frontend.

Custom ReportsCustom ReportsCustom Reports

Data Source Adapters

Each report uses a data source adapter to retrieve and prepare its data. Pimcore ships with the following adapters:

  • SQL - query data with custom SQL statements

    caution

    When using the SQL adapter, several SQL keywords cannot appear unquoted in column names:

    ALTER|CREATE|DROP|RENAME|TRUNCATE|UPDATE|DELETE

    For example, a column named lastUpdate needs backtick quoting:

    SELECT `lastUpdate`
  • Statistics Explorer (Enterprise) - aggregation and pivot-table reports via Statistics Explorer Custom Report Integration

Enabling/Disabling Adapters

Each registered adapter can be enabled or disabled via the enabled_adapters option. Adapters that are not listed default to enabled.

pimcore_custom_reports:
enabled_adapters:
sql: true
myAdapter: false

Trying to use a disabled adapter throws an exception.

caution

We recommend disabling the built-in sql adapter (enabled_adapters: { sql: false }) unless you specifically need it. Any user with the reports_config permission can use it to define arbitrary SELECT statements against your application's database, including tables it was never intended to expose (e.g. the users table). Only keep it enabled for trusted users, and grant reports_config sparingly.

Custom Report Permissions

Custom report permissions control report visibility:

  • Share globally: all users with the reports permission can access the report.
  • Visible to users: only the listed users can access the report.
  • Visible to roles: only users with the listed roles can access the report.

Custom Data Source Adapters

Implement custom source adapters for specialized data retrieval. A custom adapter consists of a PHP backend (data retrieval) and a Pimcore Studio frontend (configuration form).

PHP Backend: Adapter Class

Implement CustomReportAdapterInterface (or extend AbstractAdapter) from Pimcore\Bundle\CustomReportsBundle\Tool\Adapter:

MethodDescription
getData(?array $filters, ?string $sort, ?string $dir, ?int $offset, ?int $limit, ?array $fields, ?array $drillDownFilters): arrayReturn report data as an array with data (rows) and total (count) keys.
getColumns(?stdClass $configuration): arrayReturn available column names.
getColumnsWithMetadata(?stdClass $configuration): arrayReturn ColumnInformation[] controlling per-column settings: sort ordering, filtering, dropdown filtering, and label editability. Override to restrict which settings are configurable.
getAvailableOptions(array $filters, string $field, array $drillDownFilters): arrayReturn distinct values for drill-down filter dropdowns.
getPagination(): boolReturn whether the adapter supports pagination (default: true).

Reference implementation: Sql.php

PHP Backend: Adapter Factory

Create a factory implementing CustomReportAdapterFactoryInterface:

interface CustomReportAdapterFactoryInterface
{
public function create(stdClass $config, ?Config $fullConfig = null): CustomReportAdapterInterface;
}

For simple adapters without dependency injection, use DefaultCustomReportAdapterFactory and pass the adapter class FQCN as argument:

services:
app.custom_report.adapter.factory.custom:
class: Pimcore\Bundle\CustomReportsBundle\Tool\Adapter\DefaultCustomReportAdapterFactory
arguments:
- 'App\CustomReport\Adapter\Custom'

For adapters requiring injected services, implement CustomReportAdapterFactoryInterface directly.

PHP Backend: Register the Adapter

Add the factory to the pimcore_custom_reports.adapters configuration. The key becomes the adapter's type identifier. At runtime, the system resolves the factory from a ServiceLocator using the type field in the report's dataSourceConfig:

pimcore_custom_reports:
adapters:
myAdapter: app.custom_report.adapter.factory.custom

When a report stores "type": "myAdapter" in its data source configuration, Pimcore looks up the myAdapter key in the registered adapter factories and calls create() on it.

Pimcore Studio Frontend: Dynamic Type Provider

After registering the PHP adapter, create a corresponding Pimcore Studio component so the adapter appears in the report configuration editor.

Extend DynamicTypeCustomReportDefinitionAbstract:

import { injectable } from 'inversify'
import { type ReactElement } from 'react'
import {
DynamicTypeCustomReportDefinitionAbstract
} from '@Pimcore/modules/reports/dynamic-types/definitions/custom-report-definition-adapters/dynamic-type-custom-report-definition-abstract'
import { type IReportConfigurationSectionProps } from '@Pimcore/modules/reports/reports-editor/types'

@injectable()
export class DynamicTypeMyCustomAdapter extends DynamicTypeCustomReportDefinitionAbstract {
readonly id = 'myAdapter'

getLabel (): ReactElement {
return <>My Custom Adapter</>
}

getPagination (): boolean {
return true
}

getCustomReportData (props: IReportConfigurationSectionProps): ReactElement {
return <MyAdapterConfigForm {...props} />
}
}

The id property must match the adapter key registered in pimcore_custom_reports.adapters.

getCustomReportData() returns the React component that renders in the report configuration view. The component receives props typed as IReportConfigurationSectionProps:

  • currentData (ReportFormData) - the current report configuration state
  • updateFormData ((data: ReportFormData) => void) - callback to update configuration values
  • form (FormInstance) - the Ant Design form instance for field binding

Register the dynamic type in a Pimcore Studio plugin. The plugin binds the class to the DI container; the module retrieves it and registers it with the dynamic type registry:

// plugin.ts
import type { IAbstractPlugin } from '@Pimcore/modules/app/plugin/plugin-api'

export const MyPlugin: IAbstractPlugin = {
name: 'my-custom-report-plugin',

onInit ({ container }) {
container.bind('MyPlugin/DynamicTypes/CustomReportDefinition/MyAdapter')
.to(DynamicTypeMyCustomAdapter)
.inSingletonScope()
},

onStartup ({ moduleSystem }) {
moduleSystem.registerModule(MyModule)
}
}
// module/index.tsx
import type { AbstractModule } from '@Pimcore/app/module-system/module-system'
import { container } from '@Pimcore/app/depency-injection'
import { serviceIds } from '@Pimcore/app/config/services/service-ids'
import {
type DynamicTypeCustomReportDefinitionRegistry
} from '@Pimcore/modules/reports/dynamic-types/definitions/custom-report-definition-adapters/dynamic-type-custom-report-definition-registry'

export const MyModule: AbstractModule = {
onInit () {
const registry = container.get<DynamicTypeCustomReportDefinitionRegistry>(
serviceIds['DynamicTypes/CustomReportDefinitionRegistry']
)
registry.registerDynamicType(
container.get<DynamicTypeMyCustomAdapter>('MyPlugin/DynamicTypes/CustomReportDefinition/MyAdapter')
)
}
}

Reference implementations:

Creating a Pimcore Studio plugin requires a Module Federation bundle. See the Pimcore Studio Extending Guide for setup instructions.