How to programmatically create customers in Magento 2.4.x

There are several ways to create customers in Magento 2. A customer can create an account on their own using the sign-up form, a customer can be created through the admin interface, and there is even a built-in Magento 2 import feature that can mass import a huge number of customers from a CSV file, provided that the customer data in the CSV is import-ready.

But what if we have thousands of customers whose data still needs to be processed before they can be created? The best way to do this would be to create the customers programmatically.

In this article, we are going to cover the topic of creating customers programmatically, and to this purpose, we are going to create a simple Magento 2 module which is going to have a custom console command and a couple of models that are going to be used to read, process and create customers.

The Body

Let’s get started by first creating a module in the /app/code directory of our Magento 2 installation. In this example, I am going to use Magepow as the module vendor and I am going to name the module CustomerCreation, but you can name them as you see fit.

Inside our module, we are going to need the following directories and files:

  • registration.php
  • /etc/module.xml
  • /Console/Command/CreateCustomers.php
  • /etc/di.xml
  • /Model/Customer.php
  • /Model/Import/CustomerImport.php


For our customer creation module to work, we need to register our module in the Magento system. Copy the following code to the registration.php file:



Our module also needs to declare its name and existence. Copy the following code to the /etc/module.xml file:

<?xml version="1.0"?>
<config xmlns:xsi="" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Magepow_CustomerCreation" setup_version="1.0.0" />

The Heart

Now that we have our module set up, we need a way to trigger our customer creation process. One way we can do this is by creating a custom console command.

Let’s start by importing a bunch of classes in the /Console/Command/CreateCustomers.php file, let’s also define the class and make it extend the imported Command class:

namespace Magepow\CustomerCreation\Console\Command;
use Exception;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Console\Cli;
use Magento\Framework\Filesystem;
use Magento\Framework\App\State;
use Magento\Framework\App\Area;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Magepow\CustomerCreation\Model\Customer;
class CreateCustomers extends Command
  // everything else goes here

You may notice that our Customer model has not yet been defined. Don’t worry about it right now, we are going to define it later.

Next, we need to create a constructor inside our class and inject some dependencies with it:

private $filesystem;
private $customer;
private $state;
public function __construct(
    Filesystem $filesystem,
    Customer $customer,
    State $state
) {
    $this->filesystem = $filesystem;
    $this->customer = $customer;
    $this->state = $state;

We also need to configure our console command by setting a name for it using the configure() method inside our class. This method is inherited from the Command class and can also be used to set the command description, input arguments, and other options.

public function configure(): void

Let’s now define the execute() method that will be triggered when we invoke the bin/magento create:customers command. This method is also inherited from the Command class, and in it we are going to write our logic.

In the execute() method, we first have to set the Area Code to global. If we don’t do this, the customer creation process will produce an error later.

After that, we have to get the absolute path of our CSV file that contains all of the customer data. In this example, the CSV file is named customers.csv and is located in the /pub/media/fixtures directory (relative to the root directory, not our module).

Once we have the absolute path, we have to call the install() method (this method is going to be defined later in our Customer model) and pass it the absolute path of the CSV file as an argument. If there are any errors, we will need to catch them and show the error messages in our CLI:

public function execute(InputInterface $input, OutputInterface $output): ?int
  try {
      $mediaDir = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA);
      $fixture = $mediaDir->getAbsolutePath() . 'fixtures/customers.csv';
      $this->customer->install($fixture, $output);
      return Cli::RETURN_SUCCESS;
  } catch (Exception $e) {
      $msg = $e->getMessage();
      $output->writeln("<error>$msg</error>", OutputInterface::OUTPUT_NORMAL);
      return Cli::RETURN_FAILURE;

For our custom command to work, we also have to configure the command name using dependency injection.

Add the following code to the /etc/di.xml file:

<?xml version="1.0"?>
<config xmlns:xsi=""
   <type name="Magento\Framework\Console\CommandList">
           <argument name="commands" xsi:type="array">
               <item name="CreateCustomers" xsi:type="object">Magepow\CustomerCreation\Console\Command\CreateCustomers</item>

The Soul

It’s time to create our Customer model. This is where we are going to read the CSV data, process it, store it in an array, and send it off to be saved.

In the /Model/Customer.php file, import the following classes and define the Customer class:

namespace Magepow\CustomerCreation\Model;
use Exception;
use Generator;
use Magento\Framework\Filesystem\Io\File;
use Magento\Store\Model\StoreManagerInterface;
use Inchoo\CustomerCreation\Model\Import\CustomerImport;
use Symfony\Component\Console\Output\OutputInterface;
class Customer
    // everything else goes here

We have to create a constructor in this class as well and inject some dependencies:

private $file;
private $storeManagerInterface;
private $customerImport;
private $output;
public function __construct(
    File $file,
    StoreManagerInterface $storeManagerInterface,
    CustomerImport $customerImport
) {
    $this->file = $file;
    $this->storeManagerInterface = $storeManagerInterface;
    $this->customerImport = $customerImport;

Let’s define the install() method inside our Customer class. This is the method that we called earlier in our custom console command.

We start by retrieving the store and website IDs. After that, we retrieve the CSV header and then iterate through each CSV row, reading the data contained in those rows and passing them to the createCustomer() method that we have yet to define.

public function install(string $fixture, OutputInterface $output): void
    $this->output = $output;
    // get store and website ID
    $store = $this->storeManagerInterface->getStore();
    $websiteId = (int) $this->storeManagerInterface->getWebsite()->getId();
    $storeId = (int) $store->getId();
    // read the csv header
    $header = $this->readCsvHeader($fixture)->current();
    // read the csv file and skip the first (header) row
    $row = $this->readCsvRows($fixture, $header);
    // while the generator is open, read current row data, create a customer and resume the generator
    while ($row->valid()) {
        $data = $row->current();
        $this->createCustomer($data, $websiteId, $storeId);

To read the CSV header and rows, we use a generator. A generator allows us to write code that can iterate over a set of data without building an array in memory. If we have a large CSV file, this can help us to not exceed the memory limit. The readCsvRows() method will read the row data and map it to the headers retrieved from the readCsvHeader() method.

Create the following methods:

private function readCsvRows(string $file, array $header): ?Generator
    $handle = fopen($file, 'rb');
    while (!feof($handle)) {
        $data = [];
        $rowData = fgetcsv($handle);
        if ($rowData) {
            foreach ($rowData as $key => $value) {
                $data[$header[$key]] = $value;
            yield $data;
private function readCsvHeader(string $file): ?Generator
    $handle = fopen($file, 'rb');
    while (!feof($handle)) {
        yield fgetcsv($handle);

The Entity

For our final step, we need to create our CustomerImport model. In the /Module/Import/CustomerImport.php file, let’s import the Customer class from the Magento CustomerImportExport module and define our CustomerImport class. Make it extend the imported Customer class.

Our class is going to contain the importCustomerData() method that we use to prepare and save our customer data.

namespace Magepow\CustomerCreation\Model\Import;
use Magento\CustomerImportExport\Model\Import\Customer;
class CustomerImport extends Customer
  // everything else goes here

Now that our class has been defined, we need to create the importCustomerData() method that handles the last steps of the customer creation process. In this method, we will create or update a customer entity and save any provided attribute data for that customer. The method will then return the customer entity ID. This method is actually a slight modification of a function found in the Customer class that we are extending.

public function importCustomerData(array $rowData)
  $entitiesToCreate = [];
  $entitiesToUpdate = [];
  $entitiesToDelete = [];
  $attributesToSave = [];
  $processedData = $this->_prepareDataForUpdate($rowData);
  $entitiesToCreate = array_merge($entitiesToCreate, $processedData[self::ENTITIES_TO_CREATE_KEY]);
  $entitiesToUpdate = array_merge($entitiesToUpdate, $processedData[self::ENTITIES_TO_UPDATE_KEY]);
  foreach ($processedData[self::ATTRIBUTES_TO_SAVE_KEY] as $tableName => $customerAttributes) {
      if (!isset($attributesToSave[$tableName])) {
          $attributesToSave[$tableName] = [];
      $attributesToSave[$tableName] = array_diff_key(
      ) + $customerAttributes;
  $this->updateItemsCounterStats($entitiesToCreate, $entitiesToUpdate, $entitiesToDelete);
    * Save prepared data
  if ($entitiesToCreate || $entitiesToUpdate) {
      $this->_saveCustomerEntities($entitiesToCreate, $entitiesToUpdate);
  if ($attributesToSave) {
  return $entitiesToCreate[0]['entity_id'] ?? $entitiesToUpdate[0]['entity_id'] ?? null;

To finish things up, we need to invoke the bin/magento setup:upgrade and bin/magento setup:di:compile commands to get our module and custom command working.

Once we run our customer creation script with bin/magento create:customers, we also need to make sure to re-index the Customer Grid indexer. We can do that by invoking the bin/magento indexer:reindex customer_grid command.

Hope this article will help you in some way, You can see useful articles in the next articles.

Anything you need support from Magento 2 feel free to contact us at Alothemes and

Phone: (+84)865633728