Auto-tag products by their options, with Mechanic.

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

Auto-tag products by their options

Use this task to add tags to your products, based on their options, for easy filtering. A shirt might be tagged with "Color-Blue" and "Size-XL", for example. Optionally, choose to ignore options that only appear on out-of-stock variants, or choose to apply all tags in lowercase.

Runs Occurs when a user manually triggers the task, Occurs when a bulk operation is completed, Occurs whenever a product is created, and Occurs whenever a product is updated, or whenever a product is ordered, or whenever a variant is added, removed, or updated. Configuration includes product options to consider, option name and value separator, use lowercase tags, ignore variants that are out of stock, run for all products daily, run for individual products when they are created or updated, and run for individual products when their inventory changes.

15-day free trial – unlimited tasks

Documentation

Use this task to add tags to your products, based on their options, for easy filtering. A shirt might be tagged with "Color-Blue" and "Size-XL", for example. Optionally, choose to ignore options that only appear on out-of-stock variants, or choose to apply all tags in lowercase.

Use this task to add tags to your products, based on their options, for easy filtering. A shirt might be tagged with "Color-Blue" and "Size-XL", for example.

Optionally, choose to ignore options that only appear on out-of-stock variants, or choose to apply all tags in lowercase.

Change the separator to change the way tags are built. Using a dash results in "Color-Blue", an underscore results in "Color_Blue", and a colon with a space yields "Color: Blue".

This task will remove option tags that are no longer applicable, by scanning for tag prefixes using the list of product options to consider.

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/trigger

{% if options.run_for_all_products_daily__boolean %}
  mechanic/scheduler/daily
{% endif %}

mechanic/shopify/bulk_operation

{% if options.run_for_individual_products_when_they_are_created_or_updated__boolean %}
  shopify/products/create
  shopify/products/update
{% endif %}

{% if options.run_for_individual_products_when_their_inventory_changes__boolean %}
  shopify/inventory_levels/update
{% endif %}
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
product options to consider (array, required), option name and value separator (required), use lowercase tags (boolean), ignore variants that are out of stock (boolean), run for all products daily (boolean), run for individual products when they are created or updated (boolean), run for individual products when their inventory changes (boolean)
Code
{% comment %}
  Preferred option order:

  {{ options.product_options_to_consider__array_required }}
  {{ options.option_name_and_value_separator__required }}
  {{ options.use_lowercase_tags__boolean }}
  {{ options.ignore_variants_that_are_out_of_stock__boolean }}
  {{ options.run_for_all_products_daily__boolean }}
  {{ options.run_for_individual_products_when_they_are_created_or_updated__boolean }}
  {{ options.run_for_individual_products_when_their_inventory_changes__boolean }}
{% endcomment %}

{% assign jobs = array %}

{% assign separator = options.option_name_and_value_separator__required %}

{% assign lowercase_product_options_to_consider = array %}
{% for option in options.product_options_to_consider__array_required %}
  {% assign lowercase_product_options_to_consider[lowercase_product_options_to_consider.size] = option | downcase %}
{% endfor %}

{% if options.use_lowercase_tags__boolean %}
  {% assign product_options_to_consider = lowercase_product_options_to_consider %}
{% else %}
  {% assign product_options_to_consider = options.product_options_to_consider__array_required %}
{% endif %}

{% if event.topic contains "shopify/products/" or event.topic contains "shopify/inventory_levels/" %}
  {% if event.topic contains "shopify/inventory_levels/" %}
    {% capture query %}
      query {
        inventoryLevel(id: {{ inventory_level.admin_graphql_api_id | json }}) {
          item {
            variant {
              product {
                legacyResourceId
              }
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% assign product = shop.products[result.data.inventoryLevel.item.variant.product.legacyResourceId] %}
  {% endif %}

  {% if event.preview %}
    {% assign product = hash %}
    {% assign product["admin_graphql_api_id"] = "gid://shopify/Product/1234567890" %}
    {% assign product["tags"] = "foo, bar, baz, " | append: options.product_options_to_consider__array_required.first | append: separator | append: "Red, " | append: options.product_options_to_consider__array_required.first | append: separator | append: "Green" %}
  {% endif %}

  {% assign jobs[0] = hash %}
  {% assign jobs[0]["product_id"] = product.admin_graphql_api_id %}
  {% assign jobs[0]["existing_tags"] = product.tags | split: ", " %}
  {% assign jobs[0]["selected_options_found"] = array %}

  {% assign cursor = nil %}

  {% for n in (0..100) %}
    {% capture query %}
      query {
        product(id: {{ product.admin_graphql_api_id | json }}) {
          variants(
            first: 250
            after: {{ cursor | json }}
          ) {
            pageInfo {
              hasNextPage
            }
            edges {
              cursor
              node {
                inventoryQuantity
                selectedOptions {
                  name
                  value
                }
              }
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% if event.preview %}
      {% capture result_json %}
        {
          "data": {
            "product": {
              "variants": {
                "edges": [
                  {
                    "node": {
                      "inventoryQuantity": 0,
                      "selectedOptions": [
                        {
                          "name": {{ options.product_options_to_consider__array_required.first | json }},
                          "value": "Green"
                        }
                      ]
                    }
                  },
                  {
                    "node": {
                      "inventoryQuantity": 10,
                      "selectedOptions": [
                        {
                          "name": "Title",
                          "value": "Default Title"
                        },
                        {
                          "name": {{ options.product_options_to_consider__array_required.first | json }},
                          "value": "Blue"
                        }
                      ]
                    }
                  }
                ]
              }
            }
          }
        }
      {% endcapture %}

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

    {% for variant_edge in result.data.product.variants.edges %}
      {% assign variant = variant_edge.node %}

      {% if options.ignore_variants_that_are_out_of_stock__boolean and variant.inventoryQuantity <= 0 %}
        {% continue %}
      {% endif %}

      {% assign jobs[0]["selected_options_found"] = jobs[0]["selected_options_found"] | concat: variant.selectedOptions %}
    {% endfor %}

    {% if result.data.product.variants.pageInfo.hasNextPage %}
      {% assign cursor = result.data.product.variants.edges.last.cursor %}
    {% else %}
      {% break %}
    {% endif %}
  {% endfor %}
{% elsif event.topic == "mechanic/user/trigger" or event.topic contains "mechanic/scheduler/" %}
  {% capture bulk_operation_query %}
    query {
      products {
        edges {
          node {
            __typename
            id
            tags
            variants {
              edges {
                node {
                  __typename
                  inventoryQuantity
                  selectedOptions {
                    name
                    value
                  }
                }
              }
            }
          }
        }
      }
    }
  {% endcapture %}

  {% action "shopify" %}
    mutation {
      bulkOperationRunQuery(
        query: {{ bulk_operation_query | json }}
      ) {
        bulkOperation {
          id
          status
        }
        userErrors {
          field
          message
        }
      }
    }
  {% endaction %}
{% elsif event.topic == "mechanic/shopify/bulk_operation" %}
  {% if event.preview %}
    {% capture bulkOperation_json %}
      {
        "objects": [
          {
            "__typename": "Product",
            "id": "gid://shopify/Product/1234567890",
            "tags": [
              "foo",
              "bar",
              "baz",
              {{ options.product_options_to_consider__array_required.first | append: separator | append: "Red" | json }},
              {{ options.product_options_to_consider__array_required.first | append: separator | append: "Green" | json }}
            ]
          },
          {
            "__typename": "ProductVariant",
            "inventoryQuantity": 0,
            "selectedOptions": [
              {
                "name": {{ options.product_options_to_consider__array_required.first | json }},
                "value": "Green"
              }
            ],
            "__parentId": "gid://shopify/Product/1234567890"
          },
          {
            "__typename": "ProductVariant",
            "inventoryQuantity": 10,
            "selectedOptions": [
              {
                "name": "Title",
                "value": "Default Title"
              },
              {
                "name": {{ options.product_options_to_consider__array_required.first | json }},
                "value": "Blue"
              }
            ],
            "__parentId": "gid://shopify/Product/1234567890"
          }
        ]
      }
    {% endcapture %}

    {% assign bulkOperation = bulkOperation_json | parse_json %}
  {% endif %}

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

  {% for product in products %}
    {% assign job = hash %}
    {% assign job["product_id"] = product.id %}
    {% assign job["existing_tags"] = product.tags %}
    {% assign job["selected_options_found"] = array %}

    {% assign product_variants = variants | where: "__parentId", product.id %}
    {% for product_variant in product_variants %}
      {% if options.ignore_variants_that_are_out_of_stock__boolean and product_variant.inventoryQuantity <= 0 %}
        {% continue %}
      {% endif %}

      {% assign job["selected_options_found"] = job["selected_options_found"] | concat: product_variant.selectedOptions %}
    {% endfor %}

    {% assign jobs[jobs.size] = job %}
  {% endfor %}
{% endif %}

{% log jobs: jobs %}

{% for job in jobs %}
  {% assign tags_to_add = array %}
  {% assign tags_to_remove = array %}

  {% assign applicable_tags = array %}
  {% for selected_option in job.selected_options_found %}
    {% assign lowercase_selected_option_name = selected_option.name | downcase %}
    {% unless lowercase_product_options_to_consider contains lowercase_selected_option_name %}
      {% continue %}
    {% endunless %}

    {% assign tag = selected_option.name | append: separator | append: selected_option.value %}

    {% if options.use_lowercase_tags__boolean %}
      {% assign tag = tag | downcase %}
    {% endif %}

    {% assign applicable_tags[applicable_tags.size] = tag %}
  {% endfor %}

  {% for tag in job.existing_tags %}
    {% assign lowercase_tag_prefix = tag | split: separator | first | downcase %}

    {% if lowercase_product_options_to_consider contains lowercase_tag_prefix %}
      {% unless applicable_tags contains tag %}
        {% assign tags_to_remove[tags_to_remove.size] = tag %}
      {% endunless %}
    {% endif %}
  {% endfor %}

  {% for tag in applicable_tags %}
    {% unless job.existing_tags contains tag %}
      {% assign tags_to_add[tags_to_add.size] = tag %}
    {% endunless %}
  {% endfor %}

  {% if tags_to_add != blank or tags_to_remove != blank %}
    {% action "shopify" %}
      mutation {
        {% if tags_to_remove != blank %}
          tagsRemove(
            id: {{ job.product_id | json }}
            tags: {{ tags_to_remove | json }}
          ) {
            userErrors {
              field
              message
            }
            node {
              ... on Product {
                onlineStoreUrl
                tags
              }
            }
          }
        {% endif %}
        {% if tags_to_add != blank %}
          tagsAdd(
            id: {{ job.product_id | json }}
            tags: {{ tags_to_add | json }}
          ) {
            userErrors {
              field
              message
            }
            node {
              ... on Product {
                onlineStoreUrl
                tags
              }
            }
          }
        {% endif %}
      }
    {% endaction %}
  {% endif %}
{% endfor %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more
Defaults
Product options to consider
["Color", "Size"]
Option name and value separator
-
Run for individual products when they are created or updated
true