Countdown Timers on Paywalls

Add a live countdown timer to a Superwall paywall with Liquid: count down from when the paywall opens, from install, or to a fixed sale end date, and hide the offer when time runs out.

A countdown timer shows how long an offer lasts, such as "Offer ends in 14

". In Superwall, a timer is a text element whose text uses the countdown_from Liquid filter. It updates every second while the paywall is on screen, and needs no code in your app.

Add a timer

  1. In the paywall editor, add a Text element, or select an existing one.

  2. Set its text to a countdown. For a 15-minute timer that starts when the paywall opens:

    {{ device.localDateTime | date_add: "15m" | countdown_from: state.now }}
  3. Preview the paywall. The text counts down from 0:15:00.

countdown_from takes the date the countdown ends on its left, and the date it counts from after the colon. Use state.now, the current time, as the start: referencing it makes the text re-render every second.

Choose when the timer ends

The end date decides whether the timer restarts:

Count down toEnd dateRestarts
A time after the paywall opensdevice.localDateTime | date_add: "15m"Every time the paywall opens
A time after installdevice.appInstallDate | date_add: "3d"Never; it's the same for each user
A fixed date, such as a sale's end"2026-12-01T08:00:00Z"Never; it's the same for everyone
A date your app sendsA user attribute or placement parameter holding an ISO 8601 dateWhen your app changes the date

For example, a 3-day launch offer that every user gets once, counted from install:

Your offer ends in {{ device.appInstallDate | date_add: "3d" | countdown_from: state.now, "long_most_significant", "days" }}

This shows 3 days, then 2 days, and so on down to the last hours and minutes.

Adding and subtracting time

date_add and date_subtract shift a date. Pass a duration such as "30s", "15m", "2h", "3d", "1w", "1M" or "1y", or a number and a unit:

{{ device.localDateTime | date_add: "90m" }}
{{ device.localDateTime | date_add: 3, "days" }}
{{ "2026-12-01T08:00:00Z" | date_subtract: "1d" }}

Capital M is months and lowercase m is minutes. date_add: "1M" adds a month.

Format the timer

countdown_from takes up to four options, in order:

{{ end_date | countdown_from: state.now, style, max_unit, column }}
  • style: how the countdown is written. The default is digital.

    StyleExample
    digital2:03:04
    narrow2h 3m 4s
    short2 hr, 3 min, 4 sec
    long2 hours, 3 minutes, 4 seconds
    long_most_significant2 hours: only the largest unit
  • max_unit: the largest unit to show. The default is hours, so 3 days shows as 72:00:00. Pass days to show 3 days, 0:00:00 instead, or 3 days with the long style. The options are years, months, weeks, days, hours, minutes and seconds.

  • column: return just one unit, as a number, for building your own layout. For example, 47:12:03 with the column set to minutes returns 12.

To show each unit in its own box, use one text element per unit:

{{ device.localDateTime | date_add: "1h" | countdown_from: state.now, "digital", "hours", "minutes" }}
{{ device.localDateTime | date_add: "1h" | countdown_from: state.now, "digital", "hours", "seconds" }}

A column returns a plain number, so pad it yourself if you want two digits:

{% assign s = device.localDateTime | date_add: "1h" | countdown_from: state.now, "digital", "hours", "seconds" %}{% if s < 10 %}0{% endif %}{{ s }}

Show it in the user's language

countdown_from writes units in English. Add localize_countdown with a language code to write them in another language:

{{ device.appInstallDate | date_add: "3d" | countdown_from: state.now, "digital", "days" | localize_countdown: device.deviceLanguageCode }}

On a French device, 1 day, 3:30:10 becomes 1 j et 3:30:10. For the rest of the paywall's text, see Paywall Localization.

When the timer runs out

countdown_from expects the end date to be later than the start. Once the end date passes, the numbers go negative. To show something else instead, read the seconds left as a number and check it:

{% assign left = device.appInstallDate | date_add: "3d" | countdown_from: state.now, "digital", "seconds", "seconds" %}{% if left > 0 %}Ends in {{ device.appInstallDate | date_add: "3d" | countdown_from: state.now }}{% else %}This offer has ended{% endif %}

To show a different paywall once an offer ends, target the users who should still see it with an audience filter, such as device.daysSinceInstall less than 3.

Things to know

  • The timer runs only while the paywall is on screen. It doesn't schedule anything on the device.
  • device.localDateTime is the device's clock, so a user who changes their clock changes the timer. Use a date your server sends when that matters.
  • A countdown is display only. It doesn't end a discount in the App Store or Google Play. End the offer with your campaign or product setup too.
  • Make sure the countdown is honest. See Are paywall events and urgency messaging App Store compliant?

How is this guide?

On this page