Skip to content

Truck Routing

A car route will happily send a 4.1 m semi-trailer under a 3.8 m bridge. Set mode to truck and describe the vehicle, and Plaza routes around every road whose posted limit the truck exceeds.

Truck mode works on Directions, Isochrones and the Distance Matrix. All three are premium endpoints, so each request counts as 4x. Truck mode and its options also work on Route Optimization, the Meeting Point, Map Matching and Routing Debug.

  • Skips restricted roads. An edge is left out when its OSM maxheight, maxwidth, maxlength, maxweight or maxaxleload is below the truck’s value. Roads with no limit tagged stay in.
  • Avoids hazmat restrictions when truck.hazmat is true: roads tagged hazmat=no or no on any hazmat class (hazmat:water, hazmat:explosive, hazmat:B, …; Plaza doesn’t ask for the load’s class, so every class counts), and tunnels in ADR category B to E (hazmat:adr_tunnel_cat). A tunnel without those tags is not avoided.
  • Keeps to roads trucks may use. Motorways down to living streets, no footways, paths or tracks. hgv=no closes a road. hgv=yes opens one that access=no would close. Without an hgv tag, motor_vehicle, then vehicle, then access decide.
  • Follows one-way streets and turn restrictions, including truck-only ones (restriction:hgv). except=hgv lets trucks make the turn.
  • Times the trip at truck speeds, capped by the road’s maxspeed (or the country’s default limit when the road has none), slowed by rough surfaces and uphill grades, plus the same junction and traffic-signal delays as cars. With depart_at, the same time-of-day profile as car routes scales them. See how Plaza prices a road.
Road classTruck speed (km/h)
motorway100
trunk85
primary70
secondary60
tertiary50
unclassified40
residential30
service20
living_street10

Link roads are slower: 60 km/h for motorway_link down to 35 km/h for tertiary_link.

OSM writes limits in many forms. Plaza reads 4.2, 4,2, 4.2 m, 420 cm, 13'6", 13 ft, 7.5 t, 7.5 to, 7500 kg, 7,500 kg, 16000 lbs and US short tons (20 st), and ignores values like none or default that carry no limit. A weight without a unit is in tonnes, OSM’s default, so 7,500 is 7.5 t. ton and tons are read as metric tonnes. When a road has both maxheight and maxheight:physical, the lower one applies. The same goes for maxwidth:physical, maxweightrating and maxweight:hgv. Conditional limits such as maxweight:conditional (a limit that only applies at certain times) are ignored.

Where Plaza has elevation for a road, climbs slow trucks more than cars: 80% of the speed on a 2-4% grade, 60% at 4-6%, 45% at 6-8% and 30% above that. Downhill stretches run at the normal speed. Grades are averaged over each OSM way, so a road over a hill with both ends at the same height counts as flat.

Send the vehicle in a truck object. Anything you leave out takes the default, which matches a standard US semi-trailer.

FieldTypeDefaultRange
truck.heightnumber4.11Metres, above 0, at most 10
truck.widthnumber2.6Metres, above 0, at most 10
truck.lengthnumber21.64Metres, above 0, at most 60
truck.weightnumber21.77Gross weight in tonnes, above 0, at most 200
truck.axle_loadnumbernoneHeaviest axle in tonnes, above 0, at most 50
truck.axle_countintegernone2 to 20
truck.hazmatbooleanfalseCarrying hazardous materials

Axle-load limits are checked against axle_load. Without it, Plaza checks weight / axle_count, and with neither it doesn’t check axle loads at all.

A value out of range, an unknown field, or a truck object without mode: "truck" is a 422 validation_error.

A 4 m truck carrying 30 t of fuel:

Terminal window
curl -X POST https://plaza.fyi/api/v1/route \
-H "x-api-key: $PLAZA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"origin": { "type": "Point", "coordinates": [-87.6298, 41.8781] },
"destination": { "type": "Point", "coordinates": [-87.9073, 41.9742] },
"mode": "truck",
"truck": { "height": 4.0, "weight": 30, "hazmat": true }
}'

The response is the usual route Feature, with three extra properties:

{
"type": "Feature",
"geometry": { "type": "LineString", "coordinates": [[-87.6298, 41.8781], "..."] },
"properties": {
"distance_m": 28417.3,
"duration_s": 1342.8,
"mode": "truck",
"truck": { "height": 4.0, "width": 2.6, "length": 21.64, "weight": 30.0, "hazmat": true },
"warnings": [
{
"type": "restriction_near_route",
"restriction": "maxheight",
"message": "3.8 m height restriction on a nearby road",
"location": [-87.6512, 41.8899],
"osm_way_id": 123456789
}
]
}
}

warnings lists up to 10 restrictions the truck exceeds on roads within about 30 m of the route: the side street with the low bridge, the parallel road with the weight limit. The route doesn’t use them, but a driver who takes a wrong turn will meet them. The list is empty when there’s nothing nearby. Routes with alternatives carry mode and truck but no warnings.

The same route in PlazaQL. The vehicle fields become keyword arguments:

route(
origin: point(41.8781, -87.6298),
destination: point(41.9742, -87.9073),
mode: "truck",
height: 4.0,
weight: 30,
hazmat: true
);

If the road network connects the two points but every path crosses a restriction the truck exceeds, you get a 404 with code no_route. details.restrictions lists what blocked the path the truck would otherwise have taken, up to 20 entries in route order:

{
"error": {
"code": "no_route",
"message": "No route fits the vehicle: every path crosses a height, width, length, weight or hazmat restriction it exceeds.",
"details": {
"reason": "vehicle_dimensions_exceeded",
"restrictions": [
{
"type": "maxheight",
"limit": 3.5,
"vehicle": 4.11,
"osm_way_id": 123456789,
"location": [-87.6512, 41.8899]
}
]
}
}
}

A hazmat entry has a limit of null. When the points aren’t connected at all, you get the ordinary not_found instead. The explanation is only given for routes without waypoints.

Some limits only hold at certain times, or only for heavier vehicles. Send depart_at and Plaza applies the conditional ones in force at that local time: hgv:conditional, maxweight:conditional, maxheight:conditional, maxwidth:conditional, maxlength:conditional, maxaxleload:conditional, plus the access and maxspeed:conditional tags every mode reads. A town centre tagged hgv:conditional = no @ (Mo-Sa 07:00-19:00) is closed to this truck at 08:30 on a Tuesday and open to it at 21:00:

Terminal window
curl -X POST https://plaza.fyi/api/v1/route \
-H "x-api-key: $PLAZA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"origin": { "type": "Point", "coordinates": [-87.6298, 41.8781] },
"destination": { "type": "Point", "coordinates": [-87.9073, 41.9742] },
"mode": "truck",
"truck": { "weight": 30 },
"depart_at": "2026-03-24T08:30:00-05:00"
}'

Vehicle conditions are checked against your truck object, so maxweight:conditional = 7.5 @ (weight>3.5) binds a 30 t truck and not a 3 t van. Without depart_at, time conditions never hold and each road keeps its plain value. The directions guide lists the conditions Plaza reads.

mode and truck work the same way on POST /api/v1/isochrone and POST /api/v1/matrix. A truck isochrone shows where the truck can get to in the time budget without breaking a limit:

Terminal window
curl -X POST https://plaza.fyi/api/v1/isochrone \
-H "x-api-key: $PLAZA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"geometry": { "type": "Point", "coordinates": [-87.6298, 41.8781] },
"time": 1800,
"mode": "truck",
"truck": { "height": 4.3, "weight": 40 }
}'
isochrone(center: point(41.8781, -87.6298), time: 1800, mode: "truck", height: 4.3, weight: 40);
matrix(
origins: [point(41.8781, -87.6298)],
destinations: [point(41.9742, -87.9073), point(41.7868, -87.7522)],
mode: "truck",
axle_count: 5
);

Neither takes depart_at for trucks, so conditional limits don’t apply there. Each road keeps its plain value.

In PlazaQL the vehicle keywords need mode: "truck" and only route(), isochrone(), matrix(), optimize(), centroid(), expansion(), map_match() and ev_route() accept them. Anything else fails the query with a 400 query_error.

Add ev to a truck route and Plaza plans the charging stops for an electric truck. The route keeps every truck rule above. Consumption scales with truck.weight: about 300 Wh/km, plus 22.5 Wh/km per tonne, plus 30 Wh/km for auxiliaries. That works out to about 0.8 kWh/km at the default 21.77 t and 1.2 kWh/km for a 40 t tractor-trailer. Climbs cost the energy it takes to lift the truck’s mass, and some of it comes back downhill.

Unless you set connector_types, the truck only stops at CCS (type2_combo, type1_combo) and MCS (socket:mcs) chargers. Plaza doesn’t know whether a truck fits a charging site, so check the stops before you rely on them.

Terminal window
curl -X POST https://plaza.fyi/api/v1/route \
-H "x-api-key: $PLAZA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"origin": { "type": "Point", "coordinates": [4.4777, 51.9244] },
"destination": { "type": "Point", "coordinates": [6.9603, 50.9375] },
"mode": "truck",
"truck": { "weight": 40 },
"ev": { "battery_capacity_wh": 540000 }
}'
ev_route(origin: point(51.9244, 4.4777), destination: point(50.9375, 6.9603), battery: 540000, mode: "truck", weight: 40);

The EV routing guide covers the charge profile and the other ev fields.

Truck mode is only as good as the OSM tags. A bridge nobody has tagged with maxheight doesn’t stop the router, and a missing tag means no limit, not an unknown one. In well-mapped countries most tunnels and low bridges are tagged. In others, check the route before you send the truck.