Free Announcements Bar Scheduler for Dawn

Topic summary

A free announcement bar scheduler has been created specifically for Shopify’s Dawn theme, eliminating the need for paid apps ($10/month) to schedule promotional messages.

Key Features:

  • Schedule multiple announcements with specific start and end dates
  • Display different messages on different days
  • Support for overlapping announcements (bar auto-resizes)
  • Ideal for planning Black Friday/promotional campaigns in advance

Installation Process:

  1. Navigate to: Sales Channel > Themes > [three dot menu] > Edit code > sections folder > New File
  2. Copy and paste the provided code snippet
  3. Add the block to your header
  4. Configure announcements with scheduling enabled

Limitations:

  • Currently works only with Dawn theme
  • Requires manual code customization for other themes

The code includes scheduling logic that checks current timestamps against configured start/end dates using dropdown selectors for month, day, and year. The solution provides basic scheduling functionality without recurring subscription costs.

Summarized with AI on November 24. AI used: claude-sonnet-4-5-20250929.

I wanted to be able to simply schedule an announcements bar ahead of time for black friday, showing a different message on each day. I made this simple Announcement Bar Scheduler, sharing for free (So you don’t have to pay for an app $10/mo for this basic functionality.)

Works with Dawn only, but could be customized to your theme.

Once installed, you just add the block to your header. Then you add as many announcements as you want to that announcements bar, and you can click “Enable Scheduling” and set a start and end date for the message. You can have more than one message appear at once, the bar will resize accordingly.

Great for setting up your Black Friday announcements ahead of time.

Installation:

Go to Sales Channel > Themes > [three dot menu] next to Edit Theme Button > Edit code > sections folder > New File.

Copy and paste code below:

{%- liquid
  # 1. Capture all active announcements first
  # This allows us to check if there is anything to show before rendering the outer bar
  capture announcement_content
    for block in section.blocks
      case block.type
        when 'announcement'
          if block.settings.text != blank
            
            # --- Scheduling Logic ---
            assign show_announcement = true
            
            if block.settings.enable_schedule
              assign current_timestamp = 'now' | date: '%s' | plus: 0
              
              # Build start date from dropdowns
              if block.settings.start_month != blank and block.settings.start_month != '' and block.settings.start_day != blank and block.settings.start_day != '' and block.settings.start_year != blank and block.settings.start_year != ''
                assign start_month = block.settings.start_month | prepend: '00' | slice: -2, 2
                assign start_day = block.settings.start_day | prepend: '00' | slice: -2, 2
                assign start_date = block.settings.start_year | append: '-' | append: start_month | append: '-' | append: start_day
                assign start_timestamp = start_date | date: '%s' | plus: 0
                if current_timestamp < start_timestamp
                  assign show_announcement = false
                endif
              endif
              
              # Build end date from dropdowns
              if block.settings.end_month != blank and block.settings.end_month != '' and block.settings.end_day != blank and block.settings.end_day != '' and block.settings.end_year != blank and block.settings.end_year != ''
                assign end_month = block.settings.end_month | prepend: '00' | slice: -2, 2
                assign end_day = block.settings.end_day | prepend: '00' | slice: -2, 2
                assign end_date = block.settings.end_year | append: '-' | append: end_month | append: '-' | append: end_day
                # Add 86399 seconds (end of day)
                assign end_timestamp = end_date | date: '%s' | plus: 86399
                if current_timestamp > end_timestamp
                  assign show_announcement = false
                endif
              endif
            endif
            # ------------------------

            if show_announcement
              # Render the individual message block
              echo '<div class="announcement-bar__message h5" ' | append: block.shopify_attributes | append: '>'
                if block.settings.link != blank
                  echo '<a href="' | append: block.settings.link | append: '" class="announcement-bar__link link link--text focus-inset animate-arrow">'
                  echo '<span>'
                  echo block.settings.text | escape
                  echo '</span>'
                  assign arrow_icon = 'icon-arrow.svg' | inline_asset_content
                  echo arrow_icon
                  echo '</a>'
                else
                  echo block.settings.text | escape
                endif
              echo '</div>'
            endif
          endif
      endcase
    endfor
  endcapture
-%}

{%- # 2. Only render the container if we actually have content to show -%}
{%- if announcement_content != blank -%}
  <div
    class="announcement-bar color-{{ section.settings.color_scheme }} gradient"
    role="region"
    aria-label="Announcements"
  >
    <div class="page-width">
      <!-- We use the alignment setting here to align all messages at once -->
      <div class="{{ section.settings.text_alignment }}">
        {{ announcement_content }}
      </div>
    </div>
  </div>
{%- endif -%}

<style>
  /* Base bar styles */
  .announcement-bar {
    border-bottom: 1px solid rgba(var(--color-foreground), 0.08);
  }

  /* Individual message styles */
  .announcement-bar__message {
    padding: 10px 0;
    margin: 0;
  }

  /* If there are multiple messages, add a faint line between them */
  .announcement-bar__message + .announcement-bar__message {
    border-top: 1px solid rgba(var(--color-foreground), 0.1);
  }

  /* Link styling */
  .announcement-bar__link {
    display: inline-block;
    text-decoration: none;
    color: inherit;
  }
  
  .announcement-bar__link:hover {
    text-decoration: underline;
  }
</style>

{% schema %}
{
  "name": "Announcement bar",
  "max_blocks": 12,
  "class": "announcement-bar-section",
  "enabled_on": {
    "groups": ["header"]
  },
  "settings": [
    {
      "type": "color_scheme",
      "id": "color_scheme",
      "label": "Color scheme",
      "default": "scheme-4"
    },
    {
      "type": "select",
      "id": "text_alignment",
      "options": [
        {
          "value": "left",
          "label": "Left"
        },
        {
          "value": "center",
          "label": "Center"
        },
        {
          "value": "right",
          "label": "Right"
        }
      ],
      "default": "center",
      "label": "Text alignment"
    }
  ],
  "blocks": [
    {
      "type": "announcement",
      "name": "Announcement",
      "limit": 12,
      "settings": [
        {
          "type": "text",
          "id": "text",
          "default": "Welcome to our store",
          "label": "Text"
        },
        {
          "type": "url",
          "id": "link",
          "label": "Link"
        },
        {
          "type": "header",
          "content": "Scheduling"
        },
        {
          "type": "checkbox",
          "id": "enable_schedule",
          "label": "Enable scheduling",
          "default": false,
          "info": "When enabled, this announcement will only show during the specified dates"
        },
        {
          "type": "paragraph",
          "content": "Start Date - Announcement will appear at 12am on this date"
        },
        {
          "type": "select",
          "id": "start_month",
          "label": "Start month",
          "options": [
            {"value": "", "label": "Select month"},
            {"value": "1", "label": "January"},
            {"value": "2", "label": "February"},
            {"value": "3", "label": "March"},
            {"value": "4", "label": "April"},
            {"value": "5", "label": "May"},
            {"value": "6", "label": "June"},
            {"value": "7", "label": "July"},
            {"value": "8", "label": "August"},
            {"value": "9", "label": "September"},
            {"value": "10", "label": "October"},
            {"value": "11", "label": "November"},
            {"value": "12", "label": "December"}
          ],
          "default": ""
        },
        {
          "type": "select",
          "id": "start_day",
          "label": "Start day",
          "options": [
            {"value": "", "label": "Select day"},
            {"value": "1", "label": "1"},
            {"value": "2", "label": "2"},
            {"value": "3", "label": "3"},
            {"value": "4", "label": "4"},
            {"value": "5", "label": "5"},
            {"value": "6", "label": "6"},
            {"value": "7", "label": "7"},
            {"value": "8", "label": "8"},
            {"value": "9", "label": "9"},
            {"value": "10", "label": "10"},
            {"value": "11", "label": "11"},
            {"value": "12", "label": "12"},
            {"value": "13", "label": "13"},
            {"value": "14", "label": "14"},
            {"value": "15", "label": "15"},
            {"value": "16", "label": "16"},
            {"value": "17", "label": "17"},
            {"value": "18", "label": "18"},
            {"value": "19", "label": "19"},
            {"value": "20", "label": "20"},
            {"value": "21", "label": "21"},
            {"value": "22", "label": "22"},
            {"value": "23", "label": "23"},
            {"value": "24", "label": "24"},
            {"value": "25", "label": "25"},
            {"value": "26", "label": "26"},
            {"value": "27", "label": "27"},
            {"value": "28", "label": "28"},
            {"value": "29", "label": "29"},
            {"value": "30", "label": "30"},
            {"value": "31", "label": "31"}
          ],
          "default": ""
        },
        {
          "type": "select",
          "id": "start_year",
          "label": "Start year",
          "options": [
            {"value": "", "label": "Select year"},
            {"value": "2025", "label": "2025"},
            {"value": "2026", "label": "2026"},
            {"value": "2027", "label": "2027"},
            {"value": "2028", "label": "2028"},
            {"value": "2029", "label": "2029"},
            {"value": "2030", "label": "2030"},
            {"value": "2031", "label": "2031"},
            {"value": "2032", "label": "2032"}
          ],
          "default": ""
        },
        {
          "type": "paragraph",
          "content": "End Date - Announcement will turn off after 11:59pm on this date"
        },
        {
          "type": "select",
          "id": "end_month",
          "label": "End month",
          "options": [
            {"value": "", "label": "Select month"},
            {"value": "1", "label": "January"},
            {"value": "2", "label": "February"},
            {"value": "3", "label": "March"},
            {"value": "4", "label": "April"},
            {"value": "5", "label": "May"},
            {"value": "6", "label": "June"},
            {"value": "7", "label": "July"},
            {"value": "8", "label": "August"},
            {"value": "9", "label": "September"},
            {"value": "10", "label": "October"},
            {"value": "11", "label": "November"},
            {"value": "12", "label": "December"}
          ],
          "default": ""
        },
        {
          "type": "select",
          "id": "end_day",
          "label": "End day",
          "options": [
            {"value": "", "label": "Select day"},
            {"value": "1", "label": "1"},
            {"value": "2", "label": "2"},
            {"value": "3", "label": "3"},
            {"value": "4", "label": "4"},
            {"value": "5", "label": "5"},
            {"value": "6", "label": "6"},
            {"value": "7", "label": "7"},
            {"value": "8", "label": "8"},
            {"value": "9", "label": "9"},
            {"value": "10", "label": "10"},
            {"value": "11", "label": "11"},
            {"value": "12", "label": "12"},
            {"value": "13", "label": "13"},
            {"value": "14", "label": "14"},
            {"value": "15", "label": "15"},
            {"value": "16", "label": "16"},
            {"value": "17", "label": "17"},
            {"value": "18", "label": "18"},
            {"value": "19", "label": "19"},
            {"value": "20", "label": "20"},
            {"value": "21", "label": "21"},
            {"value": "22", "label": "22"},
            {"value": "23", "label": "23"},
            {"value": "24", "label": "24"},
            {"value": "25", "label": "25"},
            {"value": "26", "label": "26"},
            {"value": "27", "label": "27"},
            {"value": "28", "label": "28"},
            {"value": "29", "label": "29"},
            {"value": "30", "label": "30"},
            {"value": "31", "label": "31"}
          ],
          "default": ""
        },
        {
          "type": "select",
          "id": "end_year",
          "label": "End year",
          "options": [
            {"value": "", "label": "Select year"},
            {"value": "2025", "label": "2025"},
            {"value": "2026", "label": "2026"},
            {"value": "2027", "label": "2027"},
            {"value": "2028", "label": "2028"},
            {"value": "2029", "label": "2029"},
            {"value": "2030", "label": "2030"},
            {"value": "2031", "label": "2031"},
            {"value": "2032", "label": "2032"}
          ],
          "default": ""
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Announcement Bar Scheduler",
      "blocks": [
        {
          "type": "announcement"
        }
      ]
    }
  ]
}
{% endschema %}
2 Likes

Thanks for sharing.

Just one warning:
Historically, getting current time was (and I believe still is) not very reliable/precize due to server-side caching.
Like this one:

 assign current_timestamp = 'now' | date: '%s' | plus: 0

Precize time-comparing was always done in visitors browsers with Javascript.

You may not notice this while you’re actively editing your theme content and cache is invalidated often, but it may resurface when your site is settled.

Have not made any recent tests myself, but hopefully would not be an issue if measuring in days.

2 Likes

Hi @Bizfound

Try this code

{%- liquid
assign announcement_content = ‘’

for block in section.blocks
case block.type
when ‘announcement’
if block.settings.text != blank

      assign show_announcement = true

      if block.settings.enable_schedule
        assign current_timestamp = 'now' | date: '%s' | plus: 0

        if block.settings.start_month != blank and block.settings.start_day != blank and block.settings.start_year != blank
          assign start_month = block.settings.start_month | prepend: '00' | slice: -2, 2
          assign start_day = block.settings.start_day | prepend: '00' | slice: -2, 2
          assign start_date = block.settings.start_year | append: '-' | append: start_month | append: '-' | append: start_day
          assign start_timestamp = start_date | date: '%s' | plus: 0

          if current_timestamp < start_timestamp
            assign show_announcement = false
          endif
        endif

        if block.settings.end_month != blank and block.settings.end_day != blank and block.settings.end_year != blank
          assign end_month = block.settings.end_month | prepend: '00' | slice: -2, 2
          assign end_day = block.settings.end_day | prepend: '00' | slice: -2, 2
          assign end_date = block.settings.end_year | append: '-' | append: end_month | append: '-' | append: end_day
          assign end_timestamp = end_date | date: '%s' | plus: 86399

          if current_timestamp > end_timestamp
            assign show_announcement = false
          endif
        endif
      endif

      if show_announcement
        assign announcement_content = announcement_content | append: '<div class="announcement-bar__message h5" ' | append: block.shopify_attributes | append: '>'
        if block.settings.link != blank
          assign announcement_content = announcement_content 
            | append: '<a href="' 
            | append: block.settings.link 
            | append: '" class="announcement-bar__link link link--text focus-inset animate-arrow"><span>'
            | append: block.settings.text | escape
            | append: '</span>'
            | append: ('icon-arrow.svg' | inline_asset_content)
            | append: '</a>'
        else
          assign announcement_content = announcement_content | append: block.settings.text | escape
        endif
        assign announcement_content = announcement_content | append: '</div>'
      endif

    endif
endcase

endfor
-%}

{%- if announcement_content != blank -%}

{{ announcement_content }}
{%- endif -%} .announcement-bar { border-bottom: 1px solid rgba(var(--color-foreground), 0.08); } .announcement-bar__message { padding: 10px 0; margin: 0; } .announcement-bar__message + .announcement-bar__message { border-top: 1px solid rgba(var(--color-foreground), 0.1); } .announcement-bar__link { display: inline-block; text-decoration: none; color: inherit; } .announcement-bar__link:hover { text-decoration: underline; }

{% schema %}
{ … your schema remains unchanged … }
{% endschema %}

Echo’ing @tim_1 's callout, this will fail if you leave it unattended.
And relying on the users clock will make a mess, there needs to be a server clock you can rely on.

You need to use either shopify-flow or other automation tool like mechanic to then do something like set a shop metafield and then reference that in the code, or some other object change like a products property that will prevent a stale cache.
Or just outright modify the settings for advanced users || developers.

Shopify-PLUS plans should look at shopify’s free launchpad app https://help.shopify.com/en/manual/promoting-marketing/create-marketing/launchpad

There’s a reason somethings take an app, predictability ,and reliability in the face of technicalities merchants are oblivious too are some pretty big ones.

Thank you so much for this feedback.

@PaulNewton I like your idea too, but from what I’ve seen, apparently sometimes metafields can be cached too??

What if I make a request to https://worldtimeapi.org/api/timezone/Etc/UTC to grab the time?

You can do that.
However, what’s their terms of service and reliability?

I would not mind using visitors time source – I doubt anyone uses offset clock. After all, it’s just an Announcement bar :slight_smile:

This will allow you to retrieve time at your location no matter the visitors timezone.

That’s why I mentioned a product property change.
In either situation cache has to be assumed, so cache busting or other remediation is in play.
BF/CM biggest sale days for a lot of ecomm stores don’t muck about , build in redundancy when doing this.

User clock-times are just like form inputs, the user cannot be trusted.

A buyer in australia can be up to sixteen hours ahead of a buyer, or a store, in the USA.
IF they even have a proper time set for their zone.
If anything is slightly off in base assumptions everything goes wrong.
Such as when they go to get the deal it wont exist yet or it’s long past.
Not good unless there’s a specific need to give some visitors deals ahead/behind others in almost random fashion due to lack of constraints.