cost, minimizeCosts, and custom objectives are deliberately powerful, but only a minority of plans need them. Before reaching for this page, check whether a standard objective (minimizeDistance, minimizeWorkingDuration, minimizeResources, and so on) already expresses the trade-off you’re after — see Built-in objectives.
The cost object
A resource’s cost object accumulates several independent contributions into a single currency figure that the minimizeCosts objective then optimizes: using (a flat cost per resource used), workedHours, km, costsByStopTag, costsByCapacity, costPerCapacityPerTravelledKm, and costPerCapacityPerTravelledHour.
These fields reformulate, in a real currency, the same underlying quantities that minimizeDistance, minimizeWorkingDuration, and minimizeResources already count in raw units (km, hours, vehicles). This isn’t a strict one-to-one equivalence — it’s a different objective, built on a monetary version of the same data, not a guaranteed substitute for the raw-unit objectives.
Piecewise-linear cost functions
Every cost field preceding this section except using accepts a CostFloorsAndCoeffs-shaped object: a constantCost base, a costCoeff rate applied past costFloor, and an optional steeper overcostCoeff rate past a second, higher overcostFloor — the same shape as a salary with a base, a free allowance, and an overtime-style rate beyond it. Modeling advanced constraints already has a worked costsByCapacity example using this object, to make capacity overflow on delivery-only tours economically unattractive rather than infeasible.
costFloor is the threshold below which costCoeff doesn’t apply; constantCost is a fixed amount charged regardless of it — so a guaranteed minimum comes from constantCost, not from costFloor. costsByCapacity works on any capacity the stops carry (weight, revenue, a counter such as nbDeliveries…); the resource doesn’t need to declare it. The cost is computed on the quantities handled at the tour’s stops, not on the vehicle’s load: each stop adds the absolute value of its quantity for that capacity, whatever its kind or sign. Loading at departure isn’t a stop and doesn’t count, so a delivery counts once; a pickup and a delivery of the same 100 units count 200. With stopTags, only stops carrying one of those tags count. costFloor and overcostFloor then apply once, to that tour total. On a delivery-only tour, the total equals the load at departure.
Guaranteeing a minimum per-tour revenue via costsByCapacity
Create a capacity that only counts deliveries rather than any real load — for example deliveredUnits. The cost doesn’t need it declared on the resource; declare it there with a ceiling high enough never to bind only if you want to read it back in the tour’s filledCapacities values. Set it on every delivery stop (1 to count stops, or the number of units delivered there) and on no other stop, then put a constantCost/costFloor on that capacity so a tour that ends below the threshold keeps paying a flat penalty:
With these figures, a tour pays a flat 200 up to 50 delivered units; past that, each extra unit lowers the cost by 4, so it reaches 0 at 100 units. Nothing stops it there: past 100 units the contribution keeps falling below zero and acts as a bonus, so size costCoeff and costFloor accordingly.
Rebuilding the default objective list with customCost
A CustomObjective (type: "custom") lets you optimize a cost expression built from costsByResourceTag instead of a built-in ObjectivesEnum entry — for example scoping a custom fuel-cost objective to just the resources carrying a given tag:
name must not collide with a built-in ObjectivesEnum value (the schema enforces this). costsByResourceTag maps a Cost-shaped object to a resource tag, so a custom objective can apply to a subset of the fleet rather than every resource.
Modeling revenue or margin per stop
costsByStopTag with negative constantCost values represents a revenue (a negative cost) rather than a charge, combined with minimizeCosts. It sits in the resource’s cost, and each entry applies once for every visited stop carrying that tag:
Here van-1 earns 30 for serving stop-1 and 63 for serving stop-2.
If you’d rather reason entirely in positive revenue figures, use a CustomObjective with direction: "maximize" and positive costsByStopTag values instead of negating them under the built-in minimizeCosts — there is no built-in maximizeCosts objective in ObjectivesEnum, only minimizeCosts. The two approaches aren’t interchangeable as they stand. objectives is a strict priority order, so put the custom objective at the exact position minimizeCosts held (third in the default list, after maximizeMandatoryStops and minimizeDelay); anywhere else, the engine trades revenue off against different objectives and can return a different plan. Also adjust whatever thresholds you’ve set elsewhere to the sign change.
See also