When a purchase order has been received, accounts payable needs a vendor bill that reflects what actually arrived, not merely what was ordered. SuiteScript vendor bill creation from item receipts provides a reliable way to automate that process in NetSuite while preserving the transaction relationship between the receipt and the bill.
The safest approach is to transform the Item Receipt into a Vendor Bill with `record.transform()` rather than creating a blank bill and copying values manually. The transformation allows NetSuite to carry forward the vendor, subsidiary, currency, purchase order relationship, item lines, quantities, rates, tax context, and other supported transaction data. A production script must still validate partial receipts, prevent duplicate bills, handle permissions, and confirm that the resulting bill is ready for approval.
What happens when NetSuite transforms an item receipt into a vendor bill?
When NetSuite transforms an Item Receipt into a Vendor Bill, it creates a bill based on the receipt transaction and preserves the source relationship through NetSuite’s transaction architecture. The resulting bill should represent the items and quantities received, subject to the account’s purchasing, billing, tax, and vendor configuration.
This differs from transforming a Purchase Order directly into a Vendor Bill. A purchase order describes what the business authorized a supplier to provide. An Item Receipt records what the warehouse or receiving team accepted. If the business pays based on received quantities, the Item Receipt is the more appropriate source transaction.
A typical procurement flow looks like this:
`Purchase Order → Item Receipt → Vendor Bill → Vendor Payment`
The important technical detail is that `record.transform()` uses NetSuite’s native transaction transformation rules. It is not equivalent to loading the Item Receipt, reading every field, and assigning those values to a new Vendor Bill. Native transformation helps retain system-managed relationships and reduces the risk of creating a bill that looks correct but is disconnected from the original purchasing process.
NetSuite still applies configuration and validation rules during the transformation. These include subsidiary restrictions, accounting preferences, vendor status, required classifications, tax settings, inventory behavior, and permissions assigned to the execution role.
SuiteScript vendor bill creation from item receipts: the basic pattern
A SuiteScript 2.1 implementation generally needs the `N/record` module, the internal ID of the Item Receipt, and a controlled execution context such as a Map/Reduce script, scheduled script, or approved user action.
Here is the core transformation pattern:
/**
* @NApiVersion 2.1
* @NScriptType MapReduceScript
*/
define(['N/record', 'N/log'], (record, log) => {
const getInputData = () => {
return [
// Replace with Item Receipt internal IDs selected by your process.
12345
];
};
const map = (context) => {
const itemReceiptId = Number(context.value);
try {
const vendorBill = record.transform({
fromType: record.Type.ITEM_RECEIPT,
fromId: itemReceiptId,
toType: record.Type.VENDOR_BILL,
isDynamic: false
});
const vendorBillId = vendorBill.save({
enableSourcing: true,
ignoreMandatoryFields: false
});
log.audit({
title: 'Vendor Bill created',
details: {
itemReceiptId,
vendorBillId
}
});
} catch (error) {
log.error({
title: `Unable to bill Item Receipt ${itemReceiptId}`,
details: error
});
throw error;
}
};
return {
getInputData,
map
};
});This example demonstrates the transformation itself, but it is not a complete production solution. The input should come from a search or controlled queue, not a hard-coded ID. The script also needs an idempotency strategy so that a retry does not create a second bill for the same receipt.
The choice of `isDynamic: false` is intentional. Standard mode is generally easier to control for predictable line updates because the script sets field values and saves the record without relying on dynamic sourcing behavior. Dynamic mode remains useful when the implementation must replicate interactive line-entry behavior, but it introduces additional sequencing requirements such as selecting a line before setting a sublist value.
How to create a vendor bill from an item receipt in SuiteScript
A reliable implementation separates transaction selection, validation, transformation, post-processing, and audit logging. Treating the entire process as one short script makes failures harder to diagnose and increases the risk of duplicate transactions.
1. Identify eligible item receipts
Start with a saved search or SuiteScript search that returns only Item Receipts eligible for billing. The search should not simply return every receipt created recently. It should reflect the business rule for when a receipt is ready to become a payable obligation.
Useful eligibility criteria include:
Transaction type is Item Receipt.
The receipt has a vendor and a source purchase order.
The receipt is approved or complete according to the organization’s process.
The receipt has not already been billed by the automation.
The receipt belongs to a permitted subsidiary, location, or accounting book.
The receipt date falls within an open accounting period.
The vendor is active and eligible for purchasing.
The received quantity is greater than zero where line-level billing is required.
The last condition deserves special attention. An Item Receipt may contain lines that are not suitable for a Vendor Bill, including informational lines, zero-quantity lines, or lines affected by a receiving correction. The script should validate the actual transaction rather than assuming every returned search result is billable.
A custom body field such as `custbody_vs_bill_created_by_script` can provide a straightforward processing marker. A more robust design also searches for Vendor Bills whose `createdfrom` relationship points to the receipt or whose lines carry a source reference. Custom fields are valuable for operational reporting, but they should not be the only duplicate control.
2. Load the source and validate its state
Before calling `record.transform()`, load the Item Receipt and inspect the fields that control the accounting outcome.
const itemReceipt = record.load({
type: record.Type.ITEM_RECEIPT,
id: itemReceiptId,
isDynamic: false
});
const vendorId = itemReceipt.getValue({ fieldId: 'entity' });
const subsidiaryId = itemReceipt.getValue({ fieldId: 'subsidiary' });
const createdFromId = itemReceipt.getValue({ fieldId: 'createdfrom' });
if (!vendorId) {
throw new Error(`Item Receipt ${itemReceiptId} has no vendor.`);
}
if (!createdFromId) {
throw new Error(`Item Receipt ${itemReceiptId} has no source transaction.`);
}The exact fields available depend on account configuration and transaction type. The implementation should confirm field availability in the target account rather than assuming every environment exposes identical custom or localized fields.
Validation should also consider accounting period status. A receipt created in a closed period might require the resulting bill to use a different transaction date, and changing dates can affect exchange rates, tax calculations, revenue or inventory timing, and reporting. Do not silently override the date. Make the decision explicit in the requirements and script configuration.
For accounts with OneWorld, subsidiary and currency validation are essential. A vendor may be available across multiple subsidiaries, but the Item Receipt and Vendor Bill still need to comply with the subsidiary structure. The script should fail clearly when the source transaction cannot produce a valid bill in the intended subsidiary.
3. Check for an existing bill before transforming
Duplicate prevention should happen before the transformation, not only after the bill saves. A scheduled script or Map/Reduce deployment can retry a failed map stage, and a user can trigger the same processing queue twice.
A practical duplicate-control approach combines three checks:
Search for a Vendor Bill already linked to the Item Receipt.
Check a custom processing field or status on the Item Receipt.
Use a controlled lock or queue record when multiple workers could process the same receipt concurrently.
NetSuite’s transaction relationship fields can vary by record type and account configuration, so test the correct search criteria in the target environment. Do not assume that a display value on the form is identical to the internal search field.
A custom external ID also helps when an external receiving system initiates the process. The external ID should be deterministic, such as a value derived from the Item Receipt internal ID and a defined billing event. This prevents a retry from creating a second transaction when the first request succeeded but the integration did not receive the response.
4. Transform the receipt and inspect the resulting lines
Once the receipt passes validation, use `record.transform()`:
const vendorBill = record.transform({
fromType: record.Type.ITEM_RECEIPT,
fromId: itemReceiptId,
toType: record.Type.VENDOR_BILL,
isDynamic: false
});At this point, inspect the transformed transaction before saving it. Confirm the vendor, subsidiary, currency, transaction date, and item count. Line-level checks are equally important.
const lineCount = vendorBill.getLineCount({
sublistId: 'item'
});
for (let line = 0; line < lineCount; line += 1) {
const itemId = vendorBill.getSublistValue({
sublistId: 'item',
fieldId: 'item',
line
});
const quantity = vendorBill.getSublistValue({
sublistId: 'item',
fieldId: 'quantity',
line
});
if (!itemId || Number(quantity) <= 0) {
throw new Error(`Invalid Vendor Bill line ${line} for Item Receipt ${itemReceiptId}.`);
}
}This is where a generic code sample becomes a real accounting control. A bill with the correct vendor but an incorrect quantity is still wrong. Review whether the transformation brings over expected rates, units, locations, departments, classes, customer or project references, tax details, and inventory dimensions.
Inventory detail deserves separate testing. Serialized and lot-numbered items contain subrecord data that is more complex than ordinary item lines. If the source receipt includes inventory detail, confirm that the transformed Vendor Bill behaves as expected in the account. Do not assume that every inventory subrecord should be manually copied. Native transformation should be tested first, and manual subrecord handling should be added only when the transaction design requires it.
5. Apply controlled field changes
The transformation should remain the source of truth for fields that NetSuite derives from the Item Receipt. Override only fields with a documented business requirement.
For example, a script may need to set a custom automation status, approval routing field, or memo. It should not casually overwrite the vendor, subsidiary, currency, item, or quantity fields simply because those values are available in the source record.
vendorBill.setValue({
fieldId: 'memo',
value: `Created from Item Receipt ${itemReceiptId}`
});
vendorBill.setValue({
fieldId: 'custbody_vs_created_by_automation',
value: true
});Mandatory classifications present a common implementation issue. If the Vendor Bill requires a department, class, location, or custom segment and the source Item Receipt does not supply it, `save()` may fail. The correct response is to define a sourcing rule, default value, or exception queue. Setting `ignoreMandatoryFields: true` hides the problem and weakens accounting controls.
Likewise, avoid forcing tax fields without understanding the account’s tax engine. Tax treatment may depend on subsidiary, nexus, vendor, item, location, and transaction date. A script that hard-codes tax values can produce bills that save successfully but require manual correction.
6. Save, verify, and record the result
Save the Vendor Bill only after validation and field updates are complete:
const vendorBillId = vendorBill.save({
enableSourcing: true,
ignoreMandatoryFields: false
});The returned internal ID confirms that NetSuite created a record, but the process should still verify the saved transaction. Reload the Vendor Bill and check that its status, source relationship, vendor, line count, and custom processing fields match expectations.
A useful audit record stores:
Item Receipt internal ID.
Vendor Bill internal ID.
Processing timestamp.
Script deployment or execution identifier.
Processing status.
Error name and message when processing fails.
Number of bill lines and total amount, where appropriate.
Avoid writing sensitive invoice or payment data into logs unnecessarily. NetSuite execution logs should help administrators diagnose the process without becoming an uncontrolled copy of financial records.
Partial receipts are the main overbilling risk
Partial receipts require a specific test matrix. Consider a purchase order for 100 units where the warehouse receives 40 units, followed by a second receipt for the remaining 60. If the script transforms each Item Receipt separately, it should create bills for 40 and 60, assuming the vendor invoice and business policy support receipt-based billing.
The risk appears when the implementation transforms the Purchase Order instead. A purchase-order transformation may expose the full ordered quantity and create an obligation for 100 units even though only 40 have arrived. That is why the transaction source is a policy decision, not merely a technical preference.
The script should also account for:
Multiple Item Receipts against one Purchase Order.
Multiple purchase orders for the same vendor.
Receipt reversals or corrections.
Lines received in different locations.
Closed or partially closed purchase orders.
Vendor invoices that combine multiple receipts.
Freight, tax, and landed cost treatment.
Foreign currency exchange rates.
Inventory and expense line differences.
When one supplier invoice covers several receipts, a one-receipt-to-one-bill transformation may not match the accounts payable process. In that scenario, a separate aggregation design is required, with clear matching logic and controls. Do not create several bills simply because several receipts exist if the organization expects one consolidated payable.
Standard mode or dynamic mode?
Standard mode is the better default for a controlled transformation because it reduces dependence on UI-style sequencing. It is especially suitable when the script validates existing lines and makes only a few body-level updates.
Dynamic mode is appropriate when the script must add, remove, or edit lines using sourcing behavior that mirrors the NetSuite form. In dynamic mode, the implementation must select lines before changing them, and field order can affect sourced values. A script that works in standard mode may not behave identically in dynamic mode.
Test both the transformed values and the saved values. Some fields are sourced during transformation, while others are calculated or validated during save. A successful script deployment is not proof that the accounting result is correct.
When should we use SuiteScript instead of native automation?
SuiteScript is appropriate when the bill creation rule depends on custom eligibility criteria, receipt status, external integrations, advanced duplicate controls, or post-transformation validation that native configuration does not cover.
Native NetSuite features should remain the first option for straightforward purchasing and accounts payable workflows. Native configuration is easier to maintain when it already supports the required approval, matching, and exception process. SuiteScript becomes valuable when the process crosses system boundaries or requires logic specific to the organization.
For broader system connectivity, our NetSuite integration platform services cover integration patterns using SuiteTalk, REST, SOAP, middleware, and custom SuiteScript integration. For a wider view of NetSuite modules and implementation capabilities, see our NetSuite ERP services.
Testing checklist for a production deployment
Test the transaction flow in a sandbox with realistic configuration before deploying the script to production. At minimum, include a full receipt, a partial receipt, multiple receipts for one order, a serialized item, a lot-numbered item, a foreign-currency vendor, a missing classification, a closed period, and a duplicate processing attempt.
Review both positive and negative outcomes. The script should create a correct bill when the source is valid, and it should create a visible exception when the source is incomplete. Silent skipping is difficult for accounts payable teams to reconcile.
Governance also matters. A Map/Reduce design is generally more suitable than a User Event when the process handles many receipts or includes searches, record loads, and post-save verification. A User Event can be appropriate for a narrow, synchronous action, but it should not create unpredictable delays during receipt entry.
Before release, confirm permissions for the deployment role, accounting preferences, approval routing, tax behavior, custom fields, and saved search access. Then document the retry process so administrators know whether to correct the receipt, clear a queue status, or reprocess the record.
Conclusion
Creating a Vendor Bill from an Item Receipt with SuiteScript is straightforward at the API level, but reliable automation requires more than a single `record.transform()` call. The implementation must use the correct transaction source, validate receipt and accounting conditions, protect against duplicates, inspect transformed lines, preserve mandatory controls, and record enough information for reconciliation.
The central design decision is whether the organization bills from what was ordered or what was received. When the payable must reflect warehouse-confirmed quantities, transforming the Item Receipt provides the clearest foundation. If your process includes multiple subsidiaries, partial receipts, external systems, inventory detail, or consolidated supplier invoices, contact Versich to discuss a controlled NetSuite automation design.

