Mechanic is a development and ecommerce automation platform for Shopify. :)
Useful for digital products, shipping insurance, gift cards, or anything else that needs to be fulfilled instantly. This task watches for new orders, and auto-fulfills all line items that don't require shipping.
Runs Occurs whenever an order is created, Occurs whenever an order is updated, with a 30 second delay, and Occurs when a user manually triggers the task. Configuration includes include products with any of these tags, exclude products with any of these tags, only process paid orders, wait until any other shippable items are fulfilled, and fulfill gift cards.
Useful for digital products, shipping insurance, gift cards, or anything else that needs to be fulfilled instantly. This task watches for new orders, and auto-fulfills all line items that don't require shipping.
Optionally, choose whether to:
- Only process paid orders
- Wait until other shippable items are fulfilled
- Fulfill gift cards
- Include or exclude products by tag
This task may also be run manually to process existing open, unfulfilled orders.
Important:
- Exclusion tags on a product will take priority over inclusion tags
- Shopify has an optional admin setting to auto-fulfill gift cards when orders are paid. Depending on how this task is configured, that setting may need to be turned off to avoid fulfillment conflicts.
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?)
shopify/orders/create shopify/orders/updated+30.seconds mechanic/user/trigger
{% assign inclusion_tags = options.include_products_with_any_of_these_tags__array %} {% assign exclusion_tags = options.exclude_products_with_any_of_these_tags__array %} {% assign only_process_paid_orders = options.only_process_paid_orders__boolean %} {% assign wait_until_any_other_shippable_items_are_fulfilled = options.wait_until_any_other_shippable_items_are_fulfilled__boolean %} {% assign fulfill_gift_cards = options.fulfill_gift_cards__boolean %} {% if event.topic contains "shopify/orders/" %} {% comment %} -- get all open or in progress fulfillment orders for this order {% endcomment %} {% capture query %} query { order(id: {{ order.admin_graphql_api_id | json }}) { id name displayFinancialStatus displayFulfillmentStatus fulfillmentOrders( first: 10 query: "status:open OR status:in_progress" ) { nodes { id assignedLocation { location { id } } lineItems(first: 50) { nodes { id remainingQuantity requiresShipping variant { id displayName product { id isGiftCard tags } } } } } } } } {% endcapture %} {% assign result = query | shopify %} {% assign orders = array | push: result.data.order %} {% elsif event.topic == "mechanic/user/trigger" %} {% comment %} -- get all open, unfulfilled orders (this includes partially fulfilled) -- if option enabled, restrict query to paid orders {% endcomment %} {% assign search_query = "status:open fulfillment_status:unfulfilled" %} {% if only_process_paid_orders %} {% assign search_query = search_query | append: " financial_status:paid " %} {% endif %} {% assign cursor = nil %} {% assign orders = array %} {% for n in (1..100) %} {% capture query %} query { orders( first: 25 after: {{ cursor | json }} query: {{ search_query | json }} ) { pageInfo { hasNextPage endCursor } nodes { id name displayFinancialStatus displayFulfillmentStatus fulfillmentOrders( first: 10 query: "status:open OR status:in_progress" ) { nodes { id assignedLocation { location { id } } lineItems(first: 50) { nodes { id remainingQuantity requiresShipping variant { id displayName product { id isGiftCard tags } } } } } } } } } {% endcapture %} {% assign result = query | shopify %} {% assign orders = result.data.orders.nodes | default: array | concat: orders %} {% if result.data.orders.pageInfo.hasNextPage %} {% assign cursor = result.data.orders.pageInfo.endCursor %} {% else %} {% break %} {% endif %} {% endfor %} {% endif %} {% if event.preview %} {% capture orders_json %} [ { "id": "gid://shopify/Order/1234567890", "name": "PREVIEW", "displayFinancialStatus": "PAID", "displayFulfillmentStatus": "PARTIALLY_FULFILLED", "fulfillmentOrders": { "nodes": [ { "id": "gid://shopify/FulfillmentOrder/1234567890", "assignedLocation": { "location": { "id": "gid://shopify/Location/1234567890" } }, "lineItems": { "nodes": [ { "id": "gid://shopify/FulfillmentOrderLineItem/1234567890", "remainingQuantity": 1, "requiresShipping": false, "variant": { "product": { "tags": {{ inclusion_tags.first | json }} } } }, { "id": "gid://shopify/FulfillmentOrderLineItem/2345678901", "remainingQuantity": 1, "requiresShipping": false, "variant": { "product": { "isGiftCard": true } } } ] } } ] } } ] {% endcapture %} {% assign orders = orders_json | parse_json %} {% endif %} {% for order in orders %} {% assign fulfillment_orders = order.fulfillmentOrders.nodes %} {% if fulfillment_orders == blank %} {% log message: "There are no open fulfillment orders to fulfill on this order.", order: order %} {% continue %} {% endif %} {% if only_process_paid_orders and order.displayFinancialStatus != "PAID" %} {% log message: "This order does not have a status of PAID, and the 'Only process paid orders' option is checked.", order: order %} {% continue %} {% endif %} {% comment %} -- fulfillments can only be created for one location at a time, so need to group fulfillment orders by location -- Shopify will also throw an error if gift cards are fulfilled with any other item types {% endcomment %} {% assign fulfillment_orders_by_location = hash %} {% assign has_unfulfilled_shippable_items = nil %} {% for fulfillment_order in fulfillment_orders %} {% assign fulfillment_order_data = hash %} {% assign fulfillment_order_data["fulfillment_order_id"] = fulfillment_order.id %} {% for fulfillment_order_line_item in fulfillment_order.lineItems.nodes %} {% comment %} -- skip items that require shipping, but set flag if any are unfulfilled in case the option to wait on them is enabled {% endcomment %} {% if fulfillment_order_line_item.requiresShipping %} {% if fulfillment_order_line_item.remainingQuantity > 0 %} {% assign has_unfulfilled_shippable_items = true %} {% endif %} {% continue %} {% endif %} {% if inclusion_tags != blank or exclusion_tags != blank %} {% assign has_exclusion_tag = nil %} {% assign has_inclusion_tag = nil %} {% assign product_tags = fulfillment_order_line_item.variant.product.tags %} {% for exclusion_tag in exclusion_tags %} {% if product_tags contains exclusion_tag %} {% assign has_exclusion_tag = true %} {% break %} {% endif %} {% endfor %} {% for inclusion_tag in inclusion_tags %} {% if product_tags contains inclusion_tag %} {% assign has_inclusion_tag = true %} {% break %} {% endif %} {% endfor %} {% comment %} -- exclusion tags have priority over inclusion tags {% endcomment %} {% if has_exclusion_tag %} {% log message: "This line item's product has at least one of the configured exclusion tags; skipping", fulfillment_order_line_item: fulfillment_order_line_item, product_tags: product_tags, exclusion_tags: exclusion_tags %} {% continue %} {% elsif inclusion_tags != blank %} {% unless has_inclusion_tag %} {% log message: "This line item's product does not contain any of the configured inclusion tags; skipping", fulfillment_order_line_item: fulfillment_order_line_item, product_tags: product_tags, inclusion_tags: inclusion_tags %} {% continue %} {% endunless %} {% endif %} {% endif %} {% comment %} -- save unfulfilled line items that do not require shipping; gift cards must be grouped separately {% endcomment %} {% if fulfillment_order_line_item.remainingQuantity > 0 %} {% if fulfillment_order_line_item.variant.product.isGiftCard %} {% if fulfill_gift_cards %} {% assign fulfillment_order_data["gift_cards_to_fulfill"] = fulfillment_order_data["gift_cards_to_fulfill"] | default: array | push: fulfillment_order_line_item %} {% endif %} {% else %} {% assign fulfillment_order_data["line_items_to_fulfill"] = fulfillment_order_data["line_items_to_fulfill"] | default: array | push: fulfillment_order_line_item %} {% endif %} {% endif %} {% endfor %} {% comment %} -- group fulfillment orders by location {% endcomment %} {% if fulfillment_order_data.line_items_to_fulfill != blank or fulfillment_order_data.gift_cards_to_fulfill != blank %} {% assign fulfillment_orders_by_location[fulfillment_order.assignedLocation.location.id] = fulfillment_orders_by_location[fulfillment_order.assignedLocation.location.id] | default: array | push: fulfillment_order_data %} {% endif %} {% endfor %} {% if wait_until_any_other_shippable_items_are_fulfilled and has_unfulfilled_shippable_items and fulfillment_orders_by_location != blank %} {% log message: "Unfulfilled shippable items exist on this order and the 'Wait until any other shippable items are fulfilled' option is checked; no auto fulfillments will be made in this task run.", order: order %} {% continue %} {% endif %} {% comment %} -- fulfill the line items that don't require shipping; fulfill any gift cards separately {% endcomment %} {% unless event.preview %} {% log order_name: order.name, fulfillment_orders_by_location: fulfillment_orders_by_location %} {% endunless %} {% for keyval in fulfillment_orders_by_location %} {% for fulfillment_order_data in keyval[1] %} {% if fulfillment_order_data.gift_cards_to_fulfill != blank %} {% action "shopify" %} mutation { fulfillmentCreate( fulfillment: { lineItemsByFulfillmentOrder: [ { fulfillmentOrderId: {{ fulfillment_order_data.fulfillment_order_id | json }} fulfillmentOrderLineItems: [ {% for line_item in fulfillment_order_data.gift_cards_to_fulfill %} { id: {{ line_item.id | json }} quantity: {{ line_item.remainingQuantity }} } {% endfor %} ] } ] notifyCustomer: false } ) { fulfillment { id status } userErrors { field message } } } {% endaction %} {% endif %} {% if fulfillment_order_data.line_items_to_fulfill != blank %} {% action "shopify" %} mutation { fulfillmentCreate( fulfillment: { lineItemsByFulfillmentOrder: [ { fulfillmentOrderId: {{ fulfillment_order_data.fulfillment_order_id | json }} fulfillmentOrderLineItems: [ {% for line_item in fulfillment_order_data.line_items_to_fulfill %} { id: {{ line_item.id | json }} quantity: {{ line_item.remainingQuantity }} } {% endfor %} ] } ] notifyCustomer: false } ) { fulfillment { id status } userErrors { field message } } } {% endaction %} {% endif %} {% endfor %} {% endfor %} {% endfor %}
true
true