Free reference · Updated 2026-08-09 · No signup

Odoo → Shopify field mapping
reference

Every field worth syncing between Odoo and Shopify — products, inventory, customers, orders — with the direction it should move in and the specific failure attached to getting it wrong. This is the sheet we work from internally. Copy it, cite it, argue with it.

Products and variants

SKU is the join key in each direction. If a SKU is blank, duplicated, or differs by whitespace or case, every downstream mapping in this table silently fails.

Odoo fieldShopify fieldDirectionWhat to watch
product.template.nameproduct.titleOdoo → ShopifyOdoo is the source of record for naming. Overwriting on every sync will undo manual Shopify edits — map once on create if merchandising owns titles.
product.product.default_codevariant.skuBoth waysThe join key. Trim whitespace and normalise case before matching; Shopify treats SKUs as free text and does not enforce uniqueness.
product.template.barcodevariant.barcodeOdoo → ShopifyUse as a secondary match key when default_code is missing on legacy Odoo records.
product.template.list_pricevariant.priceOdoo → ShopifyRetail price only. B2B rates belong in a price list, not in variant.price — pushing customer-specific pricing here exposes it to every visitor.
product.template.standard_pricevariant.inventoryItem.costOdoo → ShopifyCost is not exposed on the storefront. Required if you want Shopify margin reporting to be meaningful.
product.template.weightvariant.inventoryItem.measurement.weightOdoo → ShopifyCheck the unit. Odoo stores in the company UoM; Shopify expects an explicit unit per variant.
product.template.description_saleproduct.descriptionHtmlOdoo → ShopifyOdoo stores plain text, Shopify expects HTML. Escape before writing or line breaks disappear.
product.template.categ_idproduct.productType / tagsOdoo → ShopifyOdoo categories are a tree, Shopify product type is a single string. Flatten to the leaf and mirror ancestors as tags if you need faceting.
product.attribute.valueproduct.options / variant.selectedOptionsOdoo → ShopifyShopify allows a limited number of options per product. Odoo attribute sets that exceed it must be split into separate products.
product.template.activeproduct.statusOdoo → ShopifyArchive in Odoo should map to DRAFT, not delete. Deleting breaks order history references in Shopify.

Inventory and locations

The single most common source of drift. Odoo reports several stock figures and only one of them is the number a buyer should see.

Odoo fieldShopify fieldDirectionWhat to watch
stock.quant.quantityinventoryLevel.quantities(on_hand)Odoo → ShopifyPhysical stock, reservations included. Publishing this oversells whenever Odoo holds a reservation Shopify cannot see.
product.product.qty_available - outgoing_qtyinventoryLevel.quantities(available)Odoo → ShopifyThis is normally the figure you want. Free-to-use quantity after outgoing moves are deducted.
stock.warehouse / stock.locationlocationOdoo → ShopifyMap one Odoo warehouse to one Shopify location explicitly. Implicit mapping by name breaks the first time someone renames a warehouse.
product.template.type = 'consu'variant.inventoryItem.tracked = falseOdoo → ShopifyNon-stocked items must be untracked in Shopify, otherwise they show as sold out permanently.
stock.quant write_date— (sync cursor)Odoo → ShopifyUse a write_date cursor for incremental pulls. Full inventory sweeps will exhaust Shopify API credits on catalogues above roughly 5,000 variants.

Customers and companies

Odoo models a company and its contacts in one table with a parent link. Shopify splits customers and companies into separate objects, so the mapping is not one-to-one.

Odoo fieldShopify fieldDirectionWhat to watch
res.partner (is_company = true)companyBoth waysOne Odoo company partner becomes one Shopify company. Match on VAT number or an external reference, never on name.
res.partner (parent_id set)companyContact / customerBoth waysChild contacts become company contacts. A contact with no email cannot be created in Shopify at all.
res.partner.emailcustomer.emailBoth waysSecondary join key. Duplicate emails across Odoo partners are the usual cause of merged or overwritten Shopify customers.
res.partner.vatcompany.externalId / metafieldOdoo → ShopifyStore it as a stable external ID. It survives renames and is the only reliable long-term match key for B2B accounts.
res.partner.property_product_pricelistcatalog / price list assignmentOdoo → ShopifyWhere Shopify's catalogue cap bites: Odoo allows unlimited pricelists, native Shopify B2B does not. Map to an uncapped pricing layer instead.
res.partner.property_payment_term_idcompany.paymentTermsOdoo → ShopifyTerms names must exist on the Shopify side first; unknown term templates are rejected rather than created.

Orders and fulfilment

Direction matters more here than anywhere else. Pick one system as the order of record and let the other mirror it, or you will get duplicate stock moves.

Odoo fieldShopify fieldDirectionWhat to watch
sale.orderorder / draftOrderShopify → OdooStorefront and portal orders originate in Shopify. Write them into Odoo as confirmed sale orders; keep the Shopify order ID on the Odoo record.
sale.order.nameorder.note / metafieldOdoo → ShopifyPush the Odoo reference back so support staff can search either system with one number.
sale.order.line.price_unitlineItem.originalUnitPriceShopify → OdooSend the resolved per-customer price, not the list price, or Odoo margin reporting is wrong on every B2B line.
stock.picking (done)fulfillmentOdoo → ShopifyFulfil in Shopify only when the Odoo picking is validated. Fulfilling on picking creation triggers customer emails for goods still in the warehouse.
stock.picking.carrier_tracking_reffulfillment.trackingInfoOdoo → ShopifyInclude the carrier name; Shopify only renders a tracking link for carriers it recognises.
account.move (invoice)— (stays in Shopify or Odoo)Odoo → ShopifyDo not attempt round-trip invoice sync. Choose one system to issue the invoice; mirroring both creates duplicate ledger entries.

Free playbook

Want this mapping as a checklist you can tick off?

We send the Odoo → Shopify migration checklist — join keys, field owners, and the pre-flight tests to run before the first sync.

No spam. Unsubscribe anytime.

Six ways this goes wrong

The pitfalls behind most broken syncs

Matching on name instead of a key

Names change; SKUs and VAT numbers do not. Every mapping in the tables above uses a stable key for exactly this reason. A sync built on names works for three months and then quietly duplicates half your catalogue.

Publishing on_hand instead of available

Odoo's on-hand figure includes reserved stock. Shopify has no visibility of Odoo reservations, so it will happily sell units already allocated to a picking. Use available quantity.

Round-trip sync on fields only one system owns

Pick an owner per field. Product titles, costs and stock are owned by the ERP; order status and fulfilment state are owned by whichever system actually ships. Round-trip writes on a single-owner field cause update loops.

Ignoring API cost, not API count

Shopify's GraphQL Admin API bills by query cost against a leaky bucket, not by request count. A nightly full sweep that passes in staging with 200 products will throttle at 5,000. Sync incrementally against a write_date cursor.

No idempotency key on writes

Retries are normal. Without a deterministic key on the Shopify side — the SKU, the Odoo record ID in a metafield — a retried create becomes a duplicate rather than an update.

Currency and unit mismatch

Odoo stores prices in the company currency and weights in the company UoM. Shopify stores per-market currency and an explicit unit per variant. Convert at the mapping layer, not in a spreadsheet afterwards.

Cite this reference

This page is free to quote and link to, in full or in part, with attribution. No permission needed.

Hasil (2026). "Odoo → Shopify field mapping reference."
https://hasil.io/odoo-shopify-field-mapping
<a href="https://hasil.io/odoo-shopify-field-mapping">Odoo → Shopify field mapping reference — Hasil</a>

FAQ

Mapping questions we get asked

What field should I use to match Odoo products to Shopify products?+

Use Odoo's default_code against Shopify's variant SKU, normalised for case and whitespace. Fall back to barcode against variant barcode for legacy records with no internal reference. Never match on product name.

Should I sync on-hand or available quantity from Odoo to Shopify?+

Available quantity — qty_available minus outgoing_qty. On-hand includes stock already reserved against Odoo pickings, which Shopify cannot see, so publishing it causes overselling.

How do Odoo pricelists map to Shopify B2B catalogues?+

They do not map cleanly. Odoo allows an unlimited number of pricelists per company, while native Shopify B2B caps catalogues and treats each one as a single pricing tier. Merchants with more pricing arrangements than catalogue slots need an uncapped per-customer price-list layer instead.

Which system should own the order record?+

Whichever system the order originates in. Storefront and B2B portal orders originate in Shopify and should be written into Odoo as confirmed sale orders, with the Shopify order ID stored on the Odoo record for reconciliation.

Why does my sync work in testing but throttle in production?+

Shopify's GraphQL Admin API charges by query cost against a leaky bucket rather than counting requests. Full catalogue sweeps that pass with a few hundred products exhaust the bucket at a few thousand. Sync incrementally using a write_date cursor.

Free playbook · 1 email/month

Not ready for a trial? Take the playbook instead.

No spam. Unsubscribe anytime.

Or skip the mapping sheet entirely

Hasil Sync ships these mappings as defaults — SKU matching, available-quantity inventory, draft orders back to Shopify — configured in about three minutes, no developer, no Zapier.

Start 14-day free trial