Hide out-of-stock products, with Mechanic.

Mechanic is an automation development platform for Shopify. :)

Hide out-of-stock products

by Isaac Bowen (team@usemechanic.com)

This task monitors inventory updates, and pulls the product from the selected sales channels whenever a product's total inventory meets your "out of stock" threshold. Optionally, it'll send you an email when it does so. This task can also be run manually, to scan all products at once.

Runs when a user triggers the task and when an inventory level is updated. Configuration includes sales channel names, email notification recipient, and out of stock inventory quantity.

15-day free trial – unlimited tasks

Documentation

This task monitors inventory updates, and pulls the product from the selected sales channels whenever a product's total inventory meets your "out of stock" threshold.

To scan your entire catalog for out of stock products, use the "Run task" button. Otherwise, this task will run whenever an inventory level is updated.

If you'd like to wait until the product has been out of stock for several days, use this task instead: Unpublish products that have been out of stock for x days.

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 an inventory level is updated (shopify/inventory_levels/update)
Options
sales channel names (required, array), email notification recipient (email), out of stock inventory quantity (number, required)
Script
{% capture query %}
  query {
    publications(first: 250) {
      edges {
        node {
          id
          name
        }
      }
    }
  }
{% endcapture %}

{% assign result = query | shopify %}

{% assign publication_ids = array %}
{% assign publication_names_by_id = hash %}

{% for publication_edge in result.data.publications.edges %}
  {% if options.sales_channel_names__required_array contains publication_edge.node.name %}
    {% assign publication_ids[publication_ids.size] = publication_edge.node.id %}
    {% assign publication_names_by_id[publication_edge.node.id] = publication_edge.node.name %}
  {% endif %}
{% endfor %}

{% unless event.preview %}
  {% if publication_ids.size != options.sales_channel_names__required_array.size %}
    {"error": "Expected to find {{ options.sales_channel_names__required_array.size }} sales channels; found {{ publication_ids.size }} instead. Double-check your sales channel names for accuracy!"}
  {% endif %}
{% endunless %}

{% if event.preview %}
  {% action "shopify" %}
    mutation {
      publishableUnpublish1: publishableUnpublish(
        id: "gid://shopify/Product/1234567890"
        input: {
          publicationId: "gid://shopify/Publication/1234567809"
        }
      ) {
        userErrors {
          field
          message
        }
      }
    }
  {% endaction %}

  {% if options.email_notification_recipient__email != blank %}
    {% capture email_subject %}
      Out of stock: Short Sleeve T-shirt
    {% endcapture %}

    {% capture email_body %}
      Hi there,

      Your product is out of stock!

      <a href="https://{{ shop.domain }}/admin/products/12345">Manage this product</a>

      Thanks,
      - Mechanic, for {{ shop.name }}
    {% endcapture %}

    {% action "email" %}
      {
        "to": {{ options.email_notification_recipient__email | json }},
        "subject": {{ email_subject | unindent | strip | json }},
        "body": {{ email_body | unindent | strip | newline_to_br | json }}
      }
    {% endaction %}
  {% endif %}
{% elsif event.topic == "mechanic/user/trigger" %}
  {% assign unpublished_product_links = array %}
  {% assign cursor = nil %}

  {% for n in (0..100) %}
    {% capture query %}
      query {
        products(
          first: 250
          after: {{ cursor | json }}
        ) {
          pageInfo {
            hasNextPage
          }
          edges {
            cursor
            node {
              id
              legacyResourceId
              title
              totalInventory

              {% for publication_id in publication_ids %}
                published{{ forloop.index }}: publishedOnPublication(publicationId: {{ publication_id | json }})
              {% endfor %}
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% for edge in result.data.products.edges %}
      {% assign product_node = edge.node %}

      {% if product_node.totalInventory > options.out_of_stock_inventory_quantity__number_required %}
        {% continue %}
      {% endif %}

      {% assign mutations = array %}
      {% assign publication_names = array %}

      {% for publication_id in publication_ids %}
        {% assign key = "published" | append: forloop.index %}
        {% if product_node[key] == false %}
          {% continue %}
        {% endif %}

        {% assign publication_names[publication_names.size] = publication_names_by_id[publication_id] %}

        {% capture mutation %}
          publishableUnpublish{{ forloop.index}}: publishableUnpublish(
            id: {{ product_node.id | json }}
            input: {
              publicationId: {{ publication_id | json }}
            }
          ) {
            userErrors {
              field
              message
            }
          }
        {% endcapture %}
        {% assign mutations[mutations.size] = mutation %}
      {% endfor %}

      {% if mutations != empty %}
        {% action "shopify" %}
          mutation {
            {{ mutations | join: newline }}
          }
        {% endaction %}

        {% capture link %}<li><a href="https://{{ shop.domain }}/admin/products/{{ product_node.legacyResourceId }}">{{ product_node.title }}</a> ({{ product_node.totalInventory }} - unpublished from {{ publication_names | join: ", " }})</li>{% endcapture %}
        {% assign unpublished_product_links[unpublished_product_links.size] = link %}
      {% endif %}
    {% endfor %}

    {% if result.data.products.pageInfo.hasNextPage %}
      {% assign cursor = result.data.products.edges.last.cursor %}
    {% else %}
      {% break %}
    {% endif %}
  {% endfor %}

  {% if unpublished_product_links != empty and options.email_notification_recipient__email != blank %}
    {% capture email_subject %}
      Found {{ unpublished_product_links.size }} {{ unpublished_product_links.size | pluralize: "product", "products" }} out of stock
    {% endcapture %}

    {% capture email_body %}
      Hi there,
      <br><br>
      These products were found to be under your out of stock minimum quantity ({{ options.out_of_stock_inventory_quantity__number_required }}), when adding up the inventory for each product.
      <br>
      <ul>{{ unpublished_product_links | join: "" }}</ul>
      <br>
      Thanks,
      <br>
      - Mechanic, for {{ shop.name }}
    {% endcapture %}

    {% action "email" %}
      {
        "to": {{ options.email_notification_recipient__email | json }},
        "subject": {{ email_subject | unindent | strip | json }},
        "body": {{ email_body | unindent | strip | json }}
      }
    {% endaction %}
  {% endif %}
{% elsif event.topic contains "shopify/inventory_levels/" %}
  {% capture query %}
    query {
      inventoryLevel(
        id: {{ inventory_level.admin_graphql_api_id | json }}
      ) {
        item {
          variant {
            product {
              id
              legacyResourceId
              title
              totalInventory

              {% for publication_id in publication_ids %}
                published{{ forloop.index }}: publishedOnPublication(publicationId: {{ publication_id | json }})
              {% endfor %}
            }
          }
        }
      }
    }
  {% endcapture %}

  {% assign result = query | shopify %}
  {% assign product_node = result.data.inventoryLevel.item.variant.product %}

  {% if product_node.totalInventory <= options.out_of_stock_inventory_quantity__number_required %}
    {% assign mutations = array %}
    {% assign publication_names = array %}

    {% for publication_id in publication_ids %}
      {% assign key = "published" | append: forloop.index %}
      {% if product_node[key] == false %}
        {% continue %}
      {% endif %}

      {% assign publication_names[publication_names.size] = publication_names_by_id[publication_id] %}

      {% capture mutation %}
        publishableUnpublish{{ forloop.index}}: publishableUnpublish(
          id: {{ product_node.id | json }}
          input: {
            publicationId: {{ publication_id | json }}
          }
        ) {
          userErrors {
            field
            message
          }
        }
      {% endcapture %}
      {% assign mutations[mutations.size] = mutation %}
    {% endfor %}

    {% if mutations != empty %}
      {% action "shopify" %}
        mutation {
          {{ mutations | join: newline }}
        }
      {% endaction %}

      {% if options.email_notification_recipient__email != blank %}
        {% capture email_subject %}
          Out of stock: {{ product_node.title }}
        {% endcapture %}

        {% capture email_body %}
          Hi there,

          Your product is out of stock! This product has been unpublished from: {{ publication_names | join: ", " }}.

          <a href="https://{{ shop.domain }}/admin/products/{{ product_node.legacyResourceId }}">Manage this product</a>

          Thanks,
          - Mechanic, for {{ shop.name }}
        {% endcapture %}

        {% action "email" %}
          {
            "to": {{ options.email_notification_recipient__email | json }},
            "subject": {{ email_subject | unindent | strip | json }},
            "body": {{ email_body | unindent | strip | newline_to_br | json }}
          }
        {% endaction %}
      {% endif %}
    {% endif %}
  {% endif %}
{% endif %}
Mechanic tasks are written in Liquid, which makes them easy to write and easy to modify. Learn more about our platform.
Defaults
Sales channel names
["Online Store"]
Out of stock inventory quantity
0