Suite Utils
Back to Blog
NetSuite TipsSep 19, 2026 • 9 min read

Create NetSuite Workflow: Requisition to Bill Directly

The standard NetSuite procurement flow forces a Purchase Order between a Purchase Requisition and a Vendor Bill.

Sarah Jenkins, CPASarah Jenkins, CPAPrincipal Finance Automation Specialist
Create NetSuite Workflow: Requisition to Bill Directly
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_req on 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 ID
  • custbody_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:

  1. Create the Purchase Order from the approved Requisition (standard Transform)
  2. Go to Transactions > Purchasing > Receive Order
  3. Enter quantities received
  4. 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.

  1. Workflow: On Entry → Go To Record
  2. Record Type: Vendor Bill
  3. Mode: Create
  4. 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:

ControlImplementation
Duplicate preventionCustom field on Requisition (custbody_bill_created) + saved search alert if Bill exists without Requisition link
Approval evidenceWorkflow logs the approver, timestamp, and Requisition ID on the Bill (custom body field)
Vendor validationEnsure the Requisition's entity field is a valid, active Vendor record, not a generic "TBD" entry
Account codingRequire account on every Requisition line; validate via saved search before Bill creation
Period controlBlock 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.

About the author

Put these ideas to work.

Suite Utils builds small NetSuite tools that fix the specific thing breaking your day. Each one runs as a native SuiteScript SuiteApp inside your account. No sales call, no onboarding.

Browse the Tools

Enjoyed this one?

Get NetSuite tips like this in your inbox. No spam. Practical guides only.

Keep reading