Chat on WhatsApp
Magento Development 11 min read

Magento 2 Product Import via CSV: The Complete Guide

How to import products into Magento 2 with CSV without losing a weekend — behaviors, images, a command-line importer, and the real fix for the "URL key already exists" error.

Magento 2 Product Import via CSV: The Complete Guide

A Magento 2 product import is how you load or update a catalog in bulk from a CSV file instead of editing products one by one. It is the fastest way to launch a store, sync a supplier feed, or fix 4,000 prices at once.

It is also where a lot of stores get hurt — the wrong import behavior, a broken image path, or one cryptic URL-key error can turn a five-minute job into a lost afternoon. This guide walks the whole path on Magento 2.4.4 — 2.4.9, from a clean import to the errors everyone hits.

3Import behaviors (Add/Update, Replace, Delete)
5Minimum columns for a new simple product
0Default allowed errors before the import aborts
1CLI command to automate it (with a small module)

Where the importer lives

In the admin, go to System → Data Transfer → Import. Set Entity Type to Products, and a set of options appears. Before you touch your own file, click Download Sample File for the Products entity. That sample is the single best source of truth for column names — it is generated from your installed modules, so it already includes columns for any extension attributes you have.

The matching export lives at System → Data Transfer → Export. The cleanest way to build a bulk-update CSV is to export the products you want to change, edit the columns you care about, delete the rest, and re-import with Add/Update. Magento only writes the columns present in your file, so a trimmed CSV is safe.

Key takeaway

Never hand-write a Magento product CSV from scratch. Export a real product (or the sample), then edit it. The column names and value formats are unforgiving, and the export already has them right.

Choose the import behavior carefully

The Import Behavior dropdown decides what happens to existing data. This is the setting that wipes catalogs when people guess.

BehaviorWhat it doesUse it when
Add/UpdateCreates new SKUs and updates existing ones, column by column. Leaves untouched columns alone.Almost always. New products, price/stock updates, attribute fixes.
ReplaceDeletes each matching SKU and re-creates it from the CSV. Anything not in the file is lost for that product.Rarely — only when the CSV is the complete, authoritative record of every product.
DeleteRemoves every SKU listed in the file. A one-column file of SKUs is enough.With care. Bulk discontinuations — on a backup-first store.

The matching key for all three is sku. Magento ignores entity_id on import, so you cannot “move” a SKU by changing an ID — you change the SKU value and it becomes a different product.

The minimum CSV for a new product

To create a new simple product, you need five columns at minimum: sku, attribute_set_code, product_type, name, and price. A workable row also sets visibility, status, stock, and a website so the product is actually shown:

sku,attribute_set_code,product_type,name,price,product_websites,visibility,status,qty,is_in_stock
PANTH-TEE-01,Default,simple,Panth Cotton Tee,24.00,base,"Catalog, Search",1,100,1

A few format rules that trip people up:

  • Multi-value fields use a comma inside the quoted cell — "Catalog, Search" for visibility, "cat1/cat2,cat1/cat3" for category paths.
  • Categories are written as paths from the root: Default Category/Men/Tees. Magento creates missing categories on import.
  • Decimals use a dot, regardless of your locale, and prices carry no currency symbol.
  • Leave a column out and Magento will not touch it on Add/Update. Include it empty and Magento will overwrite it with blank.

Importing images

Product images are not embedded in the CSV — you upload the files to the server and reference them by name. By default Magento reads them from pub/media/import/. Drop your images there (keep the structure flat or mirror it in the CSV), then point the columns at them:

sku,base_image,small_image,thumbnail_image,additional_images
PANTH-TEE-01,/panth-tee-front.jpg,/panth-tee-front.jpg,/panth-tee-front.jpg,"/panth-tee-back.jpg,/panth-tee-detail.jpg"

The leading slash is relative to the images directory you set in Images File Directory on the import screen (the default value resolves to pub/media/import). For thousands of images, a remote URL also works in those columns — Magento downloads each one, which is slower but avoids an upload step.

Key takeaway

If images import but never appear, it is almost always a permissions problem: files under pub/media added over SFTP or git inherit a restrictive umask and the web server 404s them. chmod -R a+r pub/media after any image drop.

Validate first, then import

Always click Check Data before Import. Validation parses every row and reports problems without writing anything. Two settings shape how strict it is:

  • Allowed Errors Count defaults to 0 meaningfully low — raise it if you expect a handful of bad rows and want the good ones to import anyway.
  • On Error can Stop on Error or Skip error entries. For a supplier feed you do not control, Skip plus a higher error count keeps the import moving.

Validation catches the boring failures early: an attribute_set_code that does not exist, a product_type Magento does not know, a required attribute left blank, or a malformed multiselect value.

Automating imports from the command line

Adobe Commerce ships Scheduled Import/Export; Magento Open Source does not. To run an unattended import on Open Source — a nightly supplier sync, for example — wrap the import model in a CLI command and call it from cron. Here is the shape of a minimal command in a Panth_Import module:

<?php
declare(strict_types=1);

namespace Panth\Import\Console\Command;

use Magento\Framework\App\State;
use Magento\Framework\Filesystem;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\ImportExport\Model\Import;
use Magento\ImportExport\Model\Import\Source\CsvFactory;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ImportProductsCommand extends Command
{
    public function __construct(
        private readonly State $state,
        private readonly Filesystem $filesystem,
        private readonly Import $import,
        private readonly CsvFactory $csvFactory
    ) {
        parent::__construct();
    }

    protected function configure(): void
    {
        $this->setName('panth:import:products')
            ->setDescription('Import products from a CSV in var/import/')
            ->addArgument('file', InputArgument::REQUIRED, 'CSV filename under var/import/');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $this->state->setAreaCode('adminhtml');
        $var = $this->filesystem->getDirectoryRead(DirectoryList::VAR_DIR);
        $absolute = $var->getAbsolutePath('import/' . $input->getArgument('file'));

        $source = $this->csvFactory->create(['file' => $absolute, 'directory' => $var]);
        $this->import->setData([
            'entity' => 'catalog_product',
            'behavior' => Import::BEHAVIOR_APPEND, // Add/Update
            'validation_strategy' => 'validation-skip-errors',
            'allowed_error_count' => 50,
        ]);

        if (!$this->import->validateSource($source)) {
            $output->writeln('<error>Validation failed</error>');
            foreach ($this->import->getErrorAggregator()->getAllErrors() as $error) {
                $output->writeln(' - ' . $error->getErrorMessage());
            }
            return Command::FAILURE;
        }

        $this->import->importSource();
        $this->import->invalidateIndex();
        $output->writeln('<info>Import complete.</info>');
        return Command::SUCCESS;
    }
}

Register it in Panth/Import/etc/di.xml under Magento\Framework\Console\CommandList, then run bin/magento panth:import:products feed.csv. Because it calls invalidateIndex(), the catalog reindex picks the changes up on the next scheduled run — the same MView flow that keeps a busy store responsive (see our note on the category indexer EAV join trap).

Fixing “URL key for specified store already exists”

This is the most-searched Magento import error, and the CSV is rarely the real cause. The error means the URL key Magento would generate for a product already points at something in the url_rewrite table. Three things cause it:

  • Stale rewrites. A product was deleted or its key changed, but the old url_rewrite row was left behind. The new import collides with the orphan.
  • Duplicate keys in the file. Two rows resolve to the same url_key — often two products with the same name and no explicit key, because Magento derives the key from the name.
  • Multi-store scope. The same key is unique per store view, and a value saved at the wrong scope clashes. This bites multi-website setups especially — see multi-store and multi-website setup.

The reliable fix

First, set an explicit, unique url_key column in your CSV so Magento is not guessing from names. If the collision persists, the orphaned rewrites have to go. Back up first, then clear the stale product rewrites and reindex:

-- inspect first
SELECT * FROM url_rewrite WHERE entity_type = 'product' AND request_path LIKE '%your-key%';

-- then remove product rewrites so Magento regenerates them
DELETE FROM url_rewrite WHERE entity_type = 'product';
bin/magento indexer:reindex catalog_product_attribute catalog_url_rewrite
bin/magento cache:flush

Deleting all product rewrites is safe because the Catalog URL Rewrite indexer rebuilds them from current product data. Re-run the import and the error is gone. If you are on an older patch level, upgrading also helps — several import URL-key bugs were fixed across the 2.4.x line.

Key takeaway

Treat “URL key already exists” as a url_rewrite hygiene problem, not a CSV problem. An explicit url_key column plus a rewrite cleanup fixes the overwhelming majority of cases.

Large files and timeouts

Big imports fail on resources, not logic. A 50,000-row file with image downloads will outrun PHP's max_execution_time and memory limits long before Magento has a problem. Three ways through it:

  • Split the CSV into chunks of a few thousand rows. Simplest and most reliable.
  • Run from the CLI with the command above — no web-server timeout applies.
  • Raise the bunching limit (bunch_size) and PHP limits if you must do it in one pass, and import images by URL only when you have to.

Frequently asked questions

What is the minimum CSV to create a Magento 2 product?

Five columns: sku, attribute_set_code, product_type, name, and price. In practice you also want product_websites, visibility, status, and stock columns so the product is visible and purchasable. Download the sample file from the import screen to get every column name exactly right.

How do I update only the price or stock without touching anything else?

Use Add/Update behavior and include only the sku column plus the columns you want to change — for example sku,price or sku,qty,is_in_stock. Magento writes only the columns present in the file, so the rest of each product is left alone.

Where do product images go for a CSV import?

Upload the image files to pub/media/import/ on the server, then reference them by filename in the base_image, small_image, thumbnail_image, and additional_images columns. The path is relative to the Images File Directory set on the import screen. Run chmod -R a+r pub/media afterward so the web server can serve them.

Why do I keep getting “URL key for specified store already exists”?

The URL key your product would use already exists in the url_rewrite table — usually an orphaned rewrite from a deleted product, a duplicate key in your file, or a multi-store scope clash. Add an explicit unique url_key column, and if it persists, back up and delete product rows from url_rewrite, then reindex catalog_url_rewrite. Magento regenerates the rewrites cleanly.

Can I schedule recurring imports in Magento Open Source?

Not from the admin — Scheduled Import/Export is an Adobe Commerce feature. On Open Source, wrap Magento\ImportExport\Model\Import in a CLI command (a small Panth_Import module) and trigger it from cron. That gives you unattended, repeatable imports without the Commerce license.

Importing a catalog or fighting an import error? I build Magento data pipelines, custom CLI importers, and supplier-feed integrations — fixed-fee from $499 audit · $2,499 sprint · ~Nh @ $25/hr. See services for the full menu.

Get help with your Magento import