
Getting products into PrestaShop and back out again is the unglamorous plumbing every catalogue eventually depends on: a migration from another platform, a nightly supplier feed that changes 4,000 prices overnight, an ERP that owns stock and needs PrestaShop to mirror it. PrestaShop gives you three genuinely different tools for this — the built-in CSV importer, the Webservice API, and SQL Manager for export — and the single most expensive mistake merchants make is reaching for the wrong one. A 200-product launch and a 40,000-SKU nightly sync are not the same job, and the tool that's perfect for one will quietly fall over on the other. This guide is specifically about product data: catalogue, combinations, images, stock and prices, in and out. (If what you actually need is order and invoice data out of PrestaShop — for accounting or reconciliation — that's a different export path, covered in CSV exports for orders and invoices.)
Last updated: June 2026.
The built-in CSV importer: where almost everyone starts

PrestaShop ships a native importer at Advanced Parameters → Import (the AdminImportController). It accepts CSV for products, combinations, categories, customers, addresses, brands (manufacturers), suppliers, aliases and — where your version exposes them — stores; you pick the entity type first, then upload. For a catalogue under a few thousand products with no recurring sync requirement, this is the right answer and you can stop reading the API section. The benefit is real: you load your whole shop from a spreadsheet, from the back office, without a developer or a line of code.
The CSV format rules that cause most failures
The importer is unforgiving about format, and a malformed file is the number-one reason an import "doesn't work." Get these right before you upload anything:
- Encoding: UTF-8 without BOM. If you built the file in Excel, use Save As → CSV UTF-8 — plain "CSV" saves as Windows-1252 and turns every accented character into mojibake (é becomes é). If your file already has a BOM, strip it:
sed -i '1s/^\xEF\xBB\xBF//' file.csv. - Separator: semicolon (;) by default. Changeable in the import screen, but never switch to comma if your descriptions contain commas — which they always do.
- Text qualifier: double quotes ("). Wrap any field that contains the separator or a line break.
- Line endings: Unix LF is safest; CRLF is accepted but occasionally injects phantom empty rows that import as blank products.
- Header row: column names either match PrestaShop's field names or you map them manually on the next screen. The importer remembers a named mapping, so a recurring supplier file only needs mapping once.
A minimal product file:
ID;Active;Name;Categories;Price (tax excl.);Tax rules ID;Reference;Quantity;Description
The setting that decides update vs. duplicate
This is the one that bites people on the second import. The importer has two modes that look almost identical but behave oppositely. By default it creates rows. To make it update existing products instead of cloning them, you must tick "Use product reference as key" (or supply the real ID column and tick the ID-as-key equivalent). Skip this on a price-update run and you don't update 4,000 products — you create 4,000 duplicates and double your catalogue. So what does that mean in practice? A recurring supplier feed must carry a stable Reference (or ID) on every row, and that checkbox must be on, every single time.
Combinations: imported separately, and order matters
Combinations (variants) are their own entity — you import the parent products first, then run a second import with the Combinations entity selected. Each row references the parent by ID or reference and carries the attribute group, value, price impact, quantity, EAN13 and combination reference:
Product ID;Attribute (Name:Type:Position);Value (Value:Position);Reference;Supplier reference;EAN13;Impact on price;Quantity
The classic trap: PrestaShop auto-creates attribute groups and values it hasn't seen, and it matches on the exact string. "Size", "size" and "SIZE" become three separate attribute groups, and your size dropdown ends up a mess. Normalise casing and spelling in the file before importing, not after.
Images by URL — and why this is where your server dies
You can populate images by putting a comma-separated list of URLs in the image column; the first becomes the cover. The mechanics are what catch people out, because each URL triggers a synchronous download during the import request:
- Download storm: 5,000 products × 3 images = 15,000 HTTP fetches inside one import job. A web-triggered import will time out long before it finishes. The reliable pattern is two passes: import products with no images first, then a second, smaller-batch pass for images only.
- Thumbnail multiplication: every imported image is regenerated into every format defined in Design → Image Settings. Eight formats means eight resize operations per image. Before a big image import, temporarily prune unused formats, then regenerate once at the end.
- "Delete all images before import": this option removes all existing images on matched products — including ones you added by hand — before loading the new set. It's the right tool for a clean re-sync and a disaster if you didn't mean it.
Making large imports survive: timeouts, memory, and the CLI
The web importer is comfortable to about 5,000 products. Past that you stop fighting CSV format and start fighting PHP and the web server:
- Execution time: the stock
max_execution_timeof 30 seconds is hopeless for a large file. Push it to 600+ in php.ini (or per-import via the worker), or you'll never finish a single batch. - Memory: a 50 MB CSV of ~20,000 products wants 256 MB+ of PHP memory; set
memory_limitto 512M for headroom. - Batch it: split into 1,000–2,000-row files. If row 7,400 fails, you re-run one batch, not the whole 40,000.
- Kill indexing during the run: in Shop Parameters → Search turn off "Index products on save," and don't regenerate images mid-import. Re-index once at the end. On large catalogues this is the single biggest time saver — re-indexing every row is most of the wall-clock cost.
- Move the big jobs off the web request: PrestaShop core does not ship a built-in
prestashop:importconsole command, so for a one-time migration of 10,000+ products the practical answers are to batch hard (1,000–2,000-row files), raise the server limits below, or drive the load from a purpose-built import module or an API script you run from the command line — anything that runs outside the web server so there's no request timeout to hit.
Getting product data out: PrestaShop's export options
Export is the platform's weaker side, and it's worth being honest about that. The native Import page is for imports and sample files — it isn't a full product export tool. The closest thing built into the back office is the Export button on the product list itself, but that only emits the basic fields shown in the list — not combinations, not specific prices, not per-warehouse stock. For anything real, you have two better routes.
SQL Manager: the export workhorse
Advanced Parameters → Database → SQL Manager lets you write a read query and download the result as CSV. It's the fastest way to get exactly the columns you want. To export every product with its current stock:
SELECT p.id_product, pl.name, p.reference, p.price, sa.quantity FROM ps_product p JOIN ps_product_lang pl ON p.id_product = pl.id_product AND pl.id_lang = 1 JOIN ps_stock_available sa ON p.id_product = sa.id_product AND sa.id_product_attribute = 0;
(Swap ps_ for your real table prefix.) This pairs perfectly with the importer for bulk edits: export to CSV, change prices or stock in a spreadsheet, re-import with reference-as-key on. The catch is that SQL Manager is read-only and manual — it's an excellent ad-hoc tool and a poor automation, because someone has to click it.
When export needs to be a recurring feed
Daily price feeds, marketplace and Google Shopping feeds, a nightly hand-off to an external system — these need a generated file on a schedule, not a person in SQL Manager. That's module territory: a generator that builds a multi-language product feed (and, for discoverability, keeps your sitemap in step with the live catalogue) on a cron, so the file is fresh without anyone touching the back office. If feed generation is the actual job, browse the export and feed modules at mypresta.rocks — the benefit being a feed that's always current and an admin who never has to remember to run it.
XML and the Webservice API: for integrations, not migrations
XML is the lingua franca of B2B feeds, ERPs and marketplaces, and here's the key fact: PrestaShop's CSV importer does not read XML at all. You have two honest options, and they suit different goals.
Option 1 — convert the XML to CSV
If a supplier hands you an XML feed and you just want it in the catalogue once, transform it to PrestaShop's CSV shape with an XSLT stylesheet, a short Python script (xml.etree.ElementTree handles most flat supplier feeds in well under 50 lines), or a data tool like Talend or Akeneo. Then feed it to the native importer. Simple, no API to enable, ideal for a one-off load.
Option 2 — talk to the Webservice API directly
For an ongoing two-way sync, the Webservice API is the right layer. It's a REST-style interface with full CRUD over products, combinations, categories, stock, customers and orders. The classic (legacy) Webservice is XML-first — that's the format to build against across the supported versions; JSON isn't generally available on the legacy webservice, so if you specifically need JSON, check whether your PrestaShop version ships the newer Symfony-based Admin API instead. Enable it under Advanced Parameters → Webservice, then create a key and grant it only the resources and verbs it needs — never hand a key blanket access to every resource. Create a product with a POST to https://yourstore.com/api/products carrying an XML body; update with PUT and the product ID in the URL; the response returns the entity with its new ID.
Three realities to plan around before you build on it:
- One entity per request. There's no bulk endpoint — 10,000 products is 10,000 calls. There's no built-in API rate limiting in the core legacy webservice, but your hosting, a security/WAF layer, or your own Apache (mod_evasive) or Nginx (limit_req) may throttle a burst, so space calls by ~100–200 ms and a 10k import becomes a planned ~30-minute window, not a surprise.
- Images upload, not download. POST the file to
/api/images/products/{id}and PrestaShop stores it directly — far more reliable than the CSV URL-download approach, because nothing depends on your server fetching a remote URL mid-import. - Build retry logic. The API answers with HTTP codes (201 created, 200 updated, 404 not found, 500 server error). Log every response and retry the 500s — over thousands of calls a few will fail transiently, and a sync that can't resume is a sync you can't trust.
Errors you'll actually see — and the fix
- "Property X is empty": a required field (name, price, category) is blank. Check every required column has a value on every row.
- "The category does not exist": you referenced a category ID that isn't there. Import categories first, or use category names — the importer accepts both and will create missing ones.
- Prices wrong after import: the importer reads prices excluding tax. If your file is tax-inclusive, divide by (1 + rate) first or map the right tax rule.
- Accents turned into garbage: the file isn't UTF-8. Convert it:
iconv -f Windows-1252 -t UTF-8 input.csv > output.csv. - Import "worked" but products are invisible: check Active=1, a real assigned category, the correct shop association in multi-shop, and that the product index has been rebuilt.
- Duplicates after a re-import: you forgot "Use product reference as key" (or ID-as-key). It updates instead of cloning — see the warning above.
Choosing the right method
Match the tool to the job, not to habit:
| Your situation | Use this | Why |
|---|---|---|
| One-time migration, under ~5,000 products | Built-in CSV importer | No code, no developer, mapping is reusable. |
| One-time migration, 10,000+ products | Hard batching, or an import module / API script run from the CLI | No web timeout; batch on failure. |
| Recurring supplier/ERP sync | Webservice API + a script | Two-way CRUD, scriptable, schedulable. |
| Recurring export feed (marketplace, Shopping) | Feed/export module on cron | Always-fresh file, zero manual steps. |
| Quick bulk price/stock edits | SQL Manager export → CSV import | Round-trip through a spreadsheet. |
| 50,000+ SKUs, complex attributes | PIM (e.g. Akeneo) feeding the API | External system owns the master data. |
Frequently asked questions
My second import created duplicates instead of updating. What did I miss?
The "Use product reference as key" checkbox (or the ID-as-key equivalent). By default the importer creates rows, so a price-update run without that box ticked clones every product instead of updating it. Make sure each row carries a stable Reference or ID and tick the key option on every recurring run.
Why did all the accented characters turn into garbage after import?
The file isn't UTF-8. Excel's plain "CSV" save uses Windows-1252, which mangles accents — use Save As → CSV UTF-8 instead, and strip a leading BOM if one is present. The post's encoding section shows exactly what the broken characters look like and the one-line iconv command that converts a Windows-1252 file back to clean UTF-8.
Can the built-in importer read an XML feed?
No — the CSV importer doesn't read XML at all. For a one-off load, transform the XML to PrestaShop's CSV shape first (an XSLT stylesheet or a short xml.etree.ElementTree script handles most flat supplier feeds). For an ongoing two-way sync, use the Webservice API directly rather than converting files each time.
My import keeps timing out on a big catalogue. How do I get it through?
The web importer is comfortable to about 5,000 products. Past that, split into 1,000–2,000-row batches, raise max_execution_time (to 600+) and memory_limit (to 512M), and turn off "Index products on save" during the run — re-indexing every row is most of the wall-clock cost. PrestaShop core ships no prestashop:import console command, so for a 10,000+ migration drive the load from an import module or an API script run outside the web request.
How do I bulk-edit prices or stock without re-importing the whole catalogue?
Round-trip through SQL Manager: export the columns you need to CSV, change them in a spreadsheet, then re-import with reference-as-key on so it updates rather than clones. For frequent price changes our mass price-update and product-feed modules do this from the back office on a schedule, so you're not hand-running SQL Manager and remembering the key checkbox every time.
Where import/export meets the rest of your operations
Import and export rarely live alone — they're usually one move inside a bigger catalogue or stock workflow. A few neighbours worth knowing:
- Stock via CSV. A bulk quantity update is just a reference-keyed import of two columns. But the deeper question — what level should those numbers be, and when to refill — is its own discipline: see when to reorder stock.
- Outgrowing the spreadsheet. If you're round-tripping CSVs constantly just to keep stock straight, that's usually the signal it's time for real inventory tooling — when spreadsheets stop working covers the transition.
- Order and invoice export. Different entity, different path — getting order data out for accounting is covered in CSV exports for orders and invoices.
Product data is the kind of work nobody celebrates and everybody depends on: clean imports mean accurate listings, correct prices and stock that matches reality. The principle underneath all three methods is the same — keep a stable key on every product, pick the tool that fits the scale of the job, and never let an import run twice without knowing whether it creates or updates. If recurring feed generation or scheduled product data hand-off is what you're really after, the export and sitemap modules at mypresta.rocks are built for exactly that — a feed that stays current without anyone remembering to press a button.
Comments
No comments yet. Be the first!
Be the first to ask a question or share useful feedback.
Leave a comment
Share a question, an installation detail, or feedback that could help another reader.