How to Add a Countdown Timer to Shopify Without an App
Countdown timers are one of the highest-leverage conversion elements you can add to a Shopify store, and you don't need to pay $10–$20/month for an app to get one. With about 30 lines of Liquid, CSS, and JavaScript, you can build a timer that lives inside your theme, loads instantly, and doesn't add another script to your storefront's performance budget. This guide walks through the whole build for a modern Shopify theme (Dawn, Refresh, Craft, or any Online Store 2.0 theme).
Why Use a Countdown Timer for Flash Sales (Without an App)
Urgency moves buyers. A visible timer that says "this offer ends in 2 hours" does three things at once: it gives shoppers a reason to decide now rather than later, it frames a discount as temporary rather than permanent, and it reduces the "I'll come back to this" problem that quietly kills e-commerce conversion.
App-based timers work fine, but they carry real costs:
- Monthly fees. Most countdown apps run on recurring subscriptions — check current pricing on the Shopify App Store before committing, because tiers change often.
- Extra scripts. Each app injects its own JavaScript and CSS into your storefront, which slows down Largest Contentful Paint and can hurt mobile conversion.
- Limited control. App timers usually render inside a fixed block with limited styling options, so they rarely match your theme's typography and spacing.
- Data you don't need. You're paying for analytics dashboards when what you actually want is a clock.
A custom snippet gives you full control over markup, styling, and behavior, adds almost no weight to the page, and costs nothing per month.
What You'll Need Before You Start
You need three things:
- A Shopify theme you can edit. Go to Online Store → Themes → Actions → Edit code. If you're on a theme you didn't build, duplicate it first so you have a rollback point.
- Basic comfort with Liquid, CSS, and JavaScript. You don't need to write code from scratch — you need to be able to paste it in the right place and change a few values.
- A test product or a draft theme. Never test a new snippet on a live theme during peak traffic hours.
One important limitation to understand up front: this timer runs in the shopper's browser, so it uses the visitor's local clock. That's fine for most flash sales. If you need a timer that's identical for every visitor regardless of their device clock or timezone, you'll need a server-side source for the end time — for example, a metafield you update manually, or a small app proxy. For most stores, a fixed end date in your theme settings is accurate enough.
Step 1: Create a New Snippet for the Timer Code
Snippets are reusable Liquid partials. Keeping the timer in a snippet means you can drop it onto a product page, a landing page, or a collection template without duplicating code.
- In Edit code, scroll to the Snippets folder.
- Click Add a new snippet and name it
countdown-timer. - Paste the following into the file:
{%- assign end_time = settings.countdown_end_time | default: '2025-12-31T23:59:59' -%}
<div class="countdown-timer" data-end="{{ end_time }}">
<span class="countdown-timer__label">{{ settings.countdown_label | default: 'Offer ends in' }}</span>
<span class="countdown-timer__clock" aria-live="polite">
<span data-unit="days">00</span>d
<span data-unit="hours">00</span>h
<span data-unit="minutes">00</span>m
<span data-unit="seconds">00</span>s
</span>
</div>
The data-end attribute is what your JavaScript will read. The aria-live="polite" attribute tells screen readers to announce updates without interrupting whatever the shopper is doing.
Save the snippet. It won't appear anywhere yet — that's expected.
Step 2: Add the Countdown Timer to Your Product Template
Now render the snippet where you want the timer to appear. The most common placement is directly under the product title or above the Add to Cart button, because that's where the purchase decision happens.
Open Sections → main-product.liquid (in Dawn and most OS 2.0 themes) and find the block that renders the product title. Add this line immediately after it:
{% render 'countdown-timer' %}
If you want the timer on every product rather than just one, that's it — you're done. If you want it on a single product only, wrap it in a conditional:
{% if product.handle == 'summer-bundle' %}
{% render 'countdown-timer' %}
{% endif %}
Replace summer-bundle with your actual product handle (the last part of the product URL). You can also render the snippet inside a custom Liquid block on any section if you'd rather not touch section code — add a Custom Liquid block in the theme editor and paste the render tag inside it.
Step 3: Style the Timer to Match Your Theme
Add styles at the bottom of Assets → base.css (or your theme's main stylesheet). Adjust the colors to match your brand:
.countdown-timer {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.75rem 1rem;
border-radius: 6px;
background: #111;
color: #fff;
font-size: 0.95rem;
font-weight: 600;
margin: 1rem 0;
}
.countdown-timer__clock span[data-unit] {
font-variant-numeric: tabular-nums;
min-width: 2ch;
display: inline-block;
text-align: center;
}
.countdown-timer--expired {
display: none;
}
Two details matter more than the rest. First, font-variant-numeric: tabular-nums keeps the digits from shifting width as they change, which prevents the whole timer from jittering every second. Second, min-width: 2ch reserves space so a "9" doesn't collapse the layout when it rolls to "10."
If your theme uses CSS variables for colors, swap the hex values for those variables so the timer inherits your palette automatically.
Step 4: Set the Timer to End at a Specific Time
You have two options for setting the end time, and the right one depends on how often you run promotions.
| Approach | Best for | How to change it | Trade-off |
|---|---|---|---|
| Hardcoded in the snippet | One-off, permanent campaigns | Edit the snippet directly | Requires a code edit each time |
| Theme setting | Frequent, non-technical updates | Theme editor → Theme settings | Requires adding a settings schema entry |
For the theme setting route, open Config → settings_schema.json and add a text field:
{
"type": "text",
"id": "countdown_end_time",
"label": "Countdown end time (ISO 8601)",
"default": "2025-12-31T23:59:59"
}
Use ISO 8601 format — 2026-01-15T18:00:00 — because JavaScript's Date parser handles it consistently across browsers. Then add the JavaScript that drives the clock, either in the snippet or in a separate asset file:
document.querySelectorAll('.countdown-timer').forEach(function (el) {
var end = new Date(el.dataset.end).getTime();
var clock = el.querySelector('.countdown-timer__clock');
function tick() {
var diff = end - Date.now();
if (diff <= 0) {
el.classList.add('countdown-timer--expired');
clearInterval(timer);
return;
}
var d = Math.floor(diff / 86400000);
var h = Math.floor((diff % 86400000) / 3600000);
var m = Math.floor((diff % 3600000) / 60000);
var s = Math.floor((diff % 60000) / 1000);
clock.querySelector('[data-unit="days"]').textContent = String(d).padStart(2, '0');
clock.querySelector('[data-unit="hours"]').textContent = String(h).padStart(2, '0');
clock.querySelector('[data-unit="minutes"]').textContent = String(m).padStart(2, '0');
clock.querySelector('[data-unit="seconds"]').textContent = String(s).padStart(2, '0');
}
tick();
var timer = setInterval(tick, 1000);
});
If the end time is in the past, the timer hides itself automatically instead of showing negative numbers — which is what you want, since a timer stuck at zero looks broken.
Step 5: Test and Troubleshoot Your Countdown Timer
Preview your theme before publishing, then work through this checklist:
- Timer doesn't appear. Confirm the
{% render 'countdown-timer' %}tag is inside a section that actually renders on that template. Check the theme editor's block order too — a hidden block won't show. - Timer shows all zeros. Your end date is in the past, or the format is wrong. Verify it's
YYYY-MM-DDTHH:MM:SSwith no timezone suffix. - Timer doesn't update. Open the browser console. A syntax error in your JavaScript will stop the whole file. Common culprits are a missing semicolon or a stray curly brace.
- Layout jumps every second. You skipped the
tabular-numsandmin-widthrules from Step 3. - Timer shows the wrong time for some visitors. This is the local-clock limitation. If exact synchronization matters, source the end time from a metafield or app proxy instead of a theme setting.
Also test on mobile. Timers that look fine on desktop often overflow narrow product pages, especially when they include a long label like "Flash sale ends in."
Conclusion
Building a countdown timer into your Shopify theme takes about 30 minutes and removes a recurring app subscription from your stack. You keep full control over styling, you avoid the performance cost of third-party scripts, and you can drop the same snippet onto any template. Start with a hardcoded end date on one product, confirm it converts, then move the setting into your theme editor so you can run promotions without touching code.
FAQ
Can I add a countdown timer to Shopify without any coding at all? Not without an app. Shopify's theme editor doesn't include a native countdown block, so the no-app route always involves editing your theme's Liquid, CSS, or JavaScript. The snippet approach above is the lowest-code version of that.
Will a countdown timer slow down my store? Not meaningfully. The snippet adds a few hundred bytes of HTML and roughly 20 lines of JavaScript, which is far less than a typical countdown app that loads its own framework and stylesheet. The main performance consideration is placing the script so it doesn't block rendering.
Does the timer reset for each visitor? No. It counts down to a fixed date and time you specify, so every visitor sees the same deadline based on their own device clock. If a shopper's device clock is wrong, their timer will be wrong too — which is why server-sourced end times are worth considering for high-stakes promotions.