Auto-delete customer metafields older than X days, with Mechanic.

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

Auto-delete customer metafields older than X days

Use this task to auto-delete customer metafields that are older than the configured minimum age in days. Useful for removing transient metafields a certain number of days after they have been created.

Runs Occurs every day at midnight (in local time) and Occurs when a user manually triggers the task. Configuration includes customer metafields to monitor, minimum age in days before deletion, and test mode.

15-day free trial – unlimited tasks

Documentation

Use this task to auto-delete customer metafields that are older than the configured minimum age in days. Useful for removing transient metafields a certain number of days after they have been created.

Enter one or more metafields using namespace.key format (e.g. "custom.recent_purchase" ), and on the daily task run it will find and delete any matching customer metafield which was created at prior to the cutoff date.

It is highly recommended to first run this task using the Test mode option, so it can log out which metafields have qualified for deletion without actually deleting them. Pair this with running the task manually to avoid waiting for the next scheduled task run to see the test mode logging.

Note:
The "cutoff date" uses the beginning of the task run day (i.e. midnight local shop time) as the start time to count backwards the configured minimum age in days.

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
mechanic/scheduler/daily
mechanic/user/trigger
Tasks use subscriptions to sign up for specific kinds of events. Learn more
Options
customer metafields to monitor (array, required) , minimum age in days before deletion (number, required) , test mode (boolean)
Code
{% assign customer_metafields_to_monitor = options.customer_metafields_to_monitor__array_required %}
{% assign minimum_age_in_days_before_deletion = options.minimum_age_in_days_before_deletion__number_required | at_least: 0 %}
{% assign test_mode = options.test_mode__boolean %}

{% comment %}
  -- use midnight local shop time as the base, so cutoff date will provide X full calendar days (excepting clock changes)
{% endcomment %}

{% assign metafield_delete_interval_s = minimum_age_in_days_before_deletion | times: 86400 %}
{% assign last_midnight_s = "now" | date: "%F" | date: "%s" | times: 1 %}
{% assign cutoff_date_s = last_midnight_s | minus: metafield_delete_interval_s %}
{% assign cutoff_date = cutoff_date_s | date: "%FT%T" %}

{% log
  cutoff_date: cutoff_date,
  task_options: task.options
%}

{% if event.topic == "mechanic/user/trigger" or event.topic contains "mechanic/scheduler/" %}
  {% comment %}
    -- query up to 25K customers [higher amounts will likely require a bulk operation query]
    -- no filter query available to only include customers who have values for any of the configured metafields
  {% endcomment %}

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

  {% for n in (1..100) %}
    {% capture query %}
      query {
        customers(
          first: 250
          after: {{ cursor | json }}
          query: {{ search_query | json }}
        ) {
          pageInfo {
            hasNextPage
            endCursor
          }
          nodes {
            id
            metafields(
              first: {{ customer_metafields_to_monitor.size }}
              keys: {{ customer_metafields_to_monitor | graphql_arguments }}
            ) {
              nodes {
                id
                createdAt
                key
                namespace
                type
                value
              }
            }
          }
        }
      }
    {% endcapture %}

    {% assign result = query | shopify %}

    {% if event.preview %}
      {% capture result_json %}
        {
          "data": {
            "customers": {
              "nodes": [
                {
                  "id": "gid://shopify/Customer/1234567890",
                  "metafields": {
                    "nodes": [
                      {
                        "id": "gid://shopify/Metafield/1234567890",
                        "createdAt": {{ cutoff_date_s | minus: 1 | json }},
                        "key": {{ customer_metafields_to_monitor.first | split: "." | first | json }},
                        "namespace": {{ customer_metafields_to_monitor.first | split: "." | last | json }}
                      }
                    ]
                  }
                }
              ]
            }
          }
        }
      {% endcapture %}

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

    {% for customer in result.data.customers.nodes %}
      {% comment %}
        -- the keys filter in the metafields query should mean only the configured metafields are returned if present on a customer
      {% endcomment %}

      {% for metafield in customer.metafields.nodes %}
        {% assign metafield_created_at_s = metafield.createdAt | date: "%s" | times: 1 %}

        {% if metafield_created_at_s < cutoff_date_s %}
          {% log
            message: "Found metafield that qualifies for deletion.",
            customer_id: customer.id,
            metafield: metafield
          %}

          {% assign metafield_input = hash %}
          {% assign metafield_input["ownerId"] = customer.id %}
          {% assign metafield_input["namespace"] = metafield.namespace %}
          {% assign metafield_input["key"] = metafield.key %}
          {% assign metafield_inputs = metafield_inputs | push: metafield_input %}
        {% endif %}
      {% endfor %}
    {% endfor %}

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

  {% if metafield_inputs == blank %}
    {% log "No metafields qualified to be deleted on this task run." %}
    {% break %}
  {% endif %}

  {% if test_mode %}
    {% log %}
      "Found {{ metafield_inputs.size }} metafields which qualified to be deleted. This task has test mode enabled, so the deletion will be skipped."
    {% endlog %}
    {% break %}
  {% endif %}

  {% comment %}
    -- bulk metafield deletion supports 250 metafields per mutation
  {% endcomment %}

  {% assign groups_of_metafield_inputs = metafield_inputs | in_groups_of: 250, fill_with: false %}

  {% for group_of_metafield_inputs in groups_of_metafield_inputs %}
    {% action "shopify" %}
      mutation {
        metafieldsDelete(
          metafields: {{ group_of_metafield_inputs | graphql_arguments }}
        ) {
          deletedMetafields {
            ownerId
            namespace
            key
          }
          userErrors {
            field
            message
          }
        }
      }
    {% endaction %}
  {% endfor %}
{% endif %}
Task code is written in Mechanic Liquid, an extension of open-source Liquid enhanced for automation. Learn more
Defaults
Test mode
true