How to Set Up Free Shipping Bar on Shopify Without an App
You can build a free shipping progress bar in Shopify without paying for an app or adding a third-party script. The whole thing is roughly 40 lines of Liquid, a small block of CSS, and two template edits. It runs on your own theme, so there's no monthly fee and no external request slowing down your store.
The bar works by reading the cart's current subtotal, comparing it to a threshold you set, and telling the customer how much more they need to spend to unlock free shipping. That single sentence moves average order value because it gives shoppers a specific, achievable target instead of a vague promise.
Why a Free Shipping Bar Lifts Average Order Value
Free shipping is one of the strongest purchase triggers in e-commerce, but a static banner that says "Free shipping over $50" only helps customers who were already planning to spend $50. A progress bar does something different: it shows the gap between what's in the cart right now and the threshold.
A customer with $38 in the cart sees "You're $12 away from free shipping." That's a concrete nudge with a defined finish line, and it's far more persuasive than a generic offer. Two behaviors follow:
- Threshold chasing. Shoppers add a small item to cross the line rather than pay a shipping fee they perceive as wasted money.
- Fewer abandoned carts at checkout. Shipping cost shock is a leading cause of cart abandonment. Showing the threshold early, on the cart page and in the header, removes the surprise.
The mechanic only works if the threshold is set where your margins can absorb it. If free shipping at $50 destroys your profit on a $45 order, set it at $65 and let the bar do its job.
What You Need Before You Start (Theme Access and Backups)
Before you touch any code:
- Theme access. You need permission to edit theme code. In Shopify admin, go to Online Store > Themes. If you're a staff member without code permissions, ask the store owner to grant it.
- A backup of your live theme. Next to your published theme, click Actions > Duplicate. This creates a copy you can restore if something breaks. Do not skip this.
- A development theme. Click Actions > Edit code on the duplicate, or create a new unpublished theme and work there. Preview it before publishing. Editing a live theme directly means customers see your mistakes in real time.
- Your free shipping threshold and currency format. Know the exact number and how your store displays money. Shopify's
moneyfilter handles formatting, so you'll use that rather than hardcoding a dollar sign. - Familiarity with Liquid basics. You need to recognize
{% if %},{{ }}, and filters likemoney_without_currency. If those are unfamiliar, Shopify's Liquid reference is free and short.
One decision to make now: will the bar appear only on the cart page, or sitewide in the header? Sitewide gets more impressions but can feel intrusive on product pages. Cart-page-only is the safer starting point.
Step 1: Add the Progress Bar Snippet to Your Theme
Snippets are reusable chunks of Liquid. Putting the bar in a snippet means you write it once and render it in multiple places.
In your theme editor, open the snippets folder and click Add a new snippet. Name it free-shipping-bar.liquid.
Paste this:
{%- assign threshold = 5000 -%}
{%- assign remaining = threshold | minus: cart.total_price -%}
<div class="fsb" data-threshold="{{ threshold }}">
{%- if cart.total_price >= threshold -%}
<p class="fsb__message fsb__message--unlocked">
You've unlocked free shipping.
</p>
{%- else -%}
<p class="fsb__message">
You're {{ remaining | money }} away from free shipping.
</p>
{%- endif -%}
<div class="fsb__track">
<div class="fsb__fill" style="width: {{ cart.total_price | times: 100 | divided_by: threshold | at_most: 100 }}%"></div>
</div>
</div>
Two things to understand here. First, threshold is written in cents, so 5000 means $50.00. Change that number to your own threshold. Second, the width calculation multiplies the cart total by 100 and divides by the threshold, then caps it at 100 with at_most so the bar never overflows past full.
If your store uses a currency where the base unit isn't cents, adjust the threshold value accordingly and test the output.
Step 2: Insert the Bar Into Your Cart and Header Templates
Now render the snippet where customers will see it.
Cart page: Open sections/main-cart-items.liquid (in some themes it's templates/cart.liquid). Place this line near the top of the cart items list, above the line items:
{% render 'free-shipping-bar' %}
Header: Open sections/header.liquid and add the same render line just inside the header wrapper, above the navigation:
{% render 'free-shipping-bar' %}
If your theme uses a different header section name, search the sections folder for "header" and open the file that contains the site logo markup.
Save both files. The bar should now appear in preview. If it doesn't, check that the snippet filename matches exactly what you typed in the render tag, including hyphens.
Step 3: Style the Bar With CSS to Match Your Brand
Add styles to your theme's main stylesheet, usually assets/base.css or assets/theme.css. Append at the bottom:
.fsb { padding: 10px 16px; text-align: center; font-size: 14px; }
.fsb__message { margin: 0 0 6px; }
.fsb__message--unlocked { font-weight: 600; }
.fsb__track {
height: 6px;
background: #e5e5e5;
border-radius: 3px;
overflow: hidden;
}
.fsb__fill {
height: 100%;
background: #111;
transition: width 0.3s ease;
}
Swap the background colors for your brand palette. Keep the track light and the fill dark enough to read at a glance. The transition makes the bar animate when a customer adds an item, which draws the eye to the change.
If your theme uses CSS variables, use those instead of hex codes so the bar inherits your brand automatically.
Step 4: Test the Bar Across Cart Values and Devices
Test these cases before publishing:
| Cart subtotal | Expected bar state | What to check |
|---|---|---|
| $0 (empty cart) | Bar mostly empty, message shows full remaining amount | Message uses correct currency format |
| $25 of $50 threshold | Bar half full, message reads "$25.00 away" | Width math is correct |
| $49.99 of $50 threshold | Bar nearly full, message reads "$0.01 away" | No rounding errors |
| $50 or more | Bar full, unlocked message shows | Width caps at 100% |
| $500 order | Bar full, no overflow | at_most filter working |
Then check devices:
- Desktop browser. Resize the window to confirm the bar doesn't break the header layout.
- Mobile. Most Shopify traffic is mobile. Check the bar doesn't push navigation off screen or wrap awkwardly.
- Cart drawer themes. If your theme uses a slide-out cart instead of a cart page, render the snippet inside the drawer's section file instead.
Add an item to the cart and watch the bar animate. Remove it and confirm the message resets.
Common Errors and How to Fix Them
- Bar always shows full. Your threshold is set in cents but you entered a dollar value, so
5000became5. Multiply your threshold by 100. - "Liquid error: divided by 0". The threshold variable didn't get assigned, usually a typo in the
assignline. Check the variable name matches every use. - Bar shows on every page including checkout. Shopify checkout is not theme-editable, so this shouldn't happen. If it does, you placed the render tag in a section that loads globally. Move it.
- Message shows wrong currency. You're using a hardcoded symbol. Use the
moneyfilter so Shopify formats it. - Bar doesn't update without a page refresh. Expected on the cart page, which reloads on change. For live updates in a cart drawer, you'll need JavaScript listening to Shopify's cart update events.
- Styles not applying. Your theme may load CSS from a different file, or a more specific selector is overriding yours. Increase specificity or add
!importantas a last resort.
Tracking Results: Measuring the Impact on AOV
Set a baseline before you publish. In Shopify admin, go to Analytics > Reports and find your average order value for the last 30 days. Note it.
After two weeks with the bar live, compare. A meaningful lift is usually 3% to 8% on stores with a sensible threshold. If AOV is flat, the threshold may be too high for your typical basket size, or the bar isn't visible enough.
Watch these alongside AOV:
- Conversion rate. A bar that annoys customers can hurt conversion even while AOV rises.
- Cart abandonment rate. Should fall if shipping cost shock was a factor.
- Units per transaction. If customers are adding cheap filler items, this rises while margin per order may not.
Use Shopify's built-in reports rather than guessing. If you want deeper segment tracking, Google Analytics 4 with Shopify's integration can show behavior by traffic source.
Conclusion
A free shipping bar is one of the few conversion tools you can build yourself in under an hour, with no app subscription and no third-party script. The code is short, the logic is simple, and the payoff shows up directly in average order value. Build it in a duplicate theme, test it against the table above, and publish only once the math checks out.
FAQ
Does this work with any Shopify theme? Yes, as long as the theme uses Liquid and you can edit sections. Online Store 2.0 themes and most legacy themes both work. The only variable is which section files hold your header and cart markup, so search the sections folder if the filenames differ.
Will the bar update live when a customer adds an item? On the cart page, no. The page reloads on cart changes, so the bar recalculates then. In a slide-out cart drawer, you'd need to add JavaScript that listens for Shopify's cart update events and re-renders the bar.
What if my free shipping threshold changes seasonally?
Edit the threshold value in the snippet. Because it's a single assign line, you can change it in seconds. If you want to change it without touching code, you'd need a theme setting block, which is more setup but lets you adjust it from the theme customizer.