Mechanic is a development and ecommerce automation platform for Shopify. :)
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, ordered, or variants are 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.
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.
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?)
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 %}
{% assign product_options_to_consider = options.product_options_to_consider__array_required %} {% assign option_name_and_value_separator = options.option_name_and_value_separator__required %} {% assign use_lowercase_tags = options.use_lowercase_tags__boolean %} {% assign ignore_variants_that_are_out_of_stock = options.ignore_variants_that_are_out_of_stock__boolean %} {% assign run_for_all_products_daily = options.run_for_all_products_daily__boolean %} {% comment %} -- use lowercase for option comparisons to capture variations in usage {% endcomment %} {% assign lowercase_product_options_to_consider = product_options_to_consider | json | downcase | parse_json %} {% assign jobs = array %} {% if event.topic contains "shopify/products/" or event.topic contains "shopify/inventory_levels/" %} {% comment %} -- for any of these events, query GraphQL to get single product object before paginating variants {% endcomment %} {% if event.topic contains "shopify/products/" %} {% capture query %} query { product(id: {{ product.admin_graphql_api_id | json }}) { id tags } } {% endcapture %} {% assign result = query | shopify %} {% assign product = result.data.product %} {% elsif event.topic contains "shopify/inventory_levels/" %} {% capture query %} query { inventoryLevel(id: {{ inventory_level.admin_graphql_api_id | json }}) { item { variant { product { id tags } } } } } {% endcapture %} {% assign result = query | shopify %} {% assign product = result.data.inventoryLevel.item.variant.product %} {% endif %} {% assign jobs[0] = hash %} {% assign jobs[0]["product_id"] = product.id %} {% assign jobs[0]["existing_tags"] = product.tags %} {% assign jobs[0]["selected_options_found"] = array %} {% comment %} -- get option names and values at variant level in case "ignore_variants_that_are_out_of_stock" is enabled -- paginate to support up to 2K variants {% endcomment %} {% assign cursor = nil %} {% assign selected_options_found = array %} {% for n in (1..8) %} {% capture query %} query { product(id: {{ product.id | json }}) { variants( first: 250 after: {{ cursor | json }} ) { pageInfo { hasNextPage endCursor } nodes { inventoryQuantity selectedOptions { name value } } } } } {% endcapture %} {% assign result = query | shopify %} {% for variant in result.data.product.variants.nodes %} {% if ignore_variants_that_are_out_of_stock and variant.inventoryQuantity <= 0 %} {% continue %} {% endif %} {% assign selected_options_found = variant.selectedOptions | default: array | concat: selected_options_found %} {% endfor %} {% if result.data.product.variants.pageInfo.hasNextPage %} {% assign cursor = result.data.product.variants.pageInfo.endCursor %} {% else %} {% break %} {% endif %} {% endfor %} {% assign jobs[0]["selected_options_found"] = selected_options_found | uniq %} {% 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 %} {% break %} {% elsif event.topic == "mechanic/shopify/bulk_operation" %} {% 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 ignore_variants_that_are_out_of_stock and product_variant.inventoryQuantity <= 0 %} {% continue %} {% endif %} {% assign job["selected_options_found"] = product_variant.selectedOptions | default: array | concat: job["selected_options_found"] | uniq %} {% endfor %} {% assign jobs[jobs.size] = job %} {% endfor %} {% endif %} {% comment %} -- single preview data instance to handle culmination of each event type above {% endcomment %} {% if event.preview %} {% capture jobs_json %} [ { "product_id": "gid://shopify/Product/1234567890", "existing_tags": [ "foo", "bar", "baz", {{ product_options_to_consider.first | append: option_name_and_value_separator | append: "Red" | json }}, {{ product_options_to_consider.first | append: option_name_and_value_separator | append: "Green" | json }} ], "selected_options_found": [ { "name": {{ product_options_to_consider.first | json }}, "value": "Green" }, { "name": "Title", "value": "Default Title" }, { "name": {{ product_options_to_consider.first | json }}, "value": "Blue" } ] } ] {% endcapture %} {% assign jobs = jobs_json | parse_json %} {% endif %} {% comment %} -- check tags and selected options of each product to make tagging decisions {% endcomment %} {% 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: option_name_and_value_separator | append: selected_option.value %} {% if use_lowercase_tags %} {% 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: option_name_and_value_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 { id tags } } } {% endif %} {% if tags_to_add != blank %} tagsAdd( id: {{ job.product_id | json }} tags: {{ tags_to_add | json }} ) { userErrors { field message } node { ... on Product { id tags } } } {% endif %} } {% endaction %} {% endif %} {% endfor %}
["Color", "Size"]
-
true