Make products unavailable, after the date/time stored in product metafields, with Mechanic.

Mechanic is an automation development platform for Shopify. :)

Make products unavailable, after the date/time stored in product metafields

by Isaac Bowen (team@usemechanic.com)

Use this task to automatically set product inventory to 0, and the product's inventory policy to deny out-of-stock purchases, after a date/time that you specify in a product metafield.

Runs when a user triggers the task and when a bulk operation finishes. Configuration includes datetime product metafield namespace, datetime product metafield key, datetime format, run hourly, run daily, and test mode.

15-day free trial – unlimited tasks

Documentation

Configure "Datetime product metafield namespace" and "Datetime product metafield key" with the namespace and key for the product metafield that holds a date/time value. Configure "Datetime format" with a valid date/time format, using http://www.strfti.me/ as a guide. This format must exactly match the values you keep in your product metafields. If a value is found that does not match this format, this task will skip that product.

When you run this task (or as it runs hourly/daily, per your configuration), the task will look for products who have a date/time value that's in the past. For qualifying products, all inventory items with a level greater than 0 will have their levels set to exactly 0, and all variants will have their inventory policies set to "deny" (preventing sales, once the variants are all out of stock).

Developer details

Mechanic is designed to benefit everybody: merchants, customers, developers, agencies, Gurus, 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.

Events
when a user triggers the task (mechanic/user/trigger)
when a bulk operation finishes (mechanic/shopify/bulk_operation)
Options
datetime product metafield namespace (required), datetime product metafield key (required), datetime format (required), run hourly (boolean), run daily (boolean), test mode (boolean)
Script
{% comment %}
  Preferred option order:

  {{ options.datetime_product_metafield_namespace__required }}
  {{ options.datetime_product_metafield_key__required }}
  {{ options.datetime_format__required }}
  {{ options.run_hourly__boolean }}
  {{ options.run_daily__boolean }}
  {{ options.test_mode__boolean }}
{% endcomment %}

{% if event.topic == "mechanic/user/trigger" or event.topic contains "mechanic/scheduler/" %}
  {% capture bulk_operation_query %}
    query {
      products {
        edges {
          node {
            __typename
            id
            metafield(
              namespace: {{ options.datetime_product_metafield_namespace__required | json }}
              key: {{ options.datetime_product_metafield_key__required | json }}
            ) {
              value
            }
            variants {
              edges {
                node {
                  __typename
                  id
                  inventoryPolicy
                  inventoryItem {
                    inventoryLevels {
                      edges {
                        node {
                          __typename
                          id
                          available
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  {% endcapture %}

  {% action "shopify" %}
    mutation {
      bulkOperationRunQuery(
        query: {{ bulk_operation_query | json }}
      ) {
        bulkOperation {
          id
          status
        }
        userErrors {
          field
          message
        }
      }
    }
  {% endaction %}
{% elsif event.topic == "mechanic/shopify/bulk_operation" %}
  {% assign now_s = "now" | date: "%s" | times: 1 %}

  {% if event.preview %}
    {% capture objects_json %}
    [
      {
        "__typename": "ProductVariant",
        "id": "gid://shopify/ProductVariant/1234567890",
        "inventoryPolicy": "CONTINUE",
        "inventoryItem": {},
        "__parentId": "gid://shopify/Product/1234567890",

        {% comment %}
          This is just stub data, remember, and it doesn't have
          the richness of a real bulkOperations.objects object. So,
          we insert this parent object in the place where we'd
          normally have access to it, even though this isn't literally
          where it would appear in the JSONL file from Shopify
        {% endcomment %}
        "__parent": {
          "__typename": "Product",
          "id": "gid://shopify/Product/1234567890",
          "metafield": {
            "value": {{ "2019-01-01" | date: options.datetime_format__required | json }}
          }
        }
      },
      {
        "__typename": "InventoryLevel",
        "id": "gid://shopify/InventoryLevel/1234567890?inventory_item_id=1234567890",
        "available": 4,
        "__parentId": "gid://shopify/ProductVariant/1234567890"
      }
    ]
    {% endcapture %}

    {% assign bulkOperation = hash %}
    {% assign bulkOperation["objects"] = objects_json | parse_json %}
  {% endif %}

  {% assign summary = array %}

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

  {% for variant in variants %}
    {% assign variant_mutations = array %}
    {% assign inventoryLevels = bulkOperation.objects | where: "__typename", "InventoryLevel" | where: "__parentId", variant.id %}
    {% assign product = variant.__parent %}

    {% if product.metafield.value == blank %}
      {% continue %}
    {% endif %}

    {% assign product_date = product.metafield.value | parse_date: "%m/%d/%y" %}
    {% if product_date == nil %}
      {% log message: "Found a product metafield value that doesn't match the configured date format. Skipping.", product_metafield_value: product.metafield.value %}
      {% continue %}
    {% endif %}

    {% assign product_date_s = product_date | date: "%s" | times: 1 %}

    {% assign product_date_formatted = product_date_s | date: options.datetime_format__required %}
    {% if product_date_formatted != product.metafield.value %}
      {% log "Product date metafield was found to be ", product.metafield.value, " which does not match the configured datetime format." %}
    {% endif %}

    {% if product_date_s > now_s %}
      {% continue %}
    {% endif %}

    {% if variant.inventoryPolicy != "DENY" %}
      {% capture mutation %}
        productVariantUpdate(
          input: {
            id: {{ variant.id | json }}
            inventoryPolicy: DENY
          }
        ) {
          product {
            id
            title
          }
          productVariant {
            inventoryPolicy
          }
          userErrors {
            field
            message
          }
        }
      {% endcapture %}

      {% assign variant_mutations[variant_mutations.size] = mutation %}
      {% assign summary[summary.size] = "Change variant inventory policy for " | append: variant.id | append: " to DENY" %}
    {% endif %}

    {% for inventoryLevel in inventoryLevels %}
      {% if inventoryLevel.available > 0 %}
        {% capture mutation %}
          inventoryAdjustQuantity(
            input: {
              inventoryLevelId: {{ inventoryLevel.id | json }}
              availableDelta: {{ inventoryLevel.available | times: -1 | json }}
            }
          ) {
            inventoryLevel {
              available
            }
            userErrors {
              field
              message
            }
          }
        {% endcapture %}

        {% assign variant_mutations[variant_mutations.size] = mutation %}
      {% assign summary[summary.size] = "Set inventory level for " | append: inventoryLevel.id | append: " to 0, by adjusting it by -" | append: inventoryLevel.available %}
      {% endif %}
    {% endfor %}

    {% if variant_mutations != empty %}
      {% if options.test_mode__boolean %}
        {% action "echo" summary %}
      {% else %}
        {% action "shopify" %}
          mutation {
            {{ variant_mutations | join: newline }}
          }
        {% endaction %}
      {% endif %}
    {% endif %}
  {% endfor %}
{% endif %}
Mechanic tasks are written in Liquid, which makes them easy to write and easy to modify. Learn more about our platform.
Defaults
Datetime format
%Y/%m/%d