Move out-of-stock products to the end of a collection, with Mechanic.

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

Move out-of-stock products to the end of a collection

This task re-sorts your collections, beginning with the sort order of your choice (alphabetically, best selling first, etc), and then moving all out-of-stock products to the very end of the collection.

Runs when a user triggers the task. Configuration includes base sort order, collection titles or ids to include, collection titles or ids to exclude, force manual sorting on collections, run hourly, and run daily.

15-day free trial – unlimited tasks

Documentation

This task re-sorts your collections, beginning with the sort order of your choice (alphabetically, best selling first, etc), and then moving all out-of-stock products to the very end of the collection.

Run this task manually to re-sort your collections on demand. Optionally, configure this task to run hourly or nightly as well.

By default, this task will run against ALL of your collections. Alternatively, you may configure this task to only include certain collections using each collection's title, or its ID. Learn how to find the collection IDs.

Conversely, you may configure this task to exclude certain collections using each collection's title, or its ID, in which case it will run against all collections except the ones in this list. [Note: if there are any collections entered into the inclusion list, then the exclusion list will be ignored.]

The combination of inclusion and exclusion options can allow multiple copies of this task to run (to use different base sorting for instance), provided they are configured properly.

This task will skip any collections it encounters if the collection sorting is not already set to manual. Check the "Force manual sorting on collections" option to have the task update those collections to the manual sorting required by this task.

You may use any of these options for the base sort order:

  • MANUAL
  • ALPHA_ASC
  • ALPHA_DESC
  • BEST_SELLING
  • CREATED
  • CREATED_DESC
  • PRICE_ASC
  • PRICE_DESC

Note: To function correctly, the "Perform action runs in sequence" option should stay enabled in the task's advanced settings.

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.

Open source
View on GitHub to contribute to this task
Events
when a user triggers the task (mechanic/user/trigger)
Options
base sort order (required), collection titles or ids to include (array), collection titles or ids to exclude (array), force manual sorting on collections (boolean), run hourly (boolean), run daily (boolean)
Script
{% assign allowed_base_sort_orders = "MANUAL,BEST_SELLING,ALPHA_ASC,ALPHA_DESC,PRICE_DESC,PRICE_ASC,CREATED_DESC,CREATED" | split: "," %}

{% unless allowed_base_sort_orders contains options.base_sort_order__required %}
  {% error %}
    {{ allowed_base_sort_orders | join: ", " | prepend: "Base sort order must be one of: " | json }}
  {% enderror %}
{% endunless %}

{% log %}
  {{ options.base_sort_order__required | prepend: "Base sort order for this task run: " | json }}
{% endlog %}

{% assign product_sort_order = options.base_sort_order__required %}
{% assign reverse_sort = nil %}

{% case product_sort_order %}
  {% when "ALPHA_ASC" %}
    {% assign product_sort_order = "TITLE" %}

  {% when "ALPHA_DESC" %}
    {% assign product_sort_order = "TITLE" %}
    {% assign reverse_sort = true %}

  {% when "CREATED_DESC" %}
    {% assign product_sort_order = "CREATED" %}
    {% assign reverse_sort = true %}

  {% when "PRICE_ASC" %}
    {% assign product_sort_order = "PRICE" %}

  {% when "PRICE_DESC" %}
    {% assign product_sort_order = "PRICE" %}
    {% assign reverse_sort = true %}
{% endcase %}

{% assign collection_titles_or_ids_to_include = options.collection_titles_or_ids_to_include__array %}
{% assign collection_titles_or_ids_to_exclude = options.collection_titles_or_ids_to_exclude__array %}
{% assign force_manual_sorting_on_collections = options.force_manual_sorting_on_collections__boolean %}

{% if event.preview %}
  {% capture shop_json %}
    {
      "collections": [
        {
          "id": {{ collection_titles_or_ids_to_include.first | default: "1234567890" | json }},
          "admin_graphql_api_id": "gid://shopify/Collection/1234567890"
        }
      ]
    }
  {% endcapture %}

  {% assign shop = shop_json | parse_json %}
{% endif %}

{% for collection in shop.collections %}
  {% assign collection_id_string = "" | append: collection.id %}

  {% if collection_titles_or_ids_to_include != blank  %}
    {% unless collection_titles_or_ids_to_include contains collection_id_string
      or collection_titles_or_ids_to_include contains collection.title %}
      {% continue %}
    {% endunless %}

  {% elsif collection_titles_or_ids_to_exclude != blank  %}
    {% if collection_titles_or_ids_to_exclude contains collection_id_string
      or collection_titles_or_ids_to_exclude contains collection.title %}
      {% continue %}
    {% endif %}
  {% endif %}

  {% if collection.sort_order != "manual" %}
    {% if force_manual_sorting_on_collections or event.preview %}
      {% action "shopify" %}
        mutation {
          collectionUpdate(
            input: {
              id: {{ collection.admin_graphql_api_id | json }}
              sortOrder: MANUAL
            }
          ) {
            userErrors {
              field
              message
            }
          }
        }
      {% endaction %}

    {% else %}
      {% log %}
        {{ collection.title | json | append: " is not configured for manual sorting; skipping." | json }}
      {% endlog %}

      {% continue %}
    {% endif %}
  {% endif %}

  {% assign all_product_ids_current_sort = array %}
  {% assign cursor = nil %}

  {% for n in (0..100) %}
    {% capture query %}
      query {
        collection(id: {{ collection.admin_graphql_api_id | json }}) {
          products(
            sortKey: COLLECTION_DEFAULT
            first: 250
            after: {{ cursor | json }}
          ) {
            pageInfo {
              hasNextPage
            }
            edges {
              cursor
              node {
                id
              }
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% for product_edge in result.data.collection.products.edges %}
      {% assign all_product_ids_current_sort[all_product_ids_current_sort.size] = product_edge.node.id %}
    {% endfor %}

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

  {% assign in_stock_product_ids = array %}
  {% assign out_of_stock_product_ids = array %}
  {% assign cursor = nil %}

  {% for n in (0..100) %}
    {% capture query %}
      query {
        collection(id: {{ collection.admin_graphql_api_id | json }}) {
          products(
            sortKey: {{ product_sort_order }}
            reverse: {{ reverse_sort | json }}
            first: 250
            after: {{ cursor | json }}
          ) {
            pageInfo {
              hasNextPage
            }
            edges {
              cursor
              node {
                id
                tracksInventory
                totalInventory
              }
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% if event.preview %}
      {% capture result_json %}
        {
          "data": {
            "collection": {
              "products": {
                "edges": [
                  {
                    "node": {
                      "id": "gid://shopify/Product/1234567890",
                      "tracksInventory": true,
                      "totalInventory": 1
                    }
                  },
                  {
                    "node": {
                      "id": "gid://shopify/Product/2345678901",
                      "tracksInventory": true,
                      "totalInventory": 0
                    }
                  },
                  {
                    "node": {
                      "id": "gid://shopify/Product/3456789012",
                      "tracksInventory": false,
                      "totalInventory": null
                    }
                  },
                  {
                    "node": {
                      "id": "gid://shopify/Product/4567890123",
                      "tracksInventory": true,
                      "totalInventory": 2
                    }
                  }
                ]
              }
            }
          }
        }
      {% endcapture %}

      {% assign result = result_json | parse_json %}
    {% endif %}

    {% for product_edge in result.data.collection.products.edges %}
      {% if product_edge.node.tracksInventory and product_edge.node.totalInventory <= 0 %}
        {% assign out_of_stock_product_ids[out_of_stock_product_ids.size] = product_edge.node.id %}
      {% else %}
        {% assign in_stock_product_ids[in_stock_product_ids.size] = product_edge.node.id %}
      {% endif %}
    {% endfor %}

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

  {% assign all_product_ids = in_stock_product_ids | concat: out_of_stock_product_ids %}

  {% assign moves = array %}

  {% for product_id in all_product_ids %}
    {% if all_product_ids_current_sort[forloop.index0] != product_id %}
      {% assign move = hash %}
      {% assign move["id"] = product_id %}
      {% assign move["newPosition"] = "" | append: forloop.index0 %}
      {% assign moves[moves.size] = move %}
    {% endif %}
  {% endfor %}

  {% assign move_groups = moves | in_groups_of: 250, fill_with: false %}

  {% for move_group in move_groups %}
    {% action "shopify" %}
      mutation {
        collectionReorderProducts(
          id: {{ collection.admin_graphql_api_id | json }}
          moves: {{ move_group | graphql_arguments }}
        ) {
          userErrors {
            field
            message
          }
        }
      }
    {% endaction %}

  {% else %}
    {% log
      message: "No position moves necessary for this collection, everything is already in its appropriate sort order.",
      collection: collection.title
    %}
  {% endfor %}
{% endfor %}
Mechanic tasks are written in Liquid, which makes them easy to write and easy to modify. Learn more about our platform.
Defaults
Base sort order
ALPHA_DESC