Scripting in QuoteWerks Web can be used to extend functionality based on system events. The QuoteWerks Web Scripting Environment is where you can write powerful async JavaScript scripts to automate, customize, and extend your quoting workflows. This guide will walk you through everything you need to know to build, debug, and deploy custom scripts using the APIs and Event Hooks.
Users can access the QuoteWerks Web Scripting Environment by navigating to the Scripting Manager on the left-hand navigation bar.
To learn how to navigate and use the QuoteWerks Web Scripting Environment please see QuoteWerks Web Scripting Manager.
Overview
Scripts are written in async JavaScript and respond to lifecycle events like AfterSaveDocument, BeforeDeleteDocument, and more. They execute in a secure sandbox with access to a rich set of API functions exposed under context.api.
Scripts may display messages, modify document data, interact with the user, make HTTP calls, and more.
How it Works
•Each event provides a context object with data and access to the context.api.
•Your script is executed as an async function.
•Set context.cancel = true or false at the end of your "BEFORE" scripts to control whether the normal execution should proceed.
•Use await with every API call.
Common Script Pattern
// Show a toast after saving a document
|
Events are triggered before or after key user actions. Below are common events with context info:
Event Name |
When it Fires |
Cancelable |
BeforeSaveDocument |
Before a document is saved |
✅ |
AfterSaveDocument |
After a document is saved |
❌ |
BeforeDeleteDocument |
Before deleting a document |
✅ |
AfterDeleteDocument |
After deleting a document |
❌ |
BeforeConvertDocument |
Before converting a document |
✅ |
AfterConvertDocument |
After converting a document |
❌ |
BeforeRenameDocument |
Before renaming a document |
✅ |
AfterRenameDocument |
After renaming a document |
❌ |
BeforeContactSelection |
Before selecting a contact |
✅ |
AfterContactSelection |
After selecting a contact |
❌ |
BeforePreviewAction |
Before print/PDF generation |
✅ |
AfterOpenDocument |
After opening a document |
❌ |
AfterLineItemAdded |
After a new line item is added |
❌ |
BeforeEmailRecipientSelection |
Before selecting email recipients |
✅ |
See the script examples section for additional details.
API Reference
System Functions
•sys_logInfo(message)
•sys_logWarn(message)
•sys_logError(message)
•sys_promptUser(type, title, message)
•sys_promptUserSelect(title, message, default, list, labelField, valueField)
•sys_promptUserDynamic(formDefinition, initialModel) (See detailed documentation below)
•sys_delay(seconds)
•sys_loaderStart(message)
•sys_loaderStop(id)
•sys_toastSuccess(message)
•sys_toastInfo(message)
•sys_toastWarn(message)
•sys_toastError(message)
•sys_aiGenerateText(userPrompt)
•sys_httpRequest(options)
Application Functions
•app_getMacro(macroName)
•app_baseCurrencySymbol()
•app_decimalCharacter()
•app_decimalPlacesExt()
•app_decimalPlacesUnit()
•app_thousandsSeparator()
•app_formatAsCurrency(value)
•app_versionMajor()
•app_versionMinor()
Document Functions
•doc_getActiveDocumentIndex()
•doc_documentNew(type)
•doc_documentClose()
•doc_documentSave()
•doc_getDocumentHeaderValue(fieldName)
•doc_setDocumentHeaderValue(fieldName, value)
•doc_anyOpen()
Item Functions
•item_addLineItemToDocument(lineItemDetails)
•item_removeLineItemFromDocument(rowIndex)
•item_lineItemGetValue(rowIndex, fieldName)
•item_lineItemSetValue(rowIndex, fieldName, value)
•item_lineItemCount()
Specific Call Details
item_addLineItemToDocument(lineItemDetails)
//let samplelineItemDetails = { // quantity: 5, // description: "High-end Widget", // unitCost: 45.5, // unitPrice: 60, // unitList: 75, // priceModifier: "", // manufacturer: "Acme Corp", // manufacturerPartNumber: "ACM-12345", // vendor: "WidgetSupply", // taxCode: "Y", // itemType: 1, // itemAttributes: 1, // insertFlag: false, // insertBeforeRow: 0 //}
Dynamic Form
Function: context.api.sys_promptUserDynamic(formDefinition: object, initialModel: object): Promise
Description: Displays a dynamic form for the user to fill out based on a provided form definition.
Returns the user's completed model object, or USERCANCELLED if the user cancels.
Form Definition Parameters:
•title (string, required): Title of the form window
•fields (array of objects, required): Defines the form fields
Each field object supports:
•key (string, required): Name of the field in the model
•label (string, required): Label displayed to the user
•type (string, required): Field type ("text", "email", "date", "number", "textarea", "radio", "checkbox", "select")
•options (array, optional): List of options for "radio" and "select" field types
•required (boolean, optional): Whether the field must be filled out (true or false)
Initial Model Parameters:
•(object, optional): Prefills the form fields with existing values.
Example Usage:
const formDefinition = { |
HTTP Requests
Function: context.api.sys_httpRequest(options: object): Promise
Description: Performs an HTTP request using the specified options.
Supports GET, POST, PUT, DELETE, and other HTTP methods. Returns a response object including status, statusText, headers, and data.
If the request fails, an object is returned with error: true, message, and any available status or data.
Options Object Parameters:
•method (string, optional): HTTP method (default is "GET")
•url (string, required): Full URL for the request
•headers (object, optional): Key-value pairs of headers to send
•data (object|string|null, optional): Payload for POST, PUT, etc.
•params (object|null, optional): Query string parameters
•timeout (number, optional): Timeout in milliseconds (default is 30000)
Example Usage:
const response = await context.api.sys_httpRequest({ method: "POST", url: "https://api.example.com/send", headers: { "Authorization": "Bearer token", "Content-Type": "application/json" }, data: { message: "Hello world" } }); |
Sample Event Scripts
AfterContactSelection
let type; switch (context.type) { case 0: type = 'Sold To' break; case 1: type = 'Ship To' break; case 2: type = 'Bill To' break case 3: type = 'All' break; }
context.api.sys_toastSuccess(`AfterContactSelection (${type})`); |
AfterConvertDocument
let result;
switch (context.result) { case 0: result = 'Success' break; case 1: result = 'Cancelled' break; case 2: result = 'Failed' break }
context.api.sys_toastSuccess(`AfterConvertDocument: ${result}`); |
AfterDeleteDocument
let result;
switch (context.result) { case 0: result = 'Success' break; case 1: result = 'Cancelled' break; case 2: result = 'Failed' break }
context.api.sys_toastSuccess(`AfterDeleteDocument (${context.docRecGuid}): ${result}`); |
AfterNewDocument
let source;
switch (context.source) { case 0: source = 'Blank' break; case 1: source = 'Template' break; case 3: source = 'Duplication' break }
context.api.sys_toastSuccess(`AfterNewDocument (${context.docRecGuid}): ${source}`); |
AfterOpenDocument
context.api.sys_toastSuccess(`AfterOpenDocument (${context.docRecGuid})`); |
AfterRenameDocument
let result;
switch (context.result) { case 0: result = 'Success' break; case 1: result = 'Failure' break; }
context.api.sys_toastSuccess(`AfterRenameDocument (${context.docRecGuid}): ${result}`); |
AfterSaveDocument
let saveAction; switch (context.saveAction) { case 0: saveAction = 'Save' break; case 1: saveAction = 'Save As' break; case 2: saveAction = 'Save As Template' break; case 3: saveAction = 'Save As Revision' break; case 4: saveAction = 'Auto Save' break; }
let result; switch (context.result) { case 0: result = 'Success' break; case 1: result = 'Save Failed' break; case 2: result = 'Save Canceled' break; case 4: result = 'Document is Locked' break; case 32: result = 'Document is ReadOnly' break; case 64: result = 'Document is ViewOnly' break; }
context.api.sys_toastSuccess(`AfterSaveDocument (${context.docRecGuid}): ${saveAction} | ${result}`); |
AfterLineItemAdded
context.api.sys_toastSuccess(`AfterLineItemAdded (${context.docRecGuid}): New Line Item Index: ${context.newI temIndex} | Action: ${context.actionName}`); |
AfterShippingSelection
let result; switch (context.result) { case 0: result = 'Success' break; case 1: result = 'Failed / User Cancelled' break; }
context.api.sys_toastSuccess(`AfterShippingSelection (${context.docRecGuid}) | ${result}`); |
BeforeContactSelection
let type;
switch (context.type) { case 0: type = 'SoldTo' break; case 1: type = 'ShipTo' break; case 2: type = 'BillTo' break; }
context.api.sys_toastSuccess(`BeforeContactSelection (${context.docRecGuid}) : ${type} `); context.cancel = false; |
BeforeConvertDocument
let destinationType;
switch (context.destinationType) { case 0: destinationType = 'Order' break; case 1: destinationType = 'Invoice' break; case 2: destinationType = 'Lost Sale' break; }
context.api.sys_toastSuccess(`BeforeConvertDocument (${context.docRecGuid}) : ${destinationType} `); context.cancel = false; |
BeforeDeleteDocument
let source;
switch (context.source) { case 0: source = 'File|Delete menu' break; case 1: source = 'File|Open menu' break; case 2: source = 'Template Tab' break }
context.api.sys_toastSuccess(`BeforeDeleteDocument: (Source: ${source})`); context.cancel = true; |
BeforeDeliver
context.api.sys_toastSuccess(`BeforeDeliver (${context.docRecGuid})`); context.cancel = false; |
BeforeEmailRecipientSelection
let recipientType;
switch(context.recipientType){ case 0: // From recipientType = 'From'; context.from = generateRandomEmail(); break; case 1: // To recipientType = 'To'; context.toList.push(generateRandomEmail()); break; case 2: // CC recipientType = 'CC'; context.ccList.push(generateRandomEmail()); break; case 3: // BCC recipientType = 'BCC'; context.bccList.push(generateRandomEmail()); break; }
context.api.sys_toastSuccess(`BeforeEamilRecipientSelection (${context.docRecGuid}) : ${recipientType} `); context.cancel = true;
function generateRandomEmail(domain = "example.com") { const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; const usernameLength = Math.floor(Math.random() * 10) + 5; // length between 5 and 14 let username = "";
for (let i = 0; i < usernameLength; i++) { username += chars.charAt(Math.floor(Math.random() * chars.length)); }
return `${username}@${domain}`; } |
BeforePreviewAction
let action; switch (context.action) { case 0: action = 'Preview / Print' break; case 1: action = 'Save As PDF' break; }
let source; switch (context.source) { case 0: source = 'Print Layout' break; case 2: source = 'PO Print Layout' break; case 3: source = 'Management Report' break; }
let layoutType; switch (context.layoutType) { case 0: layoutType = 'Quote' break; case 1: layoutType = 'Order' break; case 2: layoutType = 'Invoice' break; case 3: layoutType = 'Sales Order' break; case 4: layoutType = 'Purchase Order' break; }
context.api.sys_toastSuccess(`BeforePreviewAction (${context.docRecGuid}) | ${action} | ${source} | ${layoutType} `); console.log(context); context.cancel = false; |
BeforeRenameDocument
context.api.sys_toastSuccess(`BeforeRenameDocument: ${context.docRecGuid}`); context.cancel = false; |
BeforeSaveDocument
context.api.sys_toastSuccess(`BeforeSave (${context.docRecGuid})`); context.cancel = false; |
BeforeShippingSelection
context.api.sys_toastSuccess(`BeforeShippingSelection (${context.docRecGuid})`); context.cancel = false; |
Field References
Header Field Names
acceptanceNotes alternateCommissionAmount alternateCurrencyIdentifier alternateCurrencySymbol alternateDepositAmount alternateGrandTotal alternateGSTTax alternateLocalTax alternateProfitAmount alternateShippingAmount alternateSubTotal alternateTaxableAmount alternateTotalCost alternateTotalList alternateTotalTax approvalRequestedMethod approvalRequestedOn approvedBy1 approvedBy2 approvedMethod1 approvedMethod2 approvedOn1 approvedOn2 attachments baseCurrency billToAddress1 billToAddress2 billToAddress3 billToCity billToCMCompanyRecID billToCMContactRecID billToCompany billToContact billToCountry billToEMail billToFax billToFaxExt billToMobile billToPhone billToPhoneExt billToPostalCode billToState billToTitle closingNotes commissionAmount commissionStructure contractEndDate contractStartDate convertedBy convertedOn convertedRef convertedFrom convertedFromBy convertedFromOn convertedFromRef convertedTo convertedToBy convertedToOn convertedToRef coverPageMessage created createdBy customDate01 customDate02 customMemo01 customMemo02 customMemo03 customMemo04 customNumber01 customNumber02 customNumber03 customNumber04 customNumber05 customText01 customText02 customText03 customText04 customText05 customText06 customText07 customText08 customText09 customText10 customText11 customText12 customText13 customText14 customText15 customText16 customText17 customText18 customText19 customText20 customText21 customText22 customText23 customText24 customText25 customText26 customText27 customText28 depositAmount depositAmountRef depositMethodData docDate docDueDate docName docNo docStatus docType dynamicStorageDH emailMessage exchangeRate exchangeRateLastUpdated expirationDate fob grandTotal gstTax gstTaxableAmount gstTaxExempt gstTaxRate holdFlags docId integrationData internalNotes introductionNotes itemsDirty lastModified lastModifiedBy lastRow loadingFlag localTax localTaxRate locked ltFileLinksList miscDirty peerReviewRequestedMethod peerReviewRequestedOn preparedBy profitAmount profitPercent projectNo psTisCompounded pstTaxableAmount purchasingNotes dynamicNotes quoteValetDocumentID readOnlyMode docRecGUID recurringRevenueAnnualSubtotal recurringRevenueAnnualSubtotalWithTax recurringRevenueMonthlySubtotal recurringRevenueMonthlySubtotalWithTax recurringRevenueQuarterlySubtotal recurringRevenueQuarterlySubtotalWithTax recurringRevenueWeeklySubtotal recurringRevenueWeeklySubtotalWithTax resources revisionMasterDocNo salesRep serviceRep shippingAmount shippingCost shippingPricingMethodData shipToAddress1 shipToAddress2 shipToAddress3 shipToCity shipToCMCompanyRecID shipToCMContactRecID shipToCompany shipToContact shipToCountry shipToEMail shipToFax shipToFaxExt shipToMobile shipToPhone shipToPhoneExt shipToPostalCode shipToState shipToTaxCode shipToTitle shipVia soldToAddress1 soldToAddress2 soldToAddress3 soldToCity soldToCMCompanyRecID soldToCMContactRecID soldToCMCallRecID soldToCMLinkedDocumentRecID soldToCMOpportunityRecID soldToCMQuoteRecID soldToCompany soldToContact soldToCountry soldToEMail soldToFax soldToFaxExt soldToMobile soldToPhone soldToPhoneExt soldToPONumber soldToPostalCode soldToPriceProfile soldToState soldToTitle stateContentChangedForQuoteValet stateContentChangedForQuoteWerks stateCRMNeedsUpdating stateDocumentIntegrityLeasingWarningMsg stateInitiallyLocked stateNeverBeenSaved stateOpenedOn statePrintedSinceOpened stateQuoteValetCustomerAcceptedStateText stateQuoteValetCustomerUploadedStateText stateQVAcceptedOnString stateQVCreatedByEntity stateQVCustomerFacingTemplateID stateQVDocStatusCode stateQVPaymentCount stateQVUploadedForCustomerOn subTotal superseded synchedWithQuoteValet taxData taxSystem templateType terms totalCost totalList totalTax totalWeight transactionLog used version viewOnlyMode |
Item Field Names
__quoteSheetItemDescription __uniqueId __vendorRFQAwarded_VendorRFQBidRequestItemRecGUID __vendorRFQViewUrl category closeProbability costModifier customDate01 customDate02 customMemo01 customMemo02 customMemo03 customMemo04 customMemo05 customNumber01 customNumber02 customNumber03 customNumber04 customNumber05 customText01 customText02 customText03 customText04 customText05 customText06 customText07 customText08 customText09 customText10 customText11 customText12 customText13 customText14 customText15 customText16 customText17 customText18 customText19 customText20 description distributorSONumber documentItemCssClasses extendedCost extendedList extendedPrice extendedShippingAmount exclusiveOptionGroup itemTaxSummary itemType itemURL lineIdentifier lineNumber lineNumberActual lineType manufacturer manufacturerPartNumber notes poNumber priceModifier profitAmount profitMargin qtyBase qtyMultiplier1 qtyMultiplier2 qtyMultiplier3 qtyMultiplier4 qtyTotal recurringAmountWithTax recurringBillingCycle recurringEndDate recurringRevenueAnnual recurringRevenueAnnualWithTax recurringRevenueMonthly recurringRevenueMonthlyWithTax recurringRevenueQuarterly recurringRevenueQuarterlyWithTax recurringRevenueWeekly recurringRevenueWeeklyWithTax recurringStartDate salesTax shippingAmount styleCode taxCode ticketNumber unitCost unitList unitPrice unitWeight unitofMeasure unitofMeasureFactor unitofPricing unitofPricingFactor vendor vendorPartNumber vendorRFQItemInfo vendorRFQViewUrl |
Example Script
// This script allows the user to look up exchange rates using the Frankfurter API. // It first retrieves the available currencies and then prompts the user to select // the 'from' and 'to' currencies using a dynamic form.
// Step 1: Fetch available currencies from the Frankfurter API const currencyResponse = await context.api.sys_httpRequest({ method: "GET", url: "https://api.frankfurter.app/currencies" });
if (currencyResponse.error) { await context.api.sys_toastError("Failed to fetch currencies."); context.continue = false; return; }
const currencies = Object.keys(currencyResponse.data);
// Step 2: Prompt the user to select 'from' and 'to' currencies const formDefinition = { title: "Currency Exchange Rate Lookup", fields: [ { key: "fromCurrency", label: "From Currency", type: "select", options: currencies, required: true }, { key: "toCurrency", label: "To Currency", type: "select", options: currencies, required: true } ] };
const initialModel = {}; const userInput = await context.api.sys_promptUserDynamic(formDefinition, initialModel);
if (userInput === "USERCANCELLED") { context.continue = false; return; }
const fromCurrency = userInput.fromCurrency; const toCurrency = userInput.toCurrency;
// Step 3: Fetch the exchange rate for the selected currencies const exchangeRateResponse = await context.api.sys_httpRequest({ method: "GET", url: `https://api.frankfurter.app/latest?from=${fromCurrency}&to=${toCurrency}` });
if (exchangeRateResponse.error) { await context.api.sys_toastError("Failed to fetch exchange rate."); context.continue = false; return; }
const exchangeRate = exchangeRateResponse.data.rates[toCurrency];
// Step 4: Display the exchange rate to the user await context.api.sys_promptUser("info", "Exchange Rate", `The exchange rate from ${fromCurrency} to ${toCurrency} is ${exchangeRate}.`);
context.continue = true; |