Sitemap
David vd Hoop

David vd Hoop

Composable Architect at Valtech, helping B2B manufacturers modernize commerce with headless and API-first architectures.

Packing Like a Pro, Smarter Shipping Logic for Multi-Item Orders

5 min readOct 20, 2025

--

In a previous post, we looked at how to integrate a multi-carrier shipping rate provider with Commercetools, so that checkout pages can display accurate, real-time shipping options based on the user’s basket contents. That integration worked well for simple orders, but as soon as multiple items are added to the cart, a common challenge arises: How do we decide which items go into which box?

If every product is treated as a separate parcel, shipping costs are vastly overestimated. If all items are assumed to fit in a single box, they’re often underestimated. The truth lies somewhere in between , and getting it right is key to both customer satisfaction and profit margin.

Press enter or click to view image in full size

The Problem

Let’s take a simple example. A customer orders four identical bottles of oil. Each bottle weighs one kilogram and easily fits into a standard 60×40×40 cm box. If we naïvely send one API call per item to our shipping rate provider, each returning $20 shipping cost, the checkout will show $80 total , even though all four bottles could have shipped together for about $20.

This mismatch happens because most shipping APIs, such as Easyship, expect parcels, not individual products. They calculate cost based on the size and weight of each parcel you send them. The logic that decides how products are packed into parcels must live in your own system.

Common Approaches

There are a few ways teams usually approach shipping calculations for multi-item orders. Each has trade-offs in accuracy and complexity.

One box per item

The simplest approach: treat every item as a separate parcel.
It’s easy to implement and ensures you never underestimate cost, but it often overestimates by a large margin, especially when multiple items could ship together.

Single combined box

Here, you assume all items fit into one parcel and calculate shipping based on total weight and combined volume.
This works for small, uniform items, but it tends to underestimate cost for mixed or bulky orders.

Category or tag-based grouping

You group products using a flag such as “can ship together,” “LTL,” or “hazardous”.
This is business-friendly and easy to control, but it requires consistent product data and maintenance as your catalog grows.

3D bin packing

A more advanced solution simulates physical packing based on item dimensions and weight.
It’s the most accurate approach, but also the most complex and computationally expensive. Maintaining it over time is non-trivial.

Get David vd Hoop’s stories in your inbox

Join Medium for free to get updates from this writer.

In most real-world setups, the best choice is something in between: fast, predictable, and close enough to real packaging behavior without excessive complexity.

A Practical Heuristic: First Fit Decreasing by Volume

One widely used method for quick, approximate packing is First Fit Decreasing (FFD). The idea is simple:

  1. Sort items by volume, largest first.
  2. Iterate through each item and try to place it in the first existing box that still has room and weight capacity.
  3. If no box fits, open a new box.
  4. If an item doesn’t fit the preferred box type at all, fall back to the next available size.

The result isn’t mathematically perfect, but it’s good enough to produce realistic box counts and shipping costs, without needing a full 3D packing algorithm.

Data You Need

For this to work, your product catalog should expose at least these attributes:

  • Weight (kg or lb)
  • Dimensions (length, width, height in cm or inches)
  • Optional tags or categories (e.g., fragile, LTL, cannotShipWithLiquids)

You’ll also need one or more box definitions, each with:

  • Inner dimensions (L×W×H)
  • Maximum allowed weight
  • Optional packing utilization (for example, 85% of total volume to leave room for padding)

Example Implementation (TypeScript)

Below is a simplified example showing how you might implement the First Fit Decreasing heuristic for a cart.

type Item = {
id: string;
l: number; w: number; h: number;
weight: number;
qty: number;
};

type BoxSpec = {
name: string;
innerL: number; innerW: number; innerH: number;
maxWeight: number;
};

type PackedBox = {
box: BoxSpec;
items: { itemId: string; count: number }[];
usedVolume: number;
usedWeight: number;
};

function packCart(items: Item[], boxCatalog: BoxSpec[], preferred: string): PackedBox[] {
const preferredBox = boxCatalog.find(b => b.name === preferred)!;
const boxVolume = (b: BoxSpec) => b.innerL * b.innerW * b.innerH;

// Expand quantities and sort largest first
const units = items.flatMap(i =>
Array.from({ length: i.qty }, () => ({
id: i.id,
l: i.l, w: i.w, h: i.h,
weight: i.weight,
volume: i.l * i.w * i.h
}))
).sort((a, b) => b.volume - a.volume);

const boxes: PackedBox[] = [];

for (const unit of units) {
let placed = false;

for (const pb of boxes) {
const maxVol = boxVolume(pb.box) * 0.85;
const fitsVolume = pb.usedVolume + unit.volume <= maxVol;
const fitsWeight = pb.usedWeight + unit.weight <= pb.box.maxWeight;
if (fitsVolume && fitsWeight) {
pb.usedVolume += unit.volume;
pb.usedWeight += unit.weight;
pb.items.push({ itemId: unit.id, count: 1 });
placed = true;
break;
}
}

if (!placed) {
const newBox = { box: preferredBox, items: [], usedVolume: 0, usedWeight: 0 };
newBox.usedVolume = unit.volume;
newBox.usedWeight = unit.weight;
newBox.items.push({ itemId: unit.id, count: 1 });
boxes.push(newBox);
}
}

return boxes;
}

Each returned PackedBox can then be translated into a parcel request for your shipping rate provider API.

Fast, Simple, and Surprisingly Accurate

  • Fast: It runs in linear time after sorting.
  • Deterministic: The same basket will always produce the same box structure. The QA on your team will thank you for this.
  • Extensible: You can add logic for fragile tags, category exclusions, or dimensional checks later.
  • Good enough: It typically gets within 10–15% of actual packaging behavior (in worst-case it uses 22% more boxes than optimal), which is plenty accurate for rate estimation.

Next Steps

You can refine the approach further by:

  • Adding fallback logic for items that don’t fit the preferred box. The simplified example in this post always opens a new preferred box, even if an item wouldn’t physically fit. In a real implementation, you should check if the item fits (considering rotation), then fall back to the smallest box in your catalog that can handle it. If no box fits, mark it as LTL or special handling.
  • Introducing multiple box sizes and testing which combination minimizes total cost.
  • Tracking differences between estimated and actual shipping cost to tune utilization.
  • Grouping by shipping tags (LTL, hazardous, fragile) before applying the FFD logic.
  • Eventually, integrating a full 3D bin-packing library if your product mix requires it.

Closing Thoughts

Shipping cost accuracy is often underestimated when teams first integrate rate APIs. But even a simple, rule-based grouping algorithm can deliver huge improvements without adding much complexity.
By taking ownership of how items are packed into parcels, your commerce system produces more realistic quotes, keeps customers happy, and reduces the risk of undercharging for shipping.

--

--

Written by David vd Hoop

0 followers

Composable Architect at Valtech, helping B2B manufacturers modernize commerce with headless and API-first architectures.