How-to Guide
Nue Platform Plugins
Pricing Engine Plugins
23 min
availability pricing engine plugins are available in release 2603 and later plugin support for change quotes and orders is on the roadmap and will be announced in a future release nue's pricing engine handles every quote, order, and change order in your salesforce org — bundles, ramps, tiered discounts, multi currency, the works pricing engine plugins are lightweight javascript snippets stored as salesforce records ruby pricingplugin c and executed automatically anywhere the pricing engine runs, including the react line editor, apex and rest apis use plugins when out of the box pricing isn't quite enough — when you need to inject pricing rules that depend on the whole quote, not just one product ("apply a 10% volume discount when the customer's total quantity tops 1,000 ") cap, floor, or round prices after the engine calculates them ("never let a single line exceed $10,000 " "round every total to the nearest $100 ") restructure ramp pricing for multi period subscriptions ("merge monthly ramps into clean yearly buckets " "snap ramp end dates to the last day of each month ") tag specific products with custom discounts based on what else is on the deal ("if they bought the essential package, give them 5% off the price builder add on under 100 seats and 15% off above ") plugins are persisted as ruby pricingplugin c records in salesforce you write them once, activate them with a checkbox, and they run automatically wherever the pricing engine runs ✅ when sales reps build quotes in the line editor (the engine runs in the browser as you edit) ✅ when your apex code or external systems call createquote / createorder rest and apex apis ✅ when subscriptions and assets get recalculated during change order activation, renewals, term updates, mid cycle quantity changes pricing engine plugins are not currently supported for change quotes, change orders, or self service apis, but support for these use cases is planned in upcoming releases same record, same code — works everywhere no deploys, no app rebuilds flip isactive c = true and the plugin is live on the next pricing call we recommend using the to create, test, and deploy your pricing engine plugins using natural language heads up not the same as ui plugins pricing engine plugins fire inside the calculation pipeline the line editor also exposes a separate family of ui plugins — ui afteredit , ui afterloading , ui beforecalculation , ui aftercalculation — that fire on user interactions like field edits or page load (not inside the engine) those have their own context shape and are documented at the two families complement each other; you can use both in the same org when can a plugin run? you pick one of three moments in the pricing flow trigger event when it runs use it to… before calculation beforecalculation right before the engine works out any prices set up the inputs — add or replace discount tags, force a price tier, decide which products get a custom dimension the engine then prices the quote using whatever you've changed after ramp generation afterrampgeneration right after the engine has split a multi period subscription into its ramp segments only fires when a line produced 2 or more ramps reshape the ramps — merge them into bigger buckets, snap their dates to month or quarter end, redistribute the total across periods after calculation aftercalculation after the engine has finished pricing everything and rolled up totals adjust the final numbers — cap a price, apply a floor, round to the nearest dollar, override the calculated total the engine takes care of refreshing related fields like acv, arr, and discount amount automatically this diagram illustrates the plugin execution sequence in the flow of pricing engine execution and the context it gets exposed with the nue pricing engine processes pricing through six sequential stages, each representing a plugin extension point where custom logic can be injected the nue pricing engine follows a structured six step flow beforecalculation – input stage where pricing rules, discounts, and tags are applied (plugin interface) price calc – core engine computes prices based on inputs ramp generation – creates time based pricing segments (ramps) afterrampgeneration – adjusts or restructures ramp data (plugin interface) reaggregation – recalculates totals and aggregates results across line items aftercalculation – final overrides and adjustments to pricing outputs (plugin interface) the highlighted stages— beforecalculation, afterrampgeneration, and aftercalculation —represent the three plugin interfaces where users can inject custom logic these allow control before pricing, during ramp structuring, and after final calculations, enabling flexible customization without modifying the core engine each stage exposes its own context (inputs/tags, engine, ramp items, restructure ramps, summaries, override totals), giving plugins targeted access to the relevant data at each point in the execution flow the dotted feedback loop at the bottom indicates that outputs from the final stage can inform or feed back into the beginning of the cycle, enabling iterative refinement of pricing logic pricing engine plugins you can have multiple plugins active at once plugins on the same trigger event run in the order they're listed; if two plugins both touch the same field, the last one wins create pricing plugins each plugin lives in a rubypricingplugin c record field purpose ruby code c es5 safe javascript source top level script — no wrapping function return at top level is forbidden triggerevent c beforecalculation , aftercalculation , or afterrampgeneration ruby plugintype c lineitem , headerobject , or any ruby ecma version c 9 (es2018) es5 syntax is safest ruby isactive c false = dormant, true = runs on every calculation ruby description c description of the plugin authoring tips es5 only var , function , no arrow functions, no template literals log with console debug("\[nue pricing plugin] ") so entries are easy to grep guard nested access — $$lineitem , $$lineitem product , $$lineitem product sku we recommend using the to create, test, and deploy your pricing engine plugins using natural language pricing engine plugin examples five worked examples below each starts with a use case statement, lists the context fields and writable fields you can use, and ends with a copy pasteable plugin \#1 trigger event = beforecalculation (headerobject scope) use beforcecalculation with headerobject scope to inject pricing rules such as discount tags, volume tiers, or replacement price tags, across multiple line items based on header level totals or custom attributes, before any pricing calculation rules context $$headerobject (read only) and $$updatedlineitems (output array) field type description $$headerobject subscriptionterm number header level term $$headerobject startdate / enddate yyyy mm dd header date range $$headerobject customcalculateattributes object custom fields injected by the caller (e g , applystandarddiscount c true ) $$headerobject lineitems\[] array array of line items — see per line fields below the fields below are pricing engine context fields (camelcase, exposed inside the plugin sandbox as $$headerobject ), not salesforce sobject field names the engine maintains its own data model distinct from the ruby c salesforce schema per line item fields — each entry in $$headerobject lineitems\[] field type description refid string line item identifier use this when pushing to $$updatedlineitems id is not exposed at beforecalculation quantity number line quantity subscriptionterm number line term startdate / enddate yyyy mm dd per line date range (may differ from header for co termed lines, ramps, etc ) pricetags array applied pricedimension + discountdimension objects product {sku, name, pricemodel, recordtype, productcategory} object product metadata (read only) uom {id, name, quantitydimension, termdimension, roundingmode, decimalscale} object uom metadata childrenlineitems\[] array direct children of this line each child has the same shape, plus parentid pointing back to this line customcalculateattributes object line level custom fields injected by the caller fields not exposed at beforecalculation listprice , netsalesprice , discount , discountamount , totalprice , and revenue metrics ( acv , arr , etc ) those only exist after the pricing engine runs — use aftercalculation if you need them writable push to $$updatedlineitems each push must include refid to identify the target line item the value comes from $$lineitem refid (or fall back to $$lineitem id if refid is absent) field effect refid required the target line item's refid (or id fallback) without it, the engine rejects the update with "refid is a required field" newpricetags pricedimension\[] add or replace tags at most 1 pricedimension (price type); multiple discountdimension s are appended pricedimensions pricedimension\[] direct override of the combined tag list a price dimension ( pricedimension ) defines how pricing or discounts are applied it includes recordtype — whether this is a pricedimension or discountdimension pricedimensiontype — applies by quantity or term pricetype — pricing model (tiered, volume, or ramp) active — whether the dimension is enabled pricetiers \[] — the list of pricing tiers each tier (pricetiers\[]) includes tiernumber — order of the tier startunit — starting unit for the tier endunit — ending unit (required for all non final tiers) chargemodel — perunit or flatfee price or discountpercentage — the value applied for multi tier configurations, every tier except the last must include an endunit otherwise, the pricing engine cannot evaluate the ranges and will throw an error example volume discount when total quantity exceeds 1,000 console debug("\[nue plugin] start volume discount tag"); if (!$$headerobject || !array isarray($$headerobject lineitems)) { console debug("\[nue plugin] no lineitems, skipping"); } else { // 1 sum quantities across every line on the quote var totalqty = 0; for (var i = 0; i < $$headerobject lineitems length; i++) { totalqty += number($$headerobject lineitems\[i] quantity) || 0; } console debug("\[nue plugin] total qty " + totalqty); // 2 above the threshold? push a volume discount tag onto every line if (totalqty > 1000) { for (var j = 0; j < $$headerobject lineitems length; j++) { var li = $$headerobject lineitems\[j]; $$updatedlineitems push({ refid li refid || li id, newpricetags \[{ recordtype 'discountdimension', pricedimensiontype 'quantity', pricetype 'volume', active true, pricetiers \[{ tiernumber 1, startunit 0, chargemodel 'flatfee', discountpercentage 10 }] }] }); } } } console debug("\[nue plugin] end volume discount tag"); what you'll see every line gets ruby systemdiscount c = 10 and ruby totalprice c = listtotalprice × 0 9 \#2 trigger event = beforecalculation (target a specific child sku) use this to apply a tiered discount on a specific add on sku within bundles — for example, "whenever a customer adds the price builder add on under the essential package bundle, give them 5% off below 100 units and 15% off at 100+ " context same as #1, plus pay attention to $$headerobject lineitems\[] childrenlineitems\[] the engine exposes children either nested under the parent example discount a specific child sku console debug("\[nue plugin] start inject child discount"); var target sku = 'price builder'; function applydiscount(target) { $$updatedlineitems push({ refid target refid || target id, newpricetags \[{ recordtype 'discountdimension', pricedimensiontype 'quantity', pricetype 'tiered', active true, pricetiers \[ // endunit is required on non final tiers { tiernumber 1, startunit 0, endunit 99, chargemodel 'perunit', discountpercentage 5 }, { tiernumber 2, startunit 100, chargemodel 'perunit', discountpercentage 15 } ] }] }); console debug("\[nue plugin] discounted refid=" + (target refid || target id)); } if (!$$headerobject || !array isarray($$headerobject lineitems)) { console debug("\[nue plugin] no lineitems, skipping"); } else { for (var i = 0; i < $$headerobject lineitems length; i++) { var li = $$headerobject lineitems\[i]; if (!li) continue; // match top level lines (engine sometimes flattens) if (li product && li product sku === target sku) { applydiscount(li); } // and any nested children if (array isarray(li childrenlineitems)) { for (var j = 0; j < li childrenlineitems length; j++) { var child = li childrenlineitems\[j]; if (child && child product && child product sku === target sku) { applydiscount(child); } } } } } console debug("\[nue plugin] end inject child discount"); what you'll see lines whose product sku matches price builder get the tiered discount; siblings/parents are untouched \#3 trigger event = aftercalculation (headerobject scope) use this to enforce caps, floors, or post calculation business rules — for example, "never let a single line's total exceed $10,000 regardless of quantity," or "round every total up to the nearest $100 " context $$headerobject (now with calculated totals) and $$updatedlineitemprices (output) header level fields field type description $$headerobject subscriptionterm number header level term $$headerobject startdate / enddate yyyy mm dd header date range $$headerobject customcalculateattributes object custom fields injected by the caller $$headerobject totalprice / totalamount / subtotal number calculated header totals $$headerobject lineitems\[] array array of line items — see per line fields below per line item fields — each entry in $$headerobject lineitems\[] field type description refid string line item identifier use this when pushing to $$updatedlineitemprices quantity number line quantity subscriptionterm number line term startdate / enddate yyyy mm dd per line date range listprice number base unit price before discounts (available here; absent at beforecalculation) netsalesprice number calculated unit selling price totalprice / totalamount / subtotal number calculated line totals product {id, sku, name, pricemodel, recordtype, productcategory} object product metadata (read only) note product id is exposed here; at beforecalculation only sku/name are present uom {id, name, quantitydimension, termdimension, roundingmode, decimalscale} object uom metadata childrenlineitems\[] array direct children, same shape plus parentid customcalculateattributes object line level custom fields pricetags is not exposed at aftercalculation — the raw tag list is replaced by the applied price result at this stage if you need to inspect tags, use beforecalculation writable — priority ordered overrides push to $$updatedlineitemprices each push must include refid (use li refid or fall back to li id ) only the highest priority override field present takes effect priority field formula 1 netsalesprice totalprice = netsalesprice × qty × term 2 discountpercentage totalprice = subtotal × (1 − pct/100) 3 discountamount totalprice = subtotal − discountamount 4 totalprice sets total directly; netsalesprice back calculated all override values must be numbers — strings or null raise pricingpluginerror the engine auto recalculates derived fields ( salesprice , revenue metrics, etc ) — do not update them yourself example cap every line's total at $10,000 console debug("\[nue plugin] start price ceiling"); var max price = 10000; if (!$$headerobject || !array isarray($$headerobject lineitems)) { console debug("\[nue plugin] no lineitems, skipping"); } else { for (var i = 0; i < $$headerobject lineitems length; i++) { var li = $$headerobject lineitems\[i]; var total = number(li totalprice) || 0; if (total > max price) { $$updatedlineitemprices push({ refid li refid || li id, totalprice max price }); console debug("\[nue plugin] capped " + (li refid || li id) + " from " + total + " to " + max price); } } } console debug("\[nue plugin] end price ceiling"); what you'll see any line whose calculated total exceeded $10,000 now reads exactly ruby totalprice c = 10000 the engine back calculates ruby netsalesprice c , ruby discountamount c , acv/tcv — you don't touch them \#4 trigger event = afterrampgeneration (lineitem scope) use this to restructure ramp segments — merge engine generated monthly ramps into yearly buckets, snap ramp end dates to fiscal month boundaries, or redistribute the summary total across ramps according to a custom schedule context $$lineitem — the parent (summary) line read only field type description $$lineitem refid string identifier for the parent line $$lineitem quantity number parent quantity $$lineitem subscriptionterm number parent term $$lineitem startdate / enddate yyyy mm dd parent date range $$lineitem pricetags array applied pricedimensions and discountdimensions $$lineitem product / uom object product and uom metadata (same shape as beforecalculation) $$lineitem customcalculateattributes object line level custom fields $$lineitem totalprice , listprice , and netsalesprice are not exposed at afterrampgeneration — the engine has zeroed them while ramps are being recomputed if you need the summary total, sum $$rampitems\[i] totalprice yourself $$rampitems\[] — engine generated ramps read only each entry field type description startdate / enddate yyyy mm dd ramp date range term number ramp term quantity number ramp quantity listtotal number list price × qty × term for this ramp netsalesprice / salesprice number computed per unit prices for this ramp totalprice / totalamount / subtotal number ramp totals discountpercentage / discountamount number applied discount systemdiscount / systemdiscountamount number engine computed dimension based discount deltacmrr / deltaarr / deltaacv / deltatcv number revenue impact metrics ramps have no identifier — neither id nor refid is exposed reference them by index ( $$rampitems\[0] ) or by startdate ramps are not guaranteed sorted — sort by startdate before processing $$updatedrampitems — output array replace semantics if non empty after execution, these entirely replace the original ramps, and the engine reaggregates the parent's totals if left empty, original ramps are preserved writable each pushed ramp must include at minimum startdate , enddate , term , totalprice use object assign({}, existingramp) to clone — do not mutate $$rampitems directly example a merge ramps into yearly segments console debug("\[nue plugin] start ramp merger"); if (!$$lineitem || !$$lineitem product || !array isarray($$rampitems) || $$rampitems length < 2) { console debug("\[nue plugin] skipping missing line item or insufficient ramps"); } else { // 1 sort by startdate var sorted = $$rampitems slice() sort(function (a, b) { return new date(a startdate + 't00 00 00') gettime() \ new date(b startdate + 't00 00 00') gettime(); }); // 2 recover the summary total — engine may have zeroed lineitem totalprice var summarytotal = number($$lineitem totalprice) || 0; if (summarytotal === 0) { for (var k = 0; k < sorted length; k++) { summarytotal += number(sorted\[k] totalprice) || 0; } } // 3 build one ramp per calendar year, even splitting the total var firststart = sorted\[0] startdate; var lastend = sorted\[sorted length 1] enddate; var firstyear = parseint(firststart substring(0, 4), 10); var lastyear = parseint(lastend substring(0, 4), 10); var yearcount = lastyear firstyear + 1; var peryear = yearcount > 0 ? summarytotal / yearcount summarytotal; for (var y = 0; y < yearcount; y++) { var start = (firstyear + y) + ' 01 01'; var end = (firstyear + y) + ' 12 31'; if (y === 0) start = firststart; if (y === yearcount 1) end = lastend; var ramp = object assign({}, sorted\[0]); ramp startdate = start; ramp enddate = end; ramp term = 12; ramp totalprice = peryear; $$updatedrampitems push(ramp); } } console debug("\[nue plugin] end ramp merger"); what you'll see original 24 monthly ramps are replaced with 2 yearly ramps summary totalprice matches the original total (engine reaggregates) example b snap ramp end dates to month end console debug("\[nue plugin] start snap ramps to month end"); function lastdayofmonth(yyyymmdd) { var year = parseint(yyyymmdd substring(0, 4), 10); var month = parseint(yyyymmdd substring(5, 7), 10); var lastday = new date(year, month, 0) getdate(); var mm = month < 10 ? '0' + month '' + month; var dd = lastday < 10 ? '0' + lastday '' + lastday; return year + ' ' + mm + ' ' + dd; } if (!$$lineitem || !array isarray($$rampitems) || $$rampitems length < 2) { console debug("\[nue plugin] insufficient ramps, skipping"); } else { var sorted = $$rampitems slice() sort(function (a, b) { return new date(a startdate + 't00 00 00') gettime() \ new date(b startdate + 't00 00 00') gettime(); }); for (var i = 0; i < sorted length; i++) { var clone = object assign({}, sorted\[i]); clone enddate = lastdayofmonth(clone enddate); $$updatedrampitems push(clone); } } console debug("\[nue plugin] end snap ramps to month end"); what you'll see each ramp's enddate becomes the last calendar day of its month ( 2026 01 15 → 2026 01 31 , 2026 02 20 → 2026 02 28 , etc ) prices and quantities are untouched
Have a question?
Get answers fast with Nue’s intelligent AI, expert support team, and a growing community of users - all here to help you succeed.
To ask a question or participate in discussions, you'll need to authenticate first.