The quick answer
Parse context.request.body in the Suitelet, validate the complete orderdetail array, create the sales order and commit every dynamic item line before saving. Write the returned ID as serialized JSON through context.response.write. Then verify that the same order contains every expected line and amount.
This walkthrough runs the actual example handler locally with N/record mocks. Its save result and native-style order view are local teaching state. It sends no request to NetSuite and does not establish deployment, authentication or account acceptance.
Before you start
The fictional customer is Green Valley Service, internal ID 810. Item 920, Inspection service, has a trusted rate of 40 USD. Item 921, Calibration service, has a trusted rate of 25 USD. Quantities 2 and 3 produce 80 + 75 = 155 USD, before tax or shipping. These are service items; no stock reservation is demonstrated.
The source discussion's author confirmed creating a sales order, then asked about stock checks and customer updates. This guide covers the JSON-to-order step. Two rows demonstrate the array iteration from the source's five-row payload. The fixed catalog is a teaching fixture: a production implementation needs authorized customer/item lookups, pricing policy and all account-required fields. SUITELET_URL is a separately authenticated deployment placeholder, not a working public endpoint.
01. Parse the string body
A Suitelet receives request.body as a string. Parse it once inside a try/catch, then validate the entire payload before creating a record. Do not confuse this with a RESTlet POST entry point, which receives a parsed object for application/json requests.
The first file is deliberately incomplete JSON. The workflow below it shows the handler sequence: parse, validate, create, commit each line, save, write the response.
let payload;
try {
payload = JSON.parse(context.request.body);
} catch {
respond({ok: false, error: 'INVALID_JSON'});
return;
}
Here, respond sets Content-Type and writes JSON. Validate the known customer, nonempty array, allowed items and positive integer quantities before record.create. An invalid later item must not leave a partially built order. The local tests also cover that case, an unknown customer and a negative quantity.
02. Reject malformed JSON
Send order-bad.json in the local terminal. The actual handler catches the syntax error and writes ok:false / INVALID_JSON. It returns before record.create, and the local order count remains 0.
INVALID_JSON is this handler's application error code. Writing that JSON does not establish an HTTP 400 status; callers need to inspect the response payload.
03. Save both item lines
Use the valid body. Customer and item values are internal IDs, not display names. The body supplies quantities; the server supplies the rates.
{
"customer_id": "810",
"orderdetail": [
{"id":"920", "quantity":2},
{"id":"921", "quantity":3}
]
}
After validation, create the dynamic sales order and set the entity. For each item, select a new line, set item and quantity, select Custom pricing, apply the trusted rate, then commit the line.
const order = record.create({
type: record.Type.SALES_ORDER,
isDynamic: true
});
order.setValue({
fieldId: 'entity',
value: Number(payload.customer_id)
});
for (const line of payload.orderdetail) {
order.selectNewLine({sublistId: 'item'});
order.setCurrentSublistValue({
sublistId: 'item', fieldId: 'item',
value: Number(line.id)
});
order.setCurrentSublistValue({
sublistId: 'item', fieldId: 'quantity',
value: line.quantity
});
order.setCurrentSublistValue({
sublistId: 'item',
fieldId: 'price',
value: -1
});
order.setCurrentSublistValue({
sublistId: 'item', fieldId: 'rate',
value: catalog[line.id]
});
order.commitLine({sublistId: 'item'});
}
const id = order.save({
enableSourcing: true,
ignoreMandatoryFields: false
});
The fixture uses Custom price level -1 before setting its rate. Verify price-level behavior for the intended account and item types. Dynamic sourcing occurs as fields change; enableSourcing on save does not fill missing required fields in dynamic mode. Keep mandatory checks enabled. Permissions, subsidiary, currency, tax, forms, workflows and custom fields can still prevent an account save.
The local handler writes salesOrderId 77001, reports two committed lines, and shows local count 1 and total 155 USD.
Write the response explicitly. Returning an object from onRequest does not send it to the caller.
context.response.setHeader({
name: 'Content-Type',
value: 'application/json'
});
context.response.write({
output: JSON.stringify({
ok: true,
salesOrderId: String(id)
})
});
04. Reconcile the saved order
Open the exact local order 77001. Confirm SO77001, Green Valley Service, US Dollar and Total 155.00. The view reads the record produced by the local handler; it is not evidence of a transaction in a NetSuite account.
On Items, reconcile both rows: Inspection service 2 × 40 = 80 and Calibration service 3 × 25 = 75. Their sum is the full 155. Checking the ID without checking the lines would miss a failed or overwritten dynamic line.
Common questions
Does a Suitelet receive a JSON object automatically?
Its request.body is a string, so this handler parses it explicitly. A RESTlet POST entry point normally receives an object when the request uses application/json; text content types can differ.
Why does returning the sales order ID not produce a response?
A Suitelet writes through context.response. Set Content-Type and pass JSON.stringify(result) as the output string. Returning a value from onRequest is not the RESTlet response contract.
Why commit every dynamic item line?
commitLine completes the current line before selecting another. Omitting it can leave a line unfinished or replace the current entry. Verify every saved quantity, rate and amount.
Can I use prices and customer names from the client?
Use validated internal IDs and server-owned pricing. This local allowlist proves only fixture membership; it does not establish customer existence, permissions or pricing in an account. Add authorization, account lookups and business limits before production use.
Does the order reserve available inventory?
This example uses service items. A stock availability check is a point-in-time observation, and sales-order creation alone is not a universal reservation guarantee. Inventory commitment needs its own account-specific validation.
Does this prove that the Suitelet works in my account?
It proves the example's local control flow and deterministic data. Deployment, authentication, permissions, sourcing, mandatory fields, tax and side effects need verification in the intended account.