Tag products by their price ranges, with Mechanic.

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

Tag products by their price ranges

Some themes (like Mogo!) support browsing products by price range. Under the hood, this functionality is powered by tags that look like "rprice-1-100". This task watches for new and updated products, making sure that the product range tags stay in sync with prices available for each product.

Runs Occurs whenever a product is created, Occurs whenever a product is updated, ordered, or variants are added, removed or updated, Occurs when a user manually triggers the task, and Occurs when a bulk operation is completed. Configuration includes tag prefix, range maximum, range minimum, range increment, use only the single smallest range that applies to each variant, and use every range that applies to each variant.

15-day free trial – unlimited tasks

Documentation

Some themes (like Mogo!) support browsing products by price range. Under the hood, this functionality is powered by tags that look like "rprice-1-100". This task watches for new and updated products, making sure that the product range tags stay in sync with prices available for each product.

Choose between tagging with every applicable price range (e.g. a product with price $50 might be tagged for the ranges 0-50, 0-75, 0-100, 25-50, 25-75, 25-100, 50-75, and 50-100), or between tagging with only the smallest applicable price ranges (e.g. only 50-75).

Run the task manually to update the tags for all active and draft products in your shop.

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
shopify/products/create
shopify/products/update
mechanic/user/trigger
mechanic/shopify/bulk_operation
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
tag prefix (required), range maximum (required, number), range minimum (required, number), range increment (required, number), use only the single smallest range that applies to each variant (boolean), use every range that applies to each variant (boolean)
Code
{% assign tag_prefix = options.tag_prefix__required %}
{% assign range_max = options.range_maximum__required_number | round %}
{% assign range_min = options.range_minimum__required_number | round %}
{% assign range_increment = options.range_increment__required_number | round %}
{% assign use_only_the_single_smallest_range = options.use_only_the_single_smallest_range_that_applies_to_each_variant__boolean %}
{% assign use_every_range = options.use_every_range_that_applies_to_each_variant__boolean %}

{% assign range_steps = range_max | minus: range_min | divided_by: range_increment %}

{% if range_max <= range_min %}
  {% error "'Range maximum' is smaller than or equal to 'Range minimum'. Try again!" %}
{% endif %}

{% assign range_max_mod = range_max | minus: range_min | modulo: range_increment %}

{% if range_max_mod != 0 %}
  {% error %}
    "Ensure both 'Range maximum' is an even multiple of {{ range_increment }} away from 'Range minimum'."
  {% enderror %}
{% endif %}

{% if use_only_the_single_smallest_range and use_every_range %}
  {% error "Use only one of the 'Use only...' options. :)" %}

{% elsif use_only_the_single_smallest_range == false and use_every_range == false %}
  {% error "Use exactly one of the 'Use only...' options. :)" %}
{% endif %}

{% if event.topic == "shopify/products/create" or event.topic == "shopify/products/update" %}
  {% comment %}
    -- query product and variant data, up to 2K variants
  {% endcomment %}

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

  {% for n in (1..8) %}
    {% capture query %}
      query {
        product(id: {{ product.admin_graphql_api_id | json }}) {
          id
          variants(
            first: 250
            after: {{ cursor | json }}
          ) {
            pageInfo {
              hasNextPage
              endCursor
            }
            nodes {
              price
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% if event.preview %}
      {% capture result_json %}
        {
          "data": {
            "product": {
              "id": "gid://shopify/Product/1234567890",
              "tags": {{ tag_prefix | append: "1-99999999999999999" | json }},
              "variants": {
                "nodes": [
                  {
                    "price": {{ range_min | plus: 0.01 | json }}
                  },
                  {
                    "price": {{ range_max | minus: 0.01 | json }}
                  }
                ]
              }
            }
          }
        }
      {% endcapture %}

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

    {% comment %}
      -- only save the product ID and tags from the product object; a separate array is used to support 2K variants
    {% endcomment %}

    {% assign products[0] = result.data.product | except: "variants" %}
    {% assign variants = variants | concat: result.data.product.variants.nodes %}

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

{% elsif event.topic == "mechanic/user/trigger" %}
  {% comment %}
    -- get all active and draft products in the shop, and all of their variant prices
  {% endcomment %}

  {% capture bulk_operation_query %}
    query {
      products(
        query: "-status:archived"
      ) {
        edges {
          node {
            __typename
            id
            tags
            variants {
              edges {
                node {
                  __typename
                  id
                  price
                }
              }
            }
          }
        }
      }
    }
  {% endcapture %}

  {% action "shopify" %}
    mutation {
      bulkOperationRunQuery(
        query: {{ bulk_operation_query | json }}
      ) {
        bulkOperation {
          id
          status
        }
        userErrors {
          field
          message
        }
      }
    }
  {% endaction %}

  {% break %}

{% elsif event.topic == "mechanic/shopify/bulk_operation" %}
  {% if event.preview %}
    {% capture jsonl_string %}
      {"__typename":"Product","id":"gid://shopify/Product/1234567890","tags":[{{ tag_prefix | append: "1-99999999999999999" | json }}]}
      {"__typename":"ProductVariant","id":"gid://shopify/ProductVariant/1234567890","price":{{ range_min | plus: 0.01 | json }},"__parentId":"gid://shopify/Product/1234567890"}
      {"__typename":"ProductVariant","id":"gid://shopify/ProductVariant/2345678901","price":{{ range_max | minus: 0.01 | json }},"__parentId":"gid://shopify/Product/1234567890"}
    {% endcapture %}

    {% assign bulkOperation = hash %}
    {% assign bulkOperation["objects"] = jsonl_string | parse_jsonl %}
  {% endif %}

  {% assign products = bulkOperation.objects | where: "__typename", "Product" %}
  {% assign bulk_variants = bulkOperation.objects | where: "__typename", "ProductVariant" %}
{% endif %}

{% comment %}
  -- using products array, either process the product which triggered the event, or all products from bulk op query
{% endcomment %}

{% for product in products %}
  {% if event.topic == "mechanic/shopify/bulk_operation" %}
    {% assign variants = bulk_variants | where: "__parentId", product.id %}
  {% endif %}

  {% assign existing_price_range_tags = array %}
  {% assign applicable_price_range_tags = array %}

  {% for tag in product.tags %}
    {% assign possible_prefix = tag | slice: 0, tag_prefix.size %}

    {% if possible_prefix == tag_prefix %}
      {% assign existing_price_range_tags = existing_price_range_tags | push: tag %}
    {% endif %}
  {% endfor %}

  {% if use_only_the_single_smallest_range %}
    {% for variant in variants %}
      {% assign variant_price = variant.price | times: 1 %}
      {% assign variant_price_left = variant_price | divided_by: range_increment | floor | times: range_increment %}
      {% assign variant_price_right = variant_price_left | plus: range_increment %}

      {% capture tag %}{{ tag_prefix }}{{ variant_price_left }}-{{ variant_price_right }}{% endcapture %}

      {% unless applicable_price_range_tags contains tag %}
        {% assign applicable_price_range_tags = applicable_price_range_tags | push: tag %}
      {% endunless %}
    {% endfor %}

  {% elsif use_every_range %}
    {% for i in (0..range_steps) %}
      {% for j in (i..range_steps) %}
        {% if j <= i %}
          {% continue %}
        {% endif %}

        {% assign step_min = i | times: range_increment | plus: range_min %}
        {% assign step_max = j | times: range_increment | plus: range_min %}

        {% for variant in variants %}
          {% assign variant_price = variant.price | times: 1 %}

          {% if variant_price >= step_min and variant_price <= step_max %}
            {% capture tag %}{{ tag_prefix }}{{ step_min }}-{{ step_max }}{% endcapture %}

            {% unless applicable_price_range_tags contains tag %}
              {% assign applicable_price_range_tags = applicable_price_range_tags | push: tag %}
            {% endunless %}
          {% endif %}
        {% endfor %}
      {% endfor %}
    {% endfor %}
  {% endif %}

  {% assign tags_to_add = array %}
  {% assign tags_to_remove = array %}

  {% for tag in applicable_price_range_tags %}
    {% unless existing_price_range_tags contains tag %}
      {% assign tags_to_add = tags_to_add | push: tag %}
    {% endunless %}
  {% endfor %}

  {% for tag in existing_price_range_tags %}
    {% unless applicable_price_range_tags contains tag %}
      {% assign tags_to_remove = tags_to_remove | push: tag %}
    {% endunless %}
  {% endfor %}

  {% if tags_to_add != blank or tags_to_remove != blank %}
    {% action "shopify" %}
      mutation {
        {% if tags_to_add != blank %}
          tagsAdd(
            id: {{ product.id | json }}
            tags: {{ tags_to_add | json }}
          ) {
            userErrors {
              field
              message
            }
          }
        {% endif %}
        {% if tags_to_remove != blank %}
          tagsRemove(
            id: {{ product.id | json }}
            tags: {{ tags_to_remove | json }}
          ) {
            userErrors {
              field
              message
            }
          }
        {% endif %}
      }
    {% endaction %}
  {% endif %}
{% endfor %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more
Defaults
Tag prefix
rprice-
Range maximum
200
Range minimum
0
Range increment
25