Auto-create collections by product type or vendor, with Mechanic.

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

Auto-create collections by product type or vendor

When products are created, this task will auto-create smart collections by product type and/or vendor, if such collections don't already exist. Additionally, configuring one or more exact sales channel names will enable publishing of any newly created collections by this task to those sales channels.

Runs Occurs whenever a product is updated, ordered, or variants are added, removed or updated and Occurs when a user manually triggers the task. Configuration includes create collections by product type, create collections by vendor, and names of sales channels to publish collections to.

15-day free trial – unlimited tasks

Documentation

When products are created, this task will auto-create smart collections by product type and/or vendor, if such collections don't already exist. Additionally, configuring one or more exact sales channel names will enable publishing of any newly created collections by this task to those sales channels.

For example:

A new product is added with a vendor of "ACME". If a collection with that exact title does not already exist, then the task will create it with a title of "ACME" and add a rule of "vendor = ACME", which will allow Shopify to auto-populate the collection.

The task may also be run manually to gather all of the product types and vendors in your shop, and then making the same decisions on whether to create new collections and publish them.

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
shopify/products/update
mechanic/user/trigger
{% if options.names_of_sales_channels_to_publish_collections_to__array != blank %}
  mechanic/actions/perform
{% endif %}
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
create collections by product type (boolean), create collections by vendor (boolean), names of sales channels to publish collections to (array)
Code
{% assign create_collections_by_product_type = options.create_collections_by_product_type__boolean %}
{% assign create_collections_by_vendor = options.create_collections_by_vendor__boolean %}
{% assign sales_channel_names = options.names_of_sales_channels_to_publish_collections_to__array %}

{% unless create_collections_by_product_type or create_collections_by_vendor %}
  {% error "Choose at least one 'Create collections by' option" %}
{% endunless %}

{% assign product_types = array %}
{% assign vendors = array %}

{% if event.topic == "mechanic/user/trigger" or event.topic == "shopify/products/update" %}
  {% if event.topic == "mechanic/user/trigger" %}
    {% comment %}
      -- get all product types and vendors in the shop
    {% endcomment %}

    {% assign cursor = nil %}

    {% for n in (1..10) %}
      {% capture query %}
        query {
          productTypes(
            first: 1000
            after: {{ cursor | json }}
          ) {
            pageInfo {
              hasNextPage
              endCursor
            }
            nodes
          }
        }
      {% endcapture %}

      {% assign result = query | shopify %}

      {% if event.preview %}
        {% capture result_json %}
          {
            "data": {
              "productTypes": {
                "nodes": [
                  "Widget"
                ]
              }
            }
          }
        {% endcapture %}

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

      {% assign product_types = product_types | concat: result.data.productTypes.nodes %}

      {% if result.data.productTypes.pageInfo.hasNextPage %}
        {% assign cursor = result.data.productTypes.pageInfo.endCursor %}
      {% else %}
        {% break %}
      {% endif %}
    {% endfor %}

    {% assign cursor = nil %}

    {% for n in (1..10) %}
      {% capture query %}
        query {
          productVendors(
            first: 1000
            after: {{ cursor | json }}
          ) {
            pageInfo {
              hasNextPage
              endCursor
            }
            nodes
          }
        }
      {% endcapture %}

      {% assign result = query | shopify %}

      {% if event.preview %}
        {% capture result_json %}
          {
            "data": {
              "productVendors": {
                "nodes": [
                  "ACME"
                ]
              }
            }
          }
        {% endcapture %}

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

      {% assign vendors = vendors | concat: result.data.productVendors.nodes %}

      {% if result.data.productVendors.pageInfo.hasNextPage %}
        {% assign cursor = result.data.productVendors.pageInfo.endCursor %}
      {% else %}
        {% break %}
      {% endif %}
    {% endfor %}

  {% elsif event.topic == "shopify/products/update" %}
    {% unless product.product_type == blank %}
      {% assign product_types[0] = product.product_type %}
    {% endunless %}

    {% unless product.vendor == blank %}
      {% assign vendors[0] = product.vendor %}
    {% endunless %}
  {% endif %}

  {% if event.preview %}
    {% assign product_types[0] = "Widget" %}
    {% assign vendors[0] = "ACME" %}
  {% endif %}

  {% if sales_channel_names != blank %}
    {% comment %}
      -- check if the configured sales channels exist in this shop by name; save the publication IDs for lookup later
    {% endcomment %}

    {% capture query %}
      query {
        publications(
          first: 250
          catalogType:APP
        ) {
          nodes {
            id
            catalog {
              ... on AppCatalog {
                apps(first: 1) {
                  nodes {
                    title
                  }
                }
              }
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% if event.preview %}
      {% capture result_json %}
        {
          "data": {
            "publications": {
              "nodes": [
                {
                  "id": "gid://shopify/Publication/1234567890",
                  "catalog": {
                    "apps": {
                      "nodes": [
                        {
                          "title": {{ sales_channel_names.first | json }}
                        }
                      ]
                    }
                  }
                }
              ]
            }
          }
        }
      {% endcapture %}

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

    {% assign publication_ids = array %}
    {% assign available_sales_channel_names = array %}

    {% for publication in result.data.publications.nodes %}
      {% assign publication_name = publication.catalog.apps.nodes.first.title %}

      {% assign available_sales_channel_names = available_sales_channel_names | push: publication_name %}

      {% if sales_channel_names contains publication_name %}
        {% assign publication_ids = publication_ids | push: publication.id %}
      {% endif %}
    {% endfor %}

    {% unless event.preview %}
      {% if publication_ids == blank  %}
        {% error
          message: "None of the sales channel configured in this task exist in the shop. Check the list of available channels and verify each configured channel exists.",
          configured_sales_channel_names: sales_channel_names,
          available_sales_channel_names: available_sales_channel_names
        %}

        {% break %}

      {% elsif publication_ids.size != sales_channel_names.size %}
        {% comment %}
          -- using action error here so the task will continue with any other configured and matched sales channels
        {% endcomment %}

        {% action "echo"
          __error: "One or more configured sales channel names do not match any of the publication names available in this shop.",
          configured_sales_channel_names: sales_channel_names,
          available_sales_channel_names: available_sales_channel_names
        %}
      {% endif %}
    {% endunless %}
  {% endif %}

  {% if create_collections_by_product_type %}
    {% for product_type in product_types %}
      {% capture query %}
        query {
          collections(
            first: 1
            query: {{ product_type | json | prepend: "title:" | json }}
          ) {
            nodes {
              id
              title
              handle
            }
          }
        }
      {% endcapture %}

      {% assign result = query | shopify %}

      {% if result.data.collections.nodes != blank %}
        {% log
          message: "A collection title matching this product type already exists; skipping.",
          product_type: product_type,
          collection: result.data.collections.nodes.first
        %}

      {% else %}
        {% capture mutation %}
          mutation {
            collectionCreate(
              input: {
                title: {{ product_type | json }}
                ruleSet: {
                  appliedDisjunctively: false
                  rules: [
                    {
                      column: TYPE
                      relation: EQUALS
                      condition: {{ product_type | json }}
                    }
                  ]
                }
              }
            ) {
              collection {
                id
                title
                handle
              }
              userErrors {
                field
                message
              }
            }
          }
        {% endcapture %}

        {% action %}
          {
            "type": "shopify",
            "options": {{ mutation | json }},
            "meta": {
              "publication_ids": {{ publication_ids | json }}
            }
          }
        {% endaction %}
      {% endif %}
    {% endfor %}
  {% endif %}

  {% if create_collections_by_vendor %}
    {% for vendor in vendors %}
      {% capture query %}
        query {
          collections(
            first: 1
            query: {{ vendor | json | prepend: "title:" | json }}
          ) {
            nodes {
              id
              title
              handle
            }
          }
        }
      {% endcapture %}

      {% assign result = query | shopify %}

      {% if result.data.collections.nodes != blank %}
        {% log
          message: "A collection title matching this vendor already exists; skipping.",
          vendor: vendor,
          collection: result.data.collections.nodes.first
        %}

      {% else %}
        {% capture mutation %}
          mutation {
            collectionCreate(
              input: {
                title: {{ vendor | json }}
                ruleSet: {
                  appliedDisjunctively: false
                  rules: [
                    {
                      column: VENDOR
                      relation: EQUALS
                      condition: {{ vendor | json }}
                    }
                  ]
                }
              }
            ) {
              collection {
                id
                title
                handle
              }
              userErrors {
                field
                message
              }
            }
          }
        {% endcapture %}

        {% action %}
          {
            "type": "shopify",
            "options": {{ mutation | json }},
            "meta": {
              "publication_ids": {{ publication_ids | json }}
            }
          }
        {% endaction %}
      {% endif %}
    {% endfor %}
  {% endif %}

{% elsif event.topic == "mechanic/actions/perform" %}
  {% comment %}
    -- only respond to successful creation of collections
  {% endcomment %}

  {% unless action.type == "shopify" and action.run.ok and action.run.result.data.collectionCreate %}
    {% break %}
  {% endunless %}

  {% assign collection_id = action.run.result.data.collectionCreate.collection.id %}
  {% assign publication_ids = action.meta.publication_ids %}

  {% comment %}
    -- publish the new collection to all of the valid publications configured in the task
  {% endcomment %}

  {% assign mutations = array %}

  {% for publication_id in publication_ids %}
    {% capture mutation %}
      publishablePublish{{ forloop.index }}: publishablePublish(
        id: {{ collection_id | json }}
        input: {
          publicationId: {{ publication_id | json }}
        }
      ) {
        publishable {
          ... on Collection {
            id
            title
            handle
          }
        }
        userErrors {
          field
          message
        }
      }
    {% endcapture %}

    {% assign mutations = mutations | push: mutation %}
  {% endfor %}

  {% if mutations != blank %}
    {% action "shopify" %}
      mutation {
        {{ mutations | join: newline }}
      }
    {% endaction %}
  {% endif %}
{% endif %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more