Skip to content
SyncMyShop

Magento and NetSuite

Magento NetSuite integration

NetSuite matrix items become configurable and simple products, price levels become customer group prices, and stock per location lands on the right inventory source. Orders, shipments and credit memos flow the other way. Built for your Magento 2 or Adobe Commerce store and your NetSuite account, then run for you.

What moves between Magento and NetSuite

Magento holds the catalog as customers see it and captures the order. NetSuite holds the item master, the stock and the books. The integration covers eight groups of fields.

Group Fields that move Direction
Keys sku against item name or internal ID, increment_id against external ID set once, both sides
Items type, tax class, price level, unit, weight NetSuite to Magento
Variants matrix parent and children, option values, barcodes NetSuite to Magento
Customers company or person, email, addresses, terms, tax flags both ways
Orders lines, payment method, shipping line, discounts, tax Magento to NetSuite
Fulfillment carrier, service, tracking number, partial shipments NetSuite to Magento
Refunds Credit Memo or Cash Refund, restock or write-off both ways
Classifications subsidiary, department, class, location rules in the integration

A Magento order has two identifiers. entity_id is the number in every API path, such as POST /V1/order/3/ship. increment_id is the number the customer sees, such as 000000003. NetSuite gets the increment_id as external ID and PO reference; the integration keeps the entity_id to post the shipment back.

Which system owns what

Data System of record Direction Sync behavior
Items, SKUs, matrix structure NetSuite NetSuite to Magento changed items, children before parents
Categories, attribute sets, content Magento stays in Magento not touched by the integration
Base, tier and group prices NetSuite price levels NetSuite to Magento bulk price endpoints, per website
Quantity per source NetSuite, per location NetSuite to Magento On Hand per source, on a schedule
Salable quantity Magento calculated in Magento source quantity minus reservations
Orders Magento until import, then NetSuite Magento to NetSuite polled on updated_at, or by event
Shipments and tracking NetSuite or the WMS NetSuite to Magento one shipment per Item Fulfillment
Credit memos where the refund starts both ways with return_to_stock_items
Company credit limit (B2B) NetSuite NetSuite to Magento credit_limit on the company

Salable quantity is the one row where Magento calculates and NetSuite only supplies. That split is where most stock differences in this pair come from.

What Magento and NetSuite store differently

Configurable products and matrix items

A NetSuite matrix is one parent and up to 2,000 child items. In Magento the parent is a configurable product and every child is a separate simple product with its own SKU and its own stock. The link is made one child at a time: POST /V1/configurable-products/TS/child accepts a single childSku.

A shirt in 6 sizes and 8 colors is 48 children. Creating it takes 48 simple products, 1 configurable product, 2 option calls for size and color, and 48 link calls: 99 requests for one style. A season of 200 styles is 19,800 requests. The simple products go through the bulk endpoint in batches; the link calls cannot be batched and run from a queue. More on the modeling in variant mapping.

Kits and bundles

A NetSuite kit is one line on the sales order that breaks into components at fulfillment. A Magento bundle arrives as a parent order item plus child items that point to it through parent_item_id. The mapping needs an explosion rule: which line carries the price, and whether NetSuite receives the kit or the components. Adobe documents one more constraint: bundle and grouped products do not support multiple sources and have to sit on the default source.

Stock: source quantity and salable quantity

NetSuite stock written to a Magento source, with and without double counting NetSuite has 20 on hand at a location: 4 committed to an open order and 16 available. If the integration writes 16 to the Magento source, Magento subtracts its own reservation of 4 for the same order and shows 12 salable. If it writes the on hand figure of 20, Magento shows 16. NetSuite, location Main 20 on hand Committed 4 Available 16 Magento source gets 16, NetSuite's Available 16 on source Reservation 4 Salable 12 Salable quantity in the store: the open order is counted twice 16 - 4 = 12 Magento source gets 20, NetSuite's On Hand 20 on source Reservation 4 Salable 16 Salable quantity in the store 20 - 4 = 16
Writing NetSuite's Available to a Magento source counts an open order twice. Writing On Hand gives the right salable quantity.

Magento's inventory has sources (where stock lies), stocks (which sources serve a website) and reservations. The salable quantity is the sum of the source quantities for a stock, minus the out-of-stock threshold, minus open reservations. A reservation is created when the order is placed and cleared when the order ships.

NetSuite's Available is On Hand minus Committed. An order that has been imported is committed in NetSuite and reserved in Magento at the same time. Write Available to the source and that order is subtracted twice: 20 on hand, 4 ordered, 16 written, 12 salable. The integration writes On Hand per location to POST /V1/inventory/source-items with sku, source_code and quantity, and posts the shipment before the next stock write, so the reservation and the source deduction line up. Which stock counts covers the general rule.

Price levels, customer groups and tier prices

A NetSuite quantity price mapped to a Magento tier price One NetSuite pricing row for item TS-BLK-M, price level Wholesale, quantity 10, currency USD and unit price 31.50 becomes one Magento tier price row with sku, customer_group, quantity, website_id, price and price_type. The price level maps to a customer group and the currency to a website. NetSuite item pricing Magento tier price item TS-BLK-M price level Wholesale quantity break 10 unit price 31.50 currency USD sku TS-BLK-M customer_group Wholesale quantity 10 price 31.50 website_id 1 needs price scope Website price_type fixed set by the integration group map website per currency
One NetSuite quantity price as one Magento tier price row. The currency decides the website, which only works with price scope set to Website.

A NetSuite price is a cell in a grid of item, price level, quantity break and currency. A Magento tier price is a row:

{
  "prices": [
    {
      "sku": "TS-BLK-M",
      "price": 31.50,
      "price_type": "fixed",
      "website_id": 1,
      "customer_group": "Wholesale",
      "quantity": 10
    }
  ]
}

Three things have to be set before this works. Each price level needs a customer group. Magento's price scope is global by default, so a price per currency needs the scope switched to Website and one website per currency. And in NetSuite the Quantity Pricing feature has to be on, since its REST item record leaves the quantity pricing fields out and the price breaks come through SuiteQL. See customer-specific pricing for prices per account.

Addresses

Magento stores the street as an array of lines, "street": ["Industrieweg 12", "Unit 4"], with company, postcode and a two-letter country_id. NetSuite has addr1, addr2 and addr3 next to addressee and attention. Company goes to addressee, the contact name to attention, each street line to its own field. A carrier label that prints 35 characters per line still cuts a long company name, so the integration checks lengths before the order reaches the warehouse.

The technical reality of this pair

The two APIs are limited in opposite ways. NetSuite counts concurrent requests. For Magento on your own server or on Adobe's cloud infrastructure, Adobe documents no general rate limit on the REST API: the maximum is what the server can handle, and an integration that reads too much at once slows the storefront.

Limit Magento 2 and Adobe Commerce NetSuite
Throttle no general limit; optional input limits 5, 15 or 20 concurrent requests
Page size 300 at most with input limits on; unlimited without 1,000 rows per page
Array per request 20 synchronous, 5,000 asynchronous, with input limits on 5,000 usage units per RESTlet call
Bulk route /async/bulk/V1/..., returns a bulk_uuid SuiteQL, 100,000 rows per query
Change notification events on Adobe Commerce 2.4.4 and up; polling on Open Source user event script, or polling on last modified date
Token lifetime admin token 4 hours OAuth 2.0 access token 60 minutes

Prices, worked through. 8,000 SKUs with 3 price levels and 4 quantity breaks is 96,000 tier price rows. With input limits on, a synchronous request carries 20 rows: 4,800 requests. The asynchronous bulk endpoint carries 5,000 rows: 20 requests. On the NetSuite side the same rows are 96 SuiteQL pages in one concurrency slot. After the first load only changed rows move.

The bulk endpoint depends on a consumer. A bulk request returns a bulk_uuid at once and the work happens in the message queue. If the consumer async.operations.all is not running, every operation stays at status 4, Open, and nothing reports an error. The integration reads GET /V1/bulk/{uuid}/status after each batch and raises an alert when operations stay open.

A search without a page size returns everything. Magento's documentation says so in one line. A product search without pageSize on a catalog of 60,000 SKUs is one response with 60,000 products. Every read the integration makes is paged.

Orders, worked through. On Open Source the integration polls GET /V1/orders filtered on updated_at. At one poll every 2 minutes that is 720 light requests a day. Writing to NetSuite is the slower side: at 3 seconds per sales order and 2 of 5 concurrency slots, 40 orders a minute. The numbers behind that are in NetSuite concurrency limits and on the NetSuite page.

Where standard connectors stop

Packaged connectors and iPaaS templates for this pair document their own limits.

  • Tier pricing in one shape. The common model is website by customer group by price level. It is documented as unsupported for bundle products, and it cannot run together with attribute sync to all store views.
  • Locations to sources, one to one or many to one. One NetSuite location feeding two Magento sources, or an allocation rule per website, is outside the settings.
  • Quantity pricing as a precondition. Tier prices sync only when NetSuite's Quantity Pricing feature is enabled; a price grid kept in custom fields or custom records is outside the model.

A built integration has none of these as fixed points: the price model, the source allocation and the kit explosion rule are written to how your account and your store work. The technical comparison is in custom integration vs iPaaS.

When it goes wrong

What fails What the integration does What you see
NetSuite answers 429 order waits in the queue, retried with a growing pause nothing, unless it lasts
Bulk operation ends with status 2 retried; status 2 is retriable count of retried rows
Bulk operation ends with status 3 row goes to the review list with its payload the SKU and Magento's message
Child linked before it exists the link step is repeated after the product step nothing
Order line with an unknown SKU that order is held, others continue the order and the SKU
Magento down for an upgrade NetSuite changes collect in the queue queue length on the dashboard

An order that keeps failing is kept with its original payload and can be sent again once the cause is fixed. A reconciliation run compares Magento orders of the last days against NetSuite external IDs, so an order missed by a poll or an event still arrives.

Before go-live the integration runs as a dry run: it reads both sides and shows the products, prices and orders it would write, without writing. You approve that result, and from then on every run leaves a diff per record in the dashboard. We host and monitor it as part of an e-commerce ERP integration, and we fix it when Adobe or Oracle changes an interface. The Magento page covers the platform side. Sibling pages: Shopify NetSuite integration, WooCommerce NetSuite integration and BigCommerce NetSuite integration. Tell us which Magento version and NetSuite edition you run.

Frequently asked questions

Does this work with Magento Open Source, or only with Adobe Commerce?

Both. The REST API for products, stock, orders, shipments and refunds is the same. Two things differ. The B2B modules with companies, shared catalogs and company credit exist only on Adobe Commerce. Adobe I/O Events and Adobe Commerce Webhooks need Adobe Commerce 2.4.4 or higher, so on Open Source new orders are picked up by polling.

Which Magento versions do you integrate?

The 2.4 line. The current release is 2.4.9, from May 2026. Regular support for 2.4.6 ended on August 11, 2026 and for 2.4.7 it ends on May 31, 2027. The integration uses only REST endpoints that exist across those versions, and we adjust it when a release changes one of them.

How do NetSuite matrix items become configurable products?

Each matrix child becomes a simple product with its own SKU, and the matrix parent becomes the configurable product. Magento links one child per call, so a matrix of 48 children is 48 link calls after the products exist. The integration creates children first, then the parent, then the links, and repeats only the step that failed.

Can NetSuite price levels set prices per Magento customer group?

Yes. A price level maps to a customer group, and each quantity break becomes a tier price row with sku, customer_group, quantity, price and website_id. On Adobe Commerce B2B every shared catalog creates its own customer group, so a price level can also follow a shared catalog.

Why does Magento show less stock than NetSuite?

Usually because an open order is subtracted twice. NetSuite's Available already excludes committed stock. Magento subtracts its own reservation for the same order from the source quantity. With 20 on hand and an open order of 4, writing 16 to the source gives a salable quantity of 12. We write the On Hand figure and let Magento do the subtraction once.

Do we have to change anything in Magento for the integration?

An integration with its own access rights, and one setting. Since 2.4.4 an integration token no longer works as a bearer token on its own; requests are signed with OAuth 1.0a, or the bearer option is switched back on in the configuration. For bulk updates the message queue consumer async.operations.all has to be running.

Tell us what you need

Tell us which systems you use and what needs to work better. We reply personally within one working day. We help you define the work, then agree the price and timing before you commit.

  • You know the price and the timeline before you commit to anything
  • We work out the connection that takes the work off your hands
  • We handle the build, hosting, monitoring and maintenance
  • Your team has less information to retype and fewer discrepancies to chase

Suppliers, products, orders per day, a rough estimate is fine.

Or email directly: carsten@syncmyshop.com