Adobe Commerce 2.3 reached end of support in September 2022.

Creating a dynamic row system config

This tutorial shows you how to add a new dynamic rows system configuration in the Magento admin, by extending the Magento/Config/Block/System/Config/Form/Field/FieldArray/AbstractFieldArray class.

Step 1: Add a new system field

etc/adminhtml/system.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Config:etc/system_file.xsd">
    <system>
        <section id="general" type="text">
            <group id="quantity_ranges" translate="label" type="text" sortOrder="10" showInDefault="1" showInWebsite="1" showInStore="1">
                <label>Quantity Ranges</label>
                <field id="ranges" translate="label" sortOrder="5" showInDefault="1" showInWebsite="1" showInStore="1">
                    <label>Ranges</label>
                    <frontend_model>Vendor\Module\Block\Adminhtml\Form\Field\Ranges</frontend_model>
                    <backend_model>Magento\Config\Model\Config\Backend\Serialized\ArraySerialized</backend_model>
                </field>
            </group>
        </section>
    </system>
</config>

This code adds a new system configuration in STORES > Settings > Configuration > GENERAL > General > Quantity Ranges.

Step 2: Create the block class to describe custom field columns

File: app/code/Vendor/Module/Block/Adminhtml/Form/Field/Ranges.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
namespace Vendor\Module\Block\Adminhtml\Form\Field;

use Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
use Vendor\Module\Block\Adminhtml\Form\Field\TaxColumn;

/**
 * Class Ranges
 */
class Ranges extends AbstractFieldArray
{
    /**
     * @var TaxColumn
     */
    private $taxRenderer;

    /**
     * Prepare rendering the new field by adding all the needed columns
     */
    protected function _prepareToRender()
    {
        $this->addColumn('from_qty', ['label' => __('From'), 'class' => 'required-entry']);
        $this->addColumn('to_qty', ['label' => __('To'), 'class' => 'required-entry']);
        $this->addColumn('price', ['label' => __('Price'), 'class' => 'required-entry']);
        $this->addColumn('tax', [
            'label' => __('Tax'),
            'renderer' => $this->getTaxRenderer()
        ]);
        $this->_addAfter = false;
        $this->_addButtonLabel = __('Add');
    }

    /**
     * Prepare existing row data object
     *
     * @param DataObject $row
     * @throws LocalizedException
     */
    protected function _prepareArrayRow(DataObject $row): void
    {
        $options = [];

        $tax = $row->getTax();
        if ($tax !== null) {
            $options['option_' . $this->getTaxRenderer()->calcOptionHash($tax)] = 'selected="selected"';
        }

        $row->setData('option_extra_attrs', $options);
    }

    /**
     * @return TaxColumn
     * @throws LocalizedException
     */
    private function getTaxRenderer()
    {
        if (!$this->taxRenderer) {
            $this->taxRenderer = $this->getLayout()->createBlock(
                TaxColumn::class,
                '',
                ['data' => ['is_render_to_js_template' => true]]
            );
        }
        return $this->taxRenderer;
    }
}

This block prepares the desired columns for inclusion in the new configuration.

Step 3: Create the block class to describe a column with drop-down input

File: app/code/Vendor/Module/Block/Adminhtml/Form/Field/TaxColumn.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
declare(strict_types=1);

namespace Vendor\Module\Block\Adminhtml\Form\Field;

use Magento\Framework\View\Element\Html\Select;

class TaxColumn extends Select
{
    /**
     * Set "name" for <select> element
     *
     * @param string $value
     * @return $this
     */
    public function setInputName($value)
    {
        return $this->setName($value);
    }

    /**
     * Set "id" for <select> element
     *
     * @param $value
     * @return $this
     */
    public function setInputId($value)
    {
        return $this->setId($value);
    }

    /**
     * Render block HTML
     *
     * @return string
     */
    public function _toHtml(): string
    {
        if (!$this->getOptions()) {
            $this->setOptions($this->getSourceOptions());
        }
        return parent::_toHtml();
    }

    private function getSourceOptions(): array
    {
        return [
            ['label' => 'Yes', 'value' => '1'],
            ['label' => 'No', 'value' => '0'],
        ];
    }
}

This block sets values for the drop-down option.

Step 4: Set default values - OPTIONAL

It is possible to set defaults for a dynamic row configuration, this is done by adding additional XML to the defaults block in the config.xml file for the module.

Add a block to the <default> section of the config.xml file and do not include any values:

1
2
3
4
5
<general>
    <quantity_ranges>
        <ranges></ranges>
    </quantity_ranges>
</general>

For each complete line of configuration, create an XML block with a repeating node that has a different value to all the others and contains XML tags for each sub-option and value. For example, you can use <item1>, <item2>.

The sub-options are the columns defined in the _prepareToRender() method as described in Step 2.

In the following excerpt, a single row for item1 contains 4 sub-options:

1
2
3
4
5
6
<item1>
    <from_qty>5</from_qty>
    <to_qty>6</to_qty>
    <price>10.00</price>
    <tax>1</tax>
</item1>

Continue building the default block by adding 3 items to the ranges configuration option in the config.xml file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<general>
    <quantity_ranges>
        <ranges>
            <item1>
                <from_qty>1</from_qty>
                <to_qty>5</to_qty>
                <price>10.00</price>
                <tax>1</tax>
            </item1>
            <item2>
                <from_qty>6</from_qty>
                <to_qty>10</to_qty>
                <price>20.00</price>
                <tax>1</tax>
            </item2>
            <item3>
                <from_qty>11</from_qty>
                <to_qty>15</to_qty>
                <price>30.00</price>
                <tax>0</tax>
            </item3>
        </ranges>
    </quantity_ranges>
</general>

To verify the default values for the configuration are correct, do the following :

  • Ensure that this configuration option has no entry in the database.
  • Continue with Step 5

Step 5: Clean cache

Clean the Magento cache with the following command:

1
bin/magento cache:clean

and clean the config with this command:

1
bin/magento cache:clean config

Result

The result is a new dynamic system row field in the Admin panel. If you have set optional default values, these should also appear.

Dynamic Rows System Config