Mechanic is a development and ecommerce automation platform for Shopify. :)
This task scans your product catalog nightly or when you hit the "Run task" button, checking for the presence of a "preorder" tag, or a tag that starts with "preorder_" (like "preorder_summer"). If this tag is found, the task updates the product to allow customers to purchase the product after it's out of stock. If no preorder tag is found, the task makes sure customers cannot purchase the product if it's out of stock.
Runs Occurs every day at midnight (in local time), Occurs when a bulk operation is completed, and Occurs when a user manually triggers the task. Configuration includes run on product create and update instead of daily.
This task scans your product catalog nightly or when you hit the "Run task" button, checking for the presence of a "preorder" tag, or a tag that starts with "preorder_" (like "preorder_summer"). If this tag is found, the task updates the product to allow customers to purchase the product after it's out of stock. If no preorder tag is found, the task makes sure customers cannot purchase the product if it's out of stock.
Optionally, choose whether to "Run on product create and update instead of daily", which includes events such as product tagging, inventory level adjustments, and inventory policy changes.
Notes:
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?)
{% if options.run_on_product_create_and_update_instead_of_daily__boolean %} shopify/products/create shopify/products/update {% else %} mechanic/scheduler/daily {% endif %} mechanic/shopify/bulk_operation mechanic/user/trigger
{% if event.topic == "shopify/products/create" or event.topic == "shopify/products/update" %} {% comment %} -- only check published products {% endcomment %} {% if product.published_at == blank %} {% unless event.preview %} {% log "Product is not published; skipping scan" %} {% break %} {% endunless %} {% endif %} {% 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 tags variants( first: 250 after: {{ cursor | json }} ) { pageInfo { hasNextPage endCursor } nodes { id inventoryPolicy } } } } {% endcapture %} {% assign result = query | shopify %} {% if event.preview %} {% capture result_json %} { "data": { "product": { "id": "gid://shopify/Product/1234567890", "tags": "preorder", "variants": { "nodes": [ { "id": "gid://shopify/ProductVariant/1234567890", "inventoryPolicy": "DENY" } ] } } } } {% 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" or event.topic contains "mechanic/scheduler/" %} {% comment %} -- get all published products in the shop, and all of their variants {% endcomment %} {% capture bulk_operation_query %} query { products( query: "gift_card:false published_status:published" ) { edges { node { __typename id tags variants { edges { node { __typename id inventoryPolicy } } } } } } } {% 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":["preorder"]} {"__typename":"ProductVariant","id":"gid://shopify/ProductVariant/1234567890","inventoryPolicy":"DENY","__parentId":"gid://shopify/Product/1234567890"} {% endcapture %} {% assign bulkOperation = hash %} {% assign bulkOperation["objects"] = jsonl_string | parse_jsonl %} {% endif %} {% comment %} -- products and variants will be separate lines in the JSONL, so filter them into their own arrays -- match variants to their parents in the products processing loop (this prevents creating another potentially large array in memory) {% endcomment %} {% 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 %} {% comment %} -- overselling should be disabled unless this product has a preorder tag {% endcomment %} {% assign expected_inventory_policy = "DENY" %} {% for tag in product.tags %} {% assign tag_parts = tag | split: "_" %} {% if tag_parts[0] == "preorder" %} {% assign expected_inventory_policy = "CONTINUE" %} {% break %} {% endif %} {% endfor %} {% comment %} -- save GraphQL mutation inputs for variants that need to have their inventory policy updated to match what is expected based on product tags {% endcomment %} {% assign variant_inputs = array %} {% for variant in variants %} {% if variant.inventoryPolicy != expected_inventory_policy %} {% assign variant_input = hash %} {% assign variant_input["id"] = variant.id %} {% assign variant_input["inventoryPolicy"] = expected_inventory_policy %} {% assign variant_inputs = variant_inputs | push: variant_input %} {% endif %} {% endfor %} {% if variant_inputs != blank %} {% comment %} -- pass variant inputs as GraphQL variables to handle products with a very large number of variants to update {% endcomment %} {% assign variables = hash %} {% assign variables["variants"] = variant_inputs %} {% capture query %} mutation productVariantsBulkUpdate($variants: [ProductVariantsBulkInput!]!) { productVariantsBulkUpdate( productId: {{ product.id | json }} variants: $variants ) { product { id title tags } userErrors { field message } } } {% endcapture %} {% action "shopify" query: query, variables: variables %} {% endif %} {% endfor %}