Quick overview of the entity attribute system

Since the early days, Magento has offered an extensible entity attribute system based on the Entity-Attribute-Value (EAV) model.

Through this system, it’s straightforward to extend a Magento entity, with some limits:

  • only EAV entities can be extended with attributes; not all entities in a default Magento installation are EAV entities, just products, categories, customers, and customer addresses.
  • attributes can only contain scalar values, that is, booleanintfloat, or string values.

The extension attributes system, introduced in Magento 2, overcomes the above limits, making it easy to:

  • extend non-EAV entities to a condition: the entity should implement the \Magento\Framework\Api\ExtensibleDataInterface;
  • use objects to have more complex types.

Unluckily, some Magento core entities don’t implement the \Magento\Framework\Api\ExtensibleDataInterface thus they are not extensible with native extension attributes. Anyway, let’s focus on the half-full glass to explore some advantages of the extension attributes system.

What are custom attributes?

With the introduction of the extension attributes system, some new terminology comes in.

EAV attributes belong to one of the following sets:

  • system attributes – created by any default Magento installation;
  • custom attributes – created in the Admin Panel or through data patches.

So, simply put, a custom attribute is an EAV attribute created by someone else.

What are extension attributes?

We have already given a definition but let’s refresh it: an extension attribute is an attribute that extends a non-EAV entity.

But obviously, there is more.

First, the good news: an extension attribute allows us to overcome the limitations of scalar values. With an extension attribute, we can extend both EAV and non-EAV with complex objects.

For example, the gift_message extension attribute added to the order (Magento\Sales\Api\Data\OrderInterface) is of the following type:

namespace Magento\GiftMessage\Api\Data;

interface MessageInterface 
  extends \Magento\Framework\Api\ExtensibleDataInterface
     * Constants for keys of data array. Identical to the name of the getter in snake case
    const GIFT_MESSAGE_ID = 'gift_message_id';
    const CUSTOMER_ID = 'customer_id';
    const SENDER = 'sender';
    const RECIPIENT = 'recipient';
    const MESSAGE = 'message';

    // ...

The price we pay for such flexibility is that extension attribute values persistency needs to be populated programmatically, whereas custom attribute values are loaded and saved automatically.

Refer to the official documentation for more details on how to add extension attributes to entity.

Let’s note that ExtensibleDataInterface doesn’t declare the methods to access extension attributes. Here is its declaration:

 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.

namespace Magento\Framework\Api;

 * Interface for entities which can be extended with extension attributes.
 * @api
interface ExtensibleDataInterface
     * Key for extension attributes object
    const EXTENSION_ATTRIBUTES_KEY = 'extension_attributes';

By convention, the methods are named setExtensionAttributes() and getExtensionAttributes() but we can’t give for granted that a class implementing ExtensibleDataInterface provides those methods, so my advice is to double check it.

It’s worth mentioning that to prevent performance degradation while fetching extension attributes in collections, Magento provides a native join functionality (documented here), but this feature is limited to scalar extension attributes.

To find an example, we can look at the declaration of the is_subscribed attribute added to a customer in the module-newsletter/etc/extension_attributes.xml file:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    <extension_attributes for="Magento\Customer\Api\Data\CustomerInterface">
        <attribute code="is_subscribed" type="boolean" >
            <join reference_table="newsletter_subscriber" 

💡 Let’s pay attention: for the join to work, it’s not enough to declare the above join; we also have to pass the collection to the \Magento\Framework\Api\ExtensionAttribute\JoinProcessorInterface::process() method to have it populated with correct data. We can see an example in the getList() method of \Magento\Catalog\Model\ProductRepository:

namespace Magento\Catalog\Model;

// ...

class ProductRepository 
  implements \Magento\Catalog\Api\ProductRepositoryInterface
    // ...

    public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
        /** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */
        $collection = $this->collectionFactory->create();
        // ..

    // ...

Another relevant feature is that we can restrict access to the values of an entity’s extension attribute by declaring an ACL resource, as shown in the official documentation.

Where to store extension attributes?

Usually, extension attributes are non-EAV attributes, meaning that we should decide where to persist their values.

If the extension attribute is a complex object, the default choice would be to use a separate custom table.

But if the extension attribute is a scalar, extending core tables with a non-ambiguous custom column is acceptable. This way, persisting values will require less code and will be more performant because it avoids additional join conditions.

There isn’t a golden rule, though; we need to properly analyze every case before making the right decision.


Custom attributes are nothing new in the Magento world: just a new name given to EAV-entity attributes that Magento installation does not create.

Extension attributes, instead, are a new way to extend non-EAV entities.

With extension attributes also comes the possibility to define complex attributes, overcoming the limit of scalar types.

All this comes at a bit of cost: we have to carefully handle persistence to ensure that data is retrieved and saved the way we expect.

First of all, let’s create some basic module and add order attribute into the database
See for the basic module here >>

Magento 2 comes with a new design pattern called the service contracts. A service contract is a set of PHP interfaces used in a module. You can check an interface in any module API folder. Service contract includes service and data interfaces, which hide business logic details.

In Magento 2 You can add extension attributes for Order by creating an extension_attributes.xml file. Extension attributes are the persistent Attribute so you can’t find extension_attributes values in a database.

Let’s assume we need to add a new Order Comment field to order entity.

You need to use interface Magento\Sales\Api\Data\OrderInterface for add extension_attributes in Order entity. Thus, we need to define our order_comment extension attribute for the order extensible data object.
FilePath: app/code/Rbj/OrderComment/etc/extension_attributes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    <extension_attributes for="Magento\Sales\Api\Data\OrderInterface">
        <attribute code="order_comment" type="string" />

Using above action we will define the additional setOrderComment( ) and getOrderComment( ) for auto-generated Magento\Sales\Api\Data\OrderExtension class.

We need to add the custom order_comment field value during the order data loaded. For this purpose, we need to create a plugin to get( ) and getList ( ) methods of order repository class. The plugin declaration is the following in global scope area,

File Path: app/code/Armmage/OrderFeedback/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    <type name="Magento\Sales\Api\OrderRepositoryInterface">
        <plugin name="ordercomment_extension_attribute"
                type="Armmage\OrderComment\Plugin\OrderRepositoryPlugin" />

The afterGet and afterGetList methods will be called after the corresponding repository methods execution. So, this way we can affect the results:

/* File: app/code/Armmage/OrderFeedback/Plugin/OrderRepositoryPlugin.php */
namespace Rbj\OrderComment\Plugin;
use Magento\Sales\Api\Data\OrderExtensionFactory;
use Magento\Sales\Api\Data\OrderExtensionInterface;
use Magento\Sales\Api\Data\OrderInterface;
use Magento\Sales\Api\Data\OrderSearchResultInterface;
use Magento\Sales\Api\OrderRepositoryInterface;
 * Class OrderRepositoryPlugin
class OrderRepositoryPlugin
     * Order Comment field name
    const FIELD_NAME = 'order_comment';
     * Order Extension Attributes Factory
     * @var OrderExtensionFactory
    protected $extensionFactory;
     * OrderRepositoryPlugin constructor
     * @param OrderExtensionFactory $extensionFactory
    public function __construct(OrderExtensionFactory $extensionFactory)
        $this->extensionFactory = $extensionFactory;
     * Add "order_comment" extension attribute to order data object to make it accessible in API data of order record
     * @return OrderInterface
    public function afterGet(OrderRepositoryInterface $subject, OrderInterface $order)
        $orderComment = $order->getData(self::FIELD_NAME);
        $extensionAttributes = $order->getExtensionAttributes();
        $extensionAttributes = $extensionAttributes ? $extensionAttributes : $this->extensionFactory->create();
        return $order;
     * Add "order_comment" extension attribute to order data object to make it accessible in API data of all order list
     * @return OrderSearchResultInterface
    public function afterGetList(OrderRepositoryInterface $subject, OrderSearchResultInterface $searchResult)
        $orders = $searchResult->getItems();
        foreach ($orders as &$order) {
            $orderComment = $order->getData(self::FIELD_NAME);
            $extensionAttributes = $order->getExtensionAttributes();
            $extensionAttributes = $extensionAttributes ? $extensionAttributes : $this->extensionFactory->create();
        return $searchResult;

Once the order entity is loaded, the Order Comment value will be added to the extension attributes data object.

Using the above way, You can get the OrderComment field in any third-party API service also.



Magento is one of the most popular open-source e-commerce platform written in PHP powered by Zend framework. In this tutorial, you will learn the steps required to install and setup Magento on your Linux/Ubuntu 18.04 server.


  1. PHP 5.6.19 +
  2. MySQL 5.6 +
  3. Apache
  4. Recommended 2GB of RAM


Step 1: First step is to install Apache2

To install and setup Apache use the following commands:

$ sudo apt install apache2
$ sudo systemctl start apache2
$ sudo systemctl enable apache2

Step 2: Create a new Apache virtual host and configure it.

Create a new configuration file name magento.conf

$ sudo vim /etc/apache2/sites-available/magento.conf

Paste the following code into the newly created config file

<VirtualHost *:80>
     ServerAdmin [email protected]
     DocumentRoot /var/www/html/magento/
     ServerName domain.com
     ServerAlias www.domain.com

     <Directory /var/www/html/magento/>
        Options Indexes FollowSymLinks MultiViews
        AllowOverride All
        Order allow,deny
        allow from all

     ErrorLog ${APACHE_LOG_DIR}/magento_error.log
     CustomLog ${APACHE_LOG_DIR}/magento_access.log combined

Now with this, you will be debugging all the Magento setup related errors in the magento_error.log and magento_access.log

Step 3: Enable Rewrite mod

$ sudo a2ensite magento.conf
$ sudo a2enmod rewrite

Step 4: Installing PHP and dependencies

Use the below command to install all required PHP dependencies:

$ sudo apt install php7.2 libapache2-mod-php7.2 php7.2-common php7.2-gmp php7.2-curl php7.2-soap php7.2-bcmath php7.2-intl php7.2-mbstring php7.2-xmlrpc php7.2-mcrypt php7.2-mysql php7.2-gd php7.2-xml php7.2-cli php7.2-zip

Open the php.ini file

$ sudo vim /etc/php/7.2/apache2/php.ini

Change the following credentials:

file_uploads = On
allow_url_fopen = On
short_open_tag = On
memory_limit = 512M
upload_max_filesize = 128M
max_execution_time = 3600

Restart Apache service.

$ sudo systemctl restart apache2

Step 5:  Setting up MySQL Server

  • Install MySQL server
$ sudo apt install mysql-server
  • Set MySQL root password

– After the installation a temporary password will be generated in the mysqld.log file you can access this password by the following command

$ sudo grep 'temporary password' /var/log/mysqld.log

– Now you will get a message something like this

[Note] A temporary password is generated for [email protected]: b*-rkuIR4Zasd

– Copy this password for the next step

– Enter this command

$ sudo mysql_secure_installation

– This will prompt you to validate your password enter Y

– After this, you will be prompted to set your password strength. Enter your choice between LOW, MEDIUM and STRONG.

– Enter new password

– You can say yes to all the other prompts that come after this

– You will now be prompted with multiple questions on how to set up the MySQL installation. You can set Y to all the prompts

– You will be able to login with the user root and a blank password

  • Login to MySQL
$ sudo mysql -u root -p
  • Create a Magento database and add User
mysql > create database magento;
mysql > CREATE USER 'magentouser'@'localhost' IDENTIFIED BY 'password';
mysql > GRANT ALL PRIVILEGES ON * . * TO 'magentouser'@'localhost';
mysql > exit;

Step 6: Install composer

You need Composer to install all the Magento and its dependencies

Use the below command to install composer globally on your Ubuntu system:

$ php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
$ sudo php composer-setup.php --install-dir=/usr/bin/ --filename=composer

Check the composer installation using the below command:

$ composer -v

Step 7: Acquire / Generate secret keys from your Magento market place user account. (Required only for installation through composer)

In order to install Magento in the next step, you will have to specify Public and Private keys from your Magento market-place account. If you haven’t registered yet you can register here.

  • Login to Magento Marketplace here.
  • Click on your username dropdown in the top navbar > My Profile
  • Click on [Access Keys]
  • Click on [Create A New Access Key]
  • Enter a key name and generate

After this, your Public and Private key will be generated. Use these for installation in the next step.

Step 8: Install Magento

  • Installation using Composer

Enter your servers root directory

cd /var/www/html

Run the composer command to install Magento

$ sudo composer create-project --repository=https://repo.magento.com/ magento/project-community-edition magento

You will be prompted to provide a username password for this. Put your public key as the username and the private key as the password. It will take some time for the Composer to download and install all the required dependencies so be patient.

  • Installation using the zip file

Open this link. In the download tab, select your preferred version and click on download.

Follow these commands

$ sudo mkdir /var/www/html/magento/
$ sudo tar -zxvf ~/Downloads/Magento-CE*.tar.gz -C /var/www/html/magento/

For copying from local to the remote machine

# On local machine
$ scp -r magento2-ce.zip [email protected]<your-domain-name/ip-address>:/var/www/html/magento/magento.zip
# On remote machine
$ cd /var/www/html
$ mkdir magento
$ sudo tar -zxvf magento.zip -C magento/

Install dependencies from using composer

$ cd /var/www/html/magento/
$ composer install

After following the installation steps we can now open the Magento setup in your specified domain name or server IP address.

Common Errors & Exceptions

If you are getting 500 error after the installation please follow these steps:

  • This problem is probably due to directory access problems. To solve that use the following commands:
$ sudo chown -R www-data:www-data /var/www/html/magento
$ sudo chmod -R 755/var/www/html/magento

You have to set the user group according to your setup.

Your user group will depend on your Http server instance. In our case it was www-data it might be nginx or apache by default, depending on the setup. You can check that by using these commands

$ ps aux | grep apache
$ groups <apache-user>

If you are still getting errors you can debug those errors by referring the following logs

$ tail -f /var/log/apache2/magento_error.log $ tail -f /var/log/apache2/magento_access.log

Step 9: Magento Setup

  • After accessing your URL this screen will be displayed
  • Click on Agree and Setup Magento
  • On the next screen click on Start Readiness Check
  • Make sure that all the checks are positive if there are any dependencies that are missing, it will be displayed with the red cross mark so you can install it and try again. After confirmation, go to the next step.
  • Enter the database credentials for Magento you’ve had created earlier in this tutorial and click on next.
  • In the next step, you can set the web configuration details as per your requirements.
  • Next step you will configure your timezone, language, and currency settings. You can select or unselect the modules you want from the list in advanced configuration.
  • In the next step, you will set your admin login credentials
  • After clicking on the Next button, your Magento setup is ready to be installed so click on the Install Now button.

Now you just have to wait for the installation to complete.

After the successful installation, all of your Magento setup details will be shown, you can note these and click on Launch Magento Admin.

This will open the login panel for Magento and you can log in with your admin credentials.

Setting up Cron Jobs

Cronjob is nothing but a scheduler that runs a specified file path at a specified interval. Magento needs some of these Cron Jobs to be set up in order to function properly.

To add these Crons follow this command.

$ cd /var/www/html/magento $ sudo php bin/magento cron:install

You can check the installed Magento Cron Jobs by using this command

$ sudo crontab -l

You should find theses settings in your Cron

* * * * * /usr/bin/php /var/www/html/magento/bin/magento cron:run 2>&1 | grep -v "Ran jobs by schedule" >> /var/www/html/magento/var/log/magento.cron.log  * * * * * /usr/bin/php /var/www/html/magento/update/cron.php >> /var/www/html/magento/var/log/update.cron.log  * * * * * /usr/bin/php /var/www/html/magento/bin/magento setup:cron:run >> /var/www/html/magento/var/log/setup.cron.log

And with this, your Magento setup is up and ready to be used for your E-Commerce site.


By following the above-mentioned steps you are now ready to create your e-commerce platform with a working Magento installation. In case you have encountered something, which is not covered as a part of this tutorial, then please feel free to share those below in the comments.

Module is a structural element of Magento 2 – the whole system is built upon modules. Typically, the first step in creating a customization is building a module.

To create a module, you need to complete the following high-level steps:

  1. Create the module folder.
  2. Create the etc/module.xml file.
  3. Create the registration.php file.
  4. Run the bin/magento setup:upgrade script to install the new module.
  5. Check that the module is working.

Let’s go through each of these steps in detail.

Create the module folder

There are two possible locations for modules in Magento 2: the app/code folder and the vendor folder

Depending on how Magento 2 has been installed, core modules can either be located in the vendor/magento/magento-*folders (for composer installation) or in the app/code/Magento/ folder (for cloning GitHub).

Which of these locations should you choose for your new module?

If you build a module for a specific project, it is best to choose the app/code folder and commit to the project’s repository.

If you build an extension to be reused, it is better to use composer to create it, and put your module in the vendor/<YOUR_VENDOR>/module-something folder.

Each module name in Magento 2 consists of two parts – the vendor and the module itself. In other words, modules are grouped into vendors, so you need to define the vendor and module names. For this example, let’s name the vendor “Learning” and the module “FirstUnit”.

Let’s create the folder app/code/Learning and inside this folder place another folder: FirstUnit. If you’re using the command line, the code would be:

  1. cd to the root folder
  2. mkdir app/code/Learning
  3. mkdir app/code/Learning/FirstUnit

Make sure you have permission to create files and folders in your installation

Next, you need to create an etc/module.xml file. This file is required for the module to exist.

This file contains the following information:

  • Module name
  • Module version
  • Dependencies

Module name is defined by the folders we just created, because in Magento 2, class names must follow the folder structure. Because we created the folders Learning/FirstUnit, our module name will be Learning_FirstUnit and all classes that belong to this module will begin with Learning\FirstUnit – for example: Learning\FirstUnit\Observer\Test.

Module version indicates the current version of the database schema and data, and is used in upgrading. For example, assume you decide to modify a table’s schema in your module. How can you be sure that this change will happen on all instances where the code is deployed? Altering the database by direct SQL queries won’t work. Instead, Magento 2 has install and upgrade scripts in every module (optionally). These scripts contain commands to modify the database schema or data. To track whether to execute a script or not, Magento 2 uses module versions. Every time you implement a new database change, you implement a new version of a module and change the corresponding module.xml. Magento saves the current module’s version in a database, and if the database value and the one in the module.xml do not match, it will execute the upgrade code.

Dependencies. If one module depends on another, the module.xml file will have a special declaration that defines a list of modules that the current module depends on. For this example, we will make our module dependent on Magento_Catalog.

Using the following command-line code, create the folder app/code/Learning/FirstUnit/etc:

mkdir app/code/Learning/FirstUnit/etc

Then put the following code into it:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Learning_FirstUnit" setup_version="0.0.1"> <sequence>
<module name="Magento_Catalog"/> </sequence>

Note that in the XML file we specified:

  • Module name: Learning_FirstUnit (based on the folders we created)
  • Version: 0.0.1 (initial version of our module)
  • Dependency: Magento_Catalog. We could have multiple dependencies. In this case, we would put <module name=”..” /> nodes under the sequence node.

Create the registration.php file

Each module must have this file, which tells Magento how to locate the module. Continuing our example, create the file app/code/Learning/FirstUnit/registration.php. Then put the following content into it:

<?php \Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE, 'Learning_FirstUnit',

The registration.php is a standardized file that follows the same pattern for all modules.

The only thing that varies is the module name, which in our case is Learning_FirstUnit.

Run the “setup:upgrade” command

Running this command makes your new module active, notifying Magento of its presence.

php bin/magento setup:upgrade

It should echo a large amount of output, one line of which should be Learning_FirstUnit. Verify that this line of code is there.

Check that the new module is active

So far, we haven’t added any useful code to our module – it is still empty (and therefore invisible). In order to verify that it has been recognized, check the file app/etc/config.php. It has a list of auto-generated modules that are active.

Never change this list manually!

grep Learning_FirstUnit app/etc/config.php

Employing these steps, you can successfully create a new module in Magento 2.