Articles on: FAQs

How Do I Customize My Packing Slip to Group Bundle Items Together?

By default, Shopify packing slips show bundle items as individual line items, making it hard to tell which items belong together in a bundle. This custom packing slip template groups bundle items under their bundle name, so your fulfillment team can quickly see what goes together.


This is How it looks


Custom Packing Slip Template


How to Set It Up


  1. In your Shopify admin, go to Settings > Shipping and delivery
  2. Scroll down to the Packing slips section
  3. Click Edit on the packing slip template
  4. Select all the existing code (Ctrl+A / Cmd+A) and delete it
  5. Paste the template code below in its place
  6. Click Save


Packing Slip Template Code


Copy the entire code block below and paste it into your packing slip template editor:


<div class="wrapper">
<div class="header">
<div class="shop-title">
<p class="to-uppercase">
{{ shop.name }}
</p>
</div>
<div class="order-title">
<p class="text-align-right">
Order {{ order.name }}
</p>
{% if order.po_number != blank %}
<p class="text-align-right">
PO number #{{ order.po_number }}
</p>
{% endif %}
<p class="text-align-right">
{{ order.created_at | date: format: "date" }}
</p>
</div>
</div>
<div class="customer-addresses">
<div class="shipping-address">
<p class="subtitle-bold to-uppercase">
{% if delivery_method.instructions != blank %}
Delivery to
{% else %}
Ship to
{% endif %}
</p>
<p class="address-detail">
{% if shipping_address != blank %}
{{ shipping_address.name }}
{% if shipping_address.company != blank %}
<br>
{{ shipping_address.company }}
{% endif %}
<br>
{{ shipping_address.address1 }}
{% if shipping_address.address2 != blank %}
<br>
{{ shipping_address.address2 }}
{% endif %}
{% if shipping_address.city_province_zip != blank %}
<br>
{{ shipping_address.city_province_zip }}
{% endif %}
<br>
{{ shipping_address.country }}
{% if shipping_address.phone != blank %}
<br>
{{ shipping_address.phone }}
{% endif %}
{% else %}
No shipping address
{% endif %}
</p>
</div>
<div class="billing-address">
<p class="subtitle-bold to-uppercase">
Bill to
</p>
<p class="address-detail">
{% if billing_address != blank %}
{{ billing_address.name }}
{% if billing_address.company != blank %}
<br>
{{ billing_address.company }}
{% endif %}
<br>
{{ billing_address.address1 }}
{% if billing_address.address2 != blank %}
<br>
{{ billing_address.address2 }}
{% endif %}
{% if billing_address.city_province_zip != blank %}
<br>
{{ billing_address.city_province_zip }}
{% endif %}
<br>
{{ billing_address.country }}
{% else %}
No billing address
{% endif %}
</p>
</div>
</div>
<hr>
<div class="order-container">
<div class="order-container-header">
<div class="order-container-header-left-content">
<p class="subtitle-bold to-uppercase">
Items
</p>
</div>
<div class="order-container-header-right-content">
<p class="subtitle-bold to-uppercase">
Quantity
</p>
</div>
</div>

{% comment %}
To adjust the size of line item images, change desired_image_size.
The other variables make sure your images print at high quality.
{% endcomment %}
{% assign desired_image_size = 58 %}
{% assign resolution_adjusted_size = desired_image_size | times: 300 | divided_by: 72 | ceil %}
{% capture effective_image_dimensions %}
{{ resolution_adjusted_size }}x{{ resolution_adjusted_size }}
{% endcapture %}

{% assign ungrouped_items_index = '' %}
{% assign group_names = '' %}
{% assign bundle_identifiers = '' %}

{% for line_item in line_items_in_shipment %}
{% if line_item.groups.size == 0 %}
{% assign ungrouped_items_index = ungrouped_items_index | append: forloop.index | append: ',' %}
{% else %}
{% for group in line_item.groups %}
{% unless group_names contains group.title %}
{% assign group_names = group_names | append: group.title | append: ',' %}
{% endunless %}
{% endfor %}

{% for property in line_item.properties %}
{% if property.first == '_Bundle' %}
{% unless bundle_identifiers contains property.last %}
{% assign bundle_identifiers = bundle_identifiers | append: property.last | append: ',' %}
{% endunless %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

{% assign ungrouped_items_index = ungrouped_items_index | split: ',' %}
{% assign group_names = group_names | split: ',' %}
{% assign bundle_identifiers = bundle_identifiers | split: ',' %}

{% if ungrouped_items_index.size > 0 %}
<h2>Individual Items</h2>
{% endif %}

{% for line_item in line_items_in_shipment %}
{% assign string_index = forloop.index | append: '' %}
{% if ungrouped_items_index contains string_index %}
<div class="flex-line-item">
<div class="flex-line-item-img">
{% if line_item.image != blank %}
<div class="aspect-ratio aspect-ratio-square" style="width: {{ desired_image_size }}px; height: {{ desired_image_size }}px;">
{{ line_item.image | img_url: effective_image_dimensions | img_tag: '', 'aspect-ratio__content' }}
</div>
{% endif %}
</div>
<div class="flex-line-item-description">
<p>
<span class="line-item-description-line">
{{ line_item.title }}
</span>
{% if line_item.variant_title != blank %}
<span class="line-item-description-line">
{{ line_item.variant_title }}
</span>
{% endif %}
{% if line_item.sku != blank %}
<span class="line-item-description-line">
{{ line_item.sku }}
</span>
{% endif %}
{% for group in line_item.groups %}
<span class="line-item-description-line">
Part of: {{ group.title }}
</span>
{% endfor %}
</p>
</div>
<div class="flex-line-item-quantity">
<p class="text-align-right">
{{ line_item.shipping_quantity }} of {{ line_item.quantity }}
</p>
</div>
</div>
{% endif %}
{% endfor %}

{% if bundle_identifiers.size == 0 %}
{% for group_name in group_names %}
<hr />
<h2>{{ group_name }}</h2>
{% for line_item in line_items_in_shipment %}
{% for group in line_item.groups %}
{% if group.title == group_name %}
<div class="flex-line-item">
<div class="flex-line-item-img">
{% if line_item.image != blank %}
<div class="aspect-ratio aspect-ratio-square" style="width: {{ desired_image_size }}px; height: {{ desired_image_size }}px;">
{{ line_item.image | img_url: effective_image_dimensions | img_tag: '', 'aspect-ratio__content' }}
</div>
{% endif %}
</div>
<div class="flex-line-item-description">
<p>
<span class="line-item-description-line">
{{ line_item.title }}
</span>
{% if line_item.variant_title != blank %}
<span class="line-item-description-line">
{{ line_item.variant_title }}
</span>
{% endif %}
{% if line_item.sku != blank %}
<span class="line-item-description-line">
{{ line_item.sku }}
</span>
{% endif %}
{% for group in line_item.groups %}
<span class="line-item-description-line">
Part of: {{ group.title }}
</span>
{% endfor %}
</p>
</div>
<div class="flex-line-item-quantity">
<p class="text-align-right">
{{ line_item.shipping_quantity }} of {{ line_item.quantity }}
</p>
</div>
</div>
{% endif %}
{% endfor %}
{% endfor %}
{% endfor %}
{% else %}
{% for group_name in group_names %}
<hr />
<h2>{{ group_name }}</h2>
{% for bundle_identifier in bundle_identifiers %}
{% assign is_heading_added = false %}

{% for line_item in line_items_in_shipment %}
{% for group in line_item.groups %}
{% if group.title == group_name %}

{% assign has_bundle_identifier = false %}
{% for property in line_item.properties %}
{% if property.first == '_Bundle' %}
{% assign has_bundle_identifier = true %}
{% endif %}
{% endfor %}


{% if has_bundle_identifier %}
{% assign belongs_to_bundle = false %}
{% for property in line_item.properties %}
{% if property.first == '_Bundle' and property.last == bundle_identifier %}
{% assign belongs_to_bundle = true %}
{% endif %}
{% endfor %}

{% unless belongs_to_bundle %}
{% break %}
{% endunless %}

{% unless is_heading_added %}
{% assign bundle_details = bundle_identifier | split: '.Q' %}
<h3>Bundle ID: {{ bundle_details[0] }}</h3>
<h3>Quantity: {{ bundle_details[1] }}</h3>
{% assign is_heading_added = true %}
{% endunless %}
{% endif %}


<div class="flex-line-item">
<div class="flex-line-item-img">
{% if line_item.image != blank %}
<div class="aspect-ratio aspect-ratio-square" style="width: {{ desired_image_size }}px; height: {{ desired_image_size }}px;">
{{ line_item.image | img_url: effective_image_dimensions | img_tag: '', 'aspect-ratio__content' }}
</div>
{% endif %}
</div>
<div class="flex-line-item-description">
<p>
<span class="line-item-description-line">
{{ line_item.title }}
</span>
{% if line_item.variant_title != blank %}
<span class="line-item-description-line">
{{ line_item.variant_title }}
</span>
{% endif %}
{% if line_item.sku != blank %}
<span class="line-item-description-line">
{{ line_item.sku }}
</span>
{% endif %}
{% for group in line_item.groups %}
<span class="line-item-description-line">
Part of: {{ group.title }}
</span>
{% endfor %}
</p>
</div>
<div class="flex-line-item-quantity">
<p class="text-align-right">
{{ line_item.shipping_quantity }} of {{ line_item.quantity }}
</p>
</div>
</div>
{% endif %}
{% endfor %}
{% endfor %}

{% unless has_bundle_identifier %}
{% break %}
{% endunless %}
{% endfor %}
{% endfor %}
{% endif %}

</div>
{% unless includes_all_line_items_in_order %}
<hr class="subdued-separator">
<p class="missing-line-items-text ">
There are other items from your order not included in this shipment.
</p>
{% endunless %}
<hr>
{% if order.note != blank %}
<div class="notes">
<p class="subtitle-bold to-uppercase">
Notes
</p>
<p class="notes-details">
{{ order.note }}
</p>
</div>
{% endif %}
{% if delivery_method.instructions != blank %}
<div class="notes">
<p class="subtitle-bold to-uppercase">
Delivery instructions
</p>
<p class="notes-details">
{{ delivery_method.instructions }}
</p>
</div>
{% endif %}
<div class="footer">
<p>
Thank you for shopping with us!
</p>
<p>
<strong>
{{ shop.name }}
</strong>
<br>
{{ shop_address.summary }}
<br>
{{ shop.email }}
<br>
{{ shop.domain }}
</p>
</div>
</div>
<style type="text/css">
body {
font-size: 15px;
}

* {
box-sizing: border-box;
}

.wrapper {
width: 831px;
margin: auto;
padding: 4em;
font-family: "Noto Sans", sans-serif;
font-weight: 250;
}

.header {
width: 100%;
display: -webkit-box;
display: -webkit-flex;
display: flex;
flex-direction: row;
align-items: top;
}

.header p {
margin: 0;
}

.shop-title {
-webkit-box-flex: 6;
-webkit-flex: 6;
flex: 6;
font-size: 1.9em;
}

.order-title {
-webkit-box-flex: 4;
-webkit-flex: 4;
flex: 4;
}

.customer-addresses {
width: 100%;
display: inline-block;
margin: 2em 0;
}

.address-detail {
margin: 0.7em 0 0;
line-height: 1.5;
}

.subtitle-bold {
font-weight: bold;
margin: 0;
font-size: 0.85em;
}

.to-uppercase {
text-transform: uppercase;
}

.text-align-right {
text-align: right;
}

.shipping-address {
float: left;
min-width: 18em;
max-width: 50%;
}

.billing-address {
padding-left: 20em;
min-width: 18em;
}

.order-container {
padding: 0 0.7em;
}

.order-container-header {
display: inline-block;
width: 100%;
margin-top: 1.4em;
}

.order-container-header-left-content {
float: left;
}

.order-container-header-right-content {
float: right;
}

.flex-line-item {
display: -webkit-box;
display: -webkit-flex;
display: flex;
flex-direction: row;
align-items: center;
margin: 1.4em 0;
page-break-inside: avoid;
}

.flex-line-item-img {
margin-right: 1.4em;
min-width: {{ desired_image_size }}px;
}

.flex-line-item-description {
-webkit-box-flex: 7;
-webkit-flex: 7;
flex: 7;
}

.line-item-description-line {
display: block;
}

.flex-line-item-description p {
margin: 0;
line-height: 1.5;
}

.flex-line-item-quantity {
-webkit-box-flex: 3;
-webkit-flex: 3;
flex: 3;
}

.subdued-separator {
height: 0.07em;
border: none;
color: lightgray;
background-color: lightgray;
margin: 0;
}

.missing-line-items-text {
margin: 1.4em 0;
padding: 0 0.7em;
}

.notes {
margin-top: 2em;
}

.notes p {
margin-bottom: 0;
}

.notes .notes-details {
margin-top: 0.7em;
}

.footer {
margin-top: 2em;
text-align: center;
line-height: 1.5;
}

.footer p {
margin: 0;
margin-bottom: 1.4em;
}

hr {
height: 0.14em;
border: none;
color: black;
background-color: black;
margin: 0;
}

.aspect-ratio {
position: relative;
display: block;
background: #fafbfc;
padding: 0;
}

.aspect-ratio::before {
z-index: 1;
content: "";
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border: 1px solid rgba(195,207,216,0.3);
}

.aspect-ratio--square {
width: 100%;
padding-bottom: 100%;
}

.aspect-ratio__content {
position: absolute;
max-width: 100%;
max-height: 100%;
display: block;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
</style>


Key Features


  • Groups bundle items together - All items from the same bundle are grouped under the bundle name heading, instead of being listed individually
  • Shows Bundle ID and quantity - Each bundle instance shows its Bundle ID and quantity, so you can distinguish between multiple bundles of the same type
  • Individual items shown separately - Non-bundle items are listed under an "Individual Items" section so they don't get mixed up with bundle products
  • Multiple bundles supported - If an order contains more than one bundle, each bundle gets its own section
  • Product images, variants, and SKUs - Each item shows its product image, variant title, and SKU for easy identification


Good to Know


  • This template replaces your entire packing slip layout. If you've previously customized your packing slip, make sure to save a backup of your current template before replacing it.
  • The template works with all FoxSell bundle types (Fixed, Mix and Match, Volume, Dynamic Add-Ons, Cross-Sell).
  • Bundle grouping uses the _Bundle line item property that FoxSell adds to each order. If you see items not being grouped, make sure your bundles are set up through FoxSell Bundles Plus.
  • This only affects packing slips. To see bundle details on your order admin page, check out our guide on enabling the FoxSell Bundle Order admin block.


Need help? Contact us via in-app chat or email support@foxsell.app.


Updated on: 20/03/2026

Was this article helpful?

Share your feedback

Cancel

Thank you!