jelonyx
No-app fixes · Fix 06

Shopify delivery date estimator
without an app.

Delivery uncertainty is one of the top reasons customers abandon product pages without buying. Showing a concrete date range like “Estimated delivery: Mon 11 – Wed 13 May” removes the guesswork. Most stores pay a monthly app fee for this. This fix calculates the window from today's date, skips weekends, and respects a dispatch cutoff time. It does this all in one Liquid snippet.

DifficultyBeginner
Time~15 minutes
CostFree
Theme accessRequired
Compatible with:Dawn 12+HorizonSenseCraftRefreshMost OS 2.0 themes

The code

One file. Edit the four config variables at the top of the script to match your store's dispatch and shipping times.

snippets/jlx-delivery-estimator.liquid
Liquid · CSS · JS
{% comment %}
  Jelonyx · Delivery Date Estimator : no app required
  Compatible with Dawn 12+, Horizon, and most Online Store 2.0 themes
  Source: jelonyx.com/shopify/no-app/delivery-date-estimator
{% endcomment %}

<div class="jlx-edd" id="jlx-edd" style="display:none" aria-live="polite">
  <span class="jlx-edd__icon" aria-hidden="true">
    <svg width="18" height="18" viewBox="0 0 18 18" fill="none">
      <rect x="1.5" y="6" width="11" height="7" rx="1" stroke="currentColor" stroke-width="1.4"/>
      <path d="M12.5 8.5H14a1 1 0 0 1 .87.5l1.13 1.95V13h-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
      <circle cx="4.5" cy="13" r="1.5" stroke="currentColor" stroke-width="1.4"/>
      <circle cx="13.5" cy="13" r="1.5" stroke="currentColor" stroke-width="1.4"/>
    </svg>
  </span>
  <span class="jlx-edd__body">
    <span class="jlx-edd__label">Estimated delivery</span>
    <span class="jlx-edd__dates" id="jlx-edd-dates"></span>
  </span>
</div>

<style>
  .jlx-edd {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 16px;
    padding: 12px 14px;
    border: 1px solid var(--color-border, rgba(0, 0, 0, 0.1));
    border-radius: 4px;
    font-family: inherit;
  }
  .jlx-edd__icon {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    color: var(--color-foreground, #1a1a1a);
    opacity: 0.45;
  }
  .jlx-edd__body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
  }
  .jlx-edd__label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-foreground, #1a1a1a);
    opacity: 0.45;
    line-height: 1;
  }
  .jlx-edd__dates {
    font-size: 14px;
    font-weight: 600;
    color: var(--color-foreground, #1a1a1a);
    line-height: 1.3;
  }
</style>

<script>
  (function () {
    'use strict';

    // ── Configuration ──────────────────────────────────────────────────────
    var CUTOFF_HOUR     = 14;   // Same-day dispatch cutoff (24-hour clock, store time)
    var PROCESSING_DAYS = 1;    // Business days from order to dispatch (0 = same-day if before cutoff)
    var MIN_SHIP_DAYS   = 3;    // Minimum business days in transit after dispatch
    var MAX_SHIP_DAYS   = 5;    // Maximum business days in transit after dispatch
    var UTC_OFFSET      = null; // Store UTC offset (e.g. 10 for AEST, -5 for EST, 0 for UTC/GMT).
                                // null = use the visitor's browser time.
    // ───────────────────────────────────────────────────────────────────────

    var el = document.getElementById('jlx-edd');
    var datesEl = document.getElementById('jlx-edd-dates');
    if (!el || !datesEl) return;

    var MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
    var DAYS   = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
    var DAY_MS = 86400000;

    // Returns store-timezone midnight as a Date, plus the current hour and day-of-week.
    // All returned date values are read with getUTC* methods to avoid browser timezone interference.
    function storeToday() {
      var now    = new Date();
      var utcMs  = now.getTime() + now.getTimezoneOffset() * 60000;
      var offset = UTC_OFFSET !== null ? UTC_OFFSET : -now.getTimezoneOffset() / 60;
      var ms     = utcMs + offset * 3600000;
      var d      = new Date(ms);
      var h      = d.getUTCHours();
      var midnight = new Date(ms - h * 3600000 - d.getUTCMinutes() * 60000
                              - d.getUTCSeconds() * 1000 - d.getUTCMilliseconds());
      return { midnight: midnight, hour: h, dow: d.getUTCDay() };
    }

    function isWE(d) { var w = d.getUTCDay(); return w === 0 || w === 6; }

    function nextBD(d) {
      var n = new Date(d.getTime() + DAY_MS);
      while (isWE(n)) n = new Date(n.getTime() + DAY_MS);
      return n;
    }

    function addBD(d, n) {
      var cur = d;
      for (var i = 0; i < n; i++) cur = nextBD(cur);
      return cur;
    }

    var today    = storeToday();
    var weekend  = today.dow === 0 || today.dow === 6;
    var late     = today.hour >= CUTOFF_HOUR;
    var start    = (weekend || late) ? nextBD(today.midnight) : today.midnight;
    var dispatch = addBD(start, PROCESSING_DAYS);
    var earliest = addBD(dispatch, MIN_SHIP_DAYS);
    var latest   = addBD(dispatch, MAX_SHIP_DAYS);

    var ed = earliest.getUTCDate(), em = earliest.getUTCMonth(), ew = earliest.getUTCDay();
    var ld = latest.getUTCDate(),   lm = latest.getUTCMonth(),   lw = latest.getUTCDay();

    datesEl.textContent = em === lm
      ? DAYS[ew] + ' ' + ed + ' – ' + DAYS[lw] + ' ' + ld + ' ' + MONTHS[lm]
      : DAYS[ew] + ' ' + ed + ' ' + MONTHS[em] + ' – ' + DAYS[lw] + ' ' + ld + ' ' + MONTHS[lm];

    el.style.display = 'flex';
  })();
</script>

How to install

  1. 01
    Open your theme code editor.

    In Shopify Admin, go to Online Store → Themes. On your active theme, click the three-dot menu and select Edit code.

  2. 02
    Create a new snippet.

    Under the Snippets folder, click Add a new snippet. Name it jlx-delivery-estimator and click Done.

  3. 03
    Paste the code and set your delivery config.

    Delete any placeholder content and paste the entire code block above. At the top of the <script> block, edit the four configuration variables to match your store's actual dispatch and shipping times. See the Configuring the estimator section below for what each variable controls. Save the file.

  4. 04
    Add the render tag via the theme customizer.

    In Shopify Admin, go to Online Store → Themes → Customize. Navigate to a product page. Click on the product information section in the left panel, then click Add block → Custom Liquid. Enter the following, then drag the block to just below the Buy buttons block. Save.

    {% render 'jlx-delivery-estimator' %}

    Alternatively, open sections/main-product.liquid in the code editor. Find {% render 'product-form' %} and add the render tag on the line directly after it.

  5. 05
    Test on a product page.

    Open any product page. Below the add to cart button you should see a row with a truck icon and a delivery date range. Check that the range looks correct for the current time of day. If you test after your cutoff hour, the range will start one extra business day later.

Configuring the estimator

Four variables at the top of the script control the output. Set them to match your store's actual operations.

  • CUTOFF_HOUR (default: 14): the hour in 24-hour clock before which same-day dispatch is possible. Orders placed before this hour use today as the start of processing; orders placed after it start from the next business day. Set to 0 to always start from the next business day, or 24 to always treat today as in-window.
  • PROCESSING_DAYS (default: 1): the number of business days between receiving the order and dispatching it. 0 = same-day dispatch for orders before the cutoff. 1 = dispatched the next business day. 2 = two days of handling before dispatch.
  • MIN_SHIP_DAYS and MAX_SHIP_DAYS (defaults: 3 and 5): the business-day range from dispatch to delivery. Set these to match your carrier's typical transit times. For express shipping, use 1 and 2. For international, use 7 and 14.
  • UTC_OFFSET (default: null): the UTC offset of your store's timezone, in hours. Examples: 10 for AEST, -5 for EST, 1 for CET. When null, the snippet uses the visitor's browser time. This is useful when your customers are in the same timezone as your warehouse. Set an explicit offset when the cutoff time must be accurate relative to your dispatch location, regardless of where the visitor is.

How it works

The snippet renders a hidden <div> with display:none. The JavaScript calculates the delivery range, writes it into the element, then sets display:flex to reveal it. This means the element is invisible if the script does not run, rather than showing empty space or a broken layout.

All date math works with Unix timestamps (milliseconds). Adding one day is always exactly 86,400,000 ms in UTC arithmetic, which avoids the daylight-saving edge cases that can occur when manipulating dates with local time methods. The storeToday() function converts the current moment into the store's timezone by shifting the UTC epoch, then reads the hour and day-of-week with getUTC* methods rather than the browser's local methods. This keeps the calculation consistent regardless of the visitor's location.

The addBD() helper steps forward one business day at a time by advancing by exactly 86,400,000 ms and skipping any result that lands on a Saturday (getUTCDay() === 6) or Sunday (getUTCDay() === 0).

Compatibility

Tested against Dawn 12+ and Horizon. The component reads --color-foreground and --color-border from your theme, so the colours match without any CSS edits.

The JavaScript uses only features available in all browsers that reach Shopify storefronts like Date, getUTCDay, getTimezoneOffset, and basic arithmetic. No polyfills required.

If you use a page speed tool that flags render-blocking scripts, note that the snippet's script block is small (under 1 KB) and wrapped in an IIFE that returns immediately if the target element is not found. It runs inline rather than deferred because the element it populates is directly below the ATC button. Deferring it would cause the element to flash in after page load.

Limitations

  • No public holiday awareness: the snippet skips weekends but does not know about public holidays. If your warehouse is closed on a holiday, orders placed before or on that day will show an optimistic delivery date. For stores with frequent holiday closures, hardcoding specific blackout dates in the script is the practical fix. You could also display a slightly wider shipping window as a buffer.
  • Same range for all products: the snippet shows the same delivery window on every product page it is rendered on. If you offer different shipping speeds for different product types (e.g. in-stock vs made-to-order), add Liquid conditionals using product.tags or product.type to render different config values per product.
  • Calendar days vs business days for shipping: MIN_SHIP_DAYS and MAX_SHIP_DAYS count business days in transit. If your carrier quotes calendar days, divide their estimate by roughly 1.4 to get the equivalent business-day range, or convert to calendar days by removing the weekend-skip logic from addBD().
  • No live countdown: the snippet calculates the window when the page loads and does not update dynamically. A visitor who leaves the tab open past the cutoff time will still see the original window. For a live “order in X hours” countdown, a more involved build is needed.
  • Headless storefronts: this is a Liquid snippet and does not apply to Hydrogen or other headless setups.
No-App Shopify Fix Sprint

Need this installed?

If you would rather not edit theme code, we can install and configure the estimator for your store. We will set it to your real dispatch times, match it to your carrier's transit window, and style it for your theme.