How to Sync NetSuite Inventory Levels to a WooCommerce Store on a Schedule
Build a Spojit workflow that runs on a schedule, reads on-hand quantities from NetSuite, maps your internal item IDs to WooCommerce product SKUs, and pushes the latest stock levels to your storefront.
What This Integration Does
If NetSuite is your system of record for inventory but you sell through a WooCommerce store, the two drift apart the moment a sale, a receipt, or an adjustment happens in the warehouse. Customers oversell on stock you no longer have, or you hide product that is actually available. This workflow closes that gap by treating NetSuite as the source of truth: on a fixed cadence it pulls current on-hand quantities, matches each item to its WooCommerce counterpart by SKU, and writes the new stock figure back to the storefront so the catalog reflects reality.
The run model is a scheduled pull-and-push. A Schedule trigger fires on a cron cadence (for example every 15 minutes) and produces a {{ scheduledAt }} timestamp. A Connector node reads inventory from NetSuite, a Transform reshapes the rows into a clean list of SKU plus quantity, and a Loop walks that list pushing each level into WooCommerce. The workflow is stateless and idempotent: writing the same quantity twice has no harmful effect, so re-runs simply reassert the current truth. Each scheduled run is an independent entry in your execution history.
Prerequisites
- A NetSuite connection added under Connections, with REST and SuiteQL access to your item and inventory records. See the NetSuite connector docs for credential setup.
- A WooCommerce connection with a REST API key that has read/write access to products. See the WooCommerce connector docs.
- Inventory tracking enabled in WooCommerce (Manage stock at product level) so that stock quantities are honored at checkout.
- A consistent SKU on each NetSuite item that matches the SKU on the corresponding WooCommerce product. This shared SKU is the join key the workflow relies on.
Step 1: Add a Schedule trigger
Create a new workflow in the Workflow Designer and set the Trigger type to Schedule. Enter a 5-field Unix cron expression and an IANA timezone. For a quarter-hourly sync during business hours, use:
*/15 8-18 * * 1-5
Australia/Sydney
A trigger can hold multiple schedules if you want, say, a slower overnight cadence plus a faster daytime one. The trigger output is {{ scheduledAt }}, which you can use later for logging. For more on cron fields and timezones, see the guide on setting up a Schedule trigger.
Step 2: Read on-hand quantities from NetSuite
Add a Connector node on the netsuite connector in Direct mode. The cleanest way to get item plus quantity in one call is the run-suiteql tool, which runs a SQL-like query against your NetSuite data. Map the query field to select each item's internal id, SKU (item id), and quantity available:
SELECT id, itemid, quantityavailable
FROM item
WHERE isinactive = 'F'
AND quantityavailable IS NOT NULL
FETCH FIRST 500 ROWS ONLY
Set limit (default 100, max 1000) and use offset for pagination if you carry more items than one page holds. If you prefer record reads over SuiteQL, the list-items tool returns inventory item records (defaulting to inventoryItem) that you can read quantity fields from instead. Bind the result to a variable such as {{ netsuite_items }}.
Step 3: Map internal IDs to SKUs with a Transform
Add a Transform node to reshape the raw NetSuite rows into a tidy list of exactly what the push needs: a SKU and an integer quantity per item. This is where you normalize field names and drop anything you do not want to sync. The Transform should emit an array shaped like this:
[
{ "sku": "WIDGET-RED-001", "quantity": 42 },
{ "sku": "WIDGET-BLUE-002", "quantity": 0 }
]
Pull sku from the NetSuite itemid field and quantity from quantityavailable, coercing it to a whole number. Bind the output to {{ stock_levels }}. If your storefront SKUs differ from your NetSuite item ids, this node is also where you apply the lookup that translates one to the other. For a deeper look at reshaping data, see using Transform nodes in structured mode.
Step 4: Loop over the stock list
Add a Loop node in ForEach mode and point it at {{ stock_levels }}. Each iteration exposes the current row, for example as {{ item.sku }} and {{ item.quantity }}. Everything in Steps 5 and 6 lives inside this loop body so it runs once per product. Keep the body lean: one read to find the product, then one write to set its stock. See using Loop nodes for iteration options and limits.
Step 5: Find the WooCommerce product by SKU
Inside the loop, add a Connector node on the woocommerce connector in Direct mode using the list-products tool. WooCommerce updates a product by its numeric product ID, not by SKU, so you first resolve the SKU to an ID. Map the sku field to {{ item.sku }} and leave per_page small (a SKU is unique, so one match is expected):
sku: {{ item.sku }}
per_page: 1
Bind the result to {{ matched_product }}. The first returned product's id is the WooCommerce product ID you will write to in the next step.
Step 6: Push the stock level to WooCommerce
Still inside the loop, add a Connector node on the woocommerce connector in Direct mode. Stock quantity lives on a product field that the update-product tool does not expose directly, so use the raw-api-request tool to send a precise PUT to the product endpoint. Set method to PUT, path to the matched product, and body to mark stock managed and set the level:
method: PUT
path: /products/{{ matched_product.id }}
body:
{
"manage_stock": true,
"stock_quantity": {{ item.quantity }}
}
Use the update-product tool in the same way when you need to change other catalog fields such as status (for example flipping a sold-out item to draft) or regular_price. Setting manage_stock to true ensures WooCommerce enforces the quantity at checkout rather than treating the product as always in stock.
Tips
- WooCommerce REST keys are rate limited on busy stores. If your catalog is large, widen the Schedule cadence or add a small delay between iterations so the Loop does not hammer the API.
- Filter the NetSuite query to items you actually sell online (for example a saved-search flag or a category condition) so you are not paginating thousands of irrelevant rows every run.
- Treat zero as a real value. A SKU that dropped to
0on hand must still be pushed so the storefront stops selling it; do not silently skip zero-quantity rows in the Transform. - Ask Miraxa, the intelligent layer across your automation, to scaffold this for you with a prompt like "Add a Loop over
{{ stock_levels }}that lists the WooCommerce product by{{ item.sku }}then sets its stock," then fine-tune each node in the properties panel.
Common Pitfalls
- SKU mismatches. If a NetSuite item id does not exactly match a WooCommerce SKU,
list-productsreturns no match and the product is silently never updated. Add a Condition that checks{{ matched_product }}is non-empty and log or email the misses. - Pagination gaps. Both
run-suiteqlandlist-productspage their results. If you read only the first page of NetSuite items, distant items never sync. Page through withoffsetuntil you have the full set. - Timezone drift on the Schedule. A cron without an explicit IANA timezone can fire at an unexpected local hour. Always pin the timezone in the Schedule trigger.
- String versus number quantities. WooCommerce expects
stock_quantityas a number. Coerce it in the Transform so a value arriving as text does not produce a rejected write.
Testing
Before scheduling it, narrow the NetSuite query to a single known SKU (add AND itemid = 'YOUR-TEST-SKU' to the SuiteQL) and run the workflow with the Run button. Confirm in your execution history that the Transform produced one clean row, that list-products resolved it to the right WooCommerce product ID, and that the raw-api-request write returned the product with the expected stock_quantity. Check the WooCommerce product page to see the new level. Once the single-item path is correct, remove the test filter, raise the query limit, and let the Schedule take over.