diff --git a/docs/README.md b/docs/README.md
index c9d52bfd..724944ce 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -27,6 +27,7 @@ This directory is built automatically. Each task's documentation is generated fr
* [Auto-add phone numbers to unfulfilled orders, when the customer is updated](./auto-add-phone-numbers-to-unfulfilled-orders-when-the-customer-is-updated)
* [Auto-add products to a custom collection when tagged](./auto-add-products-to-a-custom-collection-when-tagged)
* [Auto-add the draft order to a new order's attributes](./auto-add-the-draft-order-id-to-an-orders-attributes)
+* [Auto-approve return requests](./auto-approve-return-requests)
* [Auto-archive orders after fulfillment](./auto-archive-orders-after-fulfillment)
* [Auto-associate products with a delivery profile, by product tag](./auto-associate-products-with-a-delivery-profile-by-product-tag)
* [Auto-associate variants with a delivery profile, by metafield value](./auto-associate-variants-with-a-delivery-profile-by-metafield-value)
@@ -1507,6 +1508,7 @@ This directory is built automatically. Each task's documentation is generated fr
### Returns
+* [Auto-approve return requests](./auto-approve-return-requests)
* [Send email notification when items are returned](./send-email-notification-when-items-are-returned)
### Reviews
diff --git a/docs/auto-approve-return-requests/README.md b/docs/auto-approve-return-requests/README.md
new file mode 100644
index 00000000..aee0b1a8
--- /dev/null
+++ b/docs/auto-approve-return-requests/README.md
@@ -0,0 +1,55 @@
+# Auto-approve return requests
+
+Tags: Returns
+
+Use this task to auto-approve return requests, typically made by customers using self-serve from their account. Choose to limit the auto-approval to orders with any of a set of tags, products with any of a set of tags, or specific product categories or types. Optionally, configure email recipients to be notified when any auto-approval occurs.
+
+* View in the task library: [tasks.mechanic.dev/auto-approve-return-requests](https://tasks.mechanic.dev/auto-approve-return-requests)
+* Task JSON, for direct import: [task.json](../../tasks/auto-approve-return-requests.json)
+* Preview task code: [script.liquid](./script.liquid)
+
+## Default options
+
+```json
+{
+ "limit_to_orders_with_any_of_these_tags__array": null,
+ "limit_to_products_with_any_of_these_tags__array": null,
+ "limit_to_these_product_categories_or_types__array": null,
+ "notification_email_recipients__array": null
+}
+```
+
+[Learn about task options in Mechanic](https://learn.mechanic.dev/core/tasks/options)
+
+## Subscriptions
+
+```liquid
+shopify/returns/request
+mechanic/actions/perform
+```
+
+[Learn about event subscriptions in Mechanic](https://learn.mechanic.dev/core/tasks/subscriptions)
+
+## Documentation
+
+Use this task to auto-approve return requests, typically made by customers using self-serve from their account. Choose to limit the auto-approval to orders with any of a set of tags, products with any of a set of tags, or specific product categories or types. Optionally, configure email recipients to be notified when any auto-approval occurs.
+
+More info on Shopify self-serve returns [here](https://help.shopify.com/en/manual/orders/refunds-returns/self-serve-returns).
+
+**Important:**
+- Adding multiple limit conditions means the return request must meet ALL of the conditions in order to be auto-approved.
+- More specifically, if any product conditions are configured, then ALL products on the return must meet those conditions.
+- Return requests that are auto-approved **cannot** later be used to exchange items.
+- Approval of return requests **cannot** be reverted; instead, the return request may be cancelled if needed.
+
+## Installing this task
+
+Find this task [in the library at tasks.mechanic.dev](https://tasks.mechanic.dev/auto-approve-return-requests), and use the "Try this task" button. Or, import [this task's JSON export](../../tasks/auto-approve-return-requests.json) – see [Importing and exporting tasks](https://learn.mechanic.dev/core/tasks/import-and-export) to learn how imports work.
+
+## Contributions
+
+Found a bug? Got an improvement to add? Start here: [../../CONTRIBUTING.md](../../CONTRIBUTING.md).
+
+## Task requests
+
+Submit your [task requests](https://mechanic.canny.io/task-requests) for consideration by the Mechanic community, and they may be chosen for development and inclusion in the [task library](https://tasks.mechanic.dev/)!
diff --git a/docs/auto-approve-return-requests/script.liquid b/docs/auto-approve-return-requests/script.liquid
new file mode 100644
index 00000000..69bd9d22
--- /dev/null
+++ b/docs/auto-approve-return-requests/script.liquid
@@ -0,0 +1,232 @@
+{% assign limit_order_tags = options.limit_to_orders_with_any_of_these_tags__array %}
+{% assign limit_product_tags = options.limit_to_products_with_any_of_these_tags__array %}
+{% assign limit_product_categories_or_types = options.limit_to_these_product_categories_or_types__array %}
+{% assign notification_email_recipients = options.notification_email_recipients__array %}
+
+{% if event.topic == "shopify/returns/request" %}
+ {% comment %}
+ -- get additional return and product data not available in the webhook
+ {% endcomment %}
+
+ {% capture query %}
+ query {
+ return(id: {{ return.admin_graphql_api_id | json }}) {
+ id
+ name
+ status
+ order {
+ name
+ tags
+ }
+ returnLineItems(first: 100) {
+ nodes {
+ fulfillmentLineItem {
+ lineItem {
+ product {
+ category {
+ name
+ }
+ productType
+ tags
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ {% endcapture %}
+
+ {% assign result = query | shopify %}
+
+ {% if event.preview %}
+ {% capture result_json %}
+ {
+ "data": {
+ "return": {
+ "id": "gid://shopify/Return/1234567890",
+ "name": "#PREVIEW-R1",
+ "status": "REQUESTED",
+ "order": {
+ "name": "#PREVIEW",
+ "tags": {{ limit_order_tags.first | json }}
+ },
+ "returnLineItems": {
+ "nodes": [
+ {
+ "fulfillmentLineItem": {
+ "lineItem": {
+ "product": {
+ "category": {
+ "name": {{ limit_product_categories_or_types.first | json }}
+ },
+ "productType": {{ limit_product_categories_or_types.first | json }},
+ "tags": {{ limit_product_tags.first | json }}
+ }
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ {% endcapture %}
+
+ {% assign result = result_json | parse_json %}
+ {% endif %}
+
+ {% assign return = result.data.return %}
+ {% assign order = return.order %}
+ {% assign return_products
+ = return.returnLineItems.nodes
+ | map: "fulfillmentLineItem"
+ | map: "lineItem"
+ | map: "product"
+ %}
+
+ {% if return.status != "REQUESTED" %}
+ {% log "This return request does not have a status of 'REQUESTED' and likely has already been acted on; skipping." %}
+ {% break %}
+ {% endif %}
+
+ {% comment %}
+ -- assume the return request will be approved unless it doesn't meet any configured tag, category, or type limits
+ {% endcomment %}
+
+ {% assign return_qualifies = true %}
+
+ {% if limit_order_tags != blank %}
+ {% comment %}
+ -- make sure the order has one of the configured tags
+ {% endcomment %}
+
+ {% assign has_qualifying_order_tag = nil %}
+
+ {% for limit_order_tag in limit_order_tags %}
+ {% if order.tags contains limit_order_tag %}
+ {% assign has_qualifying_order_tag = true %}
+ {% break %}
+ {% endif %}
+ {% endfor %}
+
+ {% unless has_qualifying_order_tag %}
+ {% assign return_qualifies = false %}
+ {% endunless %}
+ {% endif %}
+
+ {% if limit_product_tags != blank %}
+ {% comment %}
+ -- make sure each returned product has one of the configured tags
+ {% endcomment %}
+
+ {% assign all_products_qualify = true %}
+
+ {% for product in return_products %}
+ {% assign product_qualifies = nil %}
+
+ {% for limit_product_tag in limit_product_tags %}
+ {% if product.tags contains limit_product_tag %}
+ {% assign product_qualifies = true %}
+ {% break %}
+ {% endif %}
+ {% endfor %}
+
+ {% unless product_qualifies %}
+ {% assign all_products_qualify = false %}
+ {% break %}
+ {% endunless %}
+ {% endfor %}
+
+ {% unless all_products_qualify %}
+ {% assign return_qualifies = false %}
+ {% endunless %}
+ {% endif %}
+
+ {% if limit_product_categories_or_types != blank %}
+ {% comment %}
+ -- make sure each returned product is one of the configured categories or types
+ {% endcomment %}
+
+ {% assign all_products_qualify = true %}
+
+ {% for product in return_products %}
+ {% assign product_qualifies = nil %}
+
+ {% for limit_product_category_or_type in limit_product_categories_or_types %}
+ {% if product.productType == limit_product_category_or_type or product.category.name == limit_product_category_or_type %}
+ {% assign product_qualifies = true %}
+ {% break %}
+ {% endif %}
+ {% endfor %}
+
+ {% unless product_qualifies %}
+ {% assign all_products_qualify = false %}
+ {% break %}
+ {% endunless %}
+ {% endfor %}
+
+ {% unless all_products_qualify %}
+ {% assign return_qualifies = false %}
+ {% endunless %}
+ {% endif %}
+
+ {% if return_qualifies %}
+ {% action "shopify" %}
+ mutation {
+ returnApproveRequest(input: {id: {{ return.id | json }}}) {
+ return {
+ id
+ name
+ status
+ order {
+ legacyResourceId
+ customer {
+ displayName
+ }
+ }
+ }
+ userErrors {
+ code
+ field
+ message
+ }
+ }
+ }
+ {% endaction %}
+ {% endif %}
+
+{% elsif event.topic == "mechanic/actions/perform" %}
+ {% unless action.type == "shopify" and action.run.ok and notification_email_recipients != blank %}
+ {% break %}
+ {% endunless %}
+
+ {% comment %}
+ -- if any notification recipients are configured, send them an email when a return request is auto-approved
+ {% endcomment %}
+
+ {% assign return = action.run.result.data.returnApproveRequest.return %}
+
+ {%- capture email_subject -%}
+ Return request {{ return.name }} was auto-approved
+ {%- endcapture -%}
+
+ {%- capture email_body -%}
+ Return request {{ return.name }}, made by {{ return.order.customer.displayName }}, was auto-approved.
+
+ Review the return details and take further action on the order admin page.
+
+ Thanks,
+ - Mechanic, for {{ shop.name }}
+ {%- endcapture -%}
+
+ {% action "email" %}
+ {
+ "to": {{ notification_email_recipients | json }},
+ "subject": {{ email_subject | json }},
+ "body": {{ email_body | newline_to_br | json }},
+ "reply_to": {{ shop.customer_email | json }},
+ "from_display_name": {{ shop.name | json }}
+ }
+ {% endaction %}
+{% endif %}
diff --git a/tasks/auto-approve-return-requests.json b/tasks/auto-approve-return-requests.json
new file mode 100644
index 00000000..de44b3fa
--- /dev/null
+++ b/tasks/auto-approve-return-requests.json
@@ -0,0 +1,53 @@
+{
+ "docs": "Use this task to auto-approve return requests, typically made by customers using self-serve from their account. Choose to limit the auto-approval to orders with any of a set of tags, products with any of a set of tags, or specific product categories or types. Optionally, configure email recipients to be notified when any auto-approval occurs.\n\nMore info on Shopify self-serve returns [here](https://help.shopify.com/en/manual/orders/refunds-returns/self-serve-returns).\n\n**Important:**\n- Adding multiple limit conditions means the return request must meet ALL of the conditions in order to be auto-approved.\n- More specifically, if any product conditions are configured, then ALL products on the return must meet those conditions.\n- Return requests that are auto-approved **cannot** later be used to exchange items.\n- Approval of return requests **cannot** be reverted; instead, the return request may be cancelled if needed.",
+ "halt_action_run_sequence_on_error": false,
+ "name": "Auto-approve return requests",
+ "online_store_javascript": null,
+ "options": {
+ "limit_to_orders_with_any_of_these_tags__array": null,
+ "limit_to_products_with_any_of_these_tags__array": null,
+ "limit_to_these_product_categories_or_types__array": null,
+ "notification_email_recipients__array": null
+ },
+ "order_status_javascript": null,
+ "perform_action_runs_in_sequence": false,
+ "preview_event_definitions": [
+ {
+ "description": "Return approve request",
+ "event_attributes": {
+ "topic": "mechanic/actions/perform",
+ "data": {
+ "type": "shopify",
+ "run": {
+ "ok": true,
+ "result": {
+ "data": {
+ "returnApproveRequest": {
+ "return": {
+ "name": "#PREVIEW-R1",
+ "status": "OPEN",
+ "order": {
+ "legacyResourceId": "1234567890",
+ "customer": {
+ "displayName": "Jean Deaux"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ ],
+ "script": "{% assign limit_order_tags = options.limit_to_orders_with_any_of_these_tags__array %}\n{% assign limit_product_tags = options.limit_to_products_with_any_of_these_tags__array %}\n{% assign limit_product_categories_or_types = options.limit_to_these_product_categories_or_types__array %}\n{% assign notification_email_recipients = options.notification_email_recipients__array %}\n\n{% if event.topic == \"shopify/returns/request\" %}\n {% comment %}\n -- get additional return and product data not available in the webhook\n {% endcomment %}\n\n {% capture query %}\n query {\n return(id: {{ return.admin_graphql_api_id | json }}) {\n id\n name\n status\n order {\n name\n tags\n }\n returnLineItems(first: 100) {\n nodes {\n fulfillmentLineItem {\n lineItem {\n product {\n category {\n name\n }\n productType\n tags\n }\n }\n }\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = query | shopify %}\n\n {% if event.preview %}\n {% capture result_json %}\n {\n \"data\": {\n \"return\": {\n \"id\": \"gid://shopify/Return/1234567890\",\n \"name\": \"#PREVIEW-R1\",\n \"status\": \"REQUESTED\",\n \"order\": {\n \"name\": \"#PREVIEW\",\n \"tags\": {{ limit_order_tags.first | json }}\n },\n \"returnLineItems\": {\n \"nodes\": [\n {\n \"fulfillmentLineItem\": {\n \"lineItem\": {\n \"product\": {\n \"category\": {\n \"name\": {{ limit_product_categories_or_types.first | json }}\n },\n \"productType\": {{ limit_product_categories_or_types.first | json }},\n \"tags\": {{ limit_product_tags.first | json }}\n }\n }\n }\n }\n ]\n }\n }\n }\n }\n {% endcapture %}\n\n {% assign result = result_json | parse_json %}\n {% endif %}\n\n {% assign return = result.data.return %}\n {% assign order = return.order %}\n {% assign return_products\n = return.returnLineItems.nodes\n | map: \"fulfillmentLineItem\"\n | map: \"lineItem\"\n | map: \"product\"\n %}\n\n {% if return.status != \"REQUESTED\" %}\n {% log \"This return request does not have a status of 'REQUESTED' and likely has already been acted on; skipping.\" %}\n {% break %}\n {% endif %}\n\n {% comment %}\n -- assume the return request will be approved unless it doesn't meet any configured tag, category, or type limits\n {% endcomment %}\n\n {% assign return_qualifies = true %}\n\n {% if limit_order_tags != blank %}\n {% comment %}\n -- make sure the order has one of the configured tags\n {% endcomment %}\n\n {% assign has_qualifying_order_tag = nil %}\n\n {% for limit_order_tag in limit_order_tags %}\n {% if order.tags contains limit_order_tag %}\n {% assign has_qualifying_order_tag = true %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% unless has_qualifying_order_tag %}\n {% assign return_qualifies = false %}\n {% endunless %}\n {% endif %}\n\n {% if limit_product_tags != blank %}\n {% comment %}\n -- make sure each returned product has one of the configured tags\n {% endcomment %}\n\n {% assign all_products_qualify = true %}\n\n {% for product in return_products %}\n {% assign product_qualifies = nil %}\n\n {% for limit_product_tag in limit_product_tags %}\n {% if product.tags contains limit_product_tag %}\n {% assign product_qualifies = true %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% unless product_qualifies %}\n {% assign all_products_qualify = false %}\n {% break %}\n {% endunless %}\n {% endfor %}\n\n {% unless all_products_qualify %}\n {% assign return_qualifies = false %}\n {% endunless %}\n {% endif %}\n\n {% if limit_product_categories_or_types != blank %}\n {% comment %}\n -- make sure each returned product is one of the configured categories or types\n {% endcomment %}\n\n {% assign all_products_qualify = true %}\n\n {% for product in return_products %}\n {% assign product_qualifies = nil %}\n\n {% for limit_product_category_or_type in limit_product_categories_or_types %}\n {% if product.productType == limit_product_category_or_type or product.category.name == limit_product_category_or_type %}\n {% assign product_qualifies = true %}\n {% break %}\n {% endif %}\n {% endfor %}\n\n {% unless product_qualifies %}\n {% assign all_products_qualify = false %}\n {% break %}\n {% endunless %}\n {% endfor %}\n\n {% unless all_products_qualify %}\n {% assign return_qualifies = false %}\n {% endunless %}\n {% endif %}\n\n {% if return_qualifies %}\n {% action \"shopify\" %}\n mutation {\n returnApproveRequest(input: {id: {{ return.id | json }}}) {\n return {\n id\n name\n status\n order {\n legacyResourceId\n customer {\n displayName\n }\n }\n }\n userErrors {\n code\n field\n message\n }\n }\n }\n {% endaction %}\n {% endif %}\n\n{% elsif event.topic == \"mechanic/actions/perform\" %}\n {% unless action.type == \"shopify\" and action.run.ok and notification_email_recipients != blank %}\n {% break %}\n {% endunless %}\n\n {% comment %}\n -- if any notification recipients are configured, send them an email when a return request is auto-approved\n {% endcomment %}\n\n {% assign return = action.run.result.data.returnApproveRequest.return %}\n\n {%- capture email_subject -%}\n Return request {{ return.name }} was auto-approved\n {%- endcapture -%}\n\n {%- capture email_body -%}\n Return request {{ return.name }}, made by {{ return.order.customer.displayName }}, was auto-approved.\n\n Review the return details and take further action on the order admin page.\n\n Thanks,\n - Mechanic, for {{ shop.name }}\n {%- endcapture -%}\n\n {% action \"email\" %}\n {\n \"to\": {{ notification_email_recipients | json }},\n \"subject\": {{ email_subject | json }},\n \"body\": {{ email_body | newline_to_br | json }},\n \"reply_to\": {{ shop.customer_email | json }},\n \"from_display_name\": {{ shop.name | json }}\n }\n {% endaction %}\n{% endif %}\n",
+ "subscriptions": [
+ "shopify/returns/request",
+ "mechanic/actions/perform"
+ ],
+ "subscriptions_template": "shopify/returns/request\nmechanic/actions/perform",
+ "tags": [
+ "Returns"
+ ]
+}