Attribute Creation Methods
Method 1: Data Patch (Recommended)
<?php
namespace Vendor\Module\Setup\Patch\Data;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Eav\Setup\EavSetupFactory;
class AddCustomAttributes implements DataPatchInterface
{
public function __construct(
private ModuleDataSetupInterface $setup,
private EavSetupFactory $eavSetupFactory
) {}
public function install(): void
{
$this->setup->startSetup();
$eavSetup = $this->eavSetupFactory->create(['setup' => $this->setup]);
// Text attribute
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'custom_text',
[
'type' => 'varchar',
'label' => 'Custom Text',
'input' => 'text',
'required' => false,
'sort_order' => 100,
'group' => 'General',
'global' => \Magento\Eav\Model\Entity\Attribute\Scope::SCOPE_STORE,
'visible_on_front' => true,
'used_in_product_listing' => true,
]
);
// Select attribute
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'custom_select',
[
'type' => 'int',
'label' => 'Custom Select',
'input' => 'select',
'source' => 'Vendor\Module\Model\Product\Attribute\Source\CustomOptions',
'required' => false,
'sort_order' => 101,
'group' => 'General',
]
);
// Textarea attribute
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'custom_textarea',
[
'type' => 'text',
'label' => 'Custom Textarea',
'input' => 'textarea',
'required' => false,
'sort_order' => 102,
'group' => 'General',
'wysiwyg_enabled' => true,
'is_html_allowed_on_front' => true,
]
);
$this->setup->endSetup();
}
public function getAliases(): array { return []; }
public function getDependencies(): array { return []; }
}
Attribute Properties
Complete Property List
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'attribute_code',
[
// Core properties
'type' => 'varchar', // Backend type
'label' => 'Label', // Admin label
'input' => 'text', // Frontend input
'required' => false, // Required in admin
'sort_order' => 100, // Display order
'group' => 'General', // Attribute group
// Scope
'global' => \Magento\Eav\Model\Entity\Attribute\Scope::SCOPE_STORE,
// SCOPE_STORE (0), SCOPE_WEBSITE (1), SCOPE_GLOBAL (2)
// Source model (for select/multiselect)
'source' => 'Vendor\Module\Model\Source\Options',
// Backend model (for data processing)
'backend' => 'Vendor\Module\Model\Backend\Custom',
// Frontend model (for rendering)
'frontend' => 'Vendor\Module\Model\Frontend\Custom',
// Validation
'unique' => false,
'validate_rules' => [
'max_text_length' => 255,
'min_text_length' => 1,
],
// Visibility
'visible' => true,
'visible_on_front' => true,
'used_in_product_listing' => true,
// Search and filtering
'filterable' => true,
'searchable' => true,
'comparable' => true,
'used_for_sort_by' => true,
'used_for_promo_rules' => true,
// WYSIWYG
'wysiwyg_enabled' => false,
'is_html_allowed_on_front' => false,
// Default value
'default' => '',
// Apply to product types
'apply_to' => 'simple,configurable,virtual,downloadable',
]
);
Key Properties
| Property | Description | Values |
|---|---|---|
| type | Backend storage type | varchar, text, int, decimal, datetime |
| input | Admin input type | text, textarea, select, boolean, price, date |
| global | Value scope | SCOPE_STORE, SCOPE_WEBSITE, SCOPE_GLOBAL |
| source | Source model class | Full class name for options |
| backend | Backend model class | Full class name for processing |
| frontend | Frontend model class | Full class name for rendering |
Custom Source Models
Database-Driven Source Model
<?php
namespace Vendor\Module\Model\Product\Attribute\Source;
use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
class CustomOptions extends AbstractSource
{
public function __construct(
private \Vendor\Module\Model\ResourceModel\Option\Collection $optionCollection
) {}
public function toOptionArray(): array
{
$options = [
['value' => '', 'label' => __('-- Please Select --')]
];
foreach ($this->optionCollection as $option) {
$options[] = [
'value' => $option->getId(),
'label' => $option->getName()
];
}
return $options;
}
}
Config-Driven Source Model
<?php
namespace Vendor\Module\Model\Product\Attribute\Source;
use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
use Magento\Framework\App\Config\ScopeConfigInterface;
class ConfigOptions extends AbstractSource
{
public function __construct(
private ScopeConfigInterface $config
) {}
public function toOptionArray(): array
{
$options = [];
$configValue = $this->config->getValue('vendor/module/options');
if ($configValue) {
$items = explode(',', $configValue);
foreach ($items as $item) {
$options[] = [
'value' => trim($item),
'label' => trim($item)
];
}
}
return $options;
}
}
Register Source Model
<!-- di.xml -->
<config>
<virtualType name="CustomOptions" type="Vendor\Module\Model\Product\Attribute\Source\CustomOptions">
<arguments>
<argument name="optionCollection" xsi:type="object">Vendor\Module\Model\ResourceModel\Option\Collection</argument>
</arguments>
</virtualType>
</config>
Frontend and Backend Models
Frontend Model
<?php
namespace Vendor\Module\Model\Product\Attribute\Frontend;
use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
use Magento\Framework\DataObject;
class CustomFrontend extends AbstractFrontend
{
public function getValue(DataObject $object): ?string
{
$value = $object->getData($this->getAttribute()->getAttributeCode());
if ($value) {
return strtoupper($value);
}
return null;
}
public function getOutputHtml(DataObject $object): string
{
$value = $this->getValue($object);
return '<span class="custom-attribute">' . htmlspecialchars($value) . '</span>';
}
}
Backend Model
<?php
namespace Vendor\Module\Model\Product\Attribute\Backend;
use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
class CustomBackend extends AbstractBackend
{
public function validate($object)
{
$value = $object->getData($this->getAttribute()->getAttributeCode());
if ($this->getAttribute()->getIsRequired() && empty($value)) {
throw new \Magento\Framework\Exception\LocalizedException(
__('Attribute %1 is required.', $this->getAttribute()->getFrontendLabel())
);
}
return true;
}
public function beforeSave($object)
{
$value = $object->getData($this->getAttribute()->getAttributeCode());
// Transform value before save
$object->setData(
$this->getAttribute()->getAttributeCode(),
trim($value)
);
return $this;
}
public function afterSave($object)
{
// Process after save
return $this;
}
}
Complete Registration
// Data patch
class AddCustomAttributeWithModels implements DataPatchInterface
{
public function install(): void
{
$this->setup->startSetup();
$eavSetup = $this->eavSetupFactory->create(['setup' => $this->setup]);
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'custom_attribute',
[
'type' => 'varchar',
'label' => 'Custom Attribute',
'input' => 'select',
'source' => 'Vendor\Module\Model\Product\Attribute\Source\CustomOptions',
'backend' => 'Vendor\Module\Model\Product\Attribute\Backend\CustomBackend',
'frontend' => 'Vendor\Module\Model\Product\Attribute\Frontend\CustomFrontend',
'required' => false,
'sort_order' => 100,
'group' => 'General',
]
);
$this->setup->endSetup();
}
}
Quiz
1. What is the recommended way to create custom attributes?
2. What does the 'source' property define?
3. What does 'apply_to' control?
Flashcards
Question
How to create a custom attribute?
Click to reveal answer
Answer
Use $eavSetup->addAttribute() with type, label, input, source properties
Question
What is a source model?
Click to reveal answer
Answer
Class providing options for select/multiselect attributes
Question
What is a backend model?
Click to reveal answer
Answer
Class handling data validation and processing on save
Question
What is a frontend model?
Click to reveal answer
Answer
Class controlling attribute rendering on frontend
Question
What does apply_to control?
Click to reveal answer
Answer
Which product types can use the attribute
Revision Notes
Key Takeaways
- 1. Use data patches to create custom attributes (recommended method)
- 2. Key properties: type, label, input, source, backend, frontend, group
- 3. Source models provide options for select/multiselect attributes
- 4. Backend models handle validation and data processing
- 5. Frontend models control attribute rendering
Interview Tips
- • Demonstrate creating a custom attribute with data patch
- • Explain source, backend, and frontend model purposes
- • Discuss attribute properties and their effects
Cheat Sheet
addAttribute() Properties:
type → varchar, text, int, decimal, datetime
input → text, textarea, select, boolean, price, date
label → Admin display name
source → Options source model
backend → Data processing model
frontend → Rendering model
group → Attribute group name
required → Required in admin
global → SCOPE_STORE, SCOPE_WEBSITE, SCOPE_GLOBAL
apply_to → simple,configurable,bundle,grouped
Methods:
$eavSetup->addAttribute($entity, $code, $options)
$eavSetup->addAttributeToSet($entity, $setId, $groupId, $code)