Create NetSuite Workflow: Requisition to Bill Directly
The standard NetSuite procurement flow forces a Purchase Order between a Purchase Requisition and a Vendor Bill.

On this page
The standard NetSuite procurement flow forces a Purchase Order between a Purchase Requisition and a Vendor Bill. For service-based purchases, expense items, or vendor invoices that arrive before goods, that extra step creates unnecessary friction. Users trying to build a workflow that transforms a Requisition straight to a Bill hit a hard limit: the native Transform Record action only offers Purchase Order as a target. This article walks through why that constraint exists, when bypassing the PO makes sense, and the practical options for teams without SuiteScript expertise.
Why the Standard Flow Requires a PO
NetSuite's procurement design centers on three-way matching, PO, Item Receipt, Vendor Bill, to enforce internal controls. The Purchase Requisition (purchaserequisition) record type is built to feed the Purchase Order (purchaseorder) record type. The Transform Record workflow action reflects this: it only exposes record types that have a transform mapping defined in the system. Requisition → PO is mapped. Requisition → Vendor Bill (vendorbill) is not.
This isn't an oversight. The PO carries vendor terms, negotiated pricing, and receiving expectations that the Bill later validates against. Skipping it removes the control point where AP verifies quantities and costs before payment. From an audit perspective, the PO is the commitment document; the Bill is the obligation document. Conflating them weakens the audit trail.
When Direct Requisition-to-Bill Makes Sense
Not every purchase fits the goods-receiving model. Common scenarios where the PO adds no value:
- Professional services, consulting, legal, marketing engagements where no physical receipt occurs
- Recurring expenses, subscriptions, utilities, rent where the Requisition is essentially a budget approval
- Employee expense reimbursements, the "vendor" is an employee, and the Requisition serves as the approval wrapper
- Vendor invoices received before PO creation, the invoice is the source document; creating a PO after the fact is backward
In these cases, the Requisition functions as an internal approval gate. Once approved, the team needs a Vendor Bill, not a PO that will never be received against.
Option 1: SuiteScript Custom Action (Most Flexible)
If you have access to a developer or are comfortable with basic SuiteScript 2.1, a Custom Action in a workflow can create the Vendor Bill programmatically. This keeps the automation inside the workflow engine, users see a single "Create Bill" button on the approved Requisition.
/**
* @NApiVersion 2.1
* @NScriptType WorkflowActionScript
*/
define(['N/record', 'N/search', 'N/runtime'],
(record, search, runtime) => {
function onAction(context) {
const req = context.newRecord;
const bill = record.create({ type: record.Type.VENDOR_BILL, isDynamic: true });
// Header fields
bill.setValue({ fieldId: 'entity', value: req.getValue({ fieldId: 'entity' }) });
bill.setValue({ fieldId: 'trandate', value: req.getValue({ fieldId: 'trandate' }) });
bill.setValue({ fieldId: 'duedate', value: req.getValue({ fieldId: 'duedate' }) });
bill.setValue({ fieldId: 'memo', value: req.getValue({ fieldId: 'memo' }) });
bill.setValue({ fieldId: 'custbody_created_from_req', value: req.id }); // custom field for traceability
// Line items — iterate Requisition lines
const lineCount = req.getLineCount({ sublistId: 'item' });
for (let i = 0; i < lineCount; i++) {
bill.selectNewLine({ sublistId: 'item' });
bill.setCurrentSublistValue({ sublistId: 'item', fieldId: 'item', value: req.getSublistValue({ sublistId: 'item', fieldId: 'item', line: i }) });
bill.setCurrentSublistValue({ sublistId: 'item', fieldId: 'quantity', value: req.getSublistValue({ sublistId: 'item', fieldId: 'quantity', line: i }) });
bill.setCurrentSublistValue({ sublistId: 'item', fieldId: 'rate', value: req.getSublistValue({ sublistId: 'item', fieldId: 'rate', line: i }) });
bill.setCurrentSublistValue({ sublistId: 'item', fieldId: 'account', value: req.getSublistValue({ sublistId: 'item', fieldId: 'account', line: i }) });
bill.setCurrentSublistValue({ sublistId: 'item', fieldId: 'department', value: req.getSublistValue({ sublistId: 'item', fieldId: 'department', line: i }) });
bill.setCurrentSublistValue({ sublistId: 'item', fieldId: 'class', value: req.getSublistValue({ sublistId: 'item', fieldId: 'class', line: i }) });
bill.setCurrentSublistValue({ sublistId: 'item', fieldId: 'location', value: req.getSublistValue({ sublistId: 'item', fieldId: 'location', line: i }) });
bill.commitLine({ sublistId: 'item' });
}
const billId = bill.save({ ignoreMandatoryFields: true });
log.audit({ title: 'Bill Created', details: `Requisition ${req.id} → Bill ${billId}` });
return billId;
}
return { onAction };
});Deploy this as a Workflow Action Script. In your workflow (on the Purchase Requisition record), add a Custom Action state after the approval state, select this script, and configure a button label like "Create Vendor Bill." The script copies vendor, dates, memo, and all line-level data, items, quantities, rates, accounts, and segmentation fields.
The custom field
custbody_created_from_reqon the Vendor Bill record is essential. It gives you a direct link back to the originating Requisition for audit tracing and prevents duplicate Bills if someone clicks twice.
Option 2: User Event Script on Requisition Approval (Zero-Code Trigger)
If you'd rather the Bill appear automatically when the Requisition hits Approved status, no button click required, a User Event Script on afterSubmit does the job. This runs server-side whenever a Requisition is saved.
/**
* @NApiVersion 2.1
* @NScriptType UserEventScript
*/
define(['N/record', 'N/runtime'],
(record, runtime) => {
function afterSubmit(context) {
if (context.type !== context.UserEventType.EDIT && context.type !== context.UserEventType.CREATE) return;
const newRec = context.newRecord;
const oldRec = context.oldRecord;
// Only act when status changes to Approved — verify the internal ID in your account
const newStatus = newRec.getValue({ fieldId: 'approvalstatus' });
const oldStatus = oldRec ? oldRec.getValue({ fieldId: 'approvalstatus' }) : null;
if (newStatus !== 'B' || oldStatus === 'B') return;
// Prevent re-processing if Bill already exists
const existingBill = newRec.getValue({ fieldId: 'custbody_generated_bill_id' });
if (existingBill) return;
// ... same Bill creation logic as Option 1 ...
const billId = createBillFromRequisition(newRec);
// Stamp the Requisition so we don't run again
record.submitFields({
type: record.Type.PURCHASE_REQUISITION,
id: newRec.id,
values: { custbody_generated_bill_id: billId }
});
}
function createBillFromRequisition(req) { /* same logic as above */ }
return { afterSubmit };
});Add two custom fields to the Purchase Requisition record:
custbody_generated_bill_id(Transaction Body, List/Record → Vendor Bill), stores the created Bill IDcustbody_auto_bill_eligible(Checkbox), lets you restrict this automation to specific subsidiaries, departments, or item types
This approach is invisible to users. The Requisition approves; the Bill exists. But it removes the human checkpoint where AP might want to verify the vendor invoice PDF against the Requisition lines first.
Verify the approval status value in your account. The internal ID for "Approved" may differ by configuration. Check System Notes on a test Requisition after approval to confirm the exact value before hardcoding it.
Option 3: Native Path, Advanced Receiving with Purchase Orders
Here's the practical fix that requires zero scripting: use the Advanced Receiving feature, which enables a "Save and Bill" action on the Item Receipt.
Enable it at Setup > Enable Features > Transactions > Purchase Transactions > Advanced Receiving. With this active, the receiving workflow changes:
- Create the Purchase Order from the approved Requisition (standard Transform)
- Go to Transactions > Purchasing > Receive Order
- Enter quantities received
- Click Save and Bill, this creates the Vendor Bill in one step
This isn't a direct Requisition-to-Bill path, but it eliminates the separate "Bill PO" step for teams already processing receipts. The trade-off: you still need the PO, but the Bill generates from the receipt without rekeying. For service purchases where no receipt occurs, this doesn't apply, stick with Option 1 or 2.
The "Transform to Bill" button on a Purchase Request record does not exist in standard NetSuite. The native "Save and Bill" action lives on the Item Receipt, not the Request or Requisition.
Option 4: SuiteFlow "Go To Record" + Manual Entry (Lowest Effort)
For teams not ready for scripting, a workflow can still reduce clicks. Build a State on the approved Requisition with a Go To Record action that opens a new Vendor Bill in edit mode, pre-populated via URL parameters.
- Workflow: On Entry → Go To Record
- Record Type: Vendor Bill
- Mode: Create
- Parameters (use Formula URL field):
/app/accounting/transactions/vendbill.nl?cf=109&entity=' || {entity} || '&trandate=' || {trandate} || '&memo=' || URLEncode({memo})This opens a fresh Bill with vendor, date, and memo filled in. The user still enters lines manually, but the header is done. For low-volume, high-variance purchases (every Bill looks different), this strikes a reasonable balance.
Control Considerations Before You Automate
Whichever path you choose, address these control points:
| Control | Implementation |
|---|---|
| Duplicate prevention | Custom field on Requisition (custbody_bill_created) + saved search alert if Bill exists without Requisition link |
| Approval evidence | Workflow logs the approver, timestamp, and Requisition ID on the Bill (custom body field) |
| Vendor validation | Ensure the Requisition's entity field is a valid, active Vendor record, not a generic "TBD" entry |
| Account coding | Require account on every Requisition line; validate via saved search before Bill creation |
| Period control | Block Bill creation if the Requisition's trandate falls in a closed period (script check: runtime.getCurrentPeriod()) |
The control here is traceability. Every generated Bill must carry the Requisition ID. Without that link, you've created a gap your auditors will flag during P2P testing.
What to Build First
Start with Option 3 (Advanced Receiving) if your use case involves physical goods, it's native, supported, and requires no maintenance. If you need Requisition's approval routing for services or expenses, deploy Option 1 (Custom Action) with a developer's help. The script is ~80 lines, testable in a sandbox, and gives you a reusable "Create Bill" button that respects your existing workflow states.
Avoid Option 2 (auto-create on approval) until you've run the manual-button version for a month and confirmed the data quality. Automatic creation without a review step is how duplicate Bills and miscoded expenses slip into the GL.
The goal isn't to eliminate the PO everywhere, it's to remove the PO where it serves no control purpose. Your auditors will thank you for keeping the three-way match intact for inventory while streamlining the service-and-expense path that never needed it.


