Skip to main content
Version: Next

Grid

The grid API has three main request-level components: Column, ColumnConfiguration, and ColumnData.

Column​

A column defines which data to fetch. It has a name, type, and locale, plus an optional configuration (e.g. sort direction).

ColumnConfiguration​

A column configuration declares how the column behaves: sortable, filterable, editable, exportable. For export support, the column value must be representable as a string.

ColumnData​

The actual data for a column. Contains a reference to the column definition and the resolved value.

Filter​

The grid filter property controls pagination and scope:

  • page - the page number
  • pageSize - the number of items per page
  • includeDescendants - whether to include child elements

Sort Filter​

The sortFilter property defines the primary sort column and direction. It is an object with the following properties:

  • key - the column key to sort by (default: id)
  • direction - sort direction, either ASC or DESC (default: ASC)
  • locale - optional locale for localized columns (e.g. en, de)
...
"filter": {
"page": 1,
"pageSize": 50,
"sortFilter": {
"key": "name",
"direction": "ASC"
}
}
...

Additional Sort Filters​

Use additionalSortFilters to add secondary sort columns as tiebreakers after the primary sortFilter. This is useful for stable pagination — when multiple rows share the same value for the primary sort column, additional sort filters ensure a deterministic order across pages (preventing duplicate or missing items).

additionalSortFilters is an array of sort filter objects, each with the same structure as sortFilter (key, direction, locale).

...
"filter": {
"page": 1,
"pageSize": 50,
"sortFilter": {
"key": "type",
"direction": "ASC"
},
"additionalSortFilters": [
{
"key": "id",
"direction": "ASC"
}
]
}
...

The example above sorts primarily by type ASC, then by id ASC as a tiebreaker. The resulting SQL would be: ORDER BY type ASC, id ASC.

You can specify multiple additional sort filters for multi-column sorting:

...
"additionalSortFilters": [
{ "key": "name", "direction": "DESC" },
{ "key": "id", "direction": "ASC" }
]
...

Note: The sortFilter and additionalSortFilters properties are also available on collection endpoints (e.g. website settings, translations, notifications) that use the CollectionFilterParameter request body.

ColumnFilter​

Add a columnFilter to the filter property to filter by specific column values. Each column filter references a column and a filter value. Some filters (e.g. system.tag) apply to the general search query and do not require a specific column key.

Available filters are:

TypefilterValueOptionskey required
system.stringstring or array of stringsString: wildcard search can be used.
Array: exact match of any value, see below
true
system.datetimeobject of ISO 8601from, to, or ontrue
system.dateobject of ISO 8601
will round to day
from, to, or ontrue
system.timeobject (12:15)from, to, or ontrue
system.tagobjectconsiderChildTags, tagsfalse
system.pqlstringPQL Queryfalse
system.idintegerfalse
system.idsarray of integerfalse
system.integerintegertrue
system.fulltextstringfalse
system.booleanarraytrue, false or nulltrue
system.numberobjectfrom, to, is, settingtrue
system.selectarraytrue
system.quantity_valuearrayfrom, to, is, setting, unitIdtrue
system.input_quantity_valuestringunitId(string), value(string)true
system.rgbaobjectr,g,b,a (0 - 1)true
classificationstore.stringstringtrue
classificationstore.rgbaobjectr,g,b,a(0 - 1)true
classificationstore.dateobject of ISO 8601
will round to day
from, to, or ontrue
classificationstore.datetimeobject of ISO 8601from, to, or ontrue
classificationstore.timeobject (12:45)from, to, or ontrue
classificationstore.quantity_valuestring, integerfrom, to, is, setting, unitIdtrue
classificationstore.input_quantity_valuestringunitId(string), value(string)true
classificationstore.selectarraytrue
classificationstore.booleanarraytrue, false or nulltrue
classificationstore.numberobjectfrom, to, is, settingtrue
crm.consentarraytrue or falsetrue
dataobject.relationarrayarray of type, ids objectstrue
system.unreferencedbooleanAsset grid only — unreferenced assetsfalse
workflowstring or nullplace name; omit/null = all states of the workflowtrue

Examples:​

Filter by a string column with a wildcard search:

...
"columnFilters" [
{
"key": "filename",
"type": "system.string",
"filterValue": "car"
}
]
...

Filter by a string column with a list of values, e.g. a list of product codes:

...
"columnFilters" [
{
"key": "sku",
"type": "system.string",
"filterValue": ["A-100", "A-101", "B-7"]
}
]
...

Rows are returned whose value equals any of the values exactly: no wildcards, and the match is case-sensitive. The values are trimmed, empty values are ignored and duplicates are removed. A list without values does not filter, and at most 1000 values are accepted. Every value must be a string; otherwise (or above the limit) the request is rejected with HTTP status 422.

Filter by a select column:

...
"columnFilters" [
{
"key": "selectKey",
"type": "metadata.select",
"filterValue": "selectValue"
}
]
...

Filter by a date column:

...
"columnFilters" [
{
"key": "dateKey",
"type": "metadata.date",
"filterValue": {
"from": "2025-04-16T22:00:09.000Z",
"to": "2025-04-17T22:00:09.000Z"
}
}
]
...

Filter by Tags:

...
"columnFilters" [
{
"type": "system.tag",
"filterValue": {
"considerChildTags": true,
"tags": [1,2,3]
}
}
]
...

Filter by Number:

...
"columnFilters" [
{
"type": "system.number",
"key": "id",
"filterValue": {
"setting": "less",
"to": 100
}
}
]
...

Classification Store Basic Filter Value: The filter value of a Classification Store looks a bit different. All filters need to have a groupId and keyId

...
"columnFilters" [
{
"key": "technicalAttributes",
"type": "classificationstore.string",
"filterValue": {
"groupId": 6,
"keyId": 12,
"value": "filtervalue"
}
}
]
...

Filter by DataObject Relation: The dataobject.relation filter allows filtering by relation fields. The filter value must be an array of objects, each containing a type (the element type: asset, object, or document) and an ids array with the relation IDs to filter by. This allows filtering by multiple element types at the same time.

To filter by this structure:

...
"columnFilters" [
{
"key": "bodyStyle",
"type": "dataobject.relation",
"filterValue": [
{
"type": "object",
"ids": [6]
},
{
"type": "asset",
"ids": [7, 9]
}
]
}
]
...

Filter unreferenced assets (asset grid only):

...
"columnFilters" [
{
"type": "system.unreferenced",
"filterValue": true
}
]
...

Filter by Workflow place: The workflow filter restricts an element grid to the elements currently in a given workflow place. The key is the workflow name and filterValue is the place name; omit filterValue (or send null) to include the elements in all states of the workflow. Folders are excluded, and the matching element ids are resolved server-side (asset and data-object grids).

...
"columnFilters" [
{
"key": "product_workflow",
"type": "workflow",
"filterValue": "in_review"
}
]
...

Advanced Columns​

Advanced columns combine multiple data sources and transformers. Data source types:

  • simpleField - calls a getter method on the object
  • relationField - resolves a value through a relation
  • staticText - a fixed text value, not related to the object

The simpleField type calls the object's getter for the specified field:

...
"columns": [
{
"key": "advanced",
"locale": "en",
"type": "dataobject.advanced",
"config": {
"advancedColumns": [
{
"key": "simpleField",
"config": {
"field": "name"
}
},
{
"key": "simpleField",
"config": {
"field": "productionYear"
}
}
]
}
}
]
...

The relationField is a relation field in the object. You can pass the relation and field to get the value of the relation.

...
"columns": [
{
"key": "advanced",
"locale": "en",
"type": "dataobject.advanced",
"config": {
"advancedColumns": [
{
"key": "relationField",
"config": {
"field": "name",
"relation": "manufacturer",
}
}
]
}

}
]
...

The staticText is a static text that is not related to the object. You can pass the text to get the value of the static text.

...
"columns": [
{
"key": "advanced",
"locale": "en",
"type": "dataobject.advanced",
"config": {
"advancedColumns": [
{
"key": "staticText",
"config": {
"text": "my-static-text",
}
}
]
}
}
]
...

Classification Store​

To display values from the Classification Store in the Grid, you must configure the corresponding groupId and keyId. The column type has to be set to dataobject.classificationstore.

...
"columns": [
{
"key": "technicalAttributes",
"locale": null,
"type": "dataobject.classificationstore",
"config": {
"groupId": 2,
"keyId": 4
}
}
]
...

Transformers​

Transformers can be applied to advanced columns to modify the output. For example, you can use the changeChase Transformer to change all values to uppercase. The transformer will be applied to all data sources of the advanced column separately.

ChangeChase Transformer​

Available modes:

  • uppercase - changes all values to uppercase
  • lowercase - changes all values to lowercase
...
"columns": [
{
"key": "advanced",
"locale": "en",
"type": "dataobject.advanced",
"config": {
"advancedColumns": [
{
"key": "staticText",
"config": {
"text": "my-static-text",
}
}
]
"transformers": [
{
"key": "changeChase",
"config": {
"mode": "uppercase"
}
}
]
}
}
]
...

Combine Transformer​

Available configurations:

  • glue - combines the values of the advanced columns into a single string
...
"columns": [
{
"key": "advanced",
"locale": "en",
"type": "dataobject.advanced",
"config": {
"advancedColumns": [
{
"key": "staticText",
"config": {
"text": "my-static-text",
}
},
{
"key": "staticText",
"config": {
"text": "my-static-text",
}
}
]
"transformers": [
{
"key": "combine",
"config": {
"glue": " - "
}
}
]
}
}
]
...

TwigOperator Transformer​

The TwigOperator transformer allows you to render custom HTML using Twig templates based on values extracted from advancedColumns.


Available Configurations:

  • template: A valid Twig template string. Use {{ value }} to reference the column data. You can also apply Twig filters like join, upper, lower, trim, date, etc.
  • advancedColumns: A list of fields to extract from the data object. Each field must be declared here to be accessible in the template.

Relation-Aware Nesting:

When an advanced column resolves through a relation (e.g., a manufacturer relation on a car object), the value is automatically nested under the relation name in the Twig context. This allows you to distinguish between fields that belong to the object itself and fields that come from a related object.

  • No relation: Access the field directly via {{ value.fieldName }} (e.g., {{ value.name }} for a field name on the object itself)
  • With relation: Access the field via {{ value.relationName.fieldName }} (e.g., {{ value.manufacturer.name }} for a field name on the related manufacturer object)

Example Configuration:

In this example, {{ value.name }} - {{ value.manufacturer.name }} resolves to the car's own name (e.g., "E-Type"), while {{ value.manufacturer.name }} resolves to the manufacturer's name (e.g., "Jaguar").


{
"key": "summary",
"locale": "en",
"type": "dataobject.advanced",
"config": {
"title": "Summary",
"advancedColumns": [
{ "key": "simpleField", "config": { "field": "id" } },
{ "key": "simpleField", "config": { "field": "name" } },
{ "key": "simpleField", "config": { "field": "color" } },
{ "key": "simpleField", "config": { "field": "fullpath" } },
{ "key": "simpleField", "config": { "field": "filename" } },
{ "key": "simpleField", "config": { "field": "classname" } },
{ "key": "simpleField", "config": { "field": "bodyStyle" } }
],
"transformers": [
{
"key": "twigOperator",
"config": {
"template": "<h1>{{ value.name|trim }}</h1><p><strong>ID:</strong> {{ value.id }}</p>{% if value.color is iterable %}<p><strong>Available Colors:</strong></p><ul>{% for color in value.color %}<li>{{ color }}</li>{% endfor %}</ul>{% else %}<p><em>No colors available.</em></p>{% endif %}{% if value.bodyStyle is defined and value.bodyStyle.fullPath is defined %}<p><strong>Body Style:</strong> {{ value.bodyStyle.fullPath }}</p>{% endif %}<p><strong>Path:</strong> {{ value.fullpath }}</p><p><strong>Filename:</strong> {{ value.filename }}</p><p><strong>Class:</strong> {{ value.classname }}</p><hr><p><em>Generated summary for car object.</em></p>"
}
}
]
}
}
...

Available Twig Filters, Functions and Tags:

Templates are rendered inside a Twig sandbox, in a dedicated Twig Environment built from scratch for this purpose alone. Only the tags, filters and functions listed below are allowed; anything else (method calls, property access, file includes, etc.) is rejected and the template fails to render. This prevents arbitrary code execution through user-provided templates.

This isolated environment is deliberately not the application's shared twig service:

  • No Pimcore Twig extension is registered on it, so functions like pimcore_object, pimcore_asset or pimcore_document do not exist for it to resolve at all - they are not merely sandboxed, there is no element/service loader reachable here to begin with.

  • Values are converted to plain data (scalars, arrays, null) before they reach the template:

    • dates arrive as ISO 8601 strings; date, date_modify and format_date accept them like a date object,
    • consent values become {consent, noteId, noteContent}, JsonSerializable objects their serialized data, backed enums their value and other enums their name,
    • any other object renders as empty.
  • Method calls and property access on objects are denied, also for objects created inside the template (e.g. by date()); use filters instead.

  • The environment uses Twig's defaults and has no settings of its own; the application's Twig configuration does not apply. Dates use PHP's default timezone and Twig's default format (F j, Y H:i), number_format defaults to no decimals, output is HTML-escaped, and strict_variables is off, so an undefined variable or key renders as empty instead of raising an error. Pass formats explicitly where they matter, e.g. value.date|date('d.m.Y').

  • Tags: if, for, set

  • Functions: date, max, min, random, range (capped - see the range() note below)

  • Filters:

    • Core: abs, capitalize, date, date_modify, default, escape, filter, find, first, format, join, json_encode, keys, last, length, lower, map, merge, nl2br, number_format, raw, reduce, replace, reverse, round, shuffle, slice, sort, split, striptags, title, trim, upper, url_encode
    • Localization (require twig/intl-extra): country_name, currency_name, currency_symbol, format_currency, format_date, format_datetime, format_number, format_time, language_name, locale_name
    • String (require twig/string-extra): plural, singular

Note: The localization and string filters depend on the corresponding Twig extra packages being installed - the isolated environment registers Twig\Extra\Intl\IntlExtension and Twig\Extra\String\StringExtension on itself directly whenever their classes are present, which is independent of whatever the application's own twig.yaml/twig/extra-bundle configuration does for the shared twig service. The localization filters additionally require the PHP intl extension.

Resource limits: the range() function returns at most 1000 elements. This is best effort: the .. operator and nested loops are not limited, so memory_limit and max_execution_time remain the limits for expensive templates.

The allow-list can be customized per project via the bundle configuration:

pimcore_studio_backend:
twig:
sandbox_security_policy:
tags: [ 'if', 'for', 'set' ]
filters: [ 'upper', 'lower', 'format_date' ]
functions: [ 'date', 'max', 'min' ]

A configured list replaces the default list of the same type, so repeat every default you want to keep. These lists only apply to Twig operator templates and are independent of core's pimcore.templating.twig.sandbox_security_policy allow-lists. pimcore_* function names are ignored (and logged as a warning): no pimcore_* function can be called from a Twig operator template.

A name in this list only takes effect if a Twig extension in the isolated environment actually registers it. Because the isolated environment never sees the application's shared twig service (see above), adding e.g. trans or a project-defined filter name here alone does not make it available - the template still fails with "is not allowed"/"Unknown filter" at render time, and the bundle logs a warning (once per process, when the transformer is created) for any allow-listed name nothing registers. To add a project-defined filter, function or tag, register your own Twig\Extension\ExtensionInterface service tagged pimcore_studio_backend.twig_operator_extension:

services:
App\Twig\MyTwigOperatorExtension:
autoconfigure: false # otherwise Symfony also adds it to the shared `twig` service
tags: [ 'pimcore_studio_backend.twig_operator_extension' ]

(The tag name is also available as TwigOperatorEnvironmentProviderInterface::TWIG_OPERATOR_EXTENSION_TAG.) It is registered into the isolated environment alongside the built-in extensions, and its filter/function/tag names still need to be added to sandbox_security_policy above to be usable. Keep it narrowly scoped to safe, side-effect-free formatting - it runs in the same sandbox as everything else on this page, with the same consequences if it is not. The isolated environment has no runtime loader, so filters and functions must be callable directly, not through a Twig runtime (RuntimeExtensionInterface).

Security: Be careful when extending the allow-list. Filters such as raw disable output escaping (potential XSS if the value is rendered as HTML). Do not add Twig functions like constant, attribute, include or source, as they can expose internal data or read files.


Blur Transformer​

The blur transformer allows you to obfuscate or anonymize sensitive string data using predefined strategies such as masking, initials, partial reveal, or complete hiding. It is especially useful for protecting personal information in grid views.


Available Configurations:

  • mask: Masks all but a portion of the string (e.g. j***@e*****.com).
  • initials: Converts names to initials (e.g. John Doe → J.D.).
  • partial: Shows the beginning and end of the string, masking the middle (e.g. 12****7890).
  • hide: Replaces the entire value with [hidden].

Optional Parameters:

  • maskChar: Character used for masking (default: "*").
  • minMaskLength: Minimum number of mask characters to apply.
  • mask Rule Specific: visiblePrefix- chars to show at start, visibleDomainSuffix: chars to show at end of domain, minDomainMaskLength: minimum mask length for domain .
  • partial Rule Specific: visiblePrefix- chars to show at start , visibleSuffix: chars to show at end.

Example Configuration:


{
"key": "blurredName",
"locale": "en",
"type": "dataobject.advanced",
"config": {
"title": "Blurred Name",
"advancedColumns": [
{
"key": "simpleField",
"config": {
"field": "name"
}
}
],
"transformers": [
{
"key": "blur",
"config": {
"rule": "partial",
"visiblePrefix": 1,
"visibleSuffix": 4,
"maskChar": "*",
"minMaskLength": 4,
"visibleDomainSuffix": 4,
"minDomainMaskLength": 5
}
}
]
}
}

...

Anonymizer Transformer​

The anonymizer transformer allows you to irreversibly obfuscate sensitive string data using cryptographic hashing strategies. This is useful when you want to store or display anonymized identifiers that cannot be reversed, such as user IDs, emails, or names.


Available Configurations:

  • md5: Hashes the string using the MD5 algorithm.Example: john.doe@example.com → fd876f8e4e6c3c3d3d3e3e3e3e3e3e3e
  • bcrypt: Hashes the string using the Bcrypt algorithm (with salt)..Example: john.doe@example.com → $2y$10$... (varies per execution) Note: Bcrypt is non-deterministic due to salting, so the output will differ each time it's applied.

Example Configuration:


{
"key": "anonymizedName",
"locale": "en",
"type": "dataobject.advanced",
"config": {
"title": "Anonymized Name",
"advancedColumns": [
{
"key": "simpleField",
"config": {
"field": "name"
}
}
],
"transformers": [
{
"key": "anonymizer",
"config": {
"rule": "bcrypt"
}
}
]
}
}
...

Translate Transformer​

The Translate transformer translates a given value using Symfony’s Translator component. You can optionally add a prefix to the translation key and specify a locale to control the translation context. This is useful for dynamically translating field values in grids or data objects according to the configured locale and translation keys.


Available Configurations:

  • prefix: Adds a prefix to the value before translation.

Example Configuration:

{
"key": "translatedName",
"locale": "en",
"type": "dataobject.advanced",
"config": {
"title": "Translated Name",
"advancedColumns": [
{
"key": "simpleField",
"config": {
"field": "name"
}
}
],
"transformers": [
{
"key": "translate",
"config": {
"prefix": "attribute.",
}
}
]
}
}
...

PhpCode Transformer​

The PhpCode transformer delegates value transformation to a custom PHP class implementing the Pimcore\Bundle\StudioBackendBundle\Grid\Column\PhpCodeTransformerInterface. This allows developers to encapsulate complex transformation logic in reusable services. To register a transformer, the service must be tagged with the appropriate Symfony service tag (studio_backend.grid.php_code_transformer) so it can be discovered by the system. Developers can write their own PHP services to handle complex transformations. This is helpful when the logic needs to use other services or custom settings.


Available Configurations:

  • phpCodeKey: The fully qualified key value of the PHPCode transformer to execute. Must implement PhpCodeTransformerInterface.

Example Configuration:

{
"key": "phpCodeTransformedName",
"locale": "en",
"type": "dataobject.advanced",
"config": {
"title": "PHP Code Transformed Name",
"advancedColumns": [
{
"key": "simpleField",
"config": {
"field": "name"
}
}
],
"transformers": [
{
"key": "phpCode",
"config": {
"phpCodeKey": "phpCodeTransformerKeyValue",
}
}
]
}
}

...