Create Attributes In Magento 2

Creating attributes in Magento 2 is very similar to Magento 1. Magento 2, for the most part is smart enough to ignore the creation of attributes which already exist, and will instead update them. This is all well and good, unless your attributes have options in them. In which case, you will need to check whether the attribute exists already or Magento will duplicate option values on subsequent runs of your installer or updater.

The fix for this is to check whether the attribute already exists before attempting to create it, however this causes another issue. Magento’s EAVConfig class caches attributes after a the getAttribute method is called – so this cache needs cleaning if we need to set other data on the attribute after creation, such as the forms it needs to exist in.

The following is an example to create a customer address attribute, taken from an installer class where $this->eavConfig is an instance of \Magento\Eav\Model\Config:

protected function createCustomerAddressAttributes(
    \Magento\Eav\Setup\EavSetup $eavSetup,
    ModuleDataSetupInterface $setup
) {

    $customerAddressSetup = $this->customerSetupFactory->create(['setup' => $setup]);
    $customerAddressEntity = $customerAddressSetup->getEavConfig()->getEntityType(
        \Magento\Customer\Api\AddressMetadataInterface::ENTITY_TYPE_ADDRESS
    );
    $attributeSetId = $customerAddressEntity->getDefaultAttributeSetId();
    $attributeSet = $this->attributeSetFactory->create();
    $attributeGroupId = $attributeSet->getDefaultGroupId($attributeSetId);

    $attributes = [];

    $attributes['attribute_code'] = [
        'type'     => 'text',
        'label'    => 'Attribute Label',
        'input'    => 'textarea',
        'visible'  => true,
        'required' => false,
        'user_defined' => true,
        'position' => 220
    ];

    foreach ($attributes as $code => $attribute) {
        $this->eavConfig->clear();
        // Don't create the attribute if it already exists
        $attributeCheck = $this->eavConfig->getAttribute(
            \Magento\Customer\Api\AddressMetadataInterface::ENTITY_TYPE_ADDRESS,
            $code
        );
        
        if ($attributeCheck->getAttributeId()) {
            continue;
        }

        // Stop Magento from loading the cached attribute with no Id.
        $this->eavConfig->clear();

        $attribute['system'] = 0;

        $eavSetup->addAttribute(
            \Magento\Customer\Api\AddressMetadataInterface::ENTITY_TYPE_ADDRESS,
            $code,
            $attribute
        );

        $createdAttribute = $this->eavConfig->getAttribute(
            \Magento\Customer\Api\AddressMetadataInterface::ENTITY_TYPE_ADDRESS,
            $code
        );

        $createdAttribute->setData('used_in_forms', ['adminhtml_customer_address', 'customer_address_edit'])
            ->setData('attribute_set_id', $attributeSetId)
            ->setData('attribute_group_id', $attributeGroupId)
            ->save();
    }

    return $this;        
}

If we didn’t have the $this->eavConfig->clear(); clear in there, the installer would use the cached version of the attribute when performing our initial $attributeCheck, and the final save would try and create the attribute again.