How to Add a Shipping Cutoff Timer to Shopify Without an App

2026-09-24 · shipping fulfillment

A shipping cutoff timer tells shoppers exactly how long they have left to order and still get their package shipped that day. It's one of the few conversion elements that works on urgency and reduces support tickets ("did my order ship yet?"). Most apps that do this charge a monthly fee, and many inject scripts that slow down your storefront. You can build the same thing directly into your Shopify theme with about 60 lines of Liquid and JavaScript — no app, no recurring cost, full control.

Why a Shipping Cutoff Timer Boosts Conversions (and Why You Don't Need an App)

The psychology is simple: a deadline converts better than a vague promise. "Order within 3 hours 12 minutes for same-day dispatch" outperforms "Fast shipping!" because it's specific and time-bound. It also sets accurate expectations, which cuts down on "where is my order" emails from customers who ordered at 9pm expecting same-day handling.

You don't need an app for this because a countdown timer is fundamentally just:

Shopify apps that offer this typically bundle it with shipping rules, delivery-date estimates, or upsells — you're paying for the bundle, not the timer. If you only want the timer, theme code is leaner, faster, and free. It also won't break when an app updates or when you switch themes (though you'll need to re-add it after a theme change — see the testing section).

One honest caveat: this approach assumes a single daily cutoff time in a single timezone (your warehouse's). If you ship from multiple warehouses with different cutoffs per region, or you need per-product handling times, an app like a delivery-date-estimate tool is genuinely easier. For the majority of small stores with one fulfillment location, the code below is enough.

What You'll Need Before You Start Editing Theme Code

Before touching anything, get these in place:

Item Where to find it Example value
Cutoff time Your fulfillment schedule 14:00
Warehouse timezone IANA timezone name America/New_York
Shipping days Your carrier pickup days Mon–Fri
Theme file editor Admin → Online Store → Themes → Edit code

Use IANA timezone names (like America/New_York, Europe/London), not abbreviations like "EST" — abbreviations are ambiguous and break across daylight saving changes.

Step 1: Create the Countdown Snippet in Your Theme

In the theme code editor, open the snippets folder and click Add a new snippet. Name it shipping-cutoff-timer.liquid. Naming it as a snippet means you can render it from multiple templates (product, cart, and even a banner) without duplicating code.

Paste this starter structure:

{%- assign cutoff_hour = 14 -%}
{%- assign cutoff_minute = 0 -%}
{%- assign cutoff_tz = 'America/New_York' -%}

<div
  id="shipping-cutoff-timer"
  class="shipping-cutoff-timer"
  data-cutoff-hour="{{ cutoff_hour }}"
  data-cutoff-minute="{{ cutoff_minute }}"
  data-cutoff-tz="{{ cutoff_tz }}"
  hidden
>
  <span class="shipping-cutoff-timer__label">Order within</span>
  <span class="shipping-cutoff-timer__clock" data-timer-clock>--:--:--</span>
  <span class="shipping-cutoff-timer__label">for same-day dispatch</span>
</div>

The hidden attribute keeps the timer invisible until JavaScript confirms it should show. This prevents a flash of "00:00:00" or a timer that appears after the cutoff has already passed.

Step 2: Add the Liquid Logic for Your Shipping Cutoff Time

The tricky part of any cutoff timer is timezones. Shopify renders Liquid on its servers, and date filters output in the store's configured timezone — not the customer's browser timezone. That mismatch is the #1 source of "my timer says 4 hours but it's already 3pm" bugs.

The clean fix: let JavaScript do the timezone math using the browser's Intl API, and use Liquid only to pass your cutoff settings into the DOM. That's what the data- attributes above do.

If you want a server-side fallback message (for customers with JavaScript disabled), add this just below the timer div:

<noscript>
  <p class="shipping-cutoff-timer__fallback">
    Orders placed before {{ cutoff_hour }}:{{ cutoff_minute }} on business days ship the same day.
  </p>
</noscript>

You can also make the cutoff configurable per store without editing code, by moving the three assign lines into a theme setting. If you're comfortable with settings_schema.json, add a text setting for the timezone and a number setting for the hour. Otherwise, editing the snippet directly is fine — it's one file, and you'll rarely change it.

Render the snippet where you want it. In sections/main-product.liquid (or your theme's product template), add near the buy button:

{% render 'shipping-cutoff-timer' %}

Step 3: Write the JavaScript Countdown and Hide It After Cutoff

Add this to the bottom of the snippet, inside a <script> tag. It calculates the next cutoff moment in your warehouse timezone, compares it to the customer's current time, and updates every second.

<script>
(function () {
  const el = document.getElementById('shipping-cutoff-timer');
  if (!el) return;

  const hour = parseInt(el.dataset.cutoffHour, 10);
  const minute = parseInt(el.dataset.cutoffMinute, 10);
  const tz = el.dataset.cutoffTz;
  const clock = el.querySelector('[data-timer-clock]');

  function nextCutoff() {
    const now = new Date();
    // Build "today at cutoff" in the warehouse timezone
    const fmt = new Intl.DateTimeFormat('en-US', {
      timeZone: tz, hour12: false,
      year: 'numeric', month: '2-digit', day: '2-digit',
      hour: '2-digit', minute: '2-digit', second: '2-digit'
    });
    const parts = Object.fromEntries(
      fmt.formatToParts(now).map(p => [p.type, p.value])
    );
    let target = Date.UTC(
      parts.year, parts.month - 1, parts.day, hour, minute, 0
    );
    // Offset between UTC and warehouse local time
    const local = Date.UTC(
      parts.year, parts.month - 1, parts.day,
      parts.hour, parts.minute, parts.second
    );
    const offset = local - now.getTime();
    target = target - offset;
    if (target <= now.getTime()) target += 86400000;
    return target;
  }

  function pad(n) { return String(n).padStart(2, '0'); }

  function tick() {
    const now = Date.now();
    let diff = nextCutoff() - now;
    if (diff <= 0) { el.hidden = true; return; }

    const h = Math.floor(diff / 3600000);
    const m = Math.floor((diff % 3600000) / 60000);
    const s = Math.floor((diff % 60000) / 1000);
    clock.textContent = h + ':' + pad(m) + ':' + pad(s);
    el.hidden = false;
  }

  tick();
  setInterval(tick, 1000);
})();
</script>

Two things this handles that naive timers don't:

Step 4: Style the Timer and Place It on Product and Cart Pages

Add CSS to your theme's assets/base.css (or theme.css, depending on your theme):

.shipping-cutoff-timer {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.9rem;
  font-weight: 600;
  padding: 0.5rem 0.75rem;
  border-radius: 6px;
  background: #f3f7f4;
  color: #1a5c3a;
  margin: 0.75rem 0;
}
.shipping-cutoff-timer__clock {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

tabular-nums keeps the digits from shifting width as the seconds change — without it, the text jitters every tick.

For placement, render the snippet in two spots:

You can also add it to a cart drawer if your theme uses one. Find the drawer's Liquid file (often snippets/cart-drawer.liquid) and render the snippet there.

Testing Your Timer and Avoiding Common Timezone Mistakes

Test before you publish. Use your theme's Preview and check these cases:

The most common mistake is using new Date() with a hardcoded offset like -05:00. That works until daylight saving flips and your timer is off by an hour for half the year. Always derive the offset from Intl.DateTimeFormat as shown above.

Second most common: rendering the timer with Liquid's date filter and assuming it reflects the customer's clock. It reflects the store's timezone setting, which may differ from both the customer and your warehouse.

Third: forgetting that theme updates overwrite your edits. When you update your theme, Shopify may replace files. Keep a copy of the snippet and the CSS block in a note, and re-add after major theme updates. Duplicating the theme before edits (from the prep section) makes rollback painless.

Conclusion

A shipping cutoff timer built into your theme costs nothing, loads faster than an app, and gives you complete control over the message and placement. The four steps above — snippet, Liquid settings, JavaScript countdown, and styling — take about 30 minutes for a single-location store. Test the timezone edge cases carefully, and the timer will quietly do its job on every product and cart page.

FAQ

Will this work if my store uses multiple currencies or languages? Yes. The timer is independent of currency and language settings. The only text is the label you write in the snippet, so translate it per market if you use Shopify Markets.

Can I show different cutoffs for different products? Not with this single-snippet approach. You'd need to pass a product-specific cutoff value into the snippet via a metafield. That's doable but adds complexity — at that point, a delivery-estimate app may be the better trade.

Does the timer keep counting if a customer leaves the tab open overnight? Yes. The setInterval recalculates against the live clock every second, so when the cutoff passes the timer hides itself, and when the next day's cutoff window opens it reappears.