Migrate a Custom Ecommerce Site to Odoo: Data, SEO and Cutover in Order
How to migrate a custom ecommerce site to Odoo: choose the edition, import products with External IDs, map redirects to protect SEO, and cut over safely.
Long Nguyen
Lập trình viên Fullstack · Kỹ sư AI · Nhà nghiên cứu
The short answer: the order that works
To migrate a custom ecommerce site to Odoo without breaking sales or search traffic, work in a fixed order: decide whether Odoo should be your storefront at all, pick the edition and version, inventory your data, import it with stable External IDs, build the redirect map, rebuild what cannot be imported, then rehearse and cut over. The import is the short part. The decisions around it are what make or break the project.
- Decide what Odoo replaces: the whole store, or only the back office.
- Choose edition, hosting and version.
- Inventory every data type and mark it import, rebuild or archive.
- Import with prefixed External IDs so every run is repeatable.
- Generate a redirect map keyed on SKU or another stable ID.
- Rebuild checkout, shipping, taxes and custom features.
- Rehearse on staging, freeze, delta-import, switch DNS, monitor.
Is migrating a custom store to Odoo worth it?
Odoo's real value is the back office: inventory, accounting, CRM and delivery in one system, with the website on top. If that is what you are missing, migrating pays off. If your hand-built storefront is the product, because its checkout or catalog experience is tuned and differentiating, moving to Odoo's website builder is a step backwards and a poor trade.
- Worth it when non-developers must run the catalog and orders, stock and accounting live in spreadsheets or a separate tool, or maintaining a custom admin has become its own cost.
- Not worth it when the current store converts well, you only need catalog plus checkout, or nobody will own Odoo upgrades after launch.
- Middle path: keep the custom frontend and use Odoo as the back office through its API. You skip the storefront rebuild and the redirect problem entirely.
We have been moving one of our own hand-built Django shops to Odoo Community. The driver was not a feature list. The shop owner is not a developer and a custom admin is a poor place for them to manage a catalog, and every hand-built shop ends up with its own structure that only its author remembers. Odoo gives a standard structure, a standard admin, and Python for the parts that need real code.
Choose the edition, hosting and version before you import anything
These three choices decide how much custom code you may write and who carries upgrades later, so settle them first. Both editions include the Website and eCommerce apps; the difference is who hosts, supports and upgrades.
| Option | Who runs it | Choose it when |
|---|---|---|
| Community, self-hosted | You or your developer host and upgrade it. Free, open source (LGPL), no official support from Odoo S.A. | You have a developer or partner who will own the server and the upgrades. |
| Enterprise, self-hosted | You host; Odoo S.A. provides support and upgrade tooling under a per-user subscription. | You want official support but need control of the server. |
| Enterprise on Odoo.sh | Odoo's managed platform for custom code, with staging and backups. | You write custom modules and want managed infrastructure. |
| Enterprise on Odoo Online | Odoo runs everything as SaaS. | Standard features cover the store. Custom Python modules are not an option here, so rule it out early if your store has custom logic. |
Our rule: choose by who owns upgrades in year two, not by the feature list. A store that ships on a version nobody upgrades becomes the next legacy site.
On version: Odoo ships a new major version every autumn, and Odoo 20 was unveiled on at Odoo Experience. Odoo supports the three most recent major versions. Before you pick one, check that every third-party module you depend on has a branch for it; new majors usually lag on module support, and a supported version with all your modules beats the newest one with gaps.
What needs to move: a data inventory
List every data type in the old store and give each one a verdict: import, rebuild or archive. Treating everything as an import is the most common planning mistake.
| Old store data | Lands in Odoo as | Default approach |
|---|---|---|
| Products and SKUs | Product templates (product.template) | Import with External IDs |
| Options and variants | Attributes, values and product variants | Normalize first, then import |
| Categories | Storefront categories (product.public.category) and internal categories (product.category) | Import before products |
| Product images | Images on the product template | Import from URLs while the old site is still online |
| Customers and addresses | Contacts (res.partner) with child delivery addresses | Import; do not carry passwords |
| Order history | Sales orders (sale.order) | Archive by default; import only if accounting or the customer portal needs it |
| Old URLs | Website redirect records | Generate from the old URL list |
| Blog and static pages | Website pages and blog posts | Recreate and keep the content |
| Coupons, shipping and tax rules, emails | Configuration | Rebuild by hand |
Variants are the hardest import. If your old catalog stores options as free text ("Large / Blue"), turn them into clean attributes and values in the source data before they ever reach Odoo.
How to import products and customers with External IDs
Odoo's import wizard accepts .xlsx and .csv files for any business object. The key is the External ID column: it lets one record point at another and lets you re-import later to update records instead of duplicating them. Per the Odoo 19 import documentation, existing data can be updated in bulk as long as the External ID stays consistent, and External IDs must be unique across all records of all objects. So prefix them by type: legacy_product_123, legacy_category_7, legacy_customer_550.
Import in dependency order: categories, then attributes, then products, then customers. Product images can be loaded from URLs in an image column, and the server fetches them during the import, so keep the old site reachable until the import finishes.
Do not guess column names. Export one dummy product from your target Odoo with I want to update data (import-compatible export) ticked and every field you plan to fill selected, then use that file's headers. The script below refuses to write a file whose columns are not in that template, so a typo fails on your laptop instead of halfway through an import.
import csv
# Import-compatible export of one dummy product from your target Odoo
TEMPLATE = "odoo_product_template.csv"
with open(TEMPLATE, newline="", encoding="utf-8") as f:
valid_headers = set(next(csv.reader(f)))
def to_odoo_row(p):
return {
"External ID": f"legacy_product_{p['id']}",
"Name": p["title"],
"Internal Reference": p["sku"],
"Sales Price": p["price"],
"Product Category/External ID": f"legacy_category_{p['category_id']}",
}
def write_csv(products, path="products_for_odoo.csv"):
rows = [to_odoo_row(p) for p in products]
unknown = set(rows[0]) - valid_headers
if unknown:
raise SystemExit(f"Not in your Odoo export template: {sorted(unknown)}")
with open(path, "w", newline="", encoding="utf-8") as f:
w = csv.DictWriter(f, fieldnames=list(rows[0]))
w.writeheader()
w.writerows(rows)
Customers follow the same pattern. Do not plan on moving password hashes between systems. Import the contact, then send a password-reset or invitation email at launch and tell customers to expect it.
CSV wizard or JSON-2 API: which import method to use
| Method | Good for | Watch out for |
|---|---|---|
| Import wizard (CSV or XLSX) | One-off catalog loads, rehearsals, and imports a non-developer can re-run. | Split very large files into chunks, and test each file before the real import. |
| JSON-2 API | Delta syncs on cutover day, scripted reconciliation, and continuous sync if the storefront stays custom. | Needs an API key and a scripted client. On Odoo Online, API access depends on your plan. |
JSON-2 arrived in Odoo 19: you POST to /json/2/<model>/<method> with an API key as a bearer token. The older XML-RPC and JSON-RPC endpoints are deprecated, and Odoo 19's documentation schedules their removal for Odoo 22 (fall 2028), so new migration scripts should target JSON-2 on Odoo 19 and later. Use it for the check that matters most after any import, comparing counts against the old store:
import requests
BASE = "https://shop.example.com/json/2"
HEADERS = {
"Authorization": "bearer YOUR_API_KEY",
"X-Odoo-Database": "mydb",
"Content-Type": "application/json; charset=utf-8",
}
def count(model, domain):
r = requests.post(f"{BASE}/{model}/search_count",
headers=HEADERS, json={"domain": domain}, timeout=30)
r.raise_for_status()
return r.json()
print("products with SKU:", count("product.template", [["default_code", "!=", False]]))
print("storefront categories:", count("product.public.category", []))
Counts only prove nothing was dropped. After every rehearsal, also spot-check a sample of SKUs for price, variants, images and category placement.
How to keep your SEO when you migrate to Odoo
Odoo's own guidance, in its Odoo 19 SEO documentation, is that migrating usually does not hurt SEO, though no platform can guarantee rankings stay put. It lists three practices: keep the existing content, redirect old URLs to their new counterparts, and monitor traffic and indexation in Google Search Console. It also says a traffic dip in the first days is normal and that reindexing can take from a few days to several weeks. Here is how to apply that to a custom store.
- Build the redirect map from three lists: your old sitemap, the pages that earn traffic in analytics, and the pages that earn impressions in Search Console. Anything in none of them can usually be dropped.
- Key the map on a stable ID, not on slug text. Odoo product URLs end with the record's database ID, so you only know each new URL after the import. Read each product's
website_urlthrough the API, join it to the old URL on SKU, and generate the redirects from that join. - Create the redirects in Odoo. They are records under Website, Configuration, Redirects, which needs developer mode. Odoo offers 301 and 308 for permanent moves. Point every old URL straight at its final URL; redirect chains waste crawl budget and dilute the signal.
- Keep the part number if buyers search by part number. On the auto-parts store we run, most organic traffic comes from part-number searches. For a store like that, SKU stays in the product name, title tag and URL, and SKU is the join key for the redirect map.
- Change one variable at a time. Do not rewrite every description during the platform move. If rankings shift, you want to know whether the platform or the copy caused it.
- Use what Odoo generates. It builds
/sitemap.xml(cached and refreshed every 12 hours, split into an index at 45,000 URLs per file), lets you edit robots.txt in the website settings, adds hreflang tags on multilingual pages, and emits schema.org product microdata. If you carry old structured-data scripts across, check you are not now emitting duplicate product markup.
What you rebuild instead of migrate
Import covers data. Behavior has to be rebuilt, and this is where the budget goes.
| Old store feature | In Odoo | Effort driver |
|---|---|---|
| Theme and layout | Website builder and a theme | How far your design must match the old one |
| Checkout and payments | Payment providers, configured and tested in sandbox mode | Number of providers and currencies |
| Shipping and taxes | Delivery methods and fiscal positions | Zones, carriers, tax rules by country |
| Custom pricing or B2B rules | Pricelists, or a custom module | Whether a standard pricelist can express the rule |
| Admin screens | Standard Odoo views | Usually shrinks, because you stop maintaining them |
| Transactional emails | Mail templates | Number of templates and languages |
When a feature needs real code, it becomes an Odoo module: Python for the models, XML for the views, and OWL for custom front-end components. If you would rather not write those yourself, Netalith's Odoo development service covers custom modules, Odoo eCommerce and integrations. Decide each custom feature one at a time: configure it, replace it with a standard behavior, or build a module. Defaulting to a module for everything is how Odoo projects slow down.
What to do with order history and customer accounts
For customers, import contacts and delivery addresses with External IDs and handle passwords through a reset email, as above. Guest checkouts have no account to migrate, only an email address.
For orders, our default is to archive. Export old orders to a read-only archive (a database snapshot plus CSV or PDF exports) that support and finance can still search, and check your local record-retention rules before deciding how long to keep it. Import orders into Odoo only if customers need their history in the portal or accounting needs the records there.
If you do import them, be careful. In a standard configuration, confirming a sales order can create deliveries and invoices, so importing history as confirmed orders can produce stock moves and accounting entries you never wanted. Rehearse on staging, then inspect inventory and accounting afterwards.
A cutover plan that avoids lost orders and duplicate indexing
- Keep staging out of search. Odoo's SEO documentation describes a way to add a noindex tag to a whole website: set a random value in the Domain field under Website Info in the settings. Do it on every staging and test database.
- Rehearse the full import more than once against a fresh database, and time it. External IDs make each run repeatable, so fix the mapping and run again.
- Reconcile. Compare counts with the script above, then spot-check SKUs, prices, variants, images and categories.
- Lower the DNS TTL a day or so before the switch so the change propagates quickly.
- Freeze. Put the old store into maintenance mode, or at least disable checkout, in your quietest window.
- Delta import. Load only the records created or changed since your snapshot, using the old store's updated-at column. External IDs update existing records in place.
- Switch DNS and turn the redirects on. Place one small real order, refund it, and confirm HTTPS, payment, confirmation emails and stock behavior.
- Submit the new sitemap in Search Console and check the indexing report and 404s every day for the first weeks. Keep the old database snapshot and a private copy of the old site for support lookups.
Get a second opinion before the freeze
The riskiest steps, the ID mapping, the redirect map and the delta import, are also the easiest to review early. If you want help with a replatforming project, Netalith's eCommerce data migration service covers moving a store to a new platform with SEO kept, priced to scope.
CÂU HỎI THƯỜNG GẶP
Câu hỏi thường gặp
Will migrating to Odoo hurt my SEO?
Odoo's documentation says that in most cases it will not, but no platform can guarantee rankings stay the same. Keep your existing content, redirect every old URL to its new counterpart, and monitor indexation in Google Search Console. A traffic dip in the first days is normal.
Can I import products and customers into Odoo from a CSV file?
Yes. The import wizard accepts .xlsx and .csv files for any business object. Give every record a unique External ID so relations between records hold and so a later re-import updates records instead of creating duplicates.
Should I use Odoo Community or Enterprise for a migrated store?
Both include the Website and eCommerce apps. Community is free and self-hosted, with no official support from Odoo S.A., so you or a partner must own upgrades. Enterprise adds official support and upgrade tooling for a per-user subscription. Choose based on who will maintain and upgrade the store in year two.
Which Odoo API should migration scripts use?
On Odoo 19 and later, use the JSON-2 API. XML-RPC and JSON-RPC are deprecated, and Odoo 19's documentation schedules their removal for Odoo 22 (fall 2028). JSON-2 uses an API key as a bearer token.
Should I import old orders into Odoo?
Usually archive them instead. Importing history as confirmed sales orders can trigger deliveries, invoices and accounting entries you do not want. Import orders only when customers need them in the portal or accounting needs them, and rehearse on staging first.
How long does it take to migrate a custom ecommerce site to Odoo?
It depends on scope, not on catalog size. Loading clean product and customer data is the short part. Rebuilding checkout, shipping, taxes and custom features, and cleaning messy variants, is the long part, so get a list of custom behaviors before estimating.