Auto-update inventory policy based on a "preorder" tag, with Mechanic.

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

Auto-update inventory policy based on a "preorder" tag

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.

15-day free trial – unlimited tasks

Documentation

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:

  • For efficiency, this task only scans published products in your catalog.

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
{% 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
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
run on product create and update instead of daily (boolean)
Code
{% 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 %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more