I want to add a column to sales order invoice grid, that should be exported as it is shown in the grid.The column is a calculated column, based on the row data, so there is no explicit database entry for the column value.

Those questions already had similar issues: Magento 2 + Custom column added sku on grid not showing data in export file and Magento 2 Add Custom Sale Order Grid Column and Export in CSV.

But the answers to those question either suggest to alter ConvertToCsv.php and ConvertToXml.php or are based on a database entry that is joined.

Altering one of the ConvertToXXX.php files in IMHO not the right way, since the generation and calculation of the column data has nothing to do with either csv or xml. Nor do I have da database entry, so the answer does not hold.

Currently I got the following solution, which shows the data in the grid but does not get exported:

File {vendor}\{module}\view\adminhtml\ui_component\sales_order_invoice_grid.xml

with content:

<?xml version="1.0" encoding="UTF-8"?>
<listing xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <columns name="sales_order_invoice_columns">
        <column name="custom_column" class="{vendor}\{module}\Ui\Component\Listing\Column\CustomColumn">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="visible" xsi:type="boolean">true</item>
                    <item name="label" xsi:type="string" translate="true">Custom Column</item>

and {vendor}\{module}\Ui\Component\Listing\Column\CustomColumn.php with content:

class CustomColumn extends Column
    protected $orderRepository;

    public function __construct(...)

    public function prepareDataSource(array $dataSource)
        if (isset($dataSource['data']['items'])) {

            foreach ($dataSource['data']['items'] as & $item) {

                $order  = $this->orderRepository->get($item["order_id"]);
                // $value = calculate value based on order data
                $item[$this->getData('name')] = $value;

        return $dataSource;

This is a perfectly fine and easy solution, it respects the Single Responsibility Principle and the Open Closed Principle. I can add multiple additional columns by just adding code, without modifying code. Drawback: The calculated value does not get exported.

In Magento core code in vendor/magento/module-sales/Ui/Component/DataProvider/Document.php they use a switch-case statement to alter some database values to export values. First, I don't know how to intercept/extend this behavior, and secondly, I don't like that switch-case statement, which does not quite adhere to the OCP.

So what is the recommended or best way to show data in the grid and get it exported (independent on which export format was chosen), in best case without doing it twice for grid and export separately.

  • Hello, did you found a solution to resolve this ? i have the same issue
    – NoOneElse
    Commented Feb 7, 2022 at 10:30
  • Kind of. It is a bit tricky. In my MetaDataProvider I have overriden the getRowData method and used the parsed data source instead of the data provider. This way the already modified values are used. I will try post an answer with my solution during the course of the day.
    – Creepin
    Commented Feb 8, 2022 at 11:21
  • @NoOneElse I posted an answer with my solution
    – Creepin
    Commented Feb 9, 2022 at 10:30

1 Answer 1


Here is the custom MetadataProvider I used to export the column values as seen in the UI, with some comments in it:


namespace Company\Module\Model\Export;

use Exception;
use Magento\Framework\Api\Search\DocumentInterface;
use Magento\Framework\View\Element\UiComponentInterface;
use Magento\Ui\Component\MassAction\Filter;
use Magento\Framework\Locale\ResolverInterface;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Ui\Model\BookmarkManagement;

class MetadataProvider extends \Magento\Ui\Model\Export\MetadataProvider
     * @var BookmarkManagement
    protected $_bookmarkManagement;

    private $parsedDataSource = [];

    private $currentComponent = null;

     * MetadataProvider constructor.
     * @param Filter $filter
     * @param TimezoneInterface $localeDate
     * @param ResolverInterface $localeResolver
     * @param BookmarkManagement $bookmarkManagement
     * @param string $dateFormat
     * @param array $data
    public function __construct(
        Filter $filter,
        TimezoneInterface $localeDate,
        ResolverInterface $localeResolver,
        BookmarkManagement $bookmarkManagement,
        $dateFormat = 'M j, Y H:i:s A',
        array $data = [])
        parent::__construct($filter, $localeDate, $localeResolver, $dateFormat, $data);
        $this->_bookmarkManagement = $bookmarkManagement;

     * @param UiComponentInterface $component
     * @return UiComponentInterface[]
     * @throws Exception
    protected function getColumns(UiComponentInterface $component) : array
        if (!isset($this->columns[$component->getName()])) {
            $this->currentComponent = $component;
            $activeColumns = $this->getActiveColumns($component);
            $columnsComponent = $this->getColumnsComponent($component);
            $columns = $columnsComponent->getChildComponents();
            foreach ($activeColumns as $sortedColumn) {
                $this->columns[$component->getName()][$sortedColumn] = $columns[$sortedColumn];
        return $this->columns[$component->getName()];

    private function getActiveColumns($component)
        $bookmark = $this->_bookmarkManagement->getByIdentifierNamespace('current', $component->getName());

        $config = $bookmark->getConfig();
        $columnSorting = $config['current']['positions'];
        $columns = $config['current']['columns'];
        $_activeColumns = [];
        foreach ($columnSorting as $column => $sorting) {
            if (true === $columns[$column]['visible'] && $column != 'ids' && $column != 'actions') {
                $_activeColumns[] = $column;
        return $_activeColumns;

    public function getRowData(DocumentInterface $document, $fields, $options) : array
        // not before here the dataProvider has set the correct filters, so we calculate the dataSource here

        // in contrast to base method, we use the prepared data source instead the data provider
        // the data provider represents the database values, the data source represents the UI data (database + custom columns)

        // getRowData is called in the same order as parsedDataSource is saved (although the grid sort order is not considered in both variants)
        $dataSourceItem = array_shift($this->parsedDataSource);

        $row = [];
        foreach ($fields as $column) {
            $row[] = $dataSourceItem[$column] ?? ""; // there are cased where there is a column, but no data exists
        return $row;

    private function prepareDataSource(UiComponentInterface $component)
        if (empty($this->parsedDataSource))
            $context = $component->getContext();
            $dataSourceData = $context->getDataSourceData($component);
            $this->parsedDataSource = $dataSourceData[$context->getDataProvider()->getName()]['config']['data']['items'];

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.