Back to Home
    PromoOS

    Help Center

    How can we help?

    Guides, feature docs, and answers to common questions about PromoOS.

    Features Overview

    What's Available Now

    Promo Calendar — Visual timeline of all your campaigns. Color-coded by status. Conflict detection built in.

    Promo Builder — Step-by-step guided flow to create promos: set a mechanism, configure discounts, target products or collections, design page placements, upload assets, and schedule — all in one place.

    Automated Deployment — PromoOS activates and deactivates your promos automatically at the scheduled times.

    Discount Code Creation — PromoOS creates the discount code in Shopify automatically when a promo goes live.

    Crossed Price (Sale Price) — Set a crossed-out original price next to a sale price directly on your products — no coupon code required. PromoOS updates product prices in Shopify automatically and restores them when the promo ends.

    Product & Collection Targeting — Apply discounts to specific products, collections, or your entire store. For BOGO promos, set separate targeting for the 'buy' items and the 'get' items.

    Advanced Discount Controls — Fine-tune every discount with usage limits, once-per-customer restrictions, minimum purchase or quantity requirements, and stacking rules (control whether your promo combines with other product, order, or shipping discounts).

    Revenue Goal Tracking — Set a revenue target on any promo. PromoOS tracks order revenue attributed to that promo's discount code and shows a live progress bar on the promo card and detail view.

    Per-Page Placement Targeting — Configure different storefront placements for the homepage, product pages, and collection pages. Switch between a shared design for all products/collections or define unique placements per product or per collection.

    Placement Validation — Built-in validation that checks whether your PromoOS theme block is actually installed in your active theme — directly from the placement editor. No manual theme inspection needed.

    Visual Asset Upload — Upload promotional images (desktop and mobile) that can be deployed to your storefront during an active promo.

    Draft Management — Save promos as drafts and come back to finish them later.

    Conflict Detection — Promos with overlapping dates and targeting surface a warning in the calendar.

    Import Existing Discounts — Import your existing Shopify discounts into PromoOS to manage them alongside your planned campaigns.

    Analytics Dashboard — Per-promo performance report: KPI cards (Net Sales, Orders, AOV, New Customers), day-by-day revenue chart vs baseline, lift percentile rank, discount summary, customer breakdown, top products, and AI-generated insights. Available for completed promos.

    Text to Promo (AI) — Describe a promo in plain English and PromoOS extracts the type, discount, dates, targeting, and promo name automatically. Review the extracted fields and confirm to open the promo builder pre-filled. Pro plan only.

    Promo Types

    Choose the type that best describes your campaign. The type determines how the discount is configured.

    TypeBest ForDiscount Options
    DiscountGeneral % or $ off campaigns with a discount codePercentage, Fixed Amount
    Discount — Crossed PriceShow a crossed-out original price next to a sale price, no coupon neededPercentage, Fixed Amount
    BOGOBuy One Get One offersBOGO config (buy X get Y)
    Free ShippingShipping promotionsFree shipping regions

    Discount Configuration

    For Discount, Bundle, and Gift with Purchase type promos, you choose:

    • Percentage — e.g. 10%, 20%, 50% off. Maximum 100%.
    • Fixed Amount — e.g. $5, $10, $25 off the order total.
    • Crossed Price — choose Discount type then select "Crossed Price" as the promo mechanism. PromoOS updates product prices directly and shows the original as a strikethrough. No discount code is created.

    Promo States

    PromoOS moves your promo through these states automatically.

    StatusWhat It MeansCan You Edit?
    DraftSaved but not scheduled. Work in progress.Yes — full edit
    ScheduledScheduled to go live. Deployment will happen automatically.Yes — before start time
    ActiveLive on your store. Customers can see the discount.No — end it and create a new one
    EndedCampaign finished. Historical record kept.No — read only
    FailedSomething went wrong during deployment.Yes — fix and reschedule

    Creating & Managing Promos

    Creating a Promo

    The promo builder is a multi-step form covering:

    1. 1Promo Mechanism & Goal — Choose how the discount is delivered — Discount Code (customers enter a code at checkout) or Crossed Price (product prices are updated directly, no code needed). Optionally set a revenue goal to track progress against a target.
    2. 2Basic Info — Name, promo type, start and end dates. Conflict warnings show here if dates overlap with existing promos.
    3. 3Discount Config — Set your discount value (percentage or fixed amount) and, if applicable, BOGO settings or free shipping regions. Fine-tune with advanced controls: usage limits, once-per-customer restrictions, minimum purchase or quantity requirements, and stacking rules.
    4. 4Target Audience — Apply to all products, specific collections, or specific products. For BOGO promos, set separate targeting for the 'buy' and 'get' item groups. For Crossed Price promos, specific targeting is required (all-products is not supported).
    5. 5Storefront Placements — Choose which pages show a placement (Homepage, Product pages, Collection pages). Configure an Announcement Bar or Hero Banner for each page. Use 'Same for all' to share one design across all products or collections, or switch to 'Per product/collection' to set unique placements for individual items. Use the Validate button to confirm your PromoOS theme block is installed in the active theme.
    6. 6Visual Assets — Upload promotional images (JPG, PNG, WebP, max 5MB each) for desktop and mobile.
    7. 7Review & Schedule — Save as Draft, schedule for a future time, or activate immediately.

    Editing a Promo

    • Click any promo card in the calendar or dashboard to open it.
    • Promos in Draft or Scheduled status can be fully edited.
    • You cannot edit a promo that is Active, Deploying, or Ended. End the active promo and create a new one if changes are needed.

    Deleting a Promo

    • Only Draft and Ended promos can be deleted.
    • Deletion is permanent. The promo record is gone. Shopify discount codes are not automatically deleted — remove them from Shopify Admin if needed.
    • For Crossed Price promos, product prices are restored automatically when the promo ends — no manual cleanup needed in Shopify.

    Discount Codes

    • Discount codes are optional. Leave the field blank for automatic (codeless) discount application.
    • If you provide a code (e.g. "SUMMER25"), PromoOS creates it in Shopify automatically when the promo goes live.
    • Codes must be unique. PromoOS warns you if the code already exists in your Shopify store.
    • After the promo ends, the discount code remains in Shopify for historical tracking but no longer applies.

    Text to Promo (AI)

    Pro plan feature. Available from the Dashboard and Calendar via the "Create with AI" button.

    Text to Promo lets you describe a promotional campaign in plain English. PromoOS reads your description, extracts the key fields — promo type, discount value, dates, targeting, and promo name — and opens the promo builder pre-filled with those values. You review and adjust before anything is saved or scheduled.

    How to open Text to Promo

    Click "Create with AI" on the Dashboard or Calendar. The button is visible on the Pro plan. If you are on a free trial or a lower plan, clicking it shows an upgrade prompt instead. Text to Promo is not available inside the standard promo builder — it is a separate entry point.

    Describing your promo

    Type a plain-English description of the promo you want to run. Include whatever details you have — promo type, discount amount, start and end dates, and which products or collections it applies to. You do not need to follow a specific format. Examples: "20% off all footwear from July 4th to July 7th, discount code JULY20" or "BOGO on the Summer Collection, runs the last week of August." The more specific your description, the more fields PromoOS can fill in without asking follow-up questions.

    Follow-up questions

    If a required field is missing or unclear, PromoOS asks one focused follow-up question at a time — up to four questions per session. Each question comes with an input widget matched to the field: a date picker for dates, a number input for discount value, a buy/get quantity picker for BOGO, and an inline selector for products or collections. You can answer each question or skip it if the field does not apply. PromoOS only asks about fields it could not confidently extract from your original description.

    The preview panel

    As you type and answer follow-up questions, a preview panel shows the fields extracted so far — promo type, discount, dates, targeting, promo name, and discount code. The panel updates in real time. Use it to spot missing or incorrect values before confirming. Fields that could not be extracted are shown as blank — you can fill them in manually once the builder opens.

    What happens after you confirm

    When you click Confirm, the standard promo builder opens with all extracted fields pre-filled. From there, you follow the normal builder flow — you can adjust any field, add placements and assets, and then save as a draft or schedule the promo. Nothing is saved or scheduled until you complete the builder and choose a save option. Text to Promo does not create or activate anything on its own.

    Starting over

    Click "Start over" at any point in the conversation to clear the description, all follow-up answers, and the preview panel. The modal resets to the initial state. Use this if you want to describe a different promo or if the extracted fields are too far off to correct.

    Limitations and caveats

    Text to Promo is available on the Pro plan only. The AI can process up to 20 descriptions per hour per store — if you hit the limit, a message appears and you can use the standard promo builder instead. Extraction accuracy depends on how clearly the promo is described. Complex targeting (specific product SKUs, exclusions) and unusual discount structures may not extract correctly — always review the preview panel before confirming. If the AI fails entirely, the modal falls back to opening the standard promo builder with your input text pre-filled as the promo name.

    When to use Text to Promo vs the standard builder

    Use Text to Promo when you have a clear idea of what you want and want to skip the step-by-step form for routine promos. Use the standard builder when your promo has complex targeting, per-product placements, or settings that are hard to express in plain English — for example, specific stacking rules or per-collection placement configurations. Both paths produce the same result: a promo in the builder ready to review and schedule.

    Automated Deployment

    When a Promo Activates

    At the scheduled start time, PromoOS automatically:

    1. 1. For Discount Code promos: creates the Shopify discount code automatically
    2. 2. For Crossed Price promos: updates product variant prices in Shopify — sets the discounted price as the selling price and the original as the crossed-out compare-at price
    3. 3. Applies targeting rules to the specified products or collections
    4. 4. Updates the promo status to Active

    When a Promo Ends

    At the scheduled end time, PromoOS automatically:

    1. 1. Transitions the promo to Ended status
    2. 2. For Discount Code promos: keeps the discount code in Shopify for historical reference (inactive)
    3. 3. For Crossed Price promos: restores all product prices to their original values automatically

    How Often Does It Check?

    PromoOS runs a background scheduler that checks every 5 minutes. This means:

    • Your promo goes live within 5 minutes of its scheduled start time
    • It ends within 5 minutes of its scheduled end time
    • This runs 24/7 — no need to be at your computer

    If Deployment Fails

    The promo will be marked as Failed. Open the promo, review the error message, fix the issue (e.g. duplicate discount code), and reschedule. PromoOS operations are safe to retry.

    Theme Block Setup

    Storefront theme blocks let PromoOS automatically show banners and announcement bars on your store pages whenever a promo is active — no manual updates needed. This is a one-time setup per page type.

    Theme block setup is a one-time step. Once added, PromoOS shows quick-access shortcuts directly in your Dashboard to jump straight into the right page in the Theme Editor.

    Works with all Online Store 2.0 themes — Dawn, Craft, Sense, Refresh, and most modern Shopify themes.

    Setup Steps

    1. 1

      Open the Theme Editor for the page you want

      In Shopify Admin, go to Online Store → Themes → Customize on your active theme. If you're logged in to PromoOS, your Dashboard has one-click shortcuts to jump directly to the right page — Homepage, Product pages, Collection pages, or the Announcement Bar position.

    2. 2

      Add the PromoOS block

      In the Theme Editor sidebar, click "Add section" (or "Add block" inside an existing section), search for PromoOS, and select the block you want — Hero Banner or Announcement Bar.

    3. 3

      Position the block

      Drag the block to the position you want on the page. To reorder it, use the drag handle. To remove it later, click the block and select Remove block from the sidebar.

    4. 4

      Save and repeat for other pages

      Click "Save" in the theme editor. Repeat steps 1–3 for any other page types where you want promos to appear (Homepage, Product pages, Collection pages).

    Available Blocks

    Hero Banner

    Full-width promotional banner displayed on the page. Ideal for homepage campaigns, seasonal sales, and product launches. Add this to your Homepage, Product, and Collection page templates.

    Announcement Bar

    Slim bar at the very top of the page, visible across your entire store. Best for short promo messages, site-wide offers, and discount code callouts.

    Where to Add It

    Homepage

    Your store's main landing page — the highest traffic page for most stores.

    Product pages

    Individual product detail pages — ideal for product-specific deals.

    Collection pages

    Category/collection listing pages — great for collection-wide sales.

    Announcement bar

    Store-wide slim bar shown on every page of your store.

    Open Theme Editor Directly

    Use the shortcut links below to jump directly into the right page in your Shopify Theme Editor — no manual navigation needed. You must be logged in to your Shopify store for these links to work.

    Log in to PromoOS to get one-click links that open the Theme Editor on the right page automatically.

    Custom Theme Integration

    If you prefer to build your own storefront components instead of using PromoOS theme blocks, you can read active promo data directly from a Shopify metafield in any Liquid template. PromoOS writes structured JSON to `shop.metafields.promoos.active_promo` whenever a promo is active, and clears it when the promo ends.

    Requires the PromoOS app to be installed. The metafield is created automatically on install. If it's completely missing, you can recreate it without reinstalling — open DevTools on your PromoOS dashboard and run the re-run setup fetch call from the test steps. Note: if the definition already exists but shows "None" for Storefront access (from an older install), you must delete it in Shopify Admin → Settings → Custom data → Shops first, then run re-run setup — the endpoint cannot upgrade an existing definition's access settings.

    Metafield JSON Schema

    Access via shop.metafields.promoos.active_promo.value in any Liquid template.

    FieldTypeDescription
    promo_idstringUUID of the active promo
    namestringPromo name as entered in PromoOS
    start_datestring (ISO 8601)Promo start datetime
    end_datestring | nullPromo end datetime, null if open-ended
    announcement_textstring | nullBest-available promo message (headline → announcement_text). Null when neither is set.
    display_textstring | nullannouncement_text truncated to 80 characters — safe to render directly
    announcement_text_shortstring | nullannouncement_text field specifically, truncated to 80 characters
    text_lengthnumberCharacter count of display_text
    compare_at_enabledbooleantrue for Crossed Price promos — product prices are updated directly, no discount code is used
    discount_codestring | nullShopify discount code. Always null for Crossed Price promos.
    discount_valuenumber | nullNumeric discount value (e.g. 20 for 20%)
    discount_typestring | null"percentage" or "fixed_amount"
    discount_displaystring | nullPre-formatted string: "20% OFF" or "$10 OFF". Always null for Crossed Price promos.
    has_imagebooleantrue if a hero image is attached to this promo
    has_ctabooleantrue if the promo has a CTA button configured
    has_discount_codebooleantrue if discount_code is set
    hero_image_desktop_1920string | nullHero desktop image at 1920px width (Shopify CDN)
    hero_image_desktop_800string | nullHero desktop image at 800px width
    hero_image_mobile_600string | nullHero mobile image at 600px width
    placementsobjectFull placement config keyed by page type (home, product, cart, …)

    Ready-to-Paste Snippets

    Announcement Bar

    Wire PromoOS data into your theme's existing announcement bar — keeps your bar's position, colours, and styling unchanged. Use the per-theme examples below to find the right line to edit.

    Where to add: Option B only — edit your existing announcement bar file. See per-theme instructions below.

    {%- assign promo = shop.metafields.promoos.active_promo.value -%}
    {%- if promo != blank -%}
      {{- promo.display_text | escape -}}
    {%- endif -%}

    Option B — Inject into your existing announcement bar

    Online Store → Themes → Edit code → find the file below → locate the text output line → replace it with the PromoOS conditional.

    Dawnsections/announcement-bar.liquid

    Dawn renders text in two places: once for a single block (line ~43) and once inside the carousel loop (line ~103). Edit both.

    Single announcement — find:

    {{ section.blocks.first.settings.text | escape }}

    Replace with:

    {%- assign _p = shop.metafields.promoos.active_promo.value -%}
    {%- if _p != blank -%}
      {{- _p.display_text | default: _p.announcement_text | escape -}}
    {%- else -%}
      {{ section.blocks.first.settings.text | escape }}
    {%- endif -%}

    Carousel (inside {% for block in section.blocks %}) — find:

    {{ block.settings.text | escape }}

    Replace with:

    {%- assign _p = shop.metafields.promoos.active_promo.value -%}
    {%- if _p != blank -%}
      {{- _p.display_text | default: _p.announcement_text | escape -}}
    {%- else -%}
      {{ block.settings.text | escape }}
    {%- endif -%}
    Horizonblocks/_announcement.liquid — or blocks/_announcement-bar.liquid

    Horizon uses a separate block file for announcement content. Open the file in Edit code and search for the line that outputs the announcement text (typically block.settings.text or block.settings.announcement_text). Wrap that output line using the same conditional pattern as shown in the 'Other themes' example below.

    Search for the text output line in the file, then wrap it:

    Replace with:

    {%- assign _p = shop.metafields.promoos.active_promo.value -%}
    {%- if _p != blank -%}
      {{- _p.display_text | default: _p.announcement_text | escape -}}
    {%- else -%}
      {{- block.settings.text -}}  {%- comment -%} replace with whatever your theme uses {%- endcomment -%}
    {%- endif -%}
    Other themes (Impulse, Prestige, Refresh, etc.)sections/announcement-bar.liquid — or search your theme for the announcement bar file

    Most OS 2.0 themes follow the same pattern. Open the file in Edit code, search for section.settings.text or block.settings.text, and wrap the text output using the same conditional.

    Find the text output line, e.g.:

    {{ section.settings.text | escape }}

    Replace with:

    {%- assign _p = shop.metafields.promoos.active_promo.value -%}
    {%- if _p != blank -%}
      {{- _p.display_text | default: _p.announcement_text | escape -}}
    {%- else -%}
      {{ section.settings.text | escape }}
    {%- endif -%}

    Hero Banner

    Replace your theme's hero image with a PromoOS campaign image when a promo is active, falling back to your original image when no promo is running. PromoOS writes the hero image as a Shopify file_reference metafield so you can use the native image_url / image_tag filters — no plain <img> tags needed.

    Where to add: Option B only — edit your existing hero section file. See per-theme instructions below.

    {%- assign promo_d = shop.metafields.promoos.hero_desktop.value -%}
    {%- assign promo_m = shop.metafields.promoos.hero_mobile.value | default: promo_d -%}
    {%- if promo_d != blank -%}
      <picture>
        {%- if promo_m != blank -%}
          <source media="(max-width: 749px)"
            srcset="{{ promo_m | image_url: width: 750 }}"
            sizes="100vw">
        {%- endif -%}
        {{- promo_d | image_url: width: 1920 | image_tag: loading: 'lazy', sizes: '100vw' -}}
      </picture>
    {%- elsif shop.metafields.promoos.active_promo.value != blank and shop.metafields.promoos.active_promo.value.has_image -%}
      {%- assign promo = shop.metafields.promoos.active_promo.value -%}
      <img src="{{ promo.hero_image_desktop_1920 }}"
           srcset="{{ promo.hero_image_desktop_800 }} 800w, {{ promo.hero_image_desktop_1920 }} 1920w"
           sizes="100vw" alt="{{ promo.placements.home.hero.altText | default: '' | escape }}"
           loading="lazy">
    {%- endif -%}

    Option B — Inject into your existing announcement bar

    Online Store → Themes → Edit code → find the file below → locate the text output line → replace it with the PromoOS conditional.

    Dawnsections/image-banner.liquid

    Dawn renders the desktop image and a separate mobile image (image_2) using the image_url + image_tag filter chain. Wrap each block so PromoOS swaps the image when a promo is active. The desktop block is around line 75; the mobile block is around line 107.

    Desktop image — find (~line 75):

    {{
      section.settings.image
      | image_url: width: 3840
      | image_tag:
        width: section.settings.image.width,
        height: image_height,
        class: image_class,
        sizes: sizes,
        widths: widths,
        fetchpriority: fetch_priority
    }}

    Replace with:

    {%- assign _promo_img = shop.metafields.promoos.hero_desktop.value -%}
    {%- if _promo_img != blank -%}
      {{-
        _promo_img
        | image_url: width: 3840
        | image_tag:
          class: image_class,
          sizes: sizes,
          widths: widths,
          fetchpriority: fetch_priority
      -}}
    {%- else -%}
      {{
        section.settings.image
        | image_url: width: 3840
        | image_tag:
          width: section.settings.image.width,
          height: image_height,
          class: image_class,
          sizes: sizes,
          widths: widths,
          fetchpriority: fetch_priority
      }}
    {%- endif -%}

    Mobile image (image_2) — find (~line 107):

    {{
      section.settings.image_2
      | image_url: width: 3840
      | image_tag:
        width: section.settings.image_2.width,
        height: image_height_2,
        class: image_class_2,
        sizes: sizes,
        widths: widths,
        fetchpriority: fetch_priority
    }}

    Replace with:

    {%- assign _promo_img_m = shop.metafields.promoos.hero_mobile.value | default: shop.metafields.promoos.hero_desktop.value -%}
    {%- if _promo_img_m != blank -%}
      {{-
        _promo_img_m
        | image_url: width: 3840
        | image_tag:
          class: image_class_2,
          sizes: sizes,
          widths: widths,
          fetchpriority: fetch_priority
      -}}
    {%- else -%}
      {{
        section.settings.image_2
        | image_url: width: 3840
        | image_tag:
          width: section.settings.image_2.width,
          height: image_height_2,
          class: image_class_2,
          sizes: sizes,
          widths: widths,
          fetchpriority: fetch_priority
      }}
    {%- endif -%}
    Horizonsections/hero.liquid

    Horizon uses a <picture> element for its hero image with a mobile <source>. Open sections/hero.liquid in the theme editor (Code), search for 'image_url' to find the desktop image block, then wrap it as shown below. The exact variable names (image_1, sizes, etc.) may vary by version — match what you see in your file.

    Find the desktop image_url | image_tag block and wrap it:

    Replace with:

    {%- assign _promo_d = shop.metafields.promoos.hero_desktop.value -%}
    {%- assign _promo_m = shop.metafields.promoos.hero_mobile.value | default: _promo_d -%}
    <picture class="hero__media">
      {%- if _promo_m != blank -%}
        <source media="(max-width: 749px)"
          srcset="{{ _promo_m | image_url: width: 750 }}"
          sizes="100vw">
      {%- else -%}
        {%- comment -%} keep your theme's original mobile source here {%- endcomment -%}
      {%- endif -%}
      {%- if _promo_d != blank -%}
        {{-
          _promo_d
          | image_url: width: 3840
          | image_tag:
            class: 'hero__media',
            sizes: sizes,
            fetchpriority: fetch_priority
        -}}
      {%- else -%}
        {%- comment -%} keep your theme's original desktop image_tag here {%- endcomment -%}
      {%- endif -%}
    </picture>
    Other themes (Impulse, Prestige, Refresh, etc.)sections/image-banner.liquid — or sections/slideshow.liquid — or your theme's hero section file

    Most themes use a section.settings.image | image_url | image_tag chain for the hero. Wrap it with a PromoOS check — the file_reference value works with the same filter chain your theme already uses.

    Find the hero image block, e.g.:

    {{ section.settings.image | image_url: width: 3840 | image_tag: class: 'hero__image', sizes: '100vw' }}

    Replace with:

    {%- assign _promo_img = shop.metafields.promoos.hero_desktop.value -%}
    {%- if _promo_img != blank -%}
      {{- _promo_img | image_url: width: 3840 | image_tag: class: 'hero__image', sizes: '100vw' -}}
    {%- else -%}
      {{ section.settings.image | image_url: width: 3840 | image_tag: class: 'hero__image', sizes: '100vw' }}
    {%- endif -%}

    Optional — add a mobile image (if your theme has a separate mobile hero image setting):

    {{ section.settings.image_mobile | image_url: width: 1100 | image_tag: class: 'hero__image hero__image--mobile', sizes: '100vw' }}

    Replace with:

    {%- assign _promo_img_m = shop.metafields.promoos.hero_mobile.value | default: shop.metafields.promoos.hero_desktop.value -%}
    {%- if _promo_img_m != blank -%}
      {{- _promo_img_m | image_url: width: 1100 | image_tag: class: 'hero__image hero__image--mobile', sizes: '100vw' -}}
    {%- else -%}
      {{ section.settings.image_mobile | image_url: width: 1100 | image_tag: class: 'hero__image hero__image--mobile', sizes: '100vw' }}
    {%- endif -%}

    Product Badge

    Wire PromoOS data into your theme's existing product card. Insert the conditional inside your product card file where you want the badge to appear — the merchant controls the markup.

    Where to add: Option B only — insert inside your existing product card file. See per-theme instructions below.

    {%- assign promo = shop.metafields.promoos.active_promo.value -%}
    {%- if promo != blank -%}
      {{- promo.discount_display | escape -}}
    {%- endif -%}

    Option B — Inject into your existing announcement bar

    Online Store → Themes → Edit code → find the file below → locate the text output line → replace it with the PromoOS conditional.

    Dawnsnippets/card-product.liquid

    Dawn has a card__badge div with sold-out and sale badge logic. Add the PromoOS badge after the closing {%- endif -%} of the existing badge block, before the closing </div>.

    Search for `card__badge` in the file. Find the closing `{%- endif -%}` and `</div>` that end the badge block, then insert before the `</div>`:

    Replace with:

    {%- assign _p = shop.metafields.promoos.active_promo.value -%}
    {%- if _p != blank and _p.discount_display != blank -%}
      <span class="badge badge--bottom-left" style="background:#e53e3e;color:#fff;">
        {{- _p.discount_display | escape -}}
      </span>
    {%- endif -%}
    HorizonTheme Editor (no code edit needed)

    Horizon product cards support Custom Liquid blocks directly in the theme editor — no file editing required.

    In the Theme Editor:

    Online Store → Themes → Customize → any collection or product page → click a product card → Add block → Custom Liquid

    Replace with:

    Paste the snippet above into the Custom Liquid block. Horizon will render it inside every product card automatically.
    Other themes (Impulse, Prestige, Refresh, etc.)snippets/card-product.liquid — or snippets/product-card.liquid — or your theme's product card snippet

    Find the badge or media area of your product card and insert the PromoOS badge span there.

    Find a suitable anchor, e.g. after an existing sold-out badge:

    {{- 'products.product.sold_out' | t -}}

    Replace with:

    {{- 'products.product.sold_out' | t -}}
    {%- assign _p = shop.metafields.promoos.active_promo.value -%}
    {%- if _p != blank and _p.discount_display != blank -%}
      <span style="display:inline-block;background:#e53e3e;color:#fff;font-size:11px;font-weight:700;padding:3px 8px;border-radius:3px;text-transform:uppercase;">
        {{- _p.discount_display | escape -}}
      </span>
    {%- endif -%}

    Cart Drawer Promo Reminder

    Wire PromoOS data into your theme's existing cart drawer. Insert the conditional inside your cart file where you want the promo reminder to appear — the merchant controls the markup.

    Where to add: Option B only — insert inside your existing cart drawer file. See per-theme instructions below.

    {%- assign promo = shop.metafields.promoos.active_promo.value -%}
    {%- if promo != blank -%}
      {{- promo.display_text | escape -}}
    {%- endif -%}

    Option B — Inject into your existing announcement bar

    Online Store → Themes → Edit code → find the file below → locate the text output line → replace it with the PromoOS conditional.

    Dawnsnippets/cart-drawer.liquid

    Dawn renders cart items inside a div with id CartDrawer-CartItems. Insert the PromoOS reminder immediately before that div so it appears at the top of the open drawer.

    Find the CartDrawer-CartItems div (~line 88):

    <div id="CartDrawer-CartItems" class="drawer__contents js-contents">

    Replace with:

    {%- assign _p = shop.metafields.promoos.active_promo.value -%}
    {%- if _p != blank -%}
      {%- assign _bg = _p.placements.cart.banner.backgroundColor | default: '#f0fdf4' -%}
      {%- assign _fg = _p.placements.cart.banner.textColor | default: '#166534' -%}
      <div style="background:{{ _bg }};color:{{ _fg }};padding:12px 16px;margin:0 0 12px;border-radius:6px;font-size:13px;">
        {%- if _p.display_text != blank -%}<strong>{{- _p.display_text | escape -}}</strong>{%- endif -%}
      </div>
    {%- endif -%}
    <div id="CartDrawer-CartItems" class="drawer__contents js-contents">
    Horizonsections/main-cart.liquid — or sections/cart.liquid

    Open your Horizon cart section file in Edit code. Search for a div that wraps the list of cart items (typically a class containing 'cart' and 'items'). Insert the PromoOS reminder block immediately before that div.

    Search for your cart items wrapper div, then insert before it:

    Replace with:

    {%- assign _p = shop.metafields.promoos.active_promo.value -%}
    {%- if _p != blank -%}
      {%- assign _bg = _p.placements.cart.banner.backgroundColor | default: '#f0fdf4' -%}
      {%- assign _fg = _p.placements.cart.banner.textColor | default: '#166534' -%}
      <div style="background:{{ _bg }};color:{{ _fg }};padding:12px 16px;margin-bottom:16px;border-radius:6px;font-size:13px;">
        {%- if _p.display_text != blank -%}<strong>{{- _p.display_text | escape -}}</strong>{%- endif -%}
      </div>
    {%- endif -%}
    Other themes (Impulse, Prestige, Refresh, etc.)sections/cart-drawer.liquid — or snippets/cart-drawer.liquid — or your theme's cart drawer file

    Find where the cart line items start (usually a <table> or <ul> with class cart-items) and insert the PromoOS reminder immediately before it.

    Find your cart items container, e.g.:

    <table class="cart-items">

    Replace with:

    {%- assign _p = shop.metafields.promoos.active_promo.value -%}
    {%- if _p != blank -%}
      <div style="background:#f0fdf4;color:#166534;padding:12px 16px;margin-bottom:12px;border-radius:6px;font-size:13px;">
        {%- if _p.display_text != blank -%}<strong>{{- _p.display_text | escape -}}</strong>{%- endif -%}
      </div>
    {%- endif -%}
    <table class="cart-items">

    Common Pitfalls

    Metafield returns blank — existing install

    If you installed PromoOS before this feature shipped, your metafield definition may be missing or lack storefront access. Go to your Dashboard, open DevTools console, and run the re-run setup fetch call from the test steps to recreate it.

    display_text is 80 chars max

    PromoOS truncates display_text to 80 characters. Don't rely on it for full-length copy — use announcement_text for the raw value if you need to truncate differently.

    Image sizing and CDN params

    hero_image_desktop_1920 and other sized variants use Shopify CDN ?width= params. These only work on images uploaded via Shopify (cdn.shopify.com). External image URLs will not be resized.

    color_scheme conflicts

    Some themes (Prestige, Impulse) apply a color_scheme setting to sections that overrides inline styles. If your announcement bar colours look wrong, wrap your snippet in a section schema that sets color_scheme, or use CSS custom properties instead of inline styles.

    No promo = no DOM output

    All four snippets render nothing when no promo is active — there are no empty containers or placeholder elements. Verify this in DevTools to make sure there's no invisible space in your layout.

    When no promo is active, shop.metafields.promoos.active_promo.value evaluates to blank in Liquid. All four snippets check for blank before rendering anything, so your theme layout is never affected by an empty state.

    AI Channel Attribution

    Beta feature

    AI Channel Attribution is a Beta feature. Data may not be fully accurate — channel signals from AI storefronts are still evolving and some orders may be misattributed or not captured at all. Treat the numbers as directional indicators, not exact figures.

    AI Channel Attribution tracks which orders were placed by shoppers arriving via AI-powered storefronts — like ChatGPT shopping, Google AI Mode, Microsoft Copilot, and Gemini. When orders arrive from these channels, PromoOS records the source alongside the revenue and surfaces a breakdown in your promo analytics.

    How it works

    Orders/create webhook

    PromoOS listens to the existing orders/create Shopify webhook. No additional subscriptions are needed.

    Channel detection

    For ChatGPT, PromoOS reads the HTTP referrer on the order. For Google AI Mode, Microsoft Copilot, and Gemini, PromoOS reads the channelInformation.channelDefinition.handle field returned by the Shopify Orders API.

    Attribution storage

    The resolved channel handle is stored with the order's revenue data. Orders with no recognisable AI signal are attributed to Online Store.

    Visibility threshold

    The Revenue by Channel breakdown only appears when at least one AI channel order exists for that promo. It stays hidden when there is no AI traffic — no clutter for merchants who haven't seen any yet.

    Supported AI channels

    ChannelDetection method
    ChatGPTHTTP referrer (openai.com / chatgpt.com)
    Google AI ModeShopify channel handle
    Microsoft CopilotShopify channel handle
    GeminiShopify channel handle

    Where to find it

    Open any active or completed promo and go to the Performance tab. If AI channel orders exist, the Revenue by Channel card appears showing Online Store vs AI Channels, with a drilldown per AI channel (expand to see ChatGPT, Google AI Mode, etc. individually).

    Known limitations

    Channel data is collected as orders arrive — historical orders placed before PromoOS was installed are not backfilled.

    ChatGPT attribution relies on a referrer header that shoppers' browsers may not send in all cases (e.g. private browsing).

    Shopify's channel handle values for AI storefronts are not formally documented and may change — handle mappings will be updated as new channels emerge.

    Some orders may arrive with no detectable channel signal even when the shopper came via an AI tool.

    Analytics Dashboard

    The analytics dashboard gives you a full picture of how each promo performed — revenue vs goal, day-by-day chart, lift vs baseline, customer breakdown, and top products. It's only available for completed promos.

    Find analytics in three places: the Performance tab inside any promo's detail panel, the Analytics link on each promo card in the calendar popover, and the Analytics section in the sidebar. Analytics are only available once a promo has ended — use the revenue goal progress bar to track active promos.

    KPI Cards

    The four cards at the top show Net Sales (revenue attributed to this promo's discount code), Orders (number of orders using the code), AOV (average order value), and New Customers (first-time buyers). Each card includes a delta badge comparing performance to your baseline period. If it's your first promo, the delta shows 'No baseline yet' — this is normal until you have prior order history for comparison.

    Day-by-Day Chart

    The chart plots two lines: your promo period (colored) and the equivalent baseline period (gray). If only one line appears, there's no baseline data for that window — this is normal for your first few promos.

    How Your Baseline is Calculated

    PromoOS looks back 4 weeks before your promo start date and uses that period as the comparison baseline. Any weeks that overlap with other promo periods are excluded to avoid contamination. If overlap is unavoidable, a warning appears on the page — the data is still shown, but flagged as potentially affected.

    Lift & Percentile Rank

    Lift is the percentage change in net sales vs baseline: (promo revenue − baseline) / baseline × 100. The percentile rank (e.g. '#2 of your 9 completed promos by lift this year') shows where this promo sits relative to your other completed promos. It appears once you have at least two completed promos to compare.

    Discount Summary

    For discount code promos: shows the code used, discount type, and redemption count. For Crossed Price promos: shows the implied discount percentage calculated from the original vs sale price. The two display differently because Crossed Price promos don't generate a code.

    Customer Breakdown

    Shows the split between new customers (first order ever at your store) and returning customers. 'Not enough data' appears when there are too few orders to show a meaningful split.

    Top Products

    A ranked table of the products that generated the most revenue during the promo period. Available for discount code promos where order line items can be attributed. Not available for Crossed Price promos.

    AI Insights

    After a promo ends, PromoOS generates a plain-English summary of what worked, what didn't, and what to try next. The verdict badge (Exceeded / Met / Missed goal) appears alongside the narrative. AI insights are in Beta — they only appear after the promo ends, never while it's active. Use the thumbs up/down buttons to share feedback.

    Results Email

    A summary email is sent within a few minutes of the promo ending. It includes the verdict, key metrics, and a link to the full analytics dashboard. Only one email is sent per promo. If you didn't receive it, check your spam folder and confirm your store's notification email is set in Shopify Admin.

    Limitations

    Understanding what PromoOS currently doesn't do helps you plan around it.

    Active promos can't be edited

    Once a promo is live (Active), you can't change its settings. To make changes, end the promo and create a new one with updated settings.

    Exclusion targeting not supported

    You can include specific products or collections, but you can't exclude them. Workaround: create a Shopify collection with exactly the products you want to target.

    Storefront blocks require one-time setup

    Automatic injection of promotional banners and badges into your storefront theme requires adding the PromoOS theme block to your theme once via the Shopify Theme Editor.

    Tips & Recommendations

    Schedule at least 1 hour ahead

    • PromoOS activates promos within 5 minutes of start time, but buffer time helps you catch last-minute errors
    • For major campaigns (BFCM, seasonal sales), schedule 24–48 hours ahead

    Use descriptive promo names

    • Include the date or season: "2025-BFCM-20off" not "Sale"
    • Descriptive names make the calendar easier to read and promos easier to find later

    Test with a short promo first

    • Create a 1-hour test promo before your first major campaign
    • Verify the discount code works and targeting rules are correct
    • Check the promo status in your dashboard to confirm activation succeeded

    Use the calendar to spot conflicts

    • Review the calendar before scheduling any new promo
    • PromoOS warns you about date overlaps but won't prevent you from proceeding
    • Two promos with the same product targeting can confuse customers

    Avoid promo fatigue

    • Running promos back-to-back trains customers to wait for discounts
    • Space major campaigns by at least a week
    • Keep some periods at full price to maintain perceived value

    Review your analytics after every promo

    • Open the Performance tab on any completed promo to see the full analytics dashboard
    • Check the lift vs baseline to understand whether the promo actually drove incremental revenue
    • Use the AI insights verdict to decide whether to repeat or adjust the promo next time

    Frequently Asked Questions

    Pricing

    Starter includes a 14-day free trial — no credit card required.

    Starter

    $49

    per month

    Start 14-day trial

    Pro

    $79

    per month

    Start 14-day trial
    FeatureStarterPro
    Promo calendar & planning
    Dashboard overview & metrics
    Unlimited active promos
    Conflict detection
    Automated scheduling & deployment
    Discount code creation (Shopify)
    Crossed price (compare-at) promos
    Product & collection targeting
    Advanced discount controls (limits, stacking)
    Revenue goal tracking
    Storefront theme blocks
    Full analytics dashboard
    AI channel attribution
    Email support
    Text to Promo — AI promo creation

    Support

    Email Support

    contact@promly.app

    Response within 24 hours

    When contacting support, include:

    • Your Shopify store URL
    • Description of the issue
    • The promo name / ID affected (if applicable)
    • Screenshots or error messages