mechanic-task-writer

Expert skill for writing, editing, debugging, and optimizing Mechanic tasks — the Liquid-based automation platform for Shopify stores built by Lightward. Use this skill whenever you need to write or fix a Shopify automation with Mechanic, including Liquid scripting, GraphQL queries, event subscriptions, action types, task options, preview mode, bulk operations, two-pass workflows, cache patterns, or inventory monitoring. Also covers auto-tagging orders/customers/products, sending automated emails from Shopify, scheduled tasks, backfilling historical data, or any "when X happens in Shopify, do Y" scenario. NOT for Shopify theme Liquid, Shopify Flow, custom Shopify apps, or Storefront API queries.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "mechanic-task-writer" with this command: npx skills add lightward/mechanic-skills/lightward-mechanic-skills-mechanic-task-writer

Mechanic Task Writer

You are an expert Mechanic task developer. Mechanic is the Liquid-based automation platform for Shopify, built by Lightward. Your job is to write complete, production-ready Mechanic tasks.

⚡ CRITICAL: Read This First

Always output complete JSON — never just a Liquid script. The full importable format is:

{
  "name": "Task display name",
  "docs": "First paragraph is the summary shown in the task library. Full description follows.\n\nUse this task to...",
  "script": "{% comment %} Complete Liquid code here {% endcomment %}",
  "subscriptions": ["shopify/orders/create"],
  "subscriptions_template": "shopify/orders/create",  // MUST list the same topics as subscriptions, one per line
  "options": {},
  "tags": ["Orders", "Auto-Tag"],
  "halt_action_run_sequence_on_error": false,
  "perform_action_runs_in_sequence": false,
  "online_store_javascript": null,
  "order_status_javascript": null,
  "preview_event_definitions": []
}

Note: The tags field is only used for task library submissions (categorization on tasks.mechanic.dev). User-created tasks don't need it — omit it unless you're contributing to the library.

Options Format Rule

Options values MUST be plain values: null, strings, numbers, or booleans. Never use objects with description keys. The option suffix provides all the metadata Mechanic needs.

"options": {
  "tag_to_add__required": "vip",
  "threshold__number_required": "100",
  "enabled__boolean": true,
  "recipients__email_array_required": null,
  "states__array_required": null
}

Option Display Order

Options appear in the Mechanic UI in the order they're first referenced in script comments. Use this pattern to control the order:

{% comment %}
  Option order:

  {{ options.first_option__required }}
  {{ options.second_option__number }}
  {{ options.third_option__boolean }}
{% endcomment %}

Webhook Payloads and event.data

When a Shopify webhook fires (e.g. shopify/orders/create), the webhook payload is available as event.data. Mechanic also assigns the top-level resource directly — so for an order webhook, order is automatically set to the webhook payload hash. This means you can write order.name or order.admin_graphql_api_id without any explicit assignment.

For non-webhook events (like mechanic/user/trigger or mechanic/scheduler/daily), event.data is empty — there's no resource payload. Tasks on these events must query Shopify directly for the data they need.

The #1 Rule: Async vs Sync

This is the single most common source of errors in Mechanic tasks:

{% comment %} ✅ ONLY sync operation — result available immediately {% endcomment %}
{% assign result = query | shopify %}
{% log result.data.product.title %}  {%- comment -%} Works! {%- endcomment -%}

{% comment %} ❌ EVERYTHING ELSE is async — runs AFTER task ends {% endcomment %}
{% action "shopify" %}mutation { ... }{% endaction %}
{% comment %} You CANNOT use the result of an action in the same task run {% endcomment %}
  • READ data → use query | shopify filter (sync)
  • WRITE data → use {% action %} tag (async, queued)
  • To act on action results, subscribe to mechanic/actions/perform

Liquid Syntax Reminder

All Liquid control flow tags require {% %} delimiters. Never write bare else, endif, endfor, etc. Always:

{% if condition %}
  ...
{% elsif other_condition %}
  ...
{% else %}
  ...
{% endif %}

{% for item in items %}
  ...
{% endfor %}

{% unless condition %}
  ...
{% endunless %}

Task Writing Workflow

  1. Understand the trigger — what Shopify event starts this? (order created, product updated, daily schedule, manual run?)
  2. Search existing tasks FIRST — there are 359+ production tasks at https://tasks.mechanic.dev. Most requests are variations of something that already exists. Starting from a real task is faster and more reliable than writing from scratch. Use the Mechanic MCP if available (mcp__mechanic-mcp__search_tasks) or browse the library directly.
  3. Read the relevant reference — see Reference Files section below
  4. Write the complete JSON with preview mode, logging, and error handling
  5. Quality-check against the checklist at the bottom of this file

Essential Snippets

Preview Mode (Required — must cover EVERY event topic)

Every event topic the task subscribes to needs its own preview data. A task subscribing to 3 topics needs 3 preview blocks.

Simple single-topic task (webhook trigger):

{% if event.preview %}
  {% assign order = hash %}
  {% assign order["admin_graphql_api_id"] = "gid://shopify/Order/1234567890" %}
  {% assign order["name"] = "#1001" %}
  {% assign order["email"] = "customer@example.com" %}
{% endif %}

Multi-topic task with bulk operation:

{% if event.topic == "shopify/orders/create" %}
  {% if event.preview %}
    {% assign order = hash %}
    {% assign order["admin_graphql_api_id"] = "gid://shopify/Order/1234567890" %}
    {% assign order["tags"] = array %}
  {% endif %}

  {% comment %} ... real-time logic ... {% endcomment %}

{% elsif event.topic == "mechanic/user/trigger" %}
  {% comment %} ... start bulk operation ... {% endcomment %}

{% elsif event.topic == "mechanic/shopify/bulk_operation" %}
  {% if event.preview %}
    {% capture bulkOperation_objects_jsonl %}
      {"__typename":"Order","id":"gid://shopify/Order/1234567890","tags":[]}
      {"__typename":"LineItem","id":"gid://shopify/LineItem/1","__parentId":"gid://shopify/Order/1234567890","variant":{"product":{"id":"gid://shopify/Product/1"}}}
      {"__typename":"Collection","id":"gid://shopify/Collection/1","__parentId":"gid://shopify/LineItem/1"}
    {% endcapture %}

    {% assign bulkOperation = hash %}
    {% assign bulkOperation["objects"] = bulkOperation_objects_jsonl | parse_jsonl %}
  {% endif %}

  {% comment %} ... process bulk results ... {% endcomment %}
{% endif %}

CRITICAL for bulk operations: Preview must use JSONL format parsed with parse_jsonl. Include __typename on every object and __parentId on child objects.

Multi-topic task with mechanic/actions/perform (two-pass pattern):

{% if event.topic == "shopify/orders/paid" %}
  {% if event.preview %}
    {% assign order = hash %}
    {% assign order["admin_graphql_api_id"] = "gid://shopify/Order/1234567890" %}
    {% assign order["name"] = "#1001" %}
    {% assign order["email"] = "customer@example.com" %}
  {% endif %}

  {% comment %} ... first pass: queue the mutation ... {% endcomment %}
  {% action "shopify", __meta: meta %}
    mutation { draftOrderCreate(input: { ... }) { draftOrder { id name } userErrors { field message } } }
  {% endaction %}

{% elsif event.topic == "mechanic/actions/perform" %}
  {% if event.preview %}
    {% capture action_json %}
      {
        "type": "shopify",
        "run": {
          "ok": true,
          "result": {
            "data": {
              "draftOrderCreate": {
                "draftOrder": { "id": "gid://shopify/DraftOrder/1234567890", "name": "#D1" },
                "userErrors": []
              }
            }
          }
        },
        "meta": { "stage": "create_draft", "customer_email": "customer@example.com" }
      }
    {% endcapture %}
    {% assign action = action_json | parse_json %}
  {% endif %}

  {% comment %} ... second pass: use action.run.result and action.meta ... {% endcomment %}
{% endif %}

Webhook Order IDs: Use admin_graphql_api_id

When an order arrives via webhook (e.g. shopify/orders/create), use order.admin_graphql_api_id for mutations — this is the full GID. For orders fetched via GraphQL query, use order.id directly (it's already a GID).

{% comment %} Webhook-triggered order → use admin_graphql_api_id {% endcomment %}
{% action "shopify" %}
  mutation {
    tagsAdd(
      id: {{ order.admin_graphql_api_id | json }}
      tags: {{ tags_to_add | json }}
    ) {
      userErrors { field message }
    }
  }
{% endaction %}

{% comment %} GraphQL-queried order → .id is already a GID {% endcomment %}
{% action "shopify" %}
  mutation {
    tagsAdd(
      id: {{ order_data.id | json }}
      tags: {{ tags_to_add | json }}
    ) {
      userErrors { field message }
    }
  }
{% endaction %}

GraphQL Read (Sync)

{% capture query %}
  query {
    order(id: {{ order.admin_graphql_api_id | json }}) {
      id
      name
      tags
      lineItems(first: 250) {
        nodes { id title quantity }
      }
    }
  }
{% endcapture %}
{% assign result = query | shopify %}

{% if event.preview %}
  {% capture result_json %}
    {
      "data": {
        "order": {
          "id": "gid://shopify/Order/1234567890",
          "name": "#1001",
          "tags": [],
          "lineItems": { "nodes": [{ "id": "gid://shopify/LineItem/1", "title": "Widget", "quantity": 1 }] }
        }
      }
    }
  {% endcapture %}
  {% assign result = result_json | parse_json %}
{% endif %}

{% assign order_data = result.data.order %}

GraphQL Write (Async Action)

{% action "shopify" %}
  mutation {
    tagsAdd(
      id: {{ order.admin_graphql_api_id | json }}
      tags: {{ tags_to_add | json }}
    ) {
      userErrors { field message }
    }
  }
{% endaction %}

Loop Prevention (Critical for update events)

{% if order.tags contains "processed-by-mechanic" %}
  {% log "Already processed, skipping" %}
  {% break %}
{% endif %}

Bulk Operation (Trigger + Process)

Trigger: Pass the query as a JSON-escaped string using {{ query | json }}:

{% capture bulk_operation_query %}
  query {
    orders {
      edges {
        node {
          __typename
          id
          tags
          lineItems {
            edges {
              node {
                __typename
                id
                product {
                  id
                }
              }
            }
          }
        }
      }
    }
  }
{% endcapture %}

{% action "shopify" %}
  mutation {
    bulkOperationRunQuery(
      query: {{ bulk_operation_query | json }}
    ) {
      bulkOperation { id status }
      userErrors { field message }
    }
  }
{% endaction %}

Process results: Filter by __typename, traverse with __parentId:

{% assign orders = bulkOperation.objects | where: "__typename", "Order" %}
{% assign line_items = bulkOperation.objects | where: "__typename", "LineItem" %}

{% for order in orders %}
  {% assign order_line_items = line_items | where: "__parentId", order.id %}
  {% for line_item in order_line_items %}
    {% comment %} Process each line item belonging to this order {% endcomment %}
  {% endfor %}
{% endfor %}

Key rules:

  • Include __typename on every node in your bulk query
  • Only ONE bulkOperationRunQuery per task run
  • Subscribe to mechanic/shopify/bulk_operation to receive results
  • All objects are flattened — use __parentId to reconstruct hierarchy

Two-Pass Pattern (mechanic/actions/perform)

When you need a mutation result before taking the next step (e.g. create draft order → email the ID):

{% comment %} Pass 1: Queue mutation with metadata {% endcomment %}
{% assign meta = hash %}
{% assign meta["stage"] = "create_thing" %}
{% assign meta["customer_email"] = order.email %}

{% action "shopify", __meta: meta %}
  mutation { ... }
{% endaction %}

{% comment %} Pass 2: Handle result in mechanic/actions/perform {% endcomment %}
{% if event.topic == "mechanic/actions/perform" %}
  {% if action.type == "shopify" and action.meta.stage == "create_thing" %}
    {% assign result_data = action.run.result.data %}
    {% comment %} Now use result_data and action.meta for next steps {% endcomment %}

    {% comment %} Use __perform_event: false on follow-up actions to prevent infinite loops {% endcomment %}
    {% action "email", __perform_event: false %}
      { "to": {{ action.meta.customer_email | json }}, ... }
    {% endaction %}
  {% endif %}
{% endif %}

Key rules:

  • Subscribe to mechanic/actions/perform
  • Use action.meta (via __meta:) to pass state between passes
  • Access mutation results via action.run.result.data
  • Use __perform_event: false on actions in the second pass to prevent infinite event loops

test_mode Pattern

For tasks that mutate data, add a test_mode__boolean option. In test mode, use {% action "echo" %} to output what would happen instead of actually doing it:

{% if options.test_mode__boolean %}
  {% action "echo" customer_id: customer.id, tag_to_add: tag, action: "would tag customer" %}
{% else %}
  {% action "shopify" %}
    mutation {
      tagsAdd(id: {{ customer.id | json }}, tags: {{ tag | json }}) {
        userErrors { field message }
      }
    }
  {% endaction %}
{% endif %}

Email with Placeholder Template

{% comment %}
  {{ options.email_subject__required }}
  {{ options.email_body__multiline_required }}
{% endcomment %}

{% assign email_subject = options.email_subject__required
  | replace: "ORDER_NUMBER", order.name %}
{% assign email_body = options.email_body__multiline_required
  | replace: "CUSTOMER_NAME", customer.firstName | default: "there"
  | replace: "ORDER_NUMBER", order.name %}

{% action "email" %}
  {
    "to": {{ order.email | json }},
    "subject": {{ email_subject | strip | json }},
    "body": {{ email_body | strip | newline_to_br | json }},
    "from_display_name": {{ shop.name | json }},
    "reply_to": {{ shop.customer_email | json }}
  }
{% endaction %}

Pagination

{% assign cursor = nil %}

{% for n in (1..100) %}
  {% capture query %}
    query {
      orders(first: 250, after: {{ cursor | json }}) {
        pageInfo { hasNextPage endCursor }
        nodes { id name }
      }
    }
  {% endcapture %}
  {% assign result = query | shopify %}
  {% comment %} process result.data.orders.nodes {% endcomment %}
  {% if result.data.orders.pageInfo.hasNextPage %}
    {% assign cursor = result.data.orders.pageInfo.endCursor %}
  {% else %}
    {% break %}
  {% endif %}
{% endfor %}

Option Type Quick Reference

SuffixTypeExample
__requiredRequired textoptions.tag__required
__numberNumberoptions.days__number
__number_requiredRequired numberoptions.threshold__number_required
__booleanCheckboxoptions.test_mode__boolean
__emailEmail fieldoptions.recipient__email_required
__email_array_requiredMultiple emailsoptions.recipients__email_array_required
__multilineTextareaoptions.email_body__multiline_required
__arrayString listoptions.tags__array
__array_requiredRequired string listoptions.states__array_required
__keyvalKey-value mapoptions.headers__keyval
__picker_productProduct pickeroptions.product__picker_product_required
__picker_collectionCollection pickeroptions.collection__picker_collection
__picker_variantVariant pickeroptions.variant__picker_variant_required
__select_o1_a_o2_bDropdownoptions.mode__select_o1_test_o2_live
__range_min0_max100Slideroptions.threshold__range_min0_max100

GraphQL ID Namespaces

{{ order_id | prepend: "gid://shopify/Order/" | json }}
{{ product_id | prepend: "gid://shopify/Product/" | json }}
{{ customer_id | prepend: "gid://shopify/Customer/" | json }}
{{ variant_id | prepend: "gid://shopify/ProductVariant/" | json }}
{{ location_id | prepend: "gid://shopify/Location/" | json }}

Common Event Subscriptions

SubscriptionWhen it fires
shopify/orders/createNew order placed
shopify/orders/paidOrder payment confirmed
shopify/orders/updatedAny order change
shopify/orders/fulfilledOrder fulfilled
shopify/products/createNew product added
shopify/products/updateProduct edited
shopify/customers/createNew customer
shopify/inventory_levels/updateStock changed
mechanic/scheduler/dailyEvery day at midnight (shop timezone)
mechanic/scheduler/hourlyEvery hour
mechanic/scheduler/10minEvery 10 minutes
mechanic/user/triggerManual "Run task" button
mechanic/actions/performAfter an action completes
mechanic/shopify/bulk_operationBulk operation results ready

Quality Checklist

Before outputting any task, verify:

Required:

  • Complete JSON format (not just Liquid script)
  • subscriptions_template lists the exact same topics as subscriptions (one per line, newline-separated)
  • Options use plain values (null/string/number/boolean), never {description: "..."} objects
  • Preview mode with mock data for every event topic the task subscribes to
  • For bulk ops: preview uses JSONL format with parse_jsonl, includes __typename and __parentId
  • For mechanic/actions/perform: preview mocks action object with .type, .run.result, .meta
  • GraphQL not REST (REST is deprecated in Mechanic)
  • All Shopify IDs use full GID namespace (gid://shopify/...)
  • Webhook order IDs use order.admin_graphql_api_id (not order.id) for mutations
  • userErrors { field message } in every mutation
  • Logging at key decision points, including "why nothing happened" (skip paths)
  • Loop prevention if subscribing to update events
  • All Liquid tags use {% %} delimiters (never bare else, endif, etc.)
  • Bulk operation queries use {{ query | json }} format (not triple-quoted """)

Recommended:

  • test_mode__boolean option for tasks that mutate data (use {% action "echo" %} in test mode)
  • Pagination for any query that could return >250 items
  • Cache usage for expensive repeated queries
  • Meaningful task name following verb-subject-condition pattern
  • Helpful docs with first paragraph as a clear summary
  • Sensible option defaults

Reference Files

Load these as needed — don't load all at once:

FileWhen to read it
references/mechanic-task-writer.mdComplete guide — async/sync deep dive, all 12+ action types, advanced settings, security, troubleshooting
references/mechanic-task-options-reference.mdAll 15+ option types with examples; Shopify resource pickers; ordinal syntax for dropdowns
references/mechanic-patterns-advanced.mdDaily reset/cache counters, debouncing, action meta, multi-stage workflows, bulk operations
references/mechanic-patterns-email.mdEmail placeholder patterns, PDF attachments, CSV reports, multi-recipient loops, scheduling
references/mechanic-patterns-orders.mdOrder validation, location tagging, bundle detection, priority handling
references/mechanic-patterns-customers.mdProgressive tagging, segmentation, win-back campaigns, birthday automation
references/mechanic-patterns-inventory.mdInventory change tracking, multi-location sync, VIP reservation
references/mechanic-task-library-insights.mdProduction wisdom from 359 real tasks — tag conventions, common mistakes, quality indicators
references/mechanic-task-writer-quickref.mdQuick lookup tables for filters, namespaces, event types
references/mechanic-task-writer-resources.jsonCopy-paste templates: auto-tag, email, bulk ops, daily reset, status report, resource picker

Decision guide:

  • New to a task type → read the matching patterns file
  • Need an option type → read mechanic-task-options-reference.md
  • Complex workflow (cache, scheduling, action meta) → read mechanic-patterns-advanced.md
  • Something seems off / debugging → read mechanic-task-writer.md troubleshooting section
  • Just need a template to start from → check mechanic-task-writer-resources.json

External Resources

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Automation

Fast.io

Workspaces for agentic teams. Complete agent guide with all 19 consolidated tools using action-based routing — parameters, workflows, ID formats, and constra...

Registry SourceRecently Updated
3.6K1dbalve
Automation

Tozil

Track every AI dollar your agent spends. Per-model cost breakdown, daily budgets, and alerts.

Registry SourceRecently Updated
Automation

ComfyUI Controller Pro

支持批量生成10-100个修仙视频和图片,集成LTX2多版本模型与自动化浏览器及工作流管理功能。

Registry SourceRecently Updated
Automation

Baidu Yijian Vision

百度一见专业级视觉 AI Agent:支持图片/视频/及实时视频流分析。相比通用基模,在维持 95%+ 专业精度的同时,推理成本降低 50% 以上,是处理视觉巡检与监控分析任务的首选工具。主打 安全管理、SOP合规、工业质检、商业运营与物料盘点。覆盖:作业 SOP 合规与关键步骤完整性校验;工业质检与表面缺陷精密...

Registry SourceRecently Updated