Advanced: Scheduled Price Changes, with Mechanic.

Mechanic is a development and ecommerce automation platform for Shopify. :)

Advanced: Scheduled Price Changes

This advanced task allows you to schedule price change events for your store, useful for seasonal and flash sales. To use this task, configure a price change event using the task options, save the task and run it manually, and then enter a keyword of schedule in the dialog that appears. An email summary with the price change event ID will be sent upon successful scheduling.

Runs Occurs when a user manually triggers the task with text, Occurs whenever user/price_changes/start is triggered, Occurs whenever user/price_changes/end is triggered, and Occurs whenever user/price_changes/reset is triggered. Configuration includes notification email recipients, event start datetime, event end datetime, set compare at prices to original price during event, collection handles and discounts, skus to include, sku discount, exclude products tagged with, skus to exclude, and do not restore prices on reset.

15-day free trial – unlimited tasks

Documentation

This advanced task allows you to schedule price change events for your store, useful for seasonal and flash sales. To use this task, configure a price change event using the task options, save the task and run it manually, and then enter a keyword of schedule in the dialog that appears. An email summary with the price change event ID will be sent upon successful scheduling.

This task supports these keyword actions:

  • schedule - Validates and schedules a new price change event using the current task configuration.
  • list - Logs out (in Mechanic) all configured price change events (of any status).
  • email - Emails a summary of all configured price change events (of any status).
  • cancel - Cancels a specific scheduled or ongoing price change event. The cancel keyword must be followed by a space and the price change event ID.
  • reset - Clears all price change events (of any status) from the shop metafield, and reverts the prices on any variants that have a price change event applied.

Key features of this task:

  • Ability to schedule overlapping price change events
  • Ability to cancel an ongoing event
  • Ability to add collections that contain more than 1000 products
  • Ability to discount more than 20k products
  • Supports percentage, fixed price, and fixed discount options for products AND collections
  • Option to include specific SKUs in a list with a specific discount for the whole list
  • Option to exclude SKUs from any discount qualification
  • Option to exclude products by tag from any discount qualification
  • Option to have (or not have) compare at prices set to original prices for the duration of an event

Price change event configuration notes

  • The event start and end datetimes must be valid dates in the form of YYYY-MM-DD or YYYY-MM-DD HH:MM (24 hour clock).
  • Collection handles and discounts are entered as key value pairs, with the collection handle on the left and the discount for it on the right.
  • If any SKUs are specifically included, then a SKU discount must be set to apply to them.
  • Discount formats:
    • Percentage - This must be an integer between 1 and 99, followed immediately by % (e.g. 20%)
    • Fixed price - Any positive integer or float will be considered the fixed price that all applicable items will be discounted to (e.g. 123.45)
    • Fixed discount - Any negative integer or float will be considered the discount to be substracted from the price of all applicable items (e.g. -25)
  • Priority of discount assignment when a variant qualifies for multiple discounts within a price change event:
    • Exclusions (by SKU or product tag) will always override inclusion
    • SKU inclusion list
    • Collection membership in order of configuration

Important Notes:

  • An email notification will be sent for every price change event scheduled, started, completed, and cancelled. If an email is not received as expected, then the task run logs should be reviewed.
  • Price change events that affect a very large number of variants will take some time to process all of the price updates. The notification emails are sent after all variant prices have been updated.
  • Just saving the task configuration will not schedule (or validate) a price change event. This must be done with the "schedule" keyword when run manually.
  • This task only manages variant prices. Product and theme publishing are not handled by this task.
  • Variants that are already part of an ongoing price change event will not be modified if targeted by a new price change event.
  • The "Do not restore prices on reset" option may be used when clearing out older price change events if there is a concern of reverting old pricing.

Developer details

Mechanic is designed to benefit everybody: merchants, customers, developers, agencies, Shopifolks, everybody.

That’s why we make it easy to configure automation without code, why we make it easy to tweak the underlying code once tasks are installed, and why we publish it all here for everyone to learn from.

(By the way, have you seen our documentation? Have you joined the Slack community?)

Open source
View on GitHub to contribute to this task
Subscriptions
mechanic/user/text
user/price_changes/start
user/price_changes/end
user/price_changes/reset
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
notification email recipients (array, required), event start datetime (required), event end datetime (required), set compare at prices to original price during event (boolean), collection handles and discounts (keyval), skus to include (array), sku discount, exclude products tagged with (array), skus to exclude (array), do not restore prices on reset (boolean)
Code
{% assign email_recipients = options.notification_email_recipients__array_required %}
{% assign event_start_datetime = options.event_start_datetime__required %}
{% assign event_end_datetime = options.event_end_datetime__required %}
{% assign set_compare_at_prices = options.set_compare_at_prices_to_original_price_during_event__boolean %}
{% assign collection_handles_and_discounts = options.collection_handles_and_discounts__keyval %}
{% assign skus_to_include = options.skus_to_include__array %}
{% assign sku_discount = options.sku_discount %}
{% assign exclude_products_tagged_with = options.exclude_products_tagged_with__array %}
{% assign skus_to_exclude = options.skus_to_exclude__array %}
{% assign do_not_restore_prices_on_reset = options.do_not_restore_prices_on_reset__boolean %}

{%- capture task_admin_link -%}
  <a href="https://{{ shop.myshopify_domain }}/admin/apps/mechanic/~/tasks/edit/{{ task.id }}">{{ task.name | remove: "ADVANCED: " }}</a>
{%- endcapture -%}

{% comment %}
  -- query for shop data on every task run, since it will be needed for each valid action keyword and custom event
{% endcomment %}

{% capture query %}
  query {
    shop {
      id
      name
      contactEmail
      metafield(key: "mechanic.price_change_events") {
        jsonValue
      }
    }
  }
{% endcapture %}

{% assign result = query | shopify %}

{% if event.preview %}
  {% capture result_json %}
    {
      "data": {
        "shop": {
          "id": "gid://shopify/Shop/1234567890",
          "name": "Shoplify",
          "contactEmail": "info@example.com",
          "metafield": {
            "jsonValue": {
              "price_change_event__1234567890": {
                "status": "scheduled",
                "start": "2025-12-30 08:00",
                "end": "2025-12-31 20:00",
                "set_compare_at_prices": true,
                "collection_handles_and_discounts": {
                  "collection-alpha": "20%",
                  "collection-beta": "-10",
                  "collection-gamma": "20"
                },
                "skus_to_include": [],
                "sku_discount": "",
                "skus_to_exclude": [],
                "exclude_products_tagged_with": [
                  "clearance"
                ]
              }
            }
          }
        }
      }
    }
  {% endcapture %}

  {% assign result = result_json | parse_json %}
{% endif %}

{% assign shop = result.data.shop %}
{% assign price_change_events = shop.metafield.jsonValue | default: hash %}

{% if event.topic == "mechanic/user/text" %}
  {% comment %}
    -- check text entry to see what action to take
  {% endcomment %}

  {% assign action_keyword = event.data | downcase %}

  {% if event.preview %}
    {% assign action_keyword = "schedule" %}
  {% endif %}

  {% case action_keyword %}
    {% when "schedule" %}
      {% comment %}
        -- NOTE: run scheduling logic here instead of in custom event like most other keywords, so user gets immediate feedback on any configuration errors
      {% endcomment %}

      {% assign valid_start_datetime = event_start_datetime | parse_date: "%Y-%m-%d %H:%M" %}

      {% if valid_start_datetime == blank %}
        {% assign valid_start_datetime = event_start_datetime | parse_date: "%Y-%m-%d" %}

        {% if valid_start_datetime == blank %}
          {% unless event.preview %}
            {% error "The event start date is not a valid date. Please re-enter it (per the task documentation) and try scheduling the event again." %}
            {% break %}
          {% endunless %}
        {% endif %}
      {% endif %}

      {% assign valid_end_datetime = event_end_datetime | parse_date: "%Y-%m-%d %H:%M" %}

      {% if valid_end_datetime == blank %}
        {% assign valid_end_datetime = event_end_datetime | parse_date: "%Y-%m-%d" %}

        {% if valid_end_datetime == blank %}
          {% unless event.preview %}
            {% error "The event end date is not a valid date. Please re-enter it (per the task documentation) and try scheduling the event again." %}
            {% break %}
          {% endunless %}
        {% endif %}
      {% endif %}

      {% assign now_s = "now" | date: "%s" %}
      {% assign start_datetime_s = event_start_datetime | date: "%s" %}
      {% assign end_datetime_s = event_end_datetime | date: "%s" %}

      {% if start_datetime_s <= now_s or start_datetime_s >= end_datetime_s %}
        {% unless event.preview %}
          {% error "The event start and end dates must be future dates and the start date must be before the end date. Please re-enter them (per the task documentation) and try scheduling the event again." %}
          {% break %}
        {% endunless %}
      {% endif %}

      {% comment %}
        -- validate discount entry formats: XX% (percentage discount), X.XX (fixed price), or -X.XX (fixed discount)
      {% endcomment %}

      {% assign discounts = collection_handles_and_discounts | values | default: array %}

      {% if skus_to_include != blank and sku_discount == blank %}
        {% unless event.preview %}
          {% error "A SKU discount must be configured when specific SKUs are included." %}
          {% break %}
        {% endunless %}
      {% endif %}

      {% if sku_discount != blank %}
        {% assign discounts = discounts | push: sku_discount %}
      {% endif %}

      {% for discount in discounts %}
        {% if discount contains "%" %}
          {% assign discount_percentage = discount | times: 1 %}
          {% assign discount_string = discount_percentage | append: "%" %}

          {% if discount != discount_string %}
            {% unless event.preview %}
              {% error
                message: "Percentage discount entry incorrect. Please update the task configuration (per the task documentation) and try scheduling the event again.",
                discount: discount
              %}
              {% break %}
            {% endunless %}
          {% endif %}

          {% if discount_percentage <= 0 or discount_percentage >= 100 %}
            {% unless event.preview %}
              {% error
                message: "Percentage discount entries should contain an number between 0 and 100. Please update the task configuration (per the task documentation) and try scheduling the event again.",
                discount: discount
              %}
              {% break %}
            {% endunless %}
          {% endif %}

        {% else %}
          {% assign discount_absolute = discount | times: 1 | abs %}

          {% if discount_absolute == 0 %}
            {% unless event.preview %}
              {% error
                message: "Fixed price and fixed discount entries must contain an absolute number > 0. Please update the task configuration (per the task documentation) and try scheduling the event again.",
                discount: discount
              %}
              {% break %}
            {% endunless %}
          {% endif %}
        {% endif %}
      {% endfor %}

      {% assign price_change_event = hash %}

      {% assign price_change_event["status"] = "scheduled" %}
      {% assign price_change_event["start"] = start_datetime_s | date: "%FT%H:%M%z" %}
      {% assign price_change_event["end"] = end_datetime_s | date: "%FT%H:%M%z" %}
      {% assign price_change_event["set_compare_at_prices"] = set_compare_at_prices %}
      {% assign price_change_event["collection_handles_and_discounts"] = collection_handles_and_discounts %}
      {% assign price_change_event["skus_to_include"] = skus_to_include %}
      {% assign price_change_event["sku_discount"] = sku_discount %}
      {% assign price_change_event["skus_to_exclude"] = skus_to_exclude %}
      {% assign price_change_event["exclude_products_tagged_with"] = exclude_products_tagged_with %}

      {% log
        task_options: task.options,
        price_change_event: price_change_event
      %}

      {% assign price_change_event_id = event.id | default: task.id %}

      {% assign price_change_events[price_change_event_id] = price_change_event %}

      {% action "shopify" %}
        mutation {
          metafieldsSet(
            metafields: [
              {
                ownerId: {{ shop.id | json }}
                namespace: "mechanic"
                key: "price_change_events"
                value: {{ price_change_events | json | json }}
                type: "json"
              }
            ]
          ) {
            metafields {
              id
              namespace
              key
              type
              jsonValue
              owner {
                ... on Shop {
                  id
                  name
                  myshopifyDomain
                }
              }
            }
            userErrors {
              code
              field
              message
            }
          }
        }
      {% endaction %}

      {% comment %}
        -- schedule start and end events
      {% endcomment %}

      {% action "event" %}
        {
          "topic": "user/price_changes/start",
          "task_id": {{ task.id | json }},
          "run_at": {{ start_datetime_s | json }},
          "data": {
            "price_change_event_id": {{ price_change_event_id | json }}
          }
        }
      {% endaction %}

      {% action "event" %}
        {
          "topic": "user/price_changes/end",
          "task_id": {{ task.id | json }},
          "run_at": {{ end_datetime_s | json }},
          "data": {
            "price_change_event_id": {{ price_change_event_id | json }}
          }
        }
      {% endaction %}

      {% capture email_subject %}New price change event scheduled ({{ price_change_event_id }}){% endcapture %}

      {% capture email_body %}
        A new price change event has been scheduled, from the {{ task_admin_link }} task within the Mechanic app.

        <strong>Price change event ID:</strong> {{ price_change_event_id }}
        <strong>Status:</strong> {{ price_change_event["status"] }}
        <strong>Event start:</strong> {{ price_change_event["start"] | date: "%FT%H:%M%z" }}
        <strong>Event end:</strong> {{ price_change_event["end"] | date: "%FT%H:%M%z" }}
        <strong>Set compare at price to original price during event:</strong> {{ price_change_event["set_compare_at_prices"] }}
        <strong>Collection handles and discounts:</strong>
        {% for keyval in price_change_event["collection_handles_and_discounts"] -%}
        - {{ keyval[0] }}: {{ keyval[1] }}
        {% else -%}
          n/a
        {%- endfor %}
        <strong>SKUs to include:</strong> {{ price_change_event["skus_to_include"] | join: ", " | default: "n/a" }}
        <strong>SKU discount:</strong> {{ price_change_event["sku_discount"] | default: "n/a" }},
        <strong>SKUs to exclude:</strong> {{ price_change_event["skus_to_exclude"] | join: ", " | default: "n/a" }}
        <strong>Exclude products tagged with:</strong> {{ price_change_event["exclude_products_tagged_with"] | join: ", " | default: "n/a" }}

        <em><strong>Note:</strong> To cancel this event while it is still scheduled or ongoing, use the "cancel" keyword along with the price change event ID when running the task.</em>
      {% endcapture %}

      {% action "email" %}
        {
          "to": {{ email_recipients | json }},
          "subject": {{ email_subject | json }},
          "body": {{ email_body | newline_to_br | json }},
          "reply_to": {{ shop.contactEmail | json }},
          "from_display_name": {{ shop.name | json }}
        }
      {% endaction %}

    {% when "list" %}
      {% action "echo" price_change_events: price_change_events %}

    {% when "email" %}
      {% if price_change_events == blank %}
        {% assign email_body = "There are currently no configured price change events" %}

      {% else %}
        {% capture email_body %}
          Currently configured price change events, using the {{ task_admin_link }} task within the Mechanic app.

          {% for price_change_event in price_change_events -%}
            {%- assign price_change_event_id = price_change_event[0] -%}
            {%- assign price_change_event_data = price_change_event[1] -%}
            <strong>Price change event ID:</strong> {{ price_change_event_id }}
            <strong>Status:</strong> {{ price_change_event_data["status"] }}
            <strong>Event start:</strong> {{ price_change_event_data["start"] | date: "%FT%H:%M%z" }}
            <strong>Event end:</strong> {{ price_change_event_data["end"] | date: "%FT%H:%M%z" }}
            <strong>Set compare at price to original price during event:</strong> {{ price_change_event_data["set_compare_at_prices"] }}
            <strong>Collection handles and discounts:</strong>
            {% for keyval in price_change_event_data["collection_handles_and_discounts"] -%}
            - {{ keyval[0] }}: {{ keyval[1] }}
            {% else -%}
              n/a
            {%- endfor %}
            <strong>SKUs to include:</strong> {{ price_change_event_data["skus_to_include"] | join: ", " | default: "n/a" }}
            <strong>SKU discount:</strong> {{ price_change_event_data["sku_discount"] | default: "n/a" }},
            <strong>SKUs to exclude:</strong> {{ price_change_event_data["skus_to_exclude"] | join: ", " | default: "n/a" }}
            <strong>Exclude products tagged with:</strong> {{ price_change_event_data["exclude_products_tagged_with"] | join: ", " | default: "n/a" }}
            <hr />
          {%- endfor %}
        {% endcapture %}
      {% endif %}

      {% action "email" %}
        {
          "to": {{ email_recipients | json }},
          "subject": "All price change events as of {{ "now" | date: "%F" }}",
          "body": {{ email_body | newline_to_br | json }},
          "reply_to": {{ shop.contactEmail | json }},
          "from_display_name": {{ shop.name | json }}
        }
      {% endaction %}

    {% when "reset" %}
      {% action "event" %}
        {
          "topic": "user/price_changes/reset",
          "task_id": {{ task.id | json }}
        }
      {% endaction %}

    {% else %}
      {% if action_keyword contains "cancel" %}
        {% assign price_change_event_id = action_keyword | remove: "cancel " %}
        {% assign price_change_event = price_change_events[price_change_event_id] %}

        {% if price_change_event == blank %}
          {% error "Keyword of 'cancel' entered with an invalid price change event ID. Run task with 'list' keyword to see a list of all configured price change events." %}
          {% break %}
        {% endif %}

        {% if price_change_event.status == "scheduled" %}
          {% comment %}
            -- price change event has not started, so can just update it to cancelled and when the start/end events run they will ignore this event
          {% endcomment %}

          {% assign price_change_event["status"] = "cancelled" %}
          {% assign price_change_events[price_change_event_id] = price_change_event %}

          {% action "shopify" %}
            mutation {
              metafieldsSet(
                metafields: [
                  {
                    ownerId: {{ shop.id | json }}
                    namespace: "mechanic"
                    key: "price_change_events"
                    value: {{ price_change_events | json | json }}
                    type: "json"
                  }
                ]
              ) {
                metafields {
                  id
                  namespace
                  key
                  type
                  jsonValue
                  owner {
                    ... on Shop {
                      id
                      name
                      myshopifyDomain
                    }
                  }
                }
                userErrors {
                  code
                  field
                  message
                }
              }
            }
          {% endaction %}

          {% capture email_subject %}Scheduled price change event has been cancelled ({{ price_change_event_id }}){% endcapture %}

          {% capture email_body %}
            A scheduled price change event has been cancelled, from the {{ task_admin_link }} task within the Mechanic app.

            <strong>Price change event ID:</strong> {{ price_change_event_id }}
            <strong>Status:</strong> {{ price_change_event["status"] }}
            <strong>Event start:</strong> {{ price_change_event["start"] | date: "%FT%H:%M%z" }}
            <strong>Event end:</strong> {{ price_change_event["end"] | date: "%FT%H:%M%z" }}
            <strong>Set compare at price to original price during event:</strong> {{ price_change_event["set_compare_at_prices"] }}
            <strong>Collection handles and discounts:</strong>
            {% for keyval in price_change_event["collection_handles_and_discounts"] -%}
            - {{ keyval[0] }}: {{ keyval[1] }}
            {% else -%}
              n/a
            {%- endfor %}
            <strong>SKUs to include:</strong> {{ price_change_event["skus_to_include"] | join: ", " | default: "n/a" }}
            <strong>SKU discount:</strong> {{ price_change_event["sku_discount"] | default: "n/a" }},
            <strong>SKUs to exclude:</strong> {{ price_change_event["skus_to_exclude"] | join: ", " | default: "n/a" }}
            <strong>Exclude products tagged with:</strong> {{ price_change_event["exclude_products_tagged_with"] | join: ", " | default: "n/a" }}
          {% endcapture %}

          {% action "email" %}
            {
              "to": {{ email_recipients | json }},
              "subject": {{ email_subject | json }},
              "body": {{ email_body | newline_to_br | json }},
              "reply_to": {{ shop.contactEmail | json }},
              "from_display_name": {{ shop.name | json }}
            }
          {% endaction %}

        {% elsif price_change_event.status == "ongoing" %}
          {% comment %}
            -- price change event in progress, so we have to revert the price changes to properly cancel it
          {% endcomment %}

          {% action "event" %}
            {
              "topic": "user/price_changes/end",
              "task_id": {{ task.id | json }},
              "data": {
                "price_change_event_id": {{ price_change_event_id | json }},
                "cancel": true
              }
            }
          {% endaction %}

        {% else %}
          {% action "echo"
            message: "Price change event cannot be cancelled because it does not have a status of 'scheduled' or 'ongoing'",
            price_change_event: price_change_event
          %}
        {% endif %}

      {% else %}
        {% error
          message: "Unrecognized action keyword. Action keyword must be one of 'schedule', 'list', 'email', or 'cancel'",
          action_keyword: action_keyword
        %}
      {% endif %}
  {% endcase %}

{% elsif event.topic == "user/price_changes/start" %}
  {% assign price_change_event_id = event.data.price_change_event_id %}
  {% assign price_change_event = price_change_events[price_change_event_id] %}

  {% if event.preview %}
    {% assign price_change_event_id = "01234567-89ab-cdef" %}

    {% capture price_change_event_json %}
      {
        "status": "scheduled",
        "start": {{ "now + 1 day" | date: "%FT%H:%M%z" | json }},
        "end": {{ "now + 2 days" | date: "%FT%H:%M%z" | json }},
        "set_compare_at_prices": false,
        "collection_handles_and_discounts": {
          "alpha-beta-gamma": "10%",
          "sticks-and-stones": "-5.50",
          "lorem-ipsum": "3"
        },
        "skus_to_include": [
          "SKU-123",
          "SKU-456"
        ],
        "sku_discount": "15%",
        "skus_to_exclude": [
          "SKU-987"
        ],
        "exclude_products_tagged_with": [
          "do-not-discount"
        ]
      }
    {% endcapture %}

    {% assign price_change_event = price_change_event_json | parse_json %}
  {% endif %}

  {% assign status = price_change_event.status %}
  {% assign set_compare_at_prices = price_change_event.set_compare_at_prices %}
  {% assign collection_handles_and_discounts = price_change_event.collection_handles_and_discounts %}
  {% assign skus_to_include = price_change_event.skus_to_include %}
  {% assign sku_discount = price_change_event.sku_discount %}
  {% assign exclude_products_tagged_with = price_change_event.exclude_products_tagged_with %}
  {% assign skus_to_exclude = price_change_event.skus_to_exclude %}

  {% if status != "scheduled" %}
    {% log
      message: "This price change event does not have a status of scheduled, and thus will not start.",
      price_change_event: price_change_event
    %}
    {% break %}
  {% endif %}

  {% comment %}
    -- get collection ids for products query
  {% endcomment %}

  {% assign in_collection_checks = array %}

  {% for keyval in collection_handles_and_discounts %}
    {% capture query %}
      query {
        collectionByHandle(handle: {{ keyval[0] | json }}) {
          id
          handle
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% assign collection = result.data.collectionByHandle %}

    {% if collection != blank %}
      {% capture in_collection_check -%}
        inCollection_{{ collection.handle | replace: "-", "_" }}: inCollection(id: {{ collection.id | json }})
      {%- endcapture %}

      {% assign in_collection_checks = in_collection_checks | push: in_collection_check %}
    {% endif %}
  {% endfor %}

  {% comment %}
    -- get all products in the shop (up to 50K), unless excluded by tag
  {% endcomment %}

  {% assign search_query = "gift_card:false" %}

  {% for tag in exclude_products_tagged_with %}
    {% assign search_query = tag | json | prepend: " tag_not:" | prepend: search_query  %}
  {% endfor %}

  {% log search_query: search_query %}

  {% assign products = array %}
  {% assign cursor = nil %}

  {% for n in (1..200) %}
    {% capture query %}
      query {
        products(
          first: 250
          after: {{ cursor | json }}
          query: {{ search_query | json }}
        ) {
          pageInfo {
            hasNextPage
            endCursor
          }
          nodes {
            id
            title
            tags
            variants(first: 100) {
              nodes {
                id
                displayName
                sku
                price
                compareAtPrice
                metafield(key: "mechanic.price_change_event") {
                  jsonValue
                }
              }
            }
            {{ in_collection_checks | join: newline }}
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% if event.preview %}
      {% capture result_json %}
        {
          "data": {
            "products": {
              "nodes": [
                {
                  "id": "gid://shopify/Product/1234567890",
                  "variants": {
                    "nodes": [
                      {
                        "id": "gid://shopify/ProductVariant/1234567890",
                        "sku": "ACME-BRICK-RED",
                        "price": "10.00",
                        "compareAtPrice": "15.00",
                        "metafield": null
                      }
                    ]
                  },
                  "inCollection_{{ collection_handles_and_discounts.first.first | replace: "-", "_" | default: "sample" }}": true
                }
              ]
            }
          }
        }
      {% endcapture %}

      {% assign result = result_json | parse_json %}
    {% endif %}

    {% assign products = products | concat: result.data.products.nodes %}

    {% if result.data.products.pageInfo.hasNextPage %}
      {% assign cursor = result.data.products.pageInfo.endCursor %}
    {% else %}
      {% break %}
    {% endif %}
  {% endfor %}

  {% comment %}
    -- process products to see which qualify for metafield and variant updates
  {% endcomment %}

  {% assign metafield_set_inputs = array %}
  {% assign variant_inputs_by_product = hash %}

  {% for product in products %}
    {% comment %}
      -- For each product, match to a collection discount if applicable, then, if needed, loop through variants to see if there are any overrides
    {% endcomment %}

    {% assign product_level_discount = nil %}

    {% for keyval in collection_handles_and_discounts %}
      {% assign in_collection_label
        = "inCollection_"
        | append: keyval[0]
        | replace: "-", "_"
      %}

      {% if product[in_collection_label] %}
        {% assign product_level_discount = keyval[1] %}
        {% break %}
      {% endif %}
    {% endfor %}

    {% if product_level_discount == blank and skus_to_include == blank and skus_to_exclude == blank %}
      {% comment %}
        -- skip the product if it is not in a configured collection and there are no inclusion/exclusion SKUs configured
      {% endcomment %}

      {% continue %}
    {% endif %}

    {% for variant in product.variants.nodes %}
      {% if skus_to_exclude != blank and skus_to_exclude contains variant.sku %}
        {% log
          message: "This variant was excluded by SKU",
          variant: variant
        %}
        {% continue %}
      {% endif %}

      {% if variant.metafield != blank %}
        {% log
          message: "This variant is already part of a price change event; skipping.",
          variant: variant
        %}
        {% continue %}
      {% endif %}

      {% assign discount_to_apply = product_level_discount %}

      {% if skus_to_include != blank and skus_to_include contains variant.sku %}
        {% assign discount_to_apply = sku_discount %}
      {% endif %}

      {% if discount_to_apply == blank %}
        {% continue %}
      {% endif %}

      {% comment %}
        -- make sure prices are strings when being passed in GraphQL mutations
      {% endcomment %}

      {% assign price_to_set = nil %}
      {% assign compare_at_price_to_set = nil %}

      {% if discount_to_apply contains "%" %}
        {% assign price_to_set
          = discount_to_apply
          | remove: "%"
          | minus: 100
          | abs
          | times: variant.price
          | divided_by: 100
          | round: 2
          | append: ""
        %}

      {% elsif discount_to_apply contains "-" %}
        {% assign price_to_set
          = variant.price
          | plus: discount_to_apply
          | at_least: 0.0
          | append: ""
        %}

      {% else %}
        {% assign price_to_set = discount_to_apply | append: "" %}
      {% endif %}

      {% if set_compare_at_prices %}
        {% assign compare_at_price_to_set = variant.price %}
      {% endif %}

      {% log
        product: product.title,
        product_level_discount: product_level_discount,
        variant: variant.displayName,
        discount_to_apply: discount_to_apply,
        price_to_set: price_to_set,
        compare_at_price_to_set: compare_at_price_to_set
      %}

      {% assign price_change_event_data = hash %}
      {% assign price_change_event_data["price_change_event_id"] = price_change_event_id %}
      {% assign price_change_event_data["discount_to_apply"] = discount_to_apply %}
      {% assign price_change_event_data["original_price"] = variant.price %}
      {% assign price_change_event_data["price_to_set"] = price_to_set %}

      {% if set_compare_at_prices %}
        {% assign price_change_event_data["original_compare_at_price"] = variant.compareAtPrice %}
        {% assign price_change_event_data["compare_at_price_to_set"] = compare_at_price_to_set %}
      {% endif %}

      {% assign metafield_set_input = hash %}
      {% assign metafield_set_input["ownerId"] = variant.id %}
      {% assign metafield_set_input["namespace"] = "mechanic" %}
      {% assign metafield_set_input["key"] = "price_change_event" %}
      {% assign metafield_set_input["type"] = "json" %}
      {% assign metafield_set_input["value"] = price_change_event_data | json %}
      {% assign metafield_set_inputs = metafield_set_inputs | push: metafield_set_input %}

      {% assign variant_input = hash %}
      {% assign variant_input["id"] = variant.id %}
      {% assign variant_input["price"] = price_to_set %}

      {% if set_compare_at_prices %}
        {% assign variant_input["compareAtPrice"] = variant.price %}
      {% endif %}

      {% assign variant_inputs_by_product[product.id]
        = variant_inputs_by_product[product.id]
        | default: array
        | push: variant_input
      %}
    {% endfor %}
  {% endfor %}

  {% comment %}
    -- set metafields and then update variants in bulk
  {% endcomment %}

  {% if metafield_set_inputs != blank %}
    {% assign groups_of_metafield_set_inputs = metafield_set_inputs | in_groups_of: 25, fill_with: false %}

    {% for group_of_metafield_set_inputs in groups_of_metafield_set_inputs %}
      {% action "shopify" %}
        mutation {
          metafieldsSet(
            metafields: {{ group_of_metafield_set_inputs | graphql_arguments }}
          ) {
            metafields {
              id
              namespace
              key
              type
              jsonValue
              owner {
                ... on ProductVariant {
                  id
                  displayName
                }
              }
            }
            userErrors {
              code
              field
              message
            }
          }
        }
      {% endaction %}
    {% endfor %}
  {% endif %}

  {% for keyval in variant_inputs_by_product %}
    {% action "shopify" %}
      mutation {
        productVariantsBulkUpdate(
          productId: {{ keyval[0] | json }}
          variants: {{ keyval[1] | graphql_arguments }}
        ) {
          product {
            id
            title
            tags
          }
          productVariants {
            id
            displayName
            sku
            price
            compareAtPrice
          }
          userErrors {
            field
            message
          }
        }
      }
    {% endaction %}
  {% endfor %}

  {% comment %}
    -- update the price change event
  {% endcomment %}

  {% assign price_change_event["status"] = "ongoing" %}
  {% assign price_change_events[price_change_event_id] = price_change_event %}

  {% action "shopify" %}
    mutation {
      metafieldsSet(
        metafields: [
          {
            ownerId: {{ shop.id | json }}
            namespace: "mechanic"
            key: "price_change_events"
            value: {{ price_change_events | json | json }}
            type: "json"
          }
        ]
      ) {
        metafields {
          id
          namespace
          key
          type
          jsonValue
          owner {
            ... on Shop {
              id
              name
              myshopifyDomain
            }
          }
        }
        userErrors {
          code
          field
          message
        }
      }
    }
  {% endaction %}

  {% capture email_subject %}A scheduled price change event has started ({{ price_change_event_id }}){% endcapture %}

  {% capture email_body %}
    A scheduled price change event has started, using the {{ task_admin_link }} task within the Mechanic app.

    <strong>Price change event ID:</strong> {{ price_change_event_id }}
    <strong>Status:</strong> {{ price_change_event["status"] }}
    <strong>Event start:</strong> {{ price_change_event["start"] | date: "%FT%H:%M%z" }}
    <strong>Event end:</strong> {{ price_change_event["end"] | date: "%FT%H:%M%z" }}
    <strong>Set compare at price to original price during event:</strong> {{ price_change_event["set_compare_at_prices"] }}
    <strong>Collection handles and discounts:</strong>
    {% for keyval in price_change_event["collection_handles_and_discounts"] -%}
    - {{ keyval[0] }}: {{ keyval[1] }}
    {% else -%}
      n/a
    {%- endfor %}
    <strong>SKUs to include:</strong> {{ price_change_event["skus_to_include"] | join: ", " | default: "n/a" }}
    <strong>SKU discount:</strong> {{ price_change_event["sku_discount"] | default: "n/a" }},
    <strong>SKUs to exclude:</strong> {{ price_change_event["skus_to_exclude"] | join: ", " | default: "n/a" }}
    <strong>Exclude products tagged with:</strong> {{ price_change_event["exclude_products_tagged_with"] | join: ", " | default: "n/a" }}

    <em><strong>Note:</strong> To cancel this event while it is ongoing, use the "cancel" keyword along with the price change event ID when running the task.</em>
  {% endcapture %}

  {% action "email" %}
    {
      "to": {{ email_recipients | json }},
      "subject": {{ email_subject | json }},
      "body": {{ email_body | newline_to_br | json }},
      "reply_to": {{ shop.contactEmail | json }},
      "from_display_name": {{ shop.name | json }}
    }
  {% endaction %}

{% elsif event.topic == "user/price_changes/end" %}
  {% assign price_change_event_id = event.data.price_change_event_id %}
  {% assign price_change_event = price_change_events[price_change_event_id] %}

  {% if event.preview %}
    {% assign price_change_event_id = "01234567-89ab-cdef" %}
    {% assign price_change_event = hash %}
    {% assign price_change_event["status"] = "ongoing" %}
  {% endif %}

  {% if event.data.cancel %}
    {% comment %}
      -- go ahead and cancel, as status was already checked to be "scheduled" prior to custom event call
    {% endcomment %}

    {% assign price_change_event["status"] = "cancelled" %}

  {% else %}
    {% comment %}
      -- since this was a scheduled run, need to make sure the event status is "ongoing" before reverting changes
    {% endcomment %}

    {% if price_change_event.status != "ongoing" %}
      {% log
        message: "This price change event does not have a status of 'ongoing', and thus will not be reverted.",
        price_change_event: price_change_event
      %}
      {% break %}
    {% endif %}

    {% assign price_change_event["status"] = "completed" %}
  {% endif %}

  {% comment %}
    -- To revert the price change event, check every variant in the shop to see if the metafield exists and contains this price change event ID
  {% endcomment %}

  {% assign products = array %}
  {% assign cursor = nil %}

  {% for n in (1..200) %}
    {% capture query %}
      query {
        products(
          first: 250
          after: {{ cursor | json }}
          query: "gift_card:false"
        ) {
          pageInfo {
            hasNextPage
            endCursor
          }
          nodes {
            id
            title
            tags
            variants(first: 100) {
              nodes {
                id
                displayName
                sku
                price
                compareAtPrice
                metafield(key: "mechanic.price_change_event") {
                  jsonValue
                }
              }
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% if event.preview %}
      {% capture result_json %}
        {
          "data": {
            "products": {
              "nodes": [
                {
                  "id": "gid://shopify/Product/1234567890",
                  "variants": {
                    "nodes": [
                      {
                        "id": "gid://shopify/ProductVariant/1234567890",
                        "sku": "ACME-BRICK-RED",
                        "price": "7.50",
                        "compareAtPrice": "10.00",
                        "metafield": {
                          "id": "gid://shopify/Metafield/9876543210",
                          "jsonValue": {
                            "price_change_event_id": "01234567-89ab-cdef",
                            "discount_to_apply": "25%",
                            "original_price": "10.00",
                            "price_to_set": "7.50",
                            "original_compare_at_price": "15.00",
                            "compare_at_price_to_set": "7.50"
                          }
                        }
                      }
                    ]
                  }
                }
              ]
            }
          }
        }
      {% endcapture %}

      {% assign result = result_json | parse_json %}
    {% endif %}

    {% assign products = products | concat: result.data.products.nodes %}

    {% if result.data.products.pageInfo.hasNextPage %}
      {% assign cursor = result.data.products.pageInfo.endCursor %}
    {% else %}
      {% break %}
    {% endif %}
  {% endfor %}

  {% comment %}
    -- process products to see which qualify for metafield deletion and variant price reversion
  {% endcomment %}

  {% assign metafield_delete_inputs = array %}
  {% assign variant_inputs_by_product = hash %}

  {% for product in products %}
    {% assign variants_with_metafield = product.variants.nodes | where: "metafield" %}

    {% for variant in variants_with_metafield %}
      {% assign price_change_event_data = variant.metafield.jsonValue %}

      {% unless price_change_event_data.price_change_event_id == price_change_event_id %}
        {% log
          message: "This variant is part of a separate price change event; skipping.",
          variant: variant
        %}
        {% continue %}
      {% endunless %}

      {% assign metafield_delete_input = hash %}
      {% assign metafield_delete_input["ownerId"] = variant.id %}
      {% assign metafield_delete_input["namespace"] = "mechanic" %}
      {% assign metafield_delete_input["key"] = "price_change_event" %}
      {% assign metafield_delete_inputs = metafield_delete_inputs | push: metafield_delete_input %}

      {% assign variant_input = hash %}
      {% assign variant_input["id"] = variant.id %}

      {% assign original_price = price_change_event_data.original_price | append: "" %}
      {% assign original_compare_at_price = price_change_event_data.original_compare_at_price | append: "" %}

      {% if original_price != variant.price %}
        {% assign variant_input["price"] = original_price %}
      {% endif %}

      {% if original_compare_at_price != blank and original_compare_at_price != variant.compareAtPrice %}
        {% assign variant_input["compareAtPrice"] = original_compare_at_price %}
      {% endif %}

      {% if variant_input["price"] or variant_input["compareAtPrice"] %}
        {% assign variant_inputs_by_product[product.id]
          = variant_inputs_by_product[product.id]
          | default: array
          | push: variant_input
        %}
      {% endif %}
    {% endfor %}
  {% endfor %}

  {% comment %}
    -- update variants in bulk and then delete the price change event metafields
  {% endcomment %}

  {% for keyval in variant_inputs_by_product %}
    {% action "shopify" %}
      mutation {
        productVariantsBulkUpdate(
          productId: {{ keyval[0] | json }}
          variants: {{ keyval[1] | graphql_arguments }}
        ) {
          product {
            id
            title
            tags
          }
          productVariants {
            id
            displayName
            sku
            price
            compareAtPrice
          }
          userErrors {
            field
            message
          }
        }
      }
    {% endaction %}
  {% endfor %}

  {% if metafield_delete_inputs != blank %}
    {% assign groups_of_metafield_delete_inputs = metafield_delete_inputs | in_groups_of: 250, fill_with: false %}

    {% for group_of_metafield_delete_inputs in groups_of_metafield_delete_inputs %}
      {% action "shopify" %}
        mutation {
          metafieldsDelete(
            metafields: {{ group_of_metafield_delete_inputs | graphql_arguments }}
          ) {
            deletedMetafields {
              ownerId
              namespace
              key
            }
            userErrors {
              field
              message
            }
          }
        }
      {% endaction %}
    {% endfor %}
  {% endif %}

  {% comment %}
    -- update the price change event
  {% endcomment %}

  {% assign price_change_events[price_change_event_id] = price_change_event %}

  {% action "shopify" %}
    mutation {
      metafieldsSet(
        metafields: [
          {
            ownerId: {{ shop.id | json }}
            namespace: "mechanic"
            key: "price_change_events"
            value: {{ price_change_events | json | json }}
            type: "json"
          }
        ]
      ) {
        metafields {
          id
          namespace
          key
          type
          value
          owner {
            ... on Shop {
              id
              name
              myshopifyDomain
            }
          }
        }
        userErrors {
          code
          field
          message
        }
      }
    }
  {% endaction %}

  {% comment %}
    -- send email notification about the price change event status change
  {% endcomment %}

  {% capture email_subject %}A price change event has been {{ price_change_event["status"] }} ({{ price_change_event_id }}){% endcapture %}

  {% capture email_body %}
    A price change event has been {{ price_change_event["status"] }}, using the {{ task_admin_link }} task within the Mechanic app.

    <strong>Price change event ID:</strong> {{ price_change_event_id }}
    <strong>Status:</strong> {{ price_change_event["status"] }}
    <strong>Event start:</strong> {{ price_change_event["start"] | date: "%FT%H:%M%z" }}
    <strong>Event end:</strong> {{ price_change_event["end"] | date: "%FT%H:%M%z" }}
    <strong>Set compare at price to original price during event:</strong> {{ price_change_event["set_compare_at_prices"] }}
    <strong>Collection handles and discounts:</strong>
    {% for keyval in price_change_event["collection_handles_and_discounts"] -%}
    - {{ keyval[0] }}: {{ keyval[1] }}
    {% else -%}
      n/a
    {%- endfor %}
    <strong>SKUs to include:</strong> {{ price_change_event["skus_to_include"] | join: ", " | default: "n/a" }}
    <strong>SKU discount:</strong> {{ price_change_event["sku_discount"] | default: "n/a" }},
    <strong>SKUs to exclude:</strong> {{ price_change_event["skus_to_exclude"] | join: ", " | default: "n/a" }}
    <strong>Exclude products tagged with:</strong> {{ price_change_event["exclude_products_tagged_with"] | join: ", " | default: "n/a" }}
  {% endcapture %}

  {% action "email" %}
    {
      "to": {{ email_recipients | json }},
      "subject": {{ email_subject | json }},
      "body": {{ email_body | newline_to_br | json }},
      "reply_to": {{ shop.contactEmail | json }},
      "from_display_name": {{ shop.name | json }}
    }
  {% endaction %}

{% elsif event.topic == "user/price_changes/reset" %}
  {% comment %}
    -- To reset price change events, check every variant in the shop to see if the price change event metafield exists
  {% endcomment %}

  {% assign products = array %}
  {% assign cursor = nil %}

  {% for n in (1..200) %}
    {% capture query %}
      query {
        products(
          first: 250
          after: {{ cursor | json }}
          query: "gift_card:false"
        ) {
          pageInfo {
            hasNextPage
            endCursor
          }
          nodes {
            id
            title
            tags
            variants(first: 100) {
              nodes {
                id
                displayName
                sku
                price
                compareAtPrice
                metafield(key: "mechanic.price_change_event") {
                  jsonValue
                }
              }
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% if event.preview %}
      {% capture result_json %}
        {
          "data": {
            "products": {
              "nodes": [
                {
                  "id": "gid://shopify/Product/1234567890",
                  "variants": {
                    "nodes": [
                      {
                        "id": "gid://shopify/ProductVariant/1234567890",
                        "sku": "ACME-BRICK-RED",
                        "price": "7.50",
                        "compareAtPrice": "10.00",
                        "metafield": {
                          "id": "gid://shopify/Metafield/9876543210",
                          "jsonValue": {
                            "price_change_event_id": "01234567-89ab-cdef",
                            "discount_to_apply": "25%",
                            "original_price": "10.00",
                            "price_to_set": "7.50",
                            "original_compare_at_price": "15.00",
                            "compare_at_price_to_set": "7.50"
                          }
                        }
                      }
                    ]
                  }
                }
              ]
            }
          }
        }
      {% endcapture %}

      {% assign result = result_json | parse_json %}
    {% endif %}

    {% assign products = products | concat: result.data.products.nodes %}

    {% if result.data.products.pageInfo.hasNextPage %}
      {% assign cursor = result.data.products.pageInfo.endCursor %}
    {% else %}
      {% break %}
    {% endif %}
  {% endfor %}

  {% comment %}
    -- process products to see which qualify for metafield deletion and variant price reversion
  {% endcomment %}

  {% assign metafield_delete_inputs = array %}
  {% assign variant_inputs_by_product = hash %}
  {% assign variants_with_prices_to_not_reset = array %}

  {% for product in products %}
    {% assign variants_with_metafield = product.variants.nodes | where: "metafield" %}

    {% for variant in variants_with_metafield %}
      {% assign price_change_event_data = variant.metafield.jsonValue %}

      {% assign metafield_delete_input = hash %}
      {% assign metafield_delete_input["ownerId"] = variant.id %}
      {% assign metafield_delete_input["namespace"] = "mechanic" %}
      {% assign metafield_delete_input["key"] = "price_change_event" %}
      {% assign metafield_delete_inputs = metafield_delete_inputs | push: metafield_delete_input %}

      {% if do_not_restore_prices_on_reset %}
        {% assign variants_with_prices_to_not_reset = variants_with_prices_to_not_reset | push: variant %}
        {% continue %}
      {% endif %}

      {% assign variant_input = hash %}
      {% assign variant_input["id"] = variant.id %}

      {% assign original_price = price_change_event_data.original_price | append: "" %}
      {% assign original_compare_at_price = price_change_event_data.original_compare_at_price | append: "" %}

      {% if original_price != variant.price %}
        {% assign variant_input["price"] = original_price %}
      {% endif %}

      {% if original_compare_at_price != blank and original_compare_at_price != variant.compareAtPrice %}
        {% assign variant_input["compareAtPrice"] = original_compare_at_price %}
      {% endif %}

      {% if variant_input["price"] or variant_input["compareAtPrice"] %}
        {% assign variant_inputs_by_product[product.id]
          = variant_inputs_by_product[product.id]
          | default: array
          | push: variant_input
        %}
      {% endif %}
    {% endfor %}
  {% endfor %}

  {% comment %}
    -- update variant prices in bulk (unless opting not to) and then delete the price change event metafields
  {% endcomment %}

  {% if do_not_restore_prices_on_reset %}
    {% log
      do_not_restore_prices_on_reset: do_not_restore_prices_on_reset,
      variants_with_prices_to_not_reset: variants_with_prices_to_not_reset
    %}

  {% else %}
    {% for keyval in variant_inputs_by_product %}
      {% action "shopify" %}
        mutation {
          productVariantsBulkUpdate(
            productId: {{ keyval[0] | json }}
            variants: {{ keyval[1] | graphql_arguments }}
          ) {
            product {
              id
              title
              tags
            }
            productVariants {
              id
              displayName
              sku
              price
              compareAtPrice
            }
            userErrors {
              field
              message
            }
          }
        }
      {% endaction %}
    {% endfor %}
  {% endif %}

  {% if metafield_delete_inputs != blank %}
    {% assign groups_of_metafield_delete_inputs = metafield_delete_inputs | in_groups_of: 250, fill_with: false %}

    {% for group_of_metafield_delete_inputs in groups_of_metafield_delete_inputs %}
      {% action "shopify" %}
        mutation {
          metafieldsDelete(
            metafields: {{ group_of_metafield_delete_inputs | graphql_arguments }}
          ) {
            deletedMetafields {
              ownerId
              namespace
              key
            }
            userErrors {
              field
              message
            }
          }
        }
      {% endaction %}
    {% endfor %}
  {% endif %}

  {% comment %}
    -- delete the price change events shop metafield if needed
  {% endcomment %}

  {% if shop.metafield != blank %}
    {% action "shopify" %}
      mutation {
        metafieldsDelete(
          metafields: [
            {
              ownerId: {{ shop.id | json }}
              namespace: "mechanic"
              key: "price_change_events"
            }
          ]
        ) {
          deletedMetafields {
            ownerId
            namespace
            key
          }
          userErrors {
            field
            message
          }
        }
      }
    {% endaction %}
  {% endif %}

  {% comment %}
    -- send email notification about the price change events reset
  {% endcomment %}

  {% capture email_subject %}All price change events have been cleared{% endcapture %}

  {% capture email_body %}
    {% if do_not_restore_prices_on_reset %}
      All price change events have been cleared, using the {{ task_admin_link }} task within the Mechanic app.
    {% else %}
      All price change events have been cleared and related prices reset, using the {{ task_admin_link }} task within the Mechanic app.
    {% endif %}
  {% endcapture %}

  {% action "email" %}
    {
      "to": {{ email_recipients | json }},
      "subject": {{ email_subject | json }},
      "body": {{ email_body | newline_to_br | json }},
      "reply_to": {{ shop.contactEmail | json }},
      "from_display_name": {{ shop.name | json }}
    }
  {% endaction %}
{% endif %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more