This multi drop route planner finds an efficient visiting order for up to 12 UK postcodes. It is built for delivery planners, hauliers and transport buyers, with total mileage, estimated HGV journey time and a numbered map.
How to use: enter your depot or start postcode, add each drop (or paste a list, one postcode per line), set your minutes per drop, then select Plan route. The tool orders the stops to cut the total distance.
Drop postcodes0 of 12
Or paste a list (one postcode per line)
Your planned round appears hereAdd your drops and select Plan route to see the optimised order, numbered map and mileage.
Total road distance
-
Drive plus drops time
-
HGV estimate, includes dwell
Stops planned
-
Stop
Postcode
Area
Leg
Cumulative time
Cumulative time is the estimated HGV drive time to reach a stop plus the dwell time for it and all earlier stops. It excludes traffic, breaks and driver hours limits.
Methodology and assumptions
Every postcode is geocoded with postcodes.io, a free open service built on Ordnance Survey and Office for National Statistics data. Full postcodes use the /postcodes endpoint and outward-only codes (for example LS10) fall back to /outcodes, which returns the district centre, so those stops are placed at outward-area level.
The optimised visiting order, total distance, drive time and route line come from the public OSRM trip service, which solves the stop order using OpenStreetMap road data. We fix the first stop as your start postcode and let the engine choose the most efficient order for the rest without forcing a return to the depot. OSRM gives a car driving time; because heavy goods vehicles are slower, we apply a 0.85 speed factor, assuming an HGV averages 85 percent of car speed, and divide the OSRM time by 0.85 (about 18 percent longer). We then add your minutes-per-drop dwell time (default 15) for each stop to build the cumulative schedule.
If the trip service does not respond within 8 seconds, we fall back to a nearest-neighbour order computed in your browser from straight-line (haversine) distances, with leg distances multiplied by 1.25 to approximate road winding and drive time at an assumed 45 mph. This fallback is clearly labelled as an estimate and draws straight lines between stops rather than the true road route. The OSRM public demo server is provided for moderate use only, so please do not hammer it. Mapping and routing data are copyright OpenStreetMap contributors and served through OSRM. Assumptions current as of July 2026. This planner gives a planning-grade schedule, not a compliance record of driver hours.
Frequently asked questions
How does the multi-drop route planner order my stops?
It sends your start and drop postcodes to the OSRM trip service, which uses OpenStreetMap road data to find an efficient visiting order that keeps the first stop as your depot. The result is the sequence, total miles and drive time, shown as a numbered map and an ordered table.
How many drops can I plan at once?
Up to 12 delivery postcodes plus your start point. You can type them into rows or paste a list, one postcode per line. For larger rounds, split the day into two routes or contact CTS about a dedicated distribution plan.
Does it plan for a van or an HGV?
The drive time is an HGV estimate: we slow the car time OSRM returns to 85 percent of car speed. It suits both vans and lorries as a planning figure, though a light van on clear roads may be a little quicker.
What does minutes per drop mean?
It is the dwell time you expect at each stop for parking, unloading and paperwork. The default is 15 minutes. The tool adds it to the drive time so the cumulative column reflects a realistic round, not just moving time.
Can I copy the route to share with a driver?
Yes. Select Copy route order to put the ordered stop list on your clipboard, ready to paste into a message, run sheet or notes app.
Is the multi-drop route planner free?
Yes, it is free with no sign-up and runs in your browser using open UK postcode and OpenStreetMap data.
Turn an efficient round into the right delivery partner
Planning the order is the easy part. Running multi-drop rounds day in, day out needs the right fleet and licensed operator. Brief CTS on your delivery profile and we place it before vetted UK operators for dedicated distribution or full contract distribution. It is free and there is no obligation.
';
L.marker([p.lat,p.lon],{icon:L.divIcon({className:'',html:html,iconSize:[26,26],iconAnchor:[13,26]})})
.bindTooltip((i===0?'Start: ':'Stop '+i+': ')+p.label,{}).addTo(layer);
});
var latlngs=line?line.map(function(c){return [c[1],c[0]];}):orderedPts.map(function(p){return [p.lat,p.lon];});
var poly=L.polyline(latlngs,{color:'#1065FF',weight:5,opacity:.85,dashArray:dashed?'8,10':null}).addTo(layer);
map.fitBounds(poly.getBounds().pad(0.15));
setTimeout(function(){map.invalidateSize();},60);
}
function nearestNeighbour(pts){
var n=pts.length,used=new Array(n).fill(false),order=[0];used[0]=true;var cur=0;
for(var k=1;k<n;k++){
var best=-1,bd=Infinity;
for(var j=0;j<n;j++){if(!used[j]){var d=haversine(pts[cur],pts[j]);if(d<bd){bd=d;best=j;}}}
order.push(best);used[best]=true;cur=best;
}
return order;
}
function setBusy(on){els.go.disabled=on;els.go.innerHTML=on?'Planning':'Plan route';}
function run(){
clearMsg();
var startRaw=els.start.value;
if(!compact(startRaw)){showMsg('Please enter a start postcode.','err');return;}
var drops=getDrops();
if(drops.length<1){showMsg('Add at least one drop postcode.','err');return;}
// dedupe exact repeats but keep first occurrence
var seen={},uniq=[];drops.forEach(function(d){var k=compact(d);if(!seen[k]){seen[k]=1;uniq.push(d);}});
var dropped=drops.length-uniq.length;
drops=uniq;
setBusy(true);
var all=[startRaw].concat(drops);
Promise.all(all.map(function(pc){return geocode(pc).then(function(g){return g;},function(){return {bad:pc};});}))
.then(function(pts){
var bad=pts.filter(function(p){return p.bad;});
if(bad.length){throw {kind:'geo',list:bad.map(function(b){return b.bad;})};}
var coordStr=pts.map(function(p){return p.lon+','+p.lat;}).join(';');
var url='https://router.project-osrm.org/trip/v1/driving/'+coordStr+'?roundtrip=false&source=first&overview=full&geometries=geojson';
return fetchJSON(url).then(function(j){
if(!j||j.code!=='Ok'||!j.trips||!j.trips.length)throw new Error('trip');
var trip=j.trips[0];
// build ordered input index list from waypoint_index
var orderByPos=[];j.waypoints.forEach(function(w,inputIdx){orderByPos[w.waypoint_index]=inputIdx;});
var orderedPts=orderByPos.map(function(idx){return pts[idx];});
var legMiles=trip.legs.map(function(l){return l.distance/1609.344;});
var legMin=trip.legs.map(function(l){return (l.duration/60)/0.85;});
render(orderedPts,legMiles,legMin,trip.distance/1609.344,trip.geometry.coordinates,false,dropped);
});
}).catch(function(err){
if(err&&err.kind==='geo'){
showMsg('We could not find: '+err.list.join(', ')+'. Check for typos or use the outward code only.','err');
els.results.classList.remove('is-on');setBusy(false);return;
}
// OSRM fallback: nearest-neighbour on already-geocoded points
Promise.all(all.map(function(pc){return geocode(pc);})).then(function(pts){
var order=nearestNeighbour(pts);
var orderedPts=order.map(function(i){return pts[i];});
var legMiles=[],legMin=[],total=0;
for(var i=0;i=0))dwell=15;
els.placeholder.style.display='none';
els.results.classList.add('is-on');
var totalDriveMin=legMin.reduce(function(a,b){return a+b;},0);
var dropCount=orderedPts.length-1;
var totalMin=totalDriveMin+dwell*dropCount;
var milesStr=fmtMiles(totalMiles)+' mi';
var kmStr=(totalMiles*1.609344).toFixed(1)+' km'+(est?' (estimate)':'');
var timeStr=fmtTime(totalMin)+(est?'*':'');
els.miles.innerHTML=fmtMiles(totalMiles)+' mi';
els.km.textContent=kmStr;
els.time.textContent=timeStr;
els.stops.textContent=dropCount;
var approx=orderedPts.some(function(p){return p.approx;});
var methodStr=est?'Nearest-neighbour estimate':(approx?'OSRM optimised trip (outward-level stops)':'OSRM optimised trip');
els.source.textContent=methodStr;
var rows='',cum=0,pdfRows=[];
orderedPts.forEach(function(p,i){
if(i===0){
rows+='
';
pdfRows.push([String(i),p.label,p.area||'',legMiles[i-1].toFixed(1)+' mi',fmtTime(cum)]);
}
});
els.body.innerHTML=rows;
lastOrder=orderedPts;
lastCalc={
start:orderedPts[0].label,
drops:dropCount,
dwell:dwell,
method:methodStr,
miles:milesStr,
km:(totalMiles*1.609344).toFixed(1)+' km'+(est?' (estimate)':''),
time:fmtTime(totalMin)+(est?' (estimate)':''),
rows:pdfRows
};
drawMap(orderedPts,line,est);
if(dropped>0)appendNote(dropped+' duplicate postcode'+(dropped>1?'s were':' was')+' removed.');
els.results.scrollIntoView({behavior:'smooth',block:'nearest'});
}
function appendNote(t){
if(els.msg.className.indexOf('is-')<0){showMsg(t,'note');}
else{els.msg.textContent=els.msg.textContent+' '+t;}
}
function esc(s){return (s+'').replace(/[&"]/g,function(c){return {'&':'&','':'>','"':'"'}[c];});}
els.add.addEventListener('click',function(){var i=addRow('');if(i)i.focus();});
els.clear.addEventListener('click',function(){els.drops.innerHTML='';addRow('');addRow('');clearMsg();});
els.go.addEventListener('click',run);
els.start.addEventListener('keydown',function(e){if(e.key==='Enter')run();});
els.dwell.addEventListener('input',function(){if(lastOrder)run();});
els.importBtn.addEventListener('click',function(){
var lines=(els.paste.value||'').split(/[n,;]+/).map(function(s){return s.trim();}).filter(function(s){return s;});
if(!lines.length){showMsg('Paste one postcode per line first.','err');return;}
els.drops.innerHTML='';
var added=0,skipped=0;
lines.forEach(function(l){if(added<MAX){if(addRow(l))added++;}else skipped++;});
if(added0)showMsg('Imported the first '+MAX+' postcodes. '+skipped+' more were not added (12 drop limit).','note');
});
els.copy.addEventListener('click',function(){
if(!lastOrder)return;
var lines=lastOrder.map(function(p,i){return (i===0?'Start: ':i+'. ')+p.label+(p.area?' ('+p.area+')':'');});
var text='Route order ('+(lastOrder.length-1)+' drops):n'+lines.join('n');
var done=function(){var o=els.copy.textContent;els.copy.textContent='Copied';setTimeout(function(){els.copy.textContent=o;},1600);};
if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(text).then(done,function(){fallbackCopy(text,done);});}
else fallbackCopy(text,done);
});
function fallbackCopy(text,cb){
var ta=document.createElement('textarea');ta.value=text;ta.style.position='fixed';ta.style.opacity='0';
document.body.appendChild(ta);ta.select();try{document.execCommand('copy');cb();}catch(e){}document.body.removeChild(ta);
}
window.ctsToolPDF={
title:'Multi Drop Route Planner UK',
subtitle:'Free multi drop route planner for up to 12 UK postcodes. Optimise stop order, then see total miles, estimated HGV journey time and a numbered delivery map.',
filename:'multi-drop-route-planner',
getSections:function(){
if(!lastCalc)return null;
return [
{h:'Inputs'},
{kv:[
['Start postcode (depot)',lastCalc.start],
['Drops planned',String(lastCalc.drops)],
['Minutes per drop',String(lastCalc.dwell)],
['Method',lastCalc.method]
]},
{h:'Results'},
{kv:[
['Total road distance',lastCalc.miles],
['Equivalent',lastCalc.km],
['Drive plus drops time',lastCalc.time],
['Stops planned',String(lastCalc.drops)]
]},
{h:'Optimised drop order'},
{table:{head:['Stop','Postcode','Area','Leg','Cumulative time'],rows:lastCalc.rows}},
{note:'Drive times are an HGV estimate (OSRM car time divided by 0.85) plus your minutes-per-drop dwell, with the visiting order solved by the OSRM trip service on OpenStreetMap road data. Assumptions current as of July 2026.'}
];
}
};
// seed with a demo round
['M1 1AE','S1 2HH','NG1 5DT'].forEach(function(v){addRow(v);});
})();