{"id":11975,"date":"2020-04-07T04:28:43","date_gmt":"2020-04-07T04:28:43","guid":{"rendered":"https:\/\/www.mageworx.com\/blog\/?p=11975"},"modified":"2023-03-17T15:01:29","modified_gmt":"2023-03-17T15:01:29","slug":"how-to-add-custom-product-attribute-as-a-filter-for-the-shipping-rates","status":"publish","type":"post","link":"https:\/\/www.mageworx.com\/blog\/how-to-add-custom-product-attribute-as-a-filter-for-the-shipping-rates","title":{"rendered":"How to Add Custom Product Attribute as a Filter for the Shipping Rates"},"content":{"rendered":"\n<!-- SEO Ultimate (http:\/\/www.seodesignsolutions.com\/wordpress-seo\/) - Code Inserter module -->\n<!-- Google Tag Manager (noscript) -->\r\n<noscript><iframe src=\"https:\/\/www.googletagmanager.com\/ns.html?id=GTM-5DTCW7B8\"\r\nheight=\"0\" width=\"0\" style=\"display:none;visibility:hidden\"><\/iframe><\/noscript>\r\n<!-- End Google Tag Manager (noscript) -->\n<!-- \/SEO Ultimate -->\n\n<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 8<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>\n<p>Often, in Magento 2, the number of standard product attributes that can be used to set up conditions is limited. Extra customization would be required to meet business needs.<br><\/p>\n\n\n\n<p>From this article, you\u2019ll learn how to achieve that and add custom product attributes as a filter for the shipping rates.<br><\/p>\n\n\n\n<p><strong>Notes:&nbsp;<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>See the complete code example on <a href=\"https:\/\/github.com\/mageworx\/MageWorx_ShippingRateByProductAttribute\">GitHub<\/a>.&nbsp;<\/li><li>The first part of the example, which adds the \u2018Volume Weight\u2019 attribute as a filter to shipping rates, is available <a href=\"https:\/\/github.com\/SiarheyUchukhlebau\/MageWorx_ShippingRateVolumeWeight\">here<\/a>.<\/li><li>The original <a href=\"https:\/\/www.mageworx.com\/magento-2-shipping-suite.html\">Magento 2 Shipping Suite Ultimate <\/a>module is required.<\/li><\/ol>\n\n\n\n<p>Let\u2019s go straight to discussing what exactly shall be done to achieve the objective.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step-by-Step Guide on Adding Custom Product Attribute&nbsp;<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1. Create a New Module by Adding the Base Files<\/h3>\n\n\n\n<p>Start with naming the module:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/registration.php\n\n&lt;?php\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n\n\\Magento\\Framework\\Component\\ComponentRegistrar::register(\n   \\Magento\\Framework\\Component\\ComponentRegistrar::MODULE,\n   'MageWorx_ShippingRateByProductAttribute',\n   __DIR__\n);\n<\/code><\/pre>\n\n\n\n<p>Then, detect and declare its requirements. You\u2019ll also need to give a name to our module for the composer, set version, and add a short description:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/composer.json\n\n{\n \"name\": \"mageworx\/module-shipping-rate-by-product-attribute\",\n \"description\": \"Shipping Rules Extension: Adds product attribute to the Rates\",\n \"require\": {\n   \"magento\/module-shipping\": \"&gt;=100.1.0 &lt; 101\",\n   \"magento\/module-ui\": \"&gt;=100.1.0 &lt; 102\",\n   \"mageworx\/module-shippingrules\": \"&gt;=2.7.1\"\n },\n \"type\": \"magento2-module\",\n \"version\": \"1.0.0\",\n \"license\": &#91;\n   \"OSL-3.0\",\n   \"AFL-3.0\"\n ],\n \"autoload\": {\n   \"files\": &#91;\n     \"registration.php\"\n   ],\n   \"psr-4\": {\n     \"MageWorx\\\\ShippingRateByProductAttribute\\\\\": \"\"\n   }\n }\n}\n<\/code><\/pre>\n\n\n\n<p>Further, we can set the initial name and version in the Magento 2 configuration, declare the sequence:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/etc\/module.xml\n\n&lt;?xml version=\"1.0\"?&gt;\n&lt;!--\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n--&gt;\n&lt;config xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\"\n       xsi:noNamespaceSchemaLocation=\"urn:magento:framework:Module\/etc\/module.xsd\"&gt;\n   &lt;module name=\"MageWorx_ShippingRateByProductAttribute\" setup_version=\"1.0.0\"&gt;\n       &lt;sequence&gt;\n           &lt;module name=\"MageWorx_ShippingRules\" \/&gt;\n       &lt;\/sequence&gt;\n   &lt;\/module&gt;\n&lt;\/config&gt;\n\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2. Create a Module Structure<\/h3>\n\n\n\n<p>Let&#8217;s assume that we have a product attribute named &#8216;shippingnew&#8217;, which was created from the admin side. It is a dropdown input type and has few options named &#8216;A, B, C, D&#8217;, etc. These options describe how we ship our items by zones. Each value has its own price, and products with the highest price will modify the shipping method cost during checkout.<\/p>\n\n\n\n<p>First of all, we need to create a separate table for our shipping rates extended conditions. Later, we will add them using the regular extension attributes of the model (the &#8216;Shipping Rate&#8217; model extends `\\Magento\\Framework\\Model\\AbstractExtensibleModel` ).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/Setup\/InstallSchema.php\n\n&lt;?php\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n\nnamespace MageWorx\\ShippingRateByProductAttribute\\Setup;\n\nuse Magento\\Framework\\DB\\Ddl\\Table;\nuse Magento\\Framework\\Setup\\InstallSchemaInterface;\nuse Magento\\Framework\\Setup\\ModuleContextInterface;\nuse Magento\\Framework\\Setup\\SchemaSetupInterface;\n\n\/**\n* Class InstallSchema\n*\/\nclass InstallSchema implements InstallSchemaInterface\n{\n   \/**\n    * Installs DB schema for a module\n    *\n    * @param SchemaSetupInterface $setup\n    * @param ModuleContextInterface $context\n    * @return void\n    * @throws \\Zend_Db_Exception\n    *\/\n   public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)\n   {\n       $installer = $setup;\n       $installer-&gt;startSetup();\n\n       $ratesTable = $installer-&gt;getTable(\\MageWorx\\ShippingRules\\Model\\Carrier::RATE_TABLE_NAME);\n\n       \/**\n        * Create table 'mageworx_shippingrules_rates_shippingnew'\n        *\/\n       $table = $installer-&gt;getConnection()-&gt;newTable(\n           $installer-&gt;getTable('mageworx_shippingrules_rates_shippingnew')\n       )-&gt;addColumn(\n           'rate_id',\n           Table::TYPE_INTEGER,\n           null,\n           &#91;'unsigned' =&gt; true, 'nullable' =&gt; false],\n           'Rate Id'\n       )-&gt;addColumn(\n           'shippingnew',\n           Table::TYPE_TEXT,\n           '120',\n           &#91;'nullable' =&gt; false],\n           'shippingnew attribute value'\n       )-&gt;addForeignKey(\n           $installer-&gt;getFkName('mageworx_shippingrules_rates_shippingnew', 'rate_id', $ratesTable, 'rate_id'),\n           'rate_id',\n           $ratesTable,\n           'rate_id',\n           Table::ACTION_CASCADE\n       )-&gt;addIndex(\n           $installer-&gt;getIdxName(\n               'mageworx_shippingrules_rates_product_attributes',\n               &#91;'rate_id', 'shippingnew'],\n               \\Magento\\Framework\\DB\\Adapter\\AdapterInterface::INDEX_TYPE_UNIQUE\n           ),\n           &#91;'rate_id', 'shippingnew'],\n           &#91;'type' =&gt; \\Magento\\Framework\\DB\\Adapter\\AdapterInterface::INDEX_TYPE_UNIQUE]\n       )-&gt;setComment(\n           'Product Attributes For Shipping Suite Rates'\n       );\n\n       $installer-&gt;getConnection()-&gt;createTable($table);\n   }\n}\n\n<\/code><\/pre>\n\n\n\n<p>We named our table as follows: `&#8217;mageworx_shippingrules_rates_shippingnew&#8217;`. It has just 2 columns. One of them is used as a foreign key. It is the `rate_id` column, which will be linked with the regular table `mageworx_shippingrules_rates` from the MageWorx Shipping Suite Ultimate module for Magento 2. Another column will contain values from the `shippingnew` attribute. <\/p>\n\n\n\n<p>Before we make an observer load\/save\/delete our custom data to the table, we must create at least two models\u2015regular model and resource model.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/Model\/ShippingNew.php\n\n&lt;?php\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n\nnamespace MageWorx\\ShippingRateByProductAttribute\\Model;\n\nuse Magento\\Framework\\Model\\AbstractModel;\n\n\/**\n* Class ShippingNew\n*\/\nclass ShippingNew extends AbstractModel\n{\n   \/**\n    * Prefix of model events names\n    *\n    * @var string\n    *\/\n   protected $_eventPrefix = 'mageworx_shippingnew';\n\n   \/**\n    * Parameter name in event\n    *\n    * In observe method you can use $observer-&gt;getEvent()-&gt;getObject() in this case\n    *\n    * @var string\n    *\/\n   protected $_eventObject = 'shippingnew';\n\n   \/**\n    * Set resource model and Id field name\n    *\n    * @return void\n    *\/\n   protected function _construct()\n   {\n       parent::_construct();\n       $this-&gt;_init('MageWorx\\ShippingRateByProductAttribute\\Model\\ResourceModel\\ShippingNew\u2019);\n       $this-&gt;setIdFieldName('rate_id');\n   }\n}\n<\/code><\/pre>\n\n\n\n<p><strong>Notes:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>`<strong>_eventPrefix<\/strong>` will be used to detect our model events.<\/li><li>`_eventObject` will be used to store data in the event object. Using this name we can get our model from the event object.<\/li><li>`$this-&gt;_init(<strong>&#8216;MageWorx\\ShippingRateByProductAttribute\\Model\\ResourceModel\\<\/strong><strong>ShippingNew\u2019<\/strong>);` links our model with the corresponding resource model.<\/li><li>`$this-&gt;setIdFieldName(<strong>&#8216;rate_id&#8217;<\/strong>);` describes, which field from the table must be used as a key (usually we call it id)<\/li><\/ol>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/Model\/ResourceModel\/ShippingNew.php\n\n&lt;?php\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n\nnamespace MageWorx\\ShippingRateByProductAttribute\\Model\\ResourceModel;\n\nuse Magento\\Framework\\Model\\ResourceModel\\Db\\AbstractDb;\n\n\/**\n* Class ShippingNew\n*\/\nclass ShippingNew extends AbstractDb\n{\n   \/**\n    * Resource initialization\n    *\n    * @return void\n    *\/\n   protected function _construct()\n   {\n       $this-&gt;_init('mageworx_shippingrules_rates_shippingnew', 'rate_id');\n   }\n\n   \/**\n    * @param $rateId\n    * @param int $shippingNew\n    * @return int\n    * @throws \\Magento\\Framework\\Exception\\LocalizedException\n    *\/\n   public function insertUpdateRecord($rateId, int $shippingNew)\n   {\n       $rowsAffected = $this-&gt;getConnection()-&gt;insertOnDuplicate(\n           $this-&gt;getMainTable(),\n           &#91;\n               'rate_id' =&gt; $rateId,\n               'shippingnew' =&gt; $shippingNew\n           ]\n       );\n\n       return $rowsAffected;\n   }\n\n   \/**\n    * @param $rateId\n    * @return int\n    * @throws \\Magento\\Framework\\Exception\\LocalizedException\n    *\/\n   public function deleteRecord($rateId)\n   {\n       $rowsAffected = $this-&gt;getConnection()-&gt;delete(\n           $this-&gt;getMainTable(),\n           &#91;\n               'rate_id = ?' =&gt; $rateId\n           ]\n       );\n\n       return $rowsAffected;\n   }\n}\n<\/code><\/pre>\n\n\n\n<p><strong>Notes:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>$this-&gt;_init(<strong>&#8216;mageworx_shippingrules_rates_shippingnew&#8217;<\/strong>, <strong>&#8216;rate_id&#8217;<\/strong>); set the main table name and id field name.<\/li><li><strong>public function <\/strong>insertUpdateRecord($rateId, <strong>int <\/strong>$shippingNew) is the method, which could help us update the attribute value in our custom table.<\/li><li><strong>public function <\/strong>deleteRecord($rateId) is designed to remove the column.<\/li><\/ol>\n\n\n\n<p>Later, we will use those methods in our observers.<\/p>\n\n\n\n<p>Now, let\u2019s add our new data as an extension attribute to the Shipping Rate model:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/etc\/extension_attributes.xml\n\n&lt;?xml version=\"1.0\"?&gt;\n&lt;!--\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n--&gt;\n&lt;config xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:Api\/etc\/extension_attributes.xsd\"&gt;\n   &lt;!-- Rate Extension --&gt;\n   &lt;extension_attributes for=\"MageWorx\\ShippingRules\\Api\\Data\\RateInterface\"&gt;\n       &lt;attribute code=\"shippingnew\" type=\"int\"&gt;\n           &lt;join reference_table=\"mageworx_shippingrules_rates_shippingnew\" reference_field=\"rate_id\" join_on_field=\"rate_id\"&gt;\n               &lt;field&gt;shippingnew&lt;\/field&gt;\n           &lt;\/join&gt;\n       &lt;\/attribute&gt;\n   &lt;\/extension_attributes&gt;\n&lt;\/config&gt;\n<\/code><\/pre>\n\n\n\n<p>We should also take care of the regular operations of our custom condition:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/etc\/events.xml\n\n&lt;?xml version=\"1.0\"?&gt;\n&lt;!--\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n--&gt;\n&lt;config xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:Event\/etc\/events.xsd\"&gt;\n   &lt;!-- Add Extension Attributes to the Rates Collection --&gt;\n   &lt;!-- Save custom attribute value during rate saving --&gt;\n   &lt;event name=\"mageworx_shippingrules_rate_save_after\"&gt;\n       &lt;observer\n               name=\"mageworx_save_shippingnew_attribute\"\n               instance=\"MageWorx\\ShippingRateByProductAttribute\\Observer\\SaveShippingNewRateAttribute\"\n       \/&gt;\n   &lt;\/event&gt;\n   &lt;!-- Add custom attribute value to the rates collection --&gt;\n   &lt;event name=\"rates_collection_render_filters_before\"&gt;\n       &lt;observer\n               name=\"mageworx_add_shippingnew_attribute\"\n               instance=\"MageWorx\\ShippingRateByProductAttribute\\Observer\\AddShippingNewToRatesCollection\"\n       \/&gt;\n   &lt;\/event&gt;\n   &lt;!-- Take care of filtering the rates grid --&gt;\n   &lt;event name=\"mageworx_suitable_rates_collection_load_before\"&gt;\n       &lt;observer\n               name=\"mageworx_filter_rates_by_shippingnew_attribute\"\n               instance=\"MageWorx\\ShippingRateByProductAttribute\\Observer\\FilterRatesCollectionByShippingNewAttribute\"\n       \/&gt;\n   &lt;\/event&gt;\n   &lt;!-- 3 event observers for the Export\/Import rates with custom attribute in conditions --&gt;\n   &lt;event name=\"mageworx_rates_export_collection_join_linked_tables_after\"&gt;\n       &lt;observer\n               name=\"mageworx_join_shipping_new_table_to_export_rates_collection\"\n               instance=\"MageWorx\\ShippingRateByProductAttribute\\Observer\\JoinShippingNewTableToExportRatesCollection\"\n       \/&gt;\n   &lt;\/event&gt;\n   &lt;event name=\"mageworx_filter_rates_data_before_insert\"&gt;\n       &lt;observer\n               name=\"mageworx_remove_shipping_new_before_insert\"\n               instance=\"MageWorx\\ShippingRateByProductAttribute\\Observer\\RemoveShippingNewBeforeInsert\"\n       \/&gt;\n   &lt;\/event&gt;\n   &lt;event name=\"mageworx_shippingrules_import_insert_rates\"&gt;\n       &lt;observer\n               name=\"mageworx_shippingrules_import_insert_update_shipping_new\"\n               instance=\"MageWorx\\ShippingRateByProductAttribute\\Observer\\InsertUpdateShippingNewDuringImport\"\n       \/&gt;\n   &lt;\/event&gt;\n&lt;\/config&gt;\n<\/code><\/pre>\n\n\n\n<p><strong>The first<\/strong> event is for save\/update\/delete custom attribute value in the rates condition.<\/p>\n\n\n\n<p><strong>The second two<\/strong> events are for adding this attribute value to the collection.<\/p>\n\n\n\n<p><strong>The last three<\/strong> events are for the Import\/Export functionality.&nbsp;<\/p>\n\n\n\n<p>Let&#8217;s analyze them one by one in more detail:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/Observer\/SaveShippingNewRateAttribute.php\n\n&lt;?php\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n\nnamespace MageWorx\\ShippingRateByProductAttribute\\Observer;\n\n\nuse Magento\\Framework\\Event\\Observer;\nuse Magento\\Framework\\Event\\ObserverInterface;\nuse Magento\\Framework\\Exception\\LocalizedException;\nuse MageWorx\\ShippingRules\\Api\\Data\\RateInterface;\n\n\/**\n* Class SaveShippingNewRateAttribute\n*\n* Saves custom attribute (`shippingnew`) values after model was saved\n*\/\nclass SaveShippingNewRateAttribute implements ObserverInterface\n{\n   \/**\n    * @var \\MageWorx\\ShippingRateByProductAttribute\\Model\\ResourceModel\\ShippingNew\n    *\/\n   private $resource;\n\n   \/**\n    * @var \\Magento\\Framework\\Message\\ManagerInterface\n    *\/\n   private $messagesManager;\n\n   \/**\n    * SaveVolumeWeightRateAttribute constructor.\n    *\n    * @param \\MageWorx\\ShippingRateByProductAttribute\\Model\\ResourceModel\\ShippingNew $resource\n    * @param \\Magento\\Framework\\Message\\ManagerInterface $messagesManager\n    *\/\n   public function __construct(\n       \\MageWorx\\ShippingRateByProductAttribute\\Model\\ResourceModel\\ShippingNew $resource,\n       \\Magento\\Framework\\Message\\ManagerInterface $messagesManager\n   ) {\n       $this-&gt;resource        = $resource;\n       $this-&gt;messagesManager = $messagesManager;\n   }\n\n   \/**\n    * @param Observer $observer\n    * @return void\n    *\/\n   public function execute(Observer $observer)\n   {\n       \/** @var RateInterface $model *\/\n       $model = $observer-&gt;getEvent()-&gt;getData('rate');\n       if (!$model instanceof RateInterface) {\n           return;\n       }\n\n       $shippingNewValue = $model-&gt;getData('shippingnew') !== '' ? $model-&gt;getData('shippingnew') : null;\n\n\n       if ($shippingNewValue === null) {\n           try {\n               $this-&gt;resource-&gt;deleteRecord($model-&gt;getRateId());\n           } catch (LocalizedException $deleteException) {\n               $this-&gt;messagesManager-&gt;addErrorMessage(\n                   __('Unable to delete the Shipping Category for the Rate %1', $model-&gt;getRateId())\n               );\n           }\n       } else {\n           try {\n               $this-&gt;resource-&gt;insertUpdateRecord($model-&gt;getRateId(), $shippingNewValue);\n           } catch (LocalizedException $saveException) {\n               $this-&gt;messagesManager-&gt;addErrorMessage(\n                   __('Unable to save the Shipping Category for the Rate %1', $model-&gt;getRateId())\n               );\n           }\n       }\n\n\n       return;\n   }\n}\n<\/code><\/pre>\n\n\n\n<p>It&#8217;s as simple as that. When we save a rate, we must take care of saving the custom attribute value too. In case its value equals `null`, just delete a record.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/Observer\/AddShippingNewToRatesCollection.php\n\n&lt;?php\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n\nnamespace MageWorx\\ShippingRateByProductAttribute\\Observer;\n\n\nuse Magento\\Framework\\Event\\Observer;\nuse Magento\\Framework\\Event\\ObserverInterface;\n\n\/**\n* Class AddShippingNewToRatesCollection\n*\n* Adds custom attribute to the rates collection.\n* It will be used later during quote validation.\n*\/\nclass AddShippingNewToRatesCollection implements ObserverInterface\n{\n   \/**\n    * Join custom table to the rates collection to obtain the `shippingnew` attribute anywhere in the code.\n    *\n    * @param Observer $observer\n    * @return void\n    *\/\n   public function execute(Observer $observer)\n   {\n       \/** @var \\MageWorx\\ShippingRules\\Model\\ResourceModel\\Rate\\Collection $collection *\/\n       $collection = $observer-&gt;getEvent()-&gt;getData('collection');\n\n       if (!$collection instanceof \\MageWorx\\ShippingRules\\Model\\ResourceModel\\Rate\\Collection) {\n           return;\n       }\n\n       if ($collection-&gt;isLoaded()) {\n           return;\n       }\n\n       $joinTable = $collection-&gt;getTable('mageworx_shippingrules_rates_shippingnew');\n       $collection-&gt;getSelect()\n                  -&gt;joinLeft(\n                      $joinTable,\n                      '`main_table`.`rate_id` = `' . $joinTable . '`.`rate_id`',\n                      &#91;'shippingnew']\n                  );\n   }\n}\n<\/code><\/pre>\n\n\n\n<p>To make validation available, when a customer goes to the checkout or <a href=\"https:\/\/www.mageworx.com\/blog\/10-workflow-optimization-tips-for-magento-2-shipping-extension\">shipping rates<\/a> estimation, let\u2019s join our table with the custom attribute to the regular rates table.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/Observer\/FilterRatesCollectionByShippingNewAttribute.php\n\n&lt;?php\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n\nnamespace MageWorx\\ShippingRateByProductAttribute\\Observer;\n\nuse Magento\\Framework\\Event\\Observer;\nuse Magento\\Framework\\Event\\ObserverInterface;\n\n\/**\n* Class FilterRatesCollectionByShippingNewAttribute\n*\n* Filter rates collection before we load it by custom attribute: shippingnew.\n*\n* For more details\n*\n* @see \\MageWorx\\ShippingRules\\Model\\Carrier\\Artificial::getSuitableRatesAccordingRequest()\n*\n*\/\nclass FilterRatesCollectionByShippingNewAttribute implements ObserverInterface\n{\n   \/**\n    * @param Observer $observer\n    * @return void\n    *\/\n   public function execute(Observer $observer)\n   {\n       \/** @var \\MageWorx\\ShippingRules\\Model\\ResourceModel\\Rate\\Collection $collection *\/\n       $collection = $observer-&gt;getEvent()-&gt;getData('rates_collection');\n       if (!$collection instanceof \\MageWorx\\ShippingRules\\Model\\ResourceModel\\Rate\\Collection) {\n           return;\n       }\n\n       \/** @var \\Magento\\Quote\\Model\\Quote\\Address\\RateRequest $request *\/\n       $request = $observer-&gt;getEvent()-&gt;getData('request');\n       if (!$request instanceof \\Magento\\Quote\\Model\\Quote\\Address\\RateRequest) {\n           return;\n       }\n\n       \/** @var \\Magento\\Quote\\Model\\Quote\\Item&#91;] $items *\/\n       $items        = $request-&gt;getAllItems() ?? &#91;];\n       $shippingCategories = &#91;];\n       foreach ($items as $item) {\n           $value = $item-&gt;getProduct()-&gt;getData('shippingnew');\n           if ($value !== null) {\n               $shippingCategories&#91;] = $value;\n           }\n       }\n       $shippingCategories = array_unique($shippingCategories);\n\n       $joinTable = $collection-&gt;getTable('mageworx_shippingrules_rates_shippingnew');\n       $collection-&gt;getSelect()\n                  -&gt;joinLeft(\n                      &#91;'sn' =&gt; $joinTable],\n                      '`main_table`.`rate_id` = `sn`.`rate_id`',\n                      &#91;'shippingnew']\n                  );\n\n       $collection-&gt;getSelect()-&gt;where(\n           \"`sn`.`shippingnew` IN (?)\",\n           $shippingCategories\n       );\n   }\n}\n<\/code><\/pre>\n\n\n\n<p><p>This is the most complicated observer in our stack. It is designed to collect all attribute values (`$shippingCategories`) from a customer\u2019s cart and adds the attribute value as a filter to the regular rates collection (our table is already joined). To keep it simple, I\u2019ve named it \u2018Filter\u2019. When work is done, a customer will see actual shipping rates for the current cart items.<\/p><p style=\"font-weight: 400;\"><span style=\"font-weight: 400;\">Another 3 event observers are designed to add and receive custom data during shipping rates export and import. We skip its code in the blog post, but it will be available in the repository with the source code.<\/span><\/p><h2><br><\/h2><\/p>\n\n\n<p><a href=\"https:\/\/www.mageworx.com\/magento-2-shipping-suite.html\"><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-11550 size-full\" src=\"https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2020\/01\/banner-3-1.png\" alt=\"MageWorx Magento 2 Extensions\" width=\"1060\" height=\"200\" srcset=\"https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2020\/01\/banner-3-1.png 1060w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2020\/01\/banner-3-1-600x113.png 600w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2020\/01\/banner-3-1-768x145.png 768w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2020\/01\/banner-3-1-250x47.png 250w, https:\/\/www.mageworx.com\/blog\/wp-content\/uploads\/2020\/01\/banner-3-1-696x131.png 696w\" sizes=\"auto, (max-width: 1060px) 100vw, 1060px\" \/><\/a><\/p>\n\n\n<h3 class=\"wp-block-heading\">Step 3. User Interface<\/h3>\n\n\n\n<p>It\u2019s time to add our attribute to the grid and to the form of the shipping rates. <\/p>\n\n\n\n<h4 class=\"wp-block-heading\">Form<\/h4>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/view\/adminhtml\/ui_component\/mageworx_shippingrules_rate_form.xml\n\n&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;\n&lt;!--\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n--&gt;\n&lt;form xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:module:Magento_Ui:etc\/ui_configuration.xsd\"&gt;\n   &lt;fieldset name=\"conditions\"&gt;\n       &lt;field name=\"shippingnew\"&gt;\n           &lt;argument name=\"data\" xsi:type=\"array\"&gt;\n               &lt;item name=\"options\" xsi:type=\"object\"&gt;MageWorx\\ShippingRateByProductAttribute\\Model\\Config\\Source\\ShippingCategory&lt;\/item&gt;\n               &lt;item name=\"config\" xsi:type=\"array\"&gt;\n                   &lt;item name=\"label\" xsi:type=\"string\" translate=\"true\"&gt;Shipping Category&lt;\/item&gt;\n                   &lt;item name=\"dataType\" xsi:type=\"string\"&gt;int&lt;\/item&gt;\n                   &lt;item name=\"formElement\" xsi:type=\"string\"&gt;select&lt;\/item&gt;\n                   &lt;item name=\"dataScope\" xsi:type=\"string\"&gt;shippingnew&lt;\/item&gt;\n                   &lt;item name=\"source\" xsi:type=\"string\"&gt;mageworx_shippingrules_rate_form.custom_attributes&lt;\/item&gt;\n               &lt;\/item&gt;\n           &lt;\/argument&gt;\n       &lt;\/field&gt;\n   &lt;\/fieldset&gt;\n&lt;\/form&gt;\n<\/code><\/pre>\n\n\n\n<h4 class=\"wp-block-heading\">Grid<br><\/h4>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/view\/adminhtml\/ui_component\/mageworx_shippingrules_rates_regular_listing.xml\n\n&lt;?xml version=\"1.0\"?&gt;\n&lt;!--\nCopyright \u00a9 MageWorx. All rights reserved.\nSee LICENSE.txt for license details.\n--&gt;\n&lt;listing xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\" xsi:noNamespaceSchemaLocation=\"urn:magento:framework:Ui\/etc\/ui_configuration.xsd\"&gt;\n   &lt;columns name=\"mageworx_shippingrules_rates_columns\"&gt;\n       &lt;column name=\"shippingnew\"&gt;\n           &lt;argument name=\"data\" xsi:type=\"array\"&gt;\n               &lt;item name=\"options\" xsi:type=\"object\"&gt;MageWorx\\ShippingRateByProductAttribute\\Model\\Config\\Source\\ShippingCategory&lt;\/item&gt;\n               &lt;item name=\"config\" xsi:type=\"array\"&gt;\n                   &lt;item name=\"filter\" xsi:type=\"string\"&gt;select&lt;\/item&gt;\n                   &lt;item name=\"component\" xsi:type=\"string\"&gt;Magento_Ui\/js\/grid\/columns\/select&lt;\/item&gt;\n                   &lt;item name=\"dataType\" xsi:type=\"string\"&gt;select&lt;\/item&gt;\n                   &lt;item name=\"label\" xsi:type=\"string\" translate=\"true\"&gt;Shipping Category&lt;\/item&gt;\n                   &lt;item name=\"visible\" xsi:type=\"boolean\"&gt;true&lt;\/item&gt;\n                   &lt;item name=\"sortOrder\" xsi:type=\"number\"&gt;40&lt;\/item&gt;\n                   &lt;item name=\"editor\" xsi:type=\"string\"&gt;select&lt;\/item&gt;\n               &lt;\/item&gt;\n           &lt;\/argument&gt;\n       &lt;\/column&gt;\n   &lt;\/columns&gt;\n&lt;\/listing&gt;\n<\/code><\/pre>\n\n\n\n<p>As you can see, we use the custom source model in those files. Let\u2019s create it. It will load a corresponding attribute (`shippingnew`) and give us all the available values.<br><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&gt; app\/code\/MageWorx\/ShippingRateByProductAttribute\/Model\/Config\/Source\/ShippingCategory.php\n\n&lt;?php\n\/**\n* Copyright \u00a9 MageWorx. All rights reserved.\n* See LICENSE.txt for license details.\n*\/\n\nnamespace MageWorx\\ShippingRateByProductAttribute\\Model\\Config\\Source;\n\nuse Magento\\Framework\\Exception\\LocalizedException;\n\n\/**\n* Class ShippingCategory\n*\n* Obtain options for specified product attribute\n*\/\nclass ShippingCategory extends \\Magento\\Eav\\Model\\Entity\\Attribute\\Source\\AbstractSource\n{\n   \/**\n    * @var \\Magento\\Catalog\\Api\\ProductAttributeRepositoryInterface\n    *\/\n   protected $productAttributeRepository;\n\n   \/**\n    * @var \\Psr\\Log\\LoggerInterface\n    *\/\n   protected $logger;\n\n   \/**\n    * ShippingCategory constructor.\n    *\n    * @param \\Magento\\Catalog\\Api\\ProductAttributeRepositoryInterface $productAttributeRepository\n    * @param \\Psr\\Log\\LoggerInterface $logger\n    *\/\n   public function __construct(\n       \\Magento\\Catalog\\Api\\ProductAttributeRepositoryInterface $productAttributeRepository,\n       \\Psr\\Log\\LoggerInterface $logger\n   ) {\n       $this-&gt;productAttributeRepository = $productAttributeRepository;\n       $this-&gt;logger                     = $logger;\n   }\n\n   \/**\n    * @inheritDoc\n    *\/\n   public function getAllOptions()\n   {\n       if (empty($this-&gt;_options)) {\n           try {\n               \/** @var \\Magento\\Catalog\\Api\\Data\\ProductAttributeInterface $attribute *\/\n               $attribute      = $this-&gt;productAttributeRepository-&gt;get('shippingnew');\n               $this-&gt;_options = $attribute-&gt;usesSource() ? $attribute-&gt;getSource()-&gt;getAllOptions() : &#91;];\n           } catch (LocalizedException $localizedException) {\n               $this-&gt;logger-&gt;critical($localizedException-&gt;getLogMessage());\n           }\n       }\n\n       return $this-&gt;_options;\n   }\n}\n<\/code><\/pre>\n\n\n\n<p>The piece of code is pretty simple. We use the attributes repository to load our attribute and then obtain all options (values) from it. Keep in mind that the attribute with the `shippingnew` code must be created in the admin panel and must have a dropdown input type with predefined options (values). You can do that from the menu \u2018Stores &gt; Attributes &gt; Product\u2019. Don&#8217;t forget to add this attribute to the attribute set you are using for the products.<br><\/p>\n\n\n\n<p>When everything is done, we only need to enable the module and run the `setup:upgrade`. Cache will be cleared automatically.<br><\/p>\n\n\n\n<p>Go to the rates grid (\u2018Stores &gt; Shipping Rates\u2019) and you\u2019ll see the new column:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/NOJFdmTRrCnbWmrudbGpU9uhMApFOpUqsVKWgswBe_ZBK3fH6kU1RAAXVnvaXK_74Z5fv7H34KA2mnS9D8GbcO_ouuBVllOh31TZHB6rzMVtDH3rPb33Ru7bLzcff53yGFNopFzI\" alt=\"How to Add Custom Product Attribute as a Filter for the Shipping Rates | MageWorx Blog\"\/><\/figure>\n\n\n\n<p>That condition will be available inside the rates form:<br><\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh6.googleusercontent.com\/WwVJw7QogOG1WjldDnMeLzWBph9sJ8_PhB8G7VMFdZVVX-LpXvTWXQZhnEeHohpLmUAsmxcI0UQGETfALaTcl1jJ1jEm7C79So--DX0hG5t5O554aIq7K2N4l_tMemIEKswDR8ZC\" alt=\"How to Add Custom Product Attribute as a Filter for the Shipping Rates | MageWorx Blog\"\/><\/figure>\n\n\n\n<p>If we set the \u2018Multiple rates price calculation\u2019 setting to the \u2018Use Rate with Max Price\u2019 in the corresponding <a href=\"https:\/\/www.mageworx.com\/blog\/shipping-suite-for-magento-2-use-cases-part-1\" data-type=\"URL\" data-id=\"https:\/\/www.mageworx.com\/blog\/shipping-suite-for-magento-2-use-cases-part-1\">Shipping Method<\/a>, the rate with the highest price will be used during shipping price calculation.\u00a0<br><\/p>\n\n\n\n<p>Here is a small example of how it works in the format of screenshots:<br><\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Set up your&nbsp; products<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/42jkdt66TqnZ-8UamolOyuNCHL4rTCyg259znzGKuBe6koDAvrLwwXiZI-mi8-RBn9gfm_OBQBMXAH18xSsInBOMYTAL9nsKp1l0KiwDNhHJjpyVwQQGlUhpyELWGWKn7MIzIwXV\" alt=\"How to Add Custom Product Attribute as a Filter for the Shipping Rates | MageWorx Blog\"\/><\/figure>\n\n\n\n<ol class=\"wp-block-list\"><li>Set up the rates<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh4.googleusercontent.com\/r2HGT_GqaWf8_xPljzMv-C3sU6glclhmThEgk_8ZHDnDVk9hTgunk9s8oyl98DQUz_oq9FBpfJtIWM5XP2SYSZCWvqTdztNe1suxje6ZPjHvbJ0O4vQyQ5ZD6h9PlzhIa1f8zu_a\" alt=\"How to Add Custom Product Attribute as a Filter for the Shipping Rates | MageWorx Blog\"\/><\/figure>\n\n\n\n<ol class=\"wp-block-list\"><li>Set up the&nbsp; price calculation algorithm (in the shipping method form)<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/LovWEYTtI0sRTGAgMnbwLV5cvcbCYFR7ueDRkwp5rKb1XxODkM7YlbbQx_-ScskbAAuZHPm9O1uZkNm26WIrzafYjrEK5WuCHzHVqNeyGya37aBVugF5YW9X3_CvaV_ytCrgI51w\" alt=\"How to Add Custom Product Attribute as a Filter for the Shipping Rates | MageWorx Blog\"\/><\/figure>\n\n\n\n<ol class=\"wp-block-list\"><li>Check shipping price for the corresponding method with selected products in the cart (on the frontend).<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh6.googleusercontent.com\/9FLGVc8bSI6yHAgw__jXshhJT8iFw17yKwasGsv5-NKJkFs0X7z0pNBnq5r0axbteZcOXrBIeE6qu3RJWdYOnoq5ka-YLNS0AUZJkhw_WkF-ga4urfJBRq3UvLpB6dTeFADxWnSC\" alt=\"How to Add Custom Product Attribute as a Filter for the Shipping Rates | MageWorx Blog\"\/><\/figure>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/lh5.googleusercontent.com\/piyx8m9SvQK4ud8blB57bxnto_wrUTC7o2yJU0WcuJep_IsHKcASNQYE2rW8S0F5PuVOm-NTMFqYgI5EvTINOjKH78auRuq1NPZyDTGIK88uS2wKGoA6MQ3WG42z447D3nEmsTu0\" alt=\"How to Add Custom Product Attribute as a Filter for the Shipping Rates | MageWorx Blog\"\/><\/figure>\n\n\n\n<p>This is not everything the <a href=\"https:\/\/www.mageworx.com\/magento-2-shipping-suite.html\">Shipping Suite module<\/a> is capable of. Feel free to play with the settings to get the desired result.<br><\/p>\n\n\n\n<p>I\u2019ll be glad to answer any questions! Thus, feel free to leave your comments in the dedicated field below.<br><\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 8<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>Often, in Magento 2, the number of standard product attributes that can be used to set up conditions is limited. Extra customization would be required to meet business needs. From this article, you\u2019ll learn how to achieve that and add custom product attributes as a filter for the shipping rates. Notes:&nbsp; See the complete code [&hellip;]<\/p>\n","protected":false},"author":15,"featured_media":12010,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[255,425,426],"tags":[436],"class_list":{"0":"post-11975","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-magento-2","8":"category-magento-how-tos","9":"category-extensions-tips-and-tricks","10":"tag-developer-diaries"},"_links":{"self":[{"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/posts\/11975","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/comments?post=11975"}],"version-history":[{"count":23,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/posts\/11975\/revisions"}],"predecessor-version":[{"id":16660,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/posts\/11975\/revisions\/16660"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/media\/12010"}],"wp:attachment":[{"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/media?parent=11975"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/categories?post=11975"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mageworx.com\/blog\/wp-json\/wp\/v2\/tags?post=11975"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}