The attribute option should have "Swatch" and "Description" set
After adding two options, I can save attribute
Only "Description" is filled
After adding two options that way, unable to save attribute
The other way to reproduce it is to add option via php, the same way as in: https://github.com/magento/magento2/issues/11032
Hi @jakubderdziak. Thank you for your report.
To help us process this issue please make sure that you provided the following information:
Please make sure that the issue is reproducible on the vanilla Magento instance following Steps to reproduce. To deploy vanilla Magento instance on our environment, please, add a comment to the issue:
@magento-engcom-team give me {$VERSION} instance
where {$VERSION} is version tags (starting from 2.2.0+) or develop branches (2.2-develop +).
For more details, please, review the Magento Contributor Assistant documentation.
@jakubderdziak do you confirm that you was able to reproduce the issue on vanilla Magento instance following steps to reproduce?
@engcom-backlog-nazar Thank you for verifying the issue. Based on the provided information internal tickets MAGETWO-95036, MAGETWO-95037 were created
Hi @jakubderdziak , thank you for your report.
We've acknowledge the issue and added to our backlog.
@jakubderdziak And seem like is_default not work, in 2.3-develop branch the issue ->After adding two options that way, unable to save attribute resolved.
Hello @engcom-backlog-nazar - I've created pull request with proposed solution for this problem. Please verify if it's acceptable by you, and let me know if something need to be corrected.
@jakubderdziak unfortunately your pull request does not fix the issue, at least not for me:
curl --request PUT \
--url https://someshopdomain/rest/V1/products/attributes/foo_bar \
--header 'authorization: Bearer xxxxxx' \
--header 'content-type: application/json' \
--data '{ "attribute": { "backend_type": "int", "frontend_input": "swatch_text", "default_frontend_label": "Default Label ", "frontend_labels": [ { "label": "Default Label", "store_id": 0 } ], "entity_type_id": "4", "scope": "website", "options": [ { "label": "1 cm (Admin)", "value": "1cm", "sort_order": 1, "is_default": false, "store_labels": [ { "store_id": 1, "label": "1 cm (Store 1)" }, { "store_id": 2, "label": "1 cm (Store 2)" } ] }, { "label": "2 cm (Admin)", "value": "2cm", "sort_order": 2, "is_default": false, "store_labels": [ { "store_id": 1, "label": "2 cm (Store 1)" }, { "store_id": 2, "label": "2 cm (Store 2)" } ] }, { "label": "3 cm (Admin)", "sort_order": 3, "is_default": false, "store_labels": [ { "store_id": 1, "label": "3 cm (Store 1)" }, { "store_id": 2, "label": "3 cm (Store 2)" } ] } ] }}'
this API call still results in an attribute created without swatch values:

I'm using 2.2.5 with your pull request applied. is something wrong with my data payload?
Hello @heldchen
I'm sorry, but my fix was written and tested for this API:
POST/V1/products/attributes/{attributeCode}/options
It looks like it doesn't cover API mentioned by you. As a fast workaround you can create empty attribute with API you've mentioned and then add options via POST/V1/products/attributes/{attributeCode}/options.
Looks like problem needs further investigation...
Also I've made a mistake in getAttributeOptionId method. It should look like this:
/**
* Get option id. If it not exist get it from dependency link array
*
* @param integer $optionId
* @return int
*/
protected function getAttributeOptionId($optionId)
{
if (substr($optionId, 0, 6) == self::BASE_OPTION_TITLE) {
$possibleOptions = [
$optionId,
substr($optionId, 7), //Let's remove "option" keyword in case data came from API instead of Admin Panel
];
foreach ($possibleOptions as $optionId) {
if (isset($this->dependencyArray[$optionId])) {
return $this->dependencyArray[$optionId];
}
}
return null
}
return $optionId;
}
I'm not able to commit it right now - will do it after weekend.
But I'm almost sure it won't resolve @heldchen problem.
thanks for the update, @jakubderdziak - I've tried your fixed version and am using the /options call now. while it is a huge improvement over the previously broken attribute state, unfortunately it sets swatch value to the label's content:
adding a new option with label N2 and value V2 like so:
curl --request POST \
--url https://someshopdomain/rest/V1/products/attributes/foo_bar/options \
--header 'authorization: Bearer xxx' \
--header 'content-type: application/json' \
--data '{ "option": {"label": "N2", "value": "V2"} }'
results in this:

Honestly I didn't know that you could add label and value in single option. I have no access to the code right now to do afix. If you know how to run xdebug, please try to debug method handleOptionsWithoutSwatches, probably this line:
foreach ($attribute->getData('option/value') as $key => $label) {
and see where value option is stored.
thanks @jakubderdziak - as our customer requires this to work, we spent a few hours debugging it. values and labels are now properly set both when creating an attribut as well as when adding an option.
here are the relevant changes (your PR + our changes, compared to v2.2.5):
diff --git a/www/vendor/magento/module-eav/Model/Entity/Attribute/OptionManagement.php b/www/vendor/magento/module-eav/Model/Entity/Attribute/OptionManagement.php
index b0508fd8cc..b1d279971c 100644
--- a/www/vendor/magento/module-eav/Model/Entity/Attribute/OptionManagement.php
+++ b/www/vendor/magento/module-eav/Model/Entity/Attribute/OptionManagement.php
@@ -51,12 +51,16 @@ class OptionManagement implements \Magento\Eav\Api\AttributeOptionManagementInte
$optionId = $this->getOptionId($option);
$options = [];
+ $swatch = [];
$options['value'][$optionId][0] = $option->getLabel();
$options['order'][$optionId] = $option->getSortOrder();
+ $swatch['value']["option_" . $optionId][0] = $option->getValue();
if (is_array($option->getStoreLabels())) {
foreach ($option->getStoreLabels() as $label) {
+ $options['label'][$optionId][$label->getStoreId()] = $option->getValue();
$options['value'][$optionId][$label->getStoreId()] = $label->getLabel();
+ $swatch['value']["option_" . $optionId][$label->getStoreId()] = $option->getValue();
}
}
@@ -65,6 +69,8 @@ class OptionManagement implements \Magento\Eav\Api\AttributeOptionManagementInte
}
$attribute->setOption($options);
+ $attribute->setSwatch($swatch);
+
try {
$this->resourceModel->save($attribute);
} catch (\Exception $e) {
diff --git a/www/vendor/magento/module-swatches/Model/Plugin/EavAttribute.php b/www/vendor/magento/module-swatches/Model/Plugin/EavAttribute.php
index d3904f058d..0b949c44da 100644
--- a/www/vendor/magento/module-swatches/Model/Plugin/EavAttribute.php
+++ b/www/vendor/magento/module-swatches/Model/Plugin/EavAttribute.php
@@ -10,6 +10,7 @@ use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Swatches\Model\Swatch;
+use Magento\Swatches\Model\ResourceModel\Swatch as SwatchResource;
/**
* Plugin model for Catalog Resource Attribute
@@ -18,6 +19,11 @@ class EavAttribute
{
const DEFAULT_STORE_ID = 0;
+ /**
+ * @var SwatchResource
+ */
+ protected $swatchResource;
+
/**
* Base option title used for string operations to detect is option already exists or new
*/
@@ -59,22 +65,32 @@ class EavAttribute
*/
private $serializer;
+ /**
+ * @var \Magento\Eav\Model\Entity\Attribute\Source\TableFactory
+ */
+ protected $tableFactory;
+
/**
* @param \Magento\Swatches\Model\ResourceModel\Swatch\CollectionFactory $collectionFactory
* @param \Magento\Swatches\Model\SwatchFactory $swatchFactory
* @param \Magento\Swatches\Helper\Data $swatchHelper
* @param Json|null $serializer
+ * @param SwatchResource|null $swatchResource
*/
public function __construct(
\Magento\Swatches\Model\ResourceModel\Swatch\CollectionFactory $collectionFactory,
\Magento\Swatches\Model\SwatchFactory $swatchFactory,
\Magento\Swatches\Helper\Data $swatchHelper,
- Json $serializer = null
+ \Magento\Eav\Model\Entity\Attribute\Source\TableFactory $tableFactory,
+ Json $serializer = null,
+ SwatchResource $swatchResource = null
) {
$this->swatchCollectionFactory = $collectionFactory;
$this->swatchFactory = $swatchFactory;
$this->swatchHelper = $swatchHelper;
$this->serializer = $serializer ?: ObjectManager::getInstance()->create(Json::class);
+ $this->swatchResource = $swatchResource ?: ObjectManager::getInstance()->create(SwatchResource::class);
+ $this->tableFactory = $tableFactory;
}
/**
@@ -141,6 +157,8 @@ class EavAttribute
if (!empty($swatchesArray)) {
$attribute->setData('swatch', $swatchesArray);
}
+
+ $this->handleOptionsWithoutSwatches($attribute);
}
}
@@ -148,6 +166,7 @@ class EavAttribute
* Prepare attribute for conversion from any swatch type to dropdown
*
* @param Attribute $attribute
+ * @throws \Magento\Framework\Exception\LocalizedException
* @return void
*/
protected function convertSwatchToDropdown(Attribute $attribute)
@@ -157,6 +176,7 @@ class EavAttribute
if (!empty($additionalData)) {
$additionalData = $this->serializer->unserialize($additionalData);
if (is_array($additionalData) && isset($additionalData[Swatch::SWATCH_INPUT_TYPE_KEY])) {
+ $this->cleanEavAttributeOptionSwatchValues($attribute->getOption());
unset($additionalData[Swatch::SWATCH_INPUT_TYPE_KEY]);
$attribute->setData('additional_data', $this->serializer->serialize($additionalData));
}
@@ -177,8 +197,13 @@ class EavAttribute
if (!empty($optionsArray) && is_array($optionsArray)) {
$optionsArray = $this->prepareOptionIds($optionsArray);
- $attributeSavedOptions = $attribute->getSource()->getAllOptions();
- $this->prepareOptionLinks($optionsArray, $attributeSavedOptions);
+
+ $adminStoreAttribute = clone $attribute;
+ $adminStoreAttribute->setStoreId(0);
+ $sourceModel = $this->tableFactory->create();
+ $sourceModel->setAttribute($adminStoreAttribute);
+
+ $this->prepareOptionLinks($optionsArray, $sourceModel);
}
return $attribute;
@@ -208,17 +233,17 @@ class EavAttribute
* Create links for non existed swatch options
*
* @param array $optionsArray
- * @param array $attributeSavedOptions
+ * @param \Magento\Eav\Model\Entity\Attribute\Source\Table $sourceModel
* @return void
*/
- protected function prepareOptionLinks(array $optionsArray, array $attributeSavedOptions)
- {
+ protected function prepareOptionLinks(
+ array $optionsArray,
+ \Magento\Eav\Model\Entity\Attribute\Source\Table $sourceModel
+ ) {
$dependencyArray = [];
if (is_array($optionsArray['value'])) {
- $optionCounter = 1;
- foreach (array_keys($optionsArray['value']) as $baseOptionId) {
- $dependencyArray[$baseOptionId] = $attributeSavedOptions[$optionCounter]['value'];
- $optionCounter++;
+ foreach ($optionsArray['value'] as $baseOptionId => $labels) {
+ $dependencyArray[$baseOptionId] = $sourceModel->getOptionId($labels[0]);
}
}
@@ -235,6 +260,7 @@ class EavAttribute
{
if ($this->swatchHelper->isVisualSwatch($attribute)) {
$this->processVisualSwatch($attribute);
+ $this->cleanTextSwatchValuesAfterSwitch($attribute->getOptiontext());
} elseif ($this->swatchHelper->isTextSwatch($attribute)) {
$this->processTextualSwatch($attribute);
}
@@ -267,6 +293,33 @@ class EavAttribute
}
}
+ /**
+ * Clean swatch option values after switching to the dropdown type.
+ *
+ * @param array $attributeOptions
+ * @param null $swatchType
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ protected function cleanEavAttributeOptionSwatchValues($attributeOptions, $swatchType = null)
+ {
+ if (count($attributeOptions) && isset($attributeOptions['value'])) {
+ $optionsIDs = array_keys($attributeOptions['value']);
+
+ $this->swatchResource->clearSwatchOptionByOptionIdAndType($optionsIDs, $swatchType);
+ }
+ }
+
+ /**
+ * Cleaning the text type of swatch option values after switching.
+ *
+ * @param array $attributeOptions
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ protected function cleanTextSwatchValuesAfterSwitch($attributeOptions)
+ {
+ $this->cleanEavAttributeOptionSwatchValues($attributeOptions, Swatch::SWATCH_TYPE_TEXTUAL);
+ }
+
/**
* @param string $value
* @return int
@@ -328,8 +381,20 @@ class EavAttribute
protected function getAttributeOptionId($optionId)
{
if (substr($optionId, 0, 6) == self::BASE_OPTION_TITLE) {
- $optionId = isset($this->dependencyArray[$optionId]) ? $this->dependencyArray[$optionId] : null;
+ $possibleOptions = [
+ $optionId,
+ substr($optionId, 7), //Let's remove "option" keyword in case data came from API instead of Admin Panel
+ ];
+
+ foreach ($possibleOptions as $optionId) {
+ if (isset($this->dependencyArray[$optionId])) {
+ return $this->dependencyArray[$optionId];
+ }
+ }
+
+ return null;
}
+
return $optionId;
}
@@ -473,4 +538,25 @@ class EavAttribute
}
return $result;
}
-}
+
+ /**
+ * @param Attribute $attribute
+ */
+ protected function handleOptionsWithoutSwatches(Attribute $attribute)
+ {
+ if (count($attribute->getData('option/value')) !== count($attribute->getData('swatch/value'))) {
+ $swatchesValues = $attribute->getData('swatch/value') ?? [];
+ foreach ($attribute->getData('option/value') as $key => $label) {
+ if (!array_key_exists($key, $swatchesValues)) {
+ $swatchesValues[sprintf('%s_%s', self::BASE_OPTION_TITLE, $key)] = $label;
+ }
+ }
+
+ if ($attribute->getData('swatch') === null) {
+ $attribute->setData('swatch', []);
+ }
+
+ $attribute->setData('swatch', ['value' => $swatchesValues]);
+ }
+ }
+}
\ No newline at end of file
diff --git a/www/vendor/magento/module-swatches/Model/SwatchAttributeType.php b/www/vendor/magento/module-swatches/Model/SwatchAttributeType.php
index 3d7a0c449c..85c421e676 100644
--- a/www/vendor/magento/module-swatches/Model/SwatchAttributeType.php
+++ b/www/vendor/magento/module-swatches/Model/SwatchAttributeType.php
@@ -44,7 +44,7 @@ class SwatchAttributeType
public function isTextSwatch(AttributeInterface $productAttribute)
{
$this->populateAdditionalDataEavAttribute($productAttribute);
- return $productAttribute->getData(Swatch::SWATCH_INPUT_TYPE_KEY) === Swatch::SWATCH_INPUT_TYPE_TEXT;
+ return $productAttribute->getData(Swatch::SWATCH_INPUT_TYPE_KEY) === Swatch::SWATCH_INPUT_TYPE_TEXT || $productAttribute->getFrontendInput() === Swatch::SWATCH_TYPE_TEXTUAL_ATTRIBUTE_FRONTEND_INPUT;
}
/**
Hi @vaibhavahalpara. Thank you for working on this issue.
Looks like this issue is already verified and confirmed. But if your want to validate it one more time, please, go though the following instruction:
Component: XXXXX label(s) to the ticket, indicating the components it may be related to.[ ] 2. Verify that the issue is reproducible on 2.3-develop branchDetails
- Add the comment @magento-engcom-team give me 2.3-develop instance to deploy test instance on Magento infrastructure.
- If the issue is reproducible on 2.3-develop branch, please, add the label Reproduced on 2.3.x.
- If the issue is not reproducible, add your comment that issue is not reproducible and close the issue and _stop verification process here_!
[ ] 3. Verify that the issue is reproducible on 2.2-develop branch. Details
- Add the comment @magento-engcom-team give me 2.2-develop instance to deploy test instance on Magento infrastructure.
- If the issue is reproducible on 2.2-develop branch, please add the label Reproduced on 2.2.x
[ ] 4. If the issue is not relevant or is not reproducible any more, feel free to close it.
Hi @bubasuma. Thank you for working on this issue.
Looks like this issue is already verified and confirmed. But if you want to validate it one more time, please, go though the following instruction:
Component: XXXXX label(s) to the ticket, indicating the components it may be related to.[ ] 2. Verify that the issue is reproducible on 2.4-develop branchDetails
- Add the comment @magento give me 2.4-develop instance to deploy test instance on Magento infrastructure.
- If the issue is reproducible on 2.4-develop branch, please, add the label Reproduced on 2.4.x.
- If the issue is not reproducible, add your comment that issue is not reproducible and close the issue and _stop verification process here_!
[ ] 3. If the issue is not relevant or is not reproducible any more, feel free to close it.
This issue is fixed in 2.3.4 https://github.com/magento/magento2/commit/1963fd0915e9a3b76871bbe0b8cb43850bc150f1
Hi @jakubderdziak, @bubasuma.
Thank you for your report and collaboration!
The related internal Jira ticket MC-21117 was closed as non-reproducible in 2.3-develop.
It means that Magento team either unable to reproduce this issue using provided _Steps to Reproduce_ from the _Description section_ on clean Magento instance or the issue has been already fixed in the scope of other tasks.
But if you still run into this problem please update or provide additional information/steps/preconditions in the _Description section_ and reopen this issue.
Hi @jakubderdziak, @bubasuma.
Thank you for your report and collaboration!
The related internal Jira ticket MC-24569 was closed as non-reproducible in 2.4-develop.
It means that Magento team either unable to reproduce this issue using provided _Steps to Reproduce_ from the _Description section_ on clean Magento instance or the issue has been already fixed in the scope of other tasks.
But if you still run into this problem please update or provide additional information/steps/preconditions in the _Description section_ and reopen this issue.
@roshanyadav89 Please refer to the comment
@roshanyadav89 I suggest to make sure the issue is reproducible in 2.4-develop. If yes, feel free to create or reopen this issue for 2.4-develop.
@roshanyadav89 I suggest to make sure the issue is reproducible in 2.4-develop. If yes, feel free to create or reopen this issue for 2.4-develop.
It's not working for multiple stores Magento 2.3.3 enterprise edition.
It's not related to API its a bug on cart page, mini cart and checkout page.
@roshanyadav89 This issue you're talking about does not seem to be related to this issue. I suggest to find related issue or create one.
@bubasuma Can you please check this out.
Hello,
I upgraded to Magento ver. 2.3.5-p1 and have the same problem!
When I send via API a new attribute text swatch, the swatch field is empty.
Is this not fixed or did I need more modifications?
my Payload for rest/V1/products/attributes/:
{
"attribute": {
"backend_type": "int",
"frontend_input": "swatch_text",
"default_frontend_label": "Default Label ",
"frontend_labels": [
{
"label": "Default Label",
"store_id": 0
}
],
"entity_type_id": "4",
"scope": "website",
"options": [
{
"label": "1 cm (Admin)",
"value": "1cm",
"sort_order": 1,
"is_default": false,
"store_labels": [
{
"store_id": 1,
"label": "1 cm (Store 1)"
},
{
"store_id": 2,
"label": "1 cm (Store 2)"
}
]
},
{
"label": "2 cm (Admin)",
"value": "2cm",
"sort_order": 2,
"is_default": false,
"store_labels": [
{
"store_id": 1,
"label": "2 cm (Store 1)"
},
{
"store_id": 2,
"label": "2 cm (Store 2)"
}
]
},
{
"label": "3 cm (Admin)",
"sort_order": 3,
"is_default": false,
"store_labels": [
{
"store_id": 1,
"label": "3 cm (Store 1)"
},
{
"store_id": 2,
"label": "3 cm (Store 2)"
}
]
}
]
}
}

Hello There,
Magento Version : 2.3.5-p2
I have converted dropdown attribute to text swatch and the admin swatch values were missing. It was showing "null" at product details page.
Most helpful comment
thanks @jakubderdziak - as our customer requires this to work, we spent a few hours debugging it. values and labels are now properly set both when creating an attribut as well as when adding an option.
here are the relevant changes (your PR + our changes, compared to v2.2.5):