How to Create a Customer and Add an Address Programmatically in Magento 2 (Step-by-Step Guide)

How to Create a Customer and Add an Address Programmatically in Magento 2 (Step-by-Step Guide)

Need to auto-create customers during a migration, ERP/CRM sync, or B2B onboarding? This guide shows a production-friendly way to create a Magento 2 customer and attach a default billing/shipping address using service contracts (repositories + factories). Works for Magento Open Source and Adobe Commerce.

When you should create customers programmatically

  • Importing customers from ERP/CRM (SAP, Dynamics, HubSpot, etc.)
  • Migrating accounts from Magento 1 / other platforms
  • B2B onboarding where customers are created in batches and assigned to groups
  • Automating account creation for marketplaces, dealers, or internal users

Quick answer

To create a customer and add an address programmatically in Magento 2:

  1. Check if the customer already exists for the current website (email + website scope).
  2. Create the account using AccountManagementInterface (handles password safely).
  3. Create the address using AddressInterfaceFactory and save it with AddressRepositoryInterface.
  4. Mark default billing and shipping if needed.

Example input data (customer + address)

In real projects, this data usually comes from an import script, integration queue, or an admin tool. Keep streets as an array, and always include website scope in your logic.

<?php
declare(strict_types=1);

$customerInfo = [
  'customer' => [
    'firstname' => 'John',
    'lastname'  => 'Doe',
    'email'     => '[email protected]',
    'password'  => 'ChangeMe-StrongPassword#2026',
    // Optional:
    // 'group_id' => 4,
  ],
  'address' => [
    'firstname'  => 'John',
    'lastname'   => 'Doe',
    'street'     => ['3148 Doctors Drive'],
    'city'       => 'Los Angeles',
    'country_id' => 'US',
    'region_id'  => 12,
    'postcode'   => '90017',
    'telephone'  => '9876543210',
    'default_billing'  => true,
    'default_shipping' => true,
    'save_in_address_book' => true
  ],
];

Best practice: put logic in a service class (not a Block)

Magento Blocks are for rendering templates. If you create customers inside a Block, your code becomes harder to reuse, test, and maintain. Use a service class so you can call it from a controller, CLI command, cron, or integration job.

Service class: create customer and address using service contracts

<?php
declare(strict_types=1);

namespace MageSpark\CustomerTools\Model;

use Magento\Customer\Api\AccountManagementInterface;
use Magento\Customer\Api\AddressRepositoryInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Customer\Api\Data\AddressInterfaceFactory;
use Magento\Customer\Api\Data\CustomerInterfaceFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Store\Model\StoreManagerInterface;

final class CustomerCreator
{
  public function __construct(
    private readonly StoreManagerInterface $storeManager,
    private readonly CustomerRepositoryInterface $customerRepository,
    private readonly CustomerInterfaceFactory $customerFactory,
    private readonly AddressInterfaceFactory $addressFactory,
    private readonly AddressRepositoryInterface $addressRepository,
    private readonly AccountManagementInterface $accountManagement
  ) {}

  /**
   * Create a customer (if not exists) and attach an address.
   *
   * @param array $data
   * @return int Customer ID
   * @throws LocalizedException
   */
  public function createCustomerWithAddress(array $data): int
  {
    $email = (string)($data['customer']['email'] ?? '');
    if ($email === '') {
      throw new LocalizedException(__('Customer email is required.'));
    }

    $websiteId = (int)$this->storeManager->getStore()->getWebsiteId();

    // 1) Check if customer exists (email is website-scoped in Magento)
    try {
      $existing = $this->customerRepository->get($email, $websiteId);
      return (int)$existing->getId();
    } catch (NoSuchEntityException $e) {
      // Not found, continue to create a new account
    }

    // 2) Create customer entity
    $customer = $this->customerFactory->create();
    $customer->setWebsiteId($websiteId);
    $customer->setFirstname((string)($data['customer']['firstname'] ?? ''));
    $customer->setLastname((string)($data['customer']['lastname'] ?? ''));
    $customer->setEmail($email);

    if (!empty($data['customer']['group_id'])) {
      $customer->setGroupId((int)$data['customer']['group_id']);
    }

    // 3) Create account (handles password correctly)
    $password = (string)($data['customer']['password'] ?? '');
    if ($password === '') {
      throw new LocalizedException(__('Customer password is required.'));
    }

    $createdCustomer = $this->accountManagement->createAccount($customer, $password);
    $customerId = (int)$createdCustomer->getId();

    // 4) Create and save address
    $addr = (array)($data['address'] ?? []);
    $address = $this->addressFactory->create();
    $address->setCustomerId($customerId);

    $address->setFirstname((string)($addr['firstname'] ?? $data['customer']['firstname'] ?? ''));
    $address->setLastname((string)($addr['lastname'] ?? $data['customer']['lastname'] ?? ''));
    $address->setTelephone((string)($addr['telephone'] ?? ''));

    $street = $addr['street'] ?? [];
    if (is_string($street)) {
      $street = [$street];
    }
    $address->setStreet($street);

    $address->setCity((string)($addr['city'] ?? ''));
    $address->setCountryId((string)($addr['country_id'] ?? ''));
    $address->setPostcode((string)($addr['postcode'] ?? ''));

    // Region is important for many countries (US, CA, AU, etc.)
    if (!empty($addr['region_id'])) {
      $address->setRegionId((int)$addr['region_id']);
    }

    if (!empty($addr['default_billing'])) {
      $address->setIsDefaultBilling(true);
    }
    if (!empty($addr['default_shipping'])) {
      $address->setIsDefaultShipping(true);
    }
    if (!empty($addr['save_in_address_book'])) {
      $address->setSaveInAddressBook(true);
    }

    $this->addressRepository->save($address);

    return $customerId;
  }
}

How to call the service

Call it from a controller, cron, CLI command, or integration worker. The key is: keep the creation logic reusable.

<?php
/** @var \MageSpark\CustomerTools\Model\CustomerCreator $customerCreator */
$customerId = $customerCreator->createCustomerWithAddress($customerInfo);

Common errors (and why your code fails)

Customer duplicates across stores

Customers are website-scoped. If you check existence without using the correct websiteId, you can accidentally create duplicates or fail to load the existing customer.

Region/state validation problems

Many stores require a valid region/state. If your country needs it, make sure you set region_id. If your integration only has a region name, map it to directory_country_region first.

Using Blocks for business logic

Blocks are for rendering output. If you create customers inside a Block, it becomes hard to reuse and maintain. A service class keeps it clean and production-ready.

Hardcoding passwords in production

For real systems, don’t hardcode passwords. Either generate secure passwords and trigger a reset password flow, or use an onboarding email depending on your security policy.

Do you need to run reindex and cache flush?

Usually, no. Creating customers and addresses does not require reindexing on every run. Reindexing is an operational task and should be used only when your environment or customizations demand it.

Conclusion

That’s the clean, production-friendly way to create a customer and add an address programmatically in Magento 2. If you tell me your exact flow (migration, ERP sync, B2B group assignment, API intake), I can adapt the same pattern to also update existing customers and add multiple addresses safely.

Talk to a Hyvä expert
Loading...