Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
40rty-ai avatar

Shopify Admin Customer Merge

  • 2 installs
  • 173 repo stars
  • Updated June 26, 2026
  • 40rty-ai/shopify-admin-skills

shopify-admin-customer-merge is a Claude Code skill that merges duplicate Shopify customer records using the native customerMerge API where supported, otherwise consolidating tags and notes onto the winner via customerUp

About

This skill merges a pair of duplicate Shopify customer records. When the store supports it, the skill calls the native customerMerge mutation to move orders, addresses, and metafields onto a winner record; otherwise it consolidates the loser's tags, notes, and marketing consent onto the winner via customerUpdate and annotates the loser for manual cleanup. It defaults to dry_run because native merge is irreversible.

  • Merges duplicate customers via Shopify's native customerMerge API when supported
  • Falls back to consolidating tags, notes, and consent onto the winner via customerUpdate
  • Irreversible native merge; defaults to dry_run to confirm winner/loser GIDs first

Shopify Admin Customer Merge by the numbers

  • 2 all-time installs (skills.sh)
  • Ranked #1,839 of 2,715 Automation & Workflows skills by installs in the Skillselion catalog
  • Data as of Aug 1, 2026 (Skillselion catalog sync)
At a glance

shopify-admin-customer-merge capabilities & compatibility

Free skill; requires an authenticated Shopify store session with read_customers and write_customers scopes.

Capabilities
customer merge · record deduplication · data consolidation
Works with
stripe
Use cases
data analysis
Pricing
Bring your own API key
From the docs

What shopify-admin-customer-merge says it does

Merges duplicate customer records: invokes Shopify's native customer merge API where supported, otherwise consolidates the loser record's tags and notes into the winner via customerUpdate.
SKILL.md
`customerMerge` is irreversible — once orders and addresses are moved to the winner, the loser record is closed and cannot be split back.
SKILL.md
npx skills add https://github.com/40rty-ai/shopify-admin-skills --skill shopify-admin-customer-merge

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs2
repo stars173
Last updatedJune 26, 2026
Repository40rty-ai/shopify-admin-skills

What it does

Merge duplicate Shopify customer records onto one winner, using native customerMerge with a customerUpdate fallback.

Who is it for?

Support teams resolving duplicate customer records identified by a duplicate-customer finder.

Skip if: Discovering which records are duplicates; that is the duplicate-customer-finder skill's job, and merging customers with active subscriptions or unfulfilled orders needs extra care.

When should I use this skill?

You have a confirmed winner and loser customer GID and want to merge them.

What you get

The loser record is merged or consolidated into the winner, with the loser annotated for manual cleanup when native merge is unavailable.

  • Merged or consolidated winner customer record
  • annotation on the loser record for manual cleanup

By the numbers

  • queries up to 25 addresses per customer
  • three GraphQL operations: customer query, customerMerge, customerUpdate

Files

SKILL.mdMarkdownGitHub ↗

Purpose

Resolves duplicate customer records identified by duplicate-customer-finder. Where the Shopify Admin API exposes customerMerge (a native merge that moves orders, addresses, subscriptions, and metafields onto a winner record), this skill calls it directly. When customerMerge is unavailable or fails for the given account pair, the skill falls back to consolidating searchable metadata — tags, notes, marketing consent — onto the winner via customerUpdate, then writes a clear annotation to the loser record so staff can complete the merge manually in Shopify Admin.

Prerequisites

  • Authenticated Shopify CLI session: shopify store auth --store <domain> --scopes read_customers,write_customers
  • API scopes: read_customers, write_customers

Parameters

ParameterTypeRequiredDefaultDescription
storestringyesStore domain (e.g., mystore.myshopify.com)
formatstringnohumanOutput format: human or json
dry_runboolnotruePreview merge plan without executing mutations
customer_winner_idstringyesGID of the customer record to keep (e.g., gid://shopify/Customer/12345)
customer_loser_idstringyesGID of the customer record to merge into the winner
use_native_mergeboolnotrueTry customerMerge first; if it fails or is unavailable, fall back to consolidation via customerUpdate
merge_tagsboolnotrueUnion the loser's tags onto the winner
merge_noteboolnotrueAppend the loser's note to the winner (with timestamp prefix)
annotate_loserboolnotrueWrite a note on the loser record pointing to the winner GID for manual cleanup

Safety

⚠️ Steps 2–4 execute mutations that modify customer records. customerMerge is irreversible — once orders and addresses are moved to the winner, the loser record is closed and cannot be split back. Run with dry_run: true first to confirm winner/loser GIDs and the merge plan. The default is dry_run: true. Always verify both records belong to the same human (matching email, phone, name) using duplicate-customer-finder output before committing. Do not merge a customer with active subscriptions or unfulfilled orders without confirming downstream systems will follow the new owner GID.

Workflow Steps

1. OPERATION: customer — query (called twice: winner and loser) Inputs: id: <customer_id>, select id, displayName, firstName, lastName, defaultEmailAddress { emailAddress }, phone, tags, note, numberOfOrders, amountSpent, emailMarketingConsent { marketingState }, smsMarketingConsent { marketingState }, addresses(first: 25) { id }, createdAt Expected output: Both records' full identity payload — abort if either GID does not resolve

2. OPERATION: customerMerge — mutation (only if use_native_merge: true and not dry_run) Inputs: customerOneId: <customer_winner_id>, customerTwoId: <customer_loser_id>, overrideFields: prefer winner's name/email/phone/locale/marketing-consent Expected output: job.id (merge runs asynchronously), userErrors. If userErrors indicates merge is not supported for this pair (B2B, gift card holder, subscriber, etc.), proceed to step 3 fallback.

3. OPERATION: customerUpdate — mutation (winner) — fallback path or when use_native_merge: false Inputs: input.id: <customer_winner_id>, input.tags: <union of winner.tags and loser.tags> (only if merge_tags), input.note: <winner.note + "\n[YYYY-MM-DD] Merged from <loser_email>:\n" + loser.note> (only if merge_note) Expected output: customer.id, customer.tags, customer.note, userErrors

4. OPERATION: customerUpdate — mutation (loser) — only if annotate_loser: true Inputs: input.id: <customer_loser_id>, input.note: "<existing note>\n[YYYY-MM-DD] DUPLICATE — merge target: <customer_winner_id>. Manually close in Shopify Admin once orders are reviewed.", input.tags: <existing + ["duplicate", "merged-loser"]> Expected output: customer.id, customer.tags, customer.note, userErrors

GraphQL Operations

# customer:query — validated against api_version 2025-01
query CustomerForMerge($id: ID!) {
  customer(id: $id) {
    id
    displayName
    firstName
    lastName
    defaultEmailAddress { emailAddress }
    phone
    tags
    note
    numberOfOrders
    amountSpent { amount currencyCode }
    emailMarketingConsent { marketingState marketingOptInLevel consentUpdatedAt }
    smsMarketingConsent { marketingState marketingOptInLevel consentUpdatedAt }
    addresses(first: 25) { id address1 city provinceCode countryCodeV2 zip }
    createdAt
  }
}
# customerMerge:mutation — validated against api_version 2025-01
mutation CustomerMerge(
  $customerOneId: ID!
  $customerTwoId: ID!
  $overrideFields: CustomerMergeOverrideFields
) {
  customerMerge(
    customerOneId: $customerOneId
    customerTwoId: $customerTwoId
    overrideFields: $overrideFields
  ) {
    job { id done }
    resultingCustomerId
    userErrors { field message code }
  }
}
# customerUpdate:mutation — validated against api_version 2025-01
mutation CustomerConsolidate($input: CustomerInput!) {
  customerUpdate(input: $input) {
    customer { id displayName tags note }
    userErrors { field message }
  }
}

Session Tracking

Claude MUST emit the following output at each stage. This is mandatory.

On start, emit:

╔══════════════════════════════════════════════╗
║  SKILL: Customer Merge                       ║
║  Store: <store domain>                       ║
║  Started: <YYYY-MM-DD HH:MM UTC>             ║
╚══════════════════════════════════════════════╝

After each step, emit:

[N/TOTAL] <QUERY|MUTATION>  <OperationName>
          → Params: <brief summary of key inputs>
          → Result: <count or outcome>

If dry_run: true, prefix every mutation step with [DRY RUN] and do not execute it.

On completion, emit:

For format: human (default):

══════════════════════════════════════════════
CUSTOMER MERGE OUTCOME
  Winner:           <name> (<email>)  Orders: <n>  Spent: $<n>
  Loser:            <name> (<email>)  Orders: <n>  Spent: $<n>
  Path used:        <native|fallback|skipped>
  Merge job:        <id or "n/a">
  Tags consolidated: <n>
  Note appended:    <yes/no>
  Loser annotated:  <yes/no>
  Errors:           <n>
  Output:           none
══════════════════════════════════════════════

For format: json, emit:

{
  "skill": "customer-merge",
  "store": "<domain>",
  "started_at": "<ISO8601>",
  "completed_at": "<ISO8601>",
  "dry_run": true,
  "winner_id": "<gid>",
  "loser_id": "<gid>",
  "outcome": {
    "path": "native|fallback",
    "merge_job_id": "<id or null>",
    "resulting_customer_id": "<gid or null>",
    "tags_consolidated": 0,
    "note_appended": false,
    "loser_annotated": false,
    "errors": 0,
    "output_file": null
  }
}

Output Format

No CSV output. The session summary reports the merge job ID, the resulting customer GID, and which path was taken. For batch merges, run this skill once per pair and capture the JSON output.

Error Handling

ErrorCauseRecovery
THROTTLEDAPI rate limit exceededWait 2 seconds, retry up to 3 times
customerMerge userError: customer has subscriptionsActive subscription on loserCancel subscription before merge or use fallback path
customerMerge userError: B2B customerCompany-affiliated recordUse fallback path; manual merge in Shopify Admin
customerMerge userError: gift card holderLoser owns gift card balanceTransfer gift card or use fallback path
Either GID not foundWrong ID or deleted customerRe-run duplicate-customer-finder
Merge job pendingAsync merge not yet completeRe-query Job(id) to confirm done: true

Best Practices

1. Always run duplicate-customer-finder first to confirm the pair is genuinely duplicate. Manual misclassifications are unrecoverable. 2. Pick the winner deliberately: typically the record with more orders, the verified email, or the older createdAt. Avoid making the marketing-consenting record the loser. 3. Run dry_run: true first; the preview shows both records' order counts and spend so you can sanity-check before committing. 4. For large dedup runs, write a wrapper script that calls this skill once per pair and feeds it from duplicate-customer-finder's CSV output — never batch merges in a single call. 5. After native merge, the job.done: false response is normal — Shopify processes merges asynchronously. Re-query the job ID until completion before assuming the loser is closed. 6. The fallback path (customerUpdate consolidation only) does not move orders. It preserves searchability via tags/notes so a human can finish the merge in Shopify Admin (Customers → Merge).

Related skills

FAQ

Is the merge reversible?

No. customerMerge is irreversible once orders and addresses move to the winner, so the skill defaults to dry_run:true to confirm the GIDs and plan first.

What happens if native merge is not supported?

It falls back to consolidating the loser's tags, notes, and marketing consent onto the winner via customerUpdate, then annotates the loser record for manual cleanup in Shopify Admin.

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.