Reference - Variables, Functions & Types
Complete technical lookup for Phasio pricing equations: built-in functions, the specification, requisition, revision and processPricing objects, the level cheat sheet, and a debugging guide.
Complete technical lookup for Phasio pricing equations. No explanations - just the facts.
Built-in functions
done(price, duration?, reviewRequired?)
Ends a Level 1 or Level 2 equation. Not available at Level 3.
| Parameter | Type | Required | Description |
|---|---|---|---|
price | number | Yes | Unit price returned to Phasio |
duration | number | No | Estimated production time in hours |
reviewRequired | boolean | No | true blocks auto-approval and flags for manual review |
done(49.90) // price only
done(49.90, 2.5) // price + duration
done(49.90, 2.5, true) // price + duration + review flag
done(49.90, printTimeHours, width > 300 || height > 300)
// Object style — equivalent
done({ price: calculatedPrice, reviewRequired: true })
done({ price: calculatedPrice, duration: printTimeHours, reviewRequired: condition })Automatic triggering: the gate also fires if the computed price is 0, negative, or NaN. Customers never see a broken price.
Review reason flags. done() has no reason string parameter. To surface why a part was flagged, use named output variables with 0/1 values - operators see these in the override panel alongside the part:
const _reasonFillRatio = variable('REVIEW: fill ratio too low', fillRatio < 0.15 ? 1 : 0)
const _reasonSurfaceComplex = variable('REVIEW: surface complexity high', swvRatio < 0.30 ? 1 : 0)
done(estimatedPrice, printTime, fillRatio < 0.15 || swvRatio < 0.30)A value of 1 means the flag is active. Name the variable to read like a sentence.
addLineItem({ name, price })
Adds a charge or discount to the order. Level 3 only. Positive = charge, negative = discount.
addLineItem({ name: 'Express shipping', price: 25.00 })
addLineItem({ name: 'Volume discount', price: -15.00 })
addLineItem({ name: 'Minimum order charge', price: round(200 - subtotal, 2) })variable(name, defaultValue)
Exposes a field in the operator quote UI for per-order override. Level 1 and Level 2 only. Always operator-facing - never visible to customers. Returns the operator-overridden value if set, otherwise defaultValue.
const printTime = variable('printTime', round(calculatedHours, 2))
const unitPrice = variable('unitPrice', round(baseCost, 2)) // always wrap final priceVariable values from Level 1 are accessible in Level 2 via processPricing.variables['name'].
Using variables as mode-switch flags. Because variable() lets operators set any value per-order, it doubles as an internal configuration toggle. Set a sensible default; the operator overrides it when needed:
// Default 1 = Standard 45° tilt. Operator sets to 0 for Direct Build (flat on bed).
const buildMode = variable('buildMode', 1) // 1 = standard, 0 = direct
const timePerLayer = buildMode === 1
? variable('timePerLayer', round(avgCrossSectionMm2 / scanSpeed, 2)) // 45° tilt
: variable('timePerLayer', round(length * width / scanSpeed, 2)) // flat on bedThis is the recommended way to run two process variants under one equation - no need to duplicate materials.
Using variables as output-only info flags - e.g. review reasons, see the done() section above.
createBands(bands, base?)
Creates a tiered lookup function. Returns the value mapped to the highest threshold ≤ input. base is returned when input is below all thresholds (default 0).
const getDiscount = createBands({ 10: 0.95, 50: 0.90, 100: 0.85 }, 1.0)
getDiscount(1) // 1.0 (below all thresholds → base)
getDiscount(10) // 0.95
getDiscount(75) // 0.90 (highest threshold ≤ 75 is 50)
getDiscount(200) // 0.85round(value, precision?)
Rounds to precision decimal places (default 0).
round(12.345, 2) // 12.35
round(12.345) // 12specification object (Level 1 and Level 2)
| Field | Type | Unit | Description |
|---|---|---|---|
specification.volume | number | mm³ | Actual material volume |
specification.area | number | mm² | Total surface area |
specification.length | number | mm | Bounding box length |
specification.width | number | mm | Bounding box width |
specification.height | number | mm | Bounding box height |
specification.convexHullVolume | number | mm³ | Tightest convex envelope |
specification.shrinkWrapVolume | number | mm³ | Tight wrap following overhangs - use for powder bed |
specification.minBoundingBoxVolume | number | mm³ | Minimum bounding box volume - use for CNC raw stock |
specification.infill | object (nullable) | - | Infill setting. Access as infill?.value ?? 0.20 |
specification.precision | object (nullable) | - | Layer height setting. Access as precision?.value ?? 0.20 |
Bounding box axis convention
width is always the largest dimension, height the medium, length the smallest. Phasio computes the minimum bounding box from the uploaded geometry - part rotation in the viewer has no effect on these values. The orientation tool and Fixed Orientation Constraint affect nesting and production planning, not pricing geometry.
Destructuring shorthand used at the top of most equations:
const { volume, area, length, width, height, convexHullVolume, shrinkWrapVolume, infill, precision } = specificationrequisition object (Level 1 and Level 2)
| Field | Type | Description |
|---|---|---|
requisition.quantity | number | Number of parts ordered |
requisition.leadTime.name | string | Lead time tier name, e.g. 'Economy', 'Standard', 'Express', 'Overnight' |
revision object (Level 1 and Level 2)
| Field | Type | Description |
|---|---|---|
revision.watertight | number | 1 = closed mesh, 0 = open mesh. Flag open mesh for review. |
revision.minimumWallThickness | number | Thinnest wall in mm. Pro / Factory Floor plans only. Use to gate fragile geometry. |
material object (Level 1 and Level 2)
| Field | Type | Description |
|---|---|---|
material.variables['key'] | number or string | Custom variable set on the material in Phasio. Must exist on the material before saving the equation. Always use bracket notation. |
Materials cannot be shared across processes
Each process has its own material list. If you need two processes that use the same physical material (e.g. Standard SLA and Direct Build SLA), create the material entries separately per process. Use material.variables and equation logic to handle any pricing differences - the equation itself is where process variants diverge, not the material configuration.
customer object (Level 1 and Level 2)
| Field | Type | Description |
|---|---|---|
customer.organisationName | string (nullable) | Organisation name. Access as customer.organisationName ?? '' |
processPricing object (Level 2 only)
| Field | Type | Description |
|---|---|---|
processPricing.price | number | Unit price returned by the Level 1 equation for this part |
processPricing.variables | object | All variable() values set in Level 1, keyed by name |
const partPrice = processPricing.price
const printTime = processPricing.variables['printTime'] // only if Level 1 exposed itLevel 3 context
| Variable | Type | Description |
|---|---|---|
parts | array | All line items in the order |
subtotal | number | Sum of all part prices (Level 1 + Level 2) before order-level adjustments |
Each item in parts:
| Field | Type | Description |
|---|---|---|
part.specification | object | Full specification (same fields as above) |
part.requisition | object | Full requisition (quantity, lead time) |
part.price | number | Level 1 unit price |
part.postProcessingPrice | number | Total Level 2 price |
Level comparison cheat sheet
| Level 1 | Level 2 | Level 3 | |
|---|---|---|---|
| Runs | Per line item | Per line item × post-process | Once per order |
specification | ✓ | ✓ | Via parts[i].specification |
requisition | ✓ | ✓ | Via parts[i].requisition |
material.variables | ✓ | ✓ | ✗ |
revision | ✓ | ✓ | ✗ |
customer | ✓ | ✓ | ✓ |
processPricing | ✗ | ✓ | ✗ |
parts (all line items) | ✗ | ✗ | ✓ |
subtotal | ✗ | ✗ | ✓ |
done() | ✓ | ✓ | ✗ |
addLineItem() | ✗ | ✗ | ✓ |
variable() | ✓ | ✓ | ✗ |
createBands() | ✓ | ✓ | ✓ |
round() | ✓ | ✓ | ✓ |
reviewRequired gate | ✓ | ✓ | ✗ |
Sandbox constraints
| Constraint | Detail |
|---|---|
| No network access | fetch(), XMLHttpRequest, and all I/O are unavailable |
| No Node globals | process, require, module, __dirname do not exist |
| No timers | setTimeout, setInterval, Promise are unavailable |
| TypeScript only | The equation body must be valid TypeScript |
| Currency is implicit | Prices are unitless numbers - never include currency symbols |
done() is required | Level 1 and Level 2 equations that don't call done() produce no price |
Debugging guide
Use variable() to surface intermediate values in the Phasio quote UI during development. They appear in the override panel when you open any quote.
// Add during development — remove before going live
const _debug_shellVolume = variable('DEBUG shellVolume mm3', shellVolume)
const _debug_printTime = variable('DEBUG printTime hours', round(rawPrintTime / 3600, 4))
const _debug_materialCost = variable('DEBUG materialCost', materialCost)
const _debug_removalRatio = variable('DEBUG removalRatio', round(removalRatio, 3))Workflow:
- Upload a simple test part with known dimensions (e.g. 20×20×20mm cube - volume ≈ 8,000 mm³, area ≈ 2,400 mm²)
- Calculate the expected price by hand
- Add
DEBUGvariable fields for each intermediate value - Run the equation and compare to your hand calculation - the first mismatch is the bug
- Test edge cases: qty=1 and qty=500, oversized part, open mesh, missing infill setting
- Remove all
DEBUGvariables before going live
Error reference
| Error | Cause | Fix |
|---|---|---|
done is not a function | Called done() at Level 3 | Use addLineItem() at Level 3 |
processPricing is not defined | Used processPricing at Level 1 | Only available at Level 2 |
variable is not a function | Used variable() at Level 3 | Not available at Level 3 |
Cannot read property of undefined | material.variables['key'] doesn't exist on this material | Create the variable on every material that uses this equation |
| Equation saves but throws on real parts | Variable exists on test material but not others | Audit all materials using this equation |
infill is undefined | Accessed infill.value directly | Use infill?.value ?? 0.20 |
precision is undefined | Accessed precision.value directly | Use precision?.value ?? 0.20 |
expense.type == 'HOURLY' always false | Enum value needs .name() | Use expense.type.name() == 'HOURLY' |
| Price is 0 and part is flagged for review | Equation produced falsy price | Auto-trigger: fix the equation or set a valid calculated price |
Common material variable keys
Convention only - must be created per material before use.
| Key | Typical value | Process |
|---|---|---|
materialDensity | 1.24 g/cm³ | FDM, SLS, MJF |
materialCostPerKg | 28 | FDM |
resinCostPerLiter | 95 | SLA, DLP |
costPerCm3 | 0.18 | MJF, SLS |
setupCost | 80 | MJF, SLS, CNC |
baseSpeedNozzle | 60 mm/s | FDM |
wallSpeedFactor | 0.5 | FDM |
infillSpeedFactor | 1.0 | FDM |
supportSpeedFactor | 0.8 | FDM |
printerCostPerHour | 8 | FDM |
machineCostPerH | 12 | SLA |
buildRateMm3PerHour | 5000 | Metal LPBF |
machiningCostPerCm3 | 2.50 | CNC |
rawMaterialCostPerKg | 15 | CNC |
mouldCost | 600 | Vacuum casting |
mouldLifespan | 20 | Vacuum casting |
castingCostPerPart | 18 | Vacuum casting |
minUnitPrice | 5.00 | All processes (floor price) |
Last updated on